{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.0"
    },
    "colab": {
      "provenance": [],
      "toc_visible": true
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "id": "cell-01",
      "metadata": {
        "id": "cell-01"
      },
      "source": [
        "# レポート課題3：業界口コミデータへの自然言語処理適用\n",
        "\n",
        "---\n",
        "\n",
        "## 使用データ\n",
        "\n",
        "合成口コミデータセット（別途Googleドライブで配布）を用いる。\n",
        "IT系スタートアップ・大手製造業・総合商社・地方金融機関・公共インフラの5業界に関する\n",
        "口コミ文と感情ラベル（positive / negative / neutral）から構成される。\n",
        "\n",
        "> **注意：** 使用するデータはすべて合成データです。  \n",
        "> 実在する企業・個人を示すものではありません。\n",
        "\n",
        "### データの列構成\n",
        "\n",
        "| 列名 | 内容 |\n",
        "|------|----- |\n",
        "| `review_id` | サンプルID |\n",
        "| `industry_type` | 業界（5種） |\n",
        "| `review_category` | 口コミの観点（残業／給与／成長／社風／人間関係） |\n",
        "| `review_text` | 口コミ本文 |\n",
        "| `sentiment_label` | 感情ラベル（positive / negative / neutral） |\n",
        "\n",
        "## 課題の目標\n",
        "\n",
        "1. spacy（ja_ginza）による分かち書きで特徴ベクトルを構築する\n",
        "2. 「データ前処理フェーズ」と「モデル構築・評価フェーズ」を分離して実験効率を体験する\n",
        "3. 識別器の失敗要因を業界・観点などのメタ情報も活用して分析し、仮説を立案する\n",
        "4. 仮説の妥当性を新たなサンプルで軽く検証する"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-02",
      "metadata": {
        "id": "cell-02"
      },
      "source": [
        "## 全体の流れ\n",
        "\n",
        "| フェーズ | Level | 内容 | 所要時間 | 実行頻度 |\n",
        "|---------|-------|------|---------|----------|\n",
        "| **Phase 1：前処理フェーズ** | 0〜1 | CSVの読み込み・spacy 処理・pickle 保存 | 数分 | 最初の1回のみ |\n",
        "| **Phase 2：モデル構築・評価フェーズ** | 2〜5 | pickle 読み込み・分割・ベクトル化・学習・評価・分析 | 数秒〜数十秒 | 繰り返し可 |\n",
        "\n",
        "上記の所要時間とは、一旦コードを書いてある状態での処理時間を指しています。コード理解や出力結果の分析等に要する時間は含みません。\n",
        "\n",
        "### 再現性について\n",
        "\n",
        "このノートブックでは、train / test の分割に **シード値 2026 で固定した層化抽出**を用いる。\n",
        "全員が同じデータセットから同じコードを実行すれば、**同一の分割結果**が得られる。\n",
        "\n",
        "### 実行手順\n",
        "1. 予め、Google Drive 直下に `2026dm-rep3` フォルダを作成しておく。このフォルダを作業フォルダと呼ぶことにする。今回の作業は全て作業フォルダ内で行うこと。\n",
        "2. 共有ドライブで配布している以下のファイルをダウンロードし、先ほど用意した作業フォルダにアップロードする。\n",
        "  - `report3_job_reviews.csv`: コーパス。\n",
        "  - `preprocess_corpus.ipynb`: Phase 1の前処理用ノートブック。\n",
        "  - `report3_2026.ipynb`: 本ノートブック。Phase 2以降の参考コード付き。\n",
        "3. Phase 1 のために、`preprocess_corpus.ipynb` を実行し、前処理を行った pickle ファイルを生成する。\n",
        "4. **以降は Phase 2 から実行する**（pickle 読み込みだけでよい）\n",
        "  - 本ノートブックには Phase 2 用のコード例を含んでいます。Phase1を含め実行するだけ終わる部分がありますが、主要な流れについては読み解いた方が良いでしょう。\n",
        "  - Level 1は、そのまま実行できます。\n",
        "  - Level 2は、モデルを変更しても良いし、そのままでも良いです。モデル変更する場合にはimportしてから利用してください。\n",
        "  - Level 3以降もコード例を書いてありますが、不完全であり、そのまま実行しても欲しい結果は得られません。課題で求めていることを理解して自分なりに取り組んでください。（コードを書いて自動分析することを求めているわけではありません）"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-03",
      "metadata": {
        "id": "cell-03"
      },
      "source": [
        "---\n",
        "\n",
        "## Phase 1：前処理フェーズ\n",
        "`preprocess_corpus.ipynb` を参照。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-11",
      "metadata": {
        "id": "cell-11"
      },
      "source": [
        "---\n",
        "\n",
        "## Phase 2：モデル構築・評価フェーズ\n",
        "\n",
        "一度Phase 1を実行して pickle ファイルを用意したら、その後はここから実行するだけで良い。**新しい Colab セッションを開いた場合は、次のセル（Drive マウント＋pickle 読み込み）から始めること。**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-12",
      "metadata": {
        "id": "cell-12"
      },
      "outputs": [],
      "source": [
        "# ── Phase 2 開始時は必ずこのセルから実行する ─────────────────────────────────\n",
        "from google.colab import drive\n",
        "drive.mount('/content/drive')\n",
        "\n",
        "import pickle\n",
        "import pandas as pd\n",
        "\n",
        "path = \"/content/drive/MyDrive/2026dm-rep3/\"\n",
        "pkl_file = path + \"job_reviews_add.pkl\"\n",
        "\n",
        "with open(pkl_file, \"rb\") as f:\n",
        "    df = pickle.load(f)\n",
        "\n",
        "print(f\"読み込み完了: {len(df)} 件\")\n",
        "#display(df[[\"review_id\", \"industry_type\", \"review_category\", \"review_text\", \"sentiment_label\"]].head(3))\n",
        "df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-13",
      "metadata": {
        "id": "cell-13"
      },
      "source": [
        "### Level 1：train / test 分割と特徴ベクトルの構築（BoW）\n",
        "\n",
        "#### train / test 分割の方針\n",
        "\n",
        "jrte-corpus（v1）とは異なり、このデータセットには事前定義の分割がない。\n",
        "そこで **シード値固定の層化抽出**（stratified split）で分割する。\n",
        "\n",
        "- **stratify**: 各ラベル（positive / negative / neutral）の比率を train / test で揃える\n",
        "- **random_state=2026**: 全員が同じコードを実行すれば同一の分割結果になる\n",
        "\n",
        "```python\n",
        "train_df, test_df = train_test_split(\n",
        "    df, test_size=0.2, random_state=2026, stratify=df[\"sentiment_label\"]\n",
        ")\n",
        "```\n",
        "\n",
        "#### CountVectorizer の使い方\n",
        "\n",
        "- train データのみで `fit_transform` → 語彙を決定\n",
        "- test データには `transform` のみ（語彙を固定したまま変換）\n",
        "\n",
        "**注意: Level 1〜3 では `CountVectorizer` をカスタマイズしてはならない。**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-14",
      "metadata": {
        "id": "cell-14"
      },
      "outputs": [],
      "source": [
        "from sklearn.model_selection import train_test_split\n",
        "\n",
        "# シード値 2026・層化抽出で再現性を保証する\n",
        "train_df, test_df = train_test_split(\n",
        "    df,\n",
        "    test_size=0.2,\n",
        "    random_state=2026,\n",
        "    stratify=df[\"sentiment_label\"]\n",
        ")\n",
        "train_df = train_df.reset_index(drop=True)\n",
        "test_df  = test_df.reset_index(drop=True)\n",
        "\n",
        "print(f\"train: {len(train_df)} 件\")\n",
        "print(f\"test : {len(test_df)} 件\")\n",
        "print()\n",
        "print(\"train の sentiment_label 分布:\")\n",
        "print(train_df[\"sentiment_label\"].value_counts())\n",
        "print()\n",
        "print(\"test の sentiment_label 分布:\")\n",
        "print(test_df[\"sentiment_label\"].value_counts())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-15",
      "metadata": {
        "id": "cell-15"
      },
      "outputs": [],
      "source": [
        "from sklearn.feature_extraction.text import CountVectorizer\n",
        "\n",
        "corpus_train = train_df[\"wakati\"].tolist()\n",
        "corpus_test  = test_df[\"wakati\"].tolist()\n",
        "y_train = train_df[\"sentiment_label\"].tolist()\n",
        "y_test  = test_df[\"sentiment_label\"].tolist()\n",
        "\n",
        "# CountVectorizer はカスタマイズ禁止（Level 2〜4）\n",
        "vectorizer = CountVectorizer()\n",
        "\n",
        "X_train = vectorizer.fit_transform(corpus_train)  # train で fit＆transform\n",
        "X_test  = vectorizer.transform(corpus_test)        # test は transform のみ\n",
        "features = vectorizer.get_feature_names_out()\n",
        "\n",
        "print(f\"語彙数（特徴数）: {len(features)}\")\n",
        "print(f\"X_train の形状: {X_train.shape}\")\n",
        "print(f\"X_test  の形状: {X_test.shape}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-16",
      "metadata": {
        "id": "cell-16"
      },
      "source": [
        "### ✍️ Level 1 報告事項\n",
        "\n",
        "**(1)** `features[:5]` の出力結果。\n",
        "\n",
        "**(2)** `len(features)` の結果（語彙サイズ）。\n",
        "\n",
        "**(3)** `X_train[0]` の出力結果（最初の訓練サンプルのスパースベクトル表現）。\n",
        "\n",
        "**(4)** `X_train[0]` の非ゼロ要素のうち、最初に現れる特徴のインデックスと特徴名を報告せよ。\n",
        "\n",
        "**(5)** `X_train[0]` に対応する元テキスト（`train_df.iloc[0][\"review_text\"]`）を掲載せよ。\n",
        "\n",
        "**(6)** train / test の件数および各ラベルの件数を報告せよ（stratify が正しく機能しているか確認）。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-17",
      "metadata": {
        "id": "cell-17"
      },
      "outputs": [],
      "source": [
        "# ── (1) features[:5] ──────────────────────────────────────────────────────\n",
        "print(\"(1) features[:5]:\")\n",
        "print(features[:5])\n",
        "print()\n",
        "\n",
        "# ── (2) len(features) ─────────────────────────────────────────────────────\n",
        "print(f\"(2) len(features): {len(features)}\")\n",
        "print()\n",
        "\n",
        "# ── (3) X_train[0] ────────────────────────────────────────────────────────\n",
        "print(\"(3) X_train[0]:\")\n",
        "print(X_train[0])\n",
        "print()\n",
        "\n",
        "# ── (4) 最初の非ゼロ特徴 ──────────────────────────────────────────────────\n",
        "first_idx = X_train[0].indices[0]\n",
        "print(f\"(4) 最初の非ゼロ特徴: インデックス = {first_idx}, 特徴名 = '{features[first_idx]}'\")\n",
        "print()\n",
        "\n",
        "# ── (5) 元テキスト ────────────────────────────────────────────────────────\n",
        "print(\"(5) 元テキスト:\")\n",
        "print(train_df.iloc[0][\"review_text\"])\n",
        "print()\n",
        "\n",
        "# ── (6) 分割件数の確認 ────────────────────────────────────────────────────\n",
        "print(f\"(6) len(y_train) = {len(y_train)},  len(y_test) = {len(y_test)}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-18",
      "metadata": {
        "id": "cell-18"
      },
      "source": [
        "### Level 2：識別学習と評価\n",
        "\n",
        "任意の分類器を用いて識別学習を行い、train / test データに対する分類精度を求めよ。\n",
        "\n",
        "**ヒント（使える分類器の例）:**\n",
        "- `sklearn.linear_model.LogisticRegression`\n",
        "- `sklearn.svm.LinearSVC`\n",
        "- `sklearn.tree.DecisionTreeClassifier`\n",
        "- `sklearn.ensemble.RandomForestClassifier`\n",
        "\n",
        "分類器の選択理由をコメントとして簡単に記載すること。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-19",
      "metadata": {
        "id": "cell-19"
      },
      "outputs": [],
      "source": [
        "from sklearn.linear_model import LogisticRegression\n",
        "\n",
        "# 選択理由: （ここにコメントを書く）\n",
        "model = LogisticRegression(max_iter=1000)  # ← 分類器を変更してみよう\n",
        "\n",
        "model.fit(X_train, y_train)\n",
        "\n",
        "train_acc = model.score(X_train, y_train)\n",
        "test_acc  = model.score(X_test, y_test)\n",
        "print(f\"train 精度: {train_acc:.4f}\")\n",
        "print(f\"test  精度: {test_acc:.4f}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-20",
      "metadata": {
        "id": "cell-20"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "from sklearn.metrics import classification_report, confusion_matrix\n",
        "\n",
        "label_order = [\"negative\", \"neutral\", \"positive\"]\n",
        "y_pred_test = model.predict(X_test)\n",
        "\n",
        "print(\"=== 混同行列（test データ）===\")\n",
        "cm = confusion_matrix(y_test, y_pred_test, labels=label_order)\n",
        "print(pd.DataFrame(cm,\n",
        "                   index=[f\"実際: {l}\" for l in label_order],\n",
        "                   columns=[f\"予測: {l}\" for l in label_order]))\n",
        "print()\n",
        "print(\"=== 分類レポート ===\")\n",
        "print(classification_report(y_test, y_pred_test, labels=label_order))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-21",
      "metadata": {
        "id": "cell-21"
      },
      "source": [
        "### ✍️ Level 2 報告事項\n",
        "\n",
        "**(1)** 使用した分類器と、選択理由。\n",
        "\n",
        "**(2)** train データおよび test データに対する分類精度。\n",
        "\n",
        "**(3)** 混同行列（または分類レポート）を掲載し、どのラベルで誤分類が多いかを簡単にコメントせよ。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-22",
      "metadata": {
        "id": "cell-22"
      },
      "source": [
        "### Level 3：失敗要因分析と仮説立案\n",
        "\n",
        "識別器が誤分類した事例を観察し、エビデンスに基づいた改善案（仮説）を立案する。\n",
        "\n",
        "#### このデータセット特有の分析方針\n",
        "\n",
        "このデータセットには **`industry_type`（業界）** と **`review_category`（観点）** の列がある。\n",
        "誤分類をこれらの軸でクロス集計することで、\n",
        "「特定の業界や観点でモデルが失敗しやすい」かどうかを定量的に確認できる。\n",
        "\n",
        "```\n",
        "分析の例:\n",
        "  失敗事例を industry_type でクロス集計\n",
        "  → 「総合商社の口コミで誤分類が多い」などの傾向を発見\n",
        "  → 「なぜか」をテキストレベルで確認\n",
        "  → 仮説を立案\n",
        "```\n",
        "\n",
        "#### 推奨手順\n",
        "\n",
        "1. テストデータで予測し、失敗事例を抽出する\n",
        "2. 失敗事例の `industry_type` / `review_category` の分布を確認する（クロス集計）\n",
        "3. 全失敗事例から N 件（N ≥ 20）をランダムにサンプリングして元テキストを観察する  \n",
        "   ※ランダム選択は [reporting bias](https://en.wikipedia.org/wiki/Reporting_bias) を避けるため\n",
        "4. 仮説ラベルを付与し、出現頻度をカウントして改善案を絞り込む\n",
        "\n",
        "**上記以外の手順で分析＆仮説立案しても良い。ただし、改善案は必ず観察結果に基づくこと。根拠のない提案は評価しない。**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-23",
      "metadata": {
        "id": "cell-23"
      },
      "outputs": [],
      "source": [
        "import random\n",
        "\n",
        "# ── テストデータの失敗事例を抽出 ─────────────────────────────────────────────\n",
        "y_pred_arr  = np.array(y_pred_test)\n",
        "y_test_arr  = np.array(y_test)\n",
        "wrong_mask  = y_pred_arr != y_test_arr\n",
        "\n",
        "wrong_test_df = test_df[wrong_mask].copy()\n",
        "wrong_test_df[\"predicted\"] = y_pred_arr[wrong_mask]\n",
        "\n",
        "print(f\"test の失敗件数: {wrong_mask.sum()} / {len(y_test)}  ({wrong_mask.mean():.1%})\")\n",
        "print()\n",
        "\n",
        "# ── industry_type 別の誤分類傾向 ─────────────────────────────────────────────\n",
        "print(\"industry_type 別の失敗件数:\")\n",
        "ind_err = wrong_test_df[\"industry_type\"].value_counts()\n",
        "ind_tot = test_df[\"industry_type\"].value_counts()\n",
        "print(pd.DataFrame({\"失敗数\": ind_err, \"テスト数\": ind_tot,\n",
        "                    \"誤分類率\": (ind_err / ind_tot).map(\"{:.0%}\".format)})\n",
        "      .fillna(0).sort_values(\"失敗数\", ascending=False))\n",
        "print()\n",
        "\n",
        "# ── review_category 別の誤分類傾向 ───────────────────────────────────────────\n",
        "print(\"review_category 別の失敗件数:\")\n",
        "cat_err = wrong_test_df[\"review_category\"].value_counts()\n",
        "cat_tot = test_df[\"review_category\"].value_counts()\n",
        "print(pd.DataFrame({\"失敗数\": cat_err, \"テスト数\": cat_tot,\n",
        "                    \"誤分類率\": (cat_err / cat_tot).map(\"{:.0%}\".format)})\n",
        "      .fillna(0).sort_values(\"失敗数\", ascending=False))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-24",
      "metadata": {
        "id": "cell-24"
      },
      "outputs": [],
      "source": [
        "# ── ランダムに N 件サンプリングして元テキストを観察 ──────────────────────────\n",
        "random.seed(2026)\n",
        "N = 20\n",
        "sampled_df = wrong_test_df.sample(n=min(N, len(wrong_test_df)), random_state=2026)\n",
        "\n",
        "print(f\"── 失敗事例（{len(sampled_df)} 件）──\\n\")\n",
        "for _, row in sampled_df.iterrows():\n",
        "    print(f\"[正解: {row['sentiment_label']:8s}, 予測: {row['predicted']:8s}]  \"\n",
        "          f\"[{row['industry_type']} / {row['review_category']}]\")\n",
        "    print(f\"  {row['review_text']}\")\n",
        "    print()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-25",
      "metadata": {
        "id": "cell-25"
      },
      "outputs": [],
      "source": [
        "# ── ここに分析結果を書く ──────────────────────────────────────────────────\n",
        "# 各失敗事例に原因ラベルを付与し、Counter で集計する。\n",
        "#\n",
        "# 例:\n",
        "#   hypothesis_labels.append(\"negation\")      # 否定表現が原因\n",
        "#   hypothesis_labels.append(\"out_of_vocab\")  # 語彙外表現（BoWで拾えない）\n",
        "#   hypothesis_labels.append(\"neutral_hard\")  # neutral の判断が難しい\n",
        "#   hypothesis_labels.append(\"short\")         # 短い文（情報不足）\n",
        "\n",
        "from collections import Counter\n",
        "\n",
        "hypothesis_labels = []  # ← ここにラベルを追加していく\n",
        "\n",
        "print(\"仮説ラベルの出現回数:\")\n",
        "print(Counter(hypothesis_labels).most_common())"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-26",
      "metadata": {
        "id": "cell-26"
      },
      "source": [
        "### ✍️ Level 3 報告事項\n",
        "\n",
        "**(1)** 分析方法の説明。\n",
        "- どのように分析したのか。\n",
        "- 分析対象は何か（train/test/両方）\n",
        "- 何件の失敗事例を観察したか。\n",
        "- 分析結果に基づく傾向があれば報告すること。（傾向が見当たらない場合にもそのことを報告すること）\n",
        "\n",
        "**(2)** 分析結果と仮説の立案。\n",
        "- 観察した失敗事例の具体例（テキストと正解・予測ラベル）を示すこと。\n",
        "- 仮説は「〜という表現を含む文は、〜という理由で分類に失敗しやすい」の形で記述すること。\n",
        "- 仮説の優先順位付け（ラベルの出現頻度など）を示すこと。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-27",
      "metadata": {
        "id": "cell-27"
      },
      "source": [
        "### Level 4：仮説の妥当性検証\n",
        "\n",
        "Level 3 で立案した仮説を検証するため、\n",
        "新たな文と正解ラベルを N 件（N ≥ 2）用意し、予測結果を確認する。\n",
        "\n",
        "**Note**: Level 1,2で構築した vectorizer を利用する必要があるため、もし別日に実行するなら改めて Level 1 から実行し直そう。\n",
        "\n",
        "#### 重要：`vectorizer.transform()` を使うこと（`fit_transform()` ではない）\n",
        "\n",
        "Level 1,2 で構築した「語彙（特徴空間）」を共有するために、\n",
        "vectorizer を再学習（`fit`）せず、`transform()` のみを使う。\n",
        "`fit_transform()` を使うと語彙が変わり、元のモデルで正しく予測できなくなる。\n",
        "\n",
        "```python\n",
        "new_X = vectorizer.transform(new_wakati)    # ✓ 正しい\n",
        "new_X = vectorizer.fit_transform(new_wakati)  # ✗ 誤り\n",
        "```\n",
        "\n",
        "`new_X.shape[1] == X_test.shape[1]` が `True` になることを確認しよう。"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# セッションが新しい場合には ja_ginza を実行できる状態にする必要があります。\n",
        "# 既に実行できる状態ならば、このセルは実行不要です。\n",
        "\n",
        "!pip install -U ginza ja_ginza\n",
        "\n",
        "import spacy\n",
        "config = {\n",
        "    \"components\": {\n",
        "        \"compound_splitter\": {\n",
        "            \"split_mode\": \"A\"\n",
        "        }\n",
        "    }\n",
        "}\n",
        "nlp = spacy.load(\"ja_ginza\", config=config)\n",
        "print(\"spacy をロードしました\")"
      ],
      "metadata": {
        "id": "4lMTdalfNAY6"
      },
      "id": "4lMTdalfNAY6",
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-28",
      "metadata": {
        "id": "cell-28"
      },
      "outputs": [],
      "source": [
        "# ── spacy のロード（セッションが新しい場合） ─────────────────────────────────\n",
        "try:\n",
        "    nlp\n",
        "except NameError:\n",
        "    print(\"spacy をロードできません\")\n",
        "    # 上記がprint出力されるなら、環境構築が必要です。\n",
        "    # 一つ手前のセルのコメントアウトを外して実行してください。\n",
        "\n",
        "# ── 検証用テキストと正解ラベルを用意する ──────────────────────────────────\n",
        "# 仮説に基づいて「モデルが失敗しそうな」文を自分で作成すること（N ≥ 2）\n",
        "# ラベル: \"positive\", \"neutral\", \"negative\"\n",
        "\n",
        "new_texts = [\n",
        "    \"文1\",   # ← 変更すること\n",
        "    \"文2\",   # ← 変更すること\n",
        "]\n",
        "new_labels = [\"positive\", \"negative\"]  # ← 正解ラベルを設定すること\n",
        "\n",
        "# ── spacy で分かち書き ────────────────────────────────────────────────────\n",
        "def text_to_wakati(text):\n",
        "    return \" \".join([token.lemma_ for token in nlp(str(text))])\n",
        "\n",
        "new_wakati = [text_to_wakati(t) for t in new_texts]\n",
        "print(\"分かち書き結果:\")\n",
        "for orig, wak in zip(new_texts, new_wakati):\n",
        "    print(f\"  {orig}\")\n",
        "    print(f\"  → {wak}\")\n",
        "    print()\n",
        "\n",
        "# ── vectorizer.transform() でベクトル化 ───────────────────────────────────\n",
        "new_X = vectorizer.transform(new_wakati)\n",
        "print(f\"new_X の形状: {new_X.shape}\")\n",
        "print(f\"X_test と列数が一致: {new_X.shape[1] == X_test.shape[1]}\")\n",
        "print()\n",
        "\n",
        "# ── 予測 ──────────────────────────────────────────────────────────────────\n",
        "new_preds = model.predict(new_X)\n",
        "\n",
        "print(\"予測結果:\")\n",
        "for text, true, pred in zip(new_texts, new_labels, new_preds):\n",
        "    mark = \"✓ 正解\" if true == pred else \"✗ 不正解（仮説通り失敗）\"\n",
        "    print(f\"  [{mark}] 正解: {true:10s}, 予測: {pred}\")\n",
        "    print(f\"    {text}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-29",
      "metadata": {
        "id": "cell-29"
      },
      "source": [
        "### ✍️ Level 4 報告事項\n",
        "\n",
        "**(1)** 作成した `new_texts` と `new_labels` を表形式で掲載すること。\n",
        "\n",
        "**(2)** 予測結果を掲載し、仮説の妥当性についての考察を記述すること。\n",
        "- 予測が仮説通り失敗した場合：なぜこの仮説は妥当と言えるか。\n",
        "- 予測が仮説に反して成功した場合：なぜ失敗しなかったと考えられるか。仮説をどう修正するか。\n",
        "\n",
        "> **注意:** 仮説が「誤り」という結論でも問題ありません。  \n",
        "> 観察結果に基づいて論理的に考察してください（減点しません）。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-30",
      "metadata": {
        "id": "cell-30"
      },
      "source": [
        "---\n",
        "\n",
        "## オプション\n",
        "\n",
        "以下は必須ではない。取り組んだ場合はレポートに記載すること（加点対象）。\n",
        "\n",
        "### (A) CountVectorizer のカスタマイズ\n",
        "\n",
        "```python\n",
        "vectorizer = CountVectorizer(token_pattern=r'(?u)\\b\\w+\\b')  # 1文字語を含める\n",
        "vectorizer = CountVectorizer(min_df=2)                       # 2件以上に出現する語のみ\n",
        "vectorizer = CountVectorizer(ngram_range=(1, 2))             # ユニグラム＋バイグラム\n",
        "vectorizer = CountVectorizer(max_features=500)               # 語彙数を制限\n",
        "```\n",
        "\n",
        "なお `CountVectorizer` がデフォルトで1文字の語を除外する理由を確認しよう（ヒント: `token_pattern` のデフォルト値を調べよ）。\n",
        "\n",
        "### (B) TfidfVectorizer による重み付き特徴ベクトル\n",
        "\n",
        "`CountVectorizer` の代わりに `TfidfVectorizer` を使い、精度を比較せよ。\n",
        "\n",
        "### (C) spacy の文ベクトル\n",
        "\n",
        "spacy の文ベクトル（`doc.vector`）を特徴として使い、BoW との精度を比較せよ。\n",
        "\n",
        "```python\n",
        "import numpy as np\n",
        "X_vec_train = np.array([nlp(text).vector for text in train_df[\"review_text\"]])\n",
        "X_vec_test  = np.array([nlp(text).vector for text in test_df[\"review_text\"]])\n",
        "```\n",
        "\n",
        "### (D) 業界・観点別の詳細分析\n",
        "\n",
        "Level 4 で発見した誤分類の傾向を深掘りし、\n",
        "「特定の業界や観点に特化した改善案」を提案せよ。  \n",
        "例: 業界ごとに異なる分類器を使う / 業界名・観点名を特徴に加える など。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cell-31",
      "metadata": {
        "id": "cell-31"
      },
      "outputs": [],
      "source": [
        "# ── オプション (B): TfidfVectorizer の例 ──────────────────────────────────\n",
        "# 以下のコメントを外して実行してみよう\n",
        "\n",
        "# from sklearn.feature_extraction.text import TfidfVectorizer\n",
        "# from sklearn.linear_model import LogisticRegression\n",
        "#\n",
        "# vec_tfidf = TfidfVectorizer()\n",
        "# X_tr_tfidf = vec_tfidf.fit_transform(corpus_train)\n",
        "# X_te_tfidf = vec_tfidf.transform(corpus_test)\n",
        "#\n",
        "# m_tfidf = LogisticRegression(max_iter=1000)\n",
        "# m_tfidf.fit(X_tr_tfidf, y_train)\n",
        "# print(f\"TF-IDF + LR の test 精度: {m_tfidf.score(X_te_tfidf, y_test):.4f}\")\n",
        "\n",
        "print(\"オプション: コメントを外して実行してみよう\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cell-32",
      "metadata": {
        "id": "cell-32"
      },
      "source": [
        "---\n",
        "\n",
        "## 提出物\n",
        "\n",
        "- **このノートブック（report3_2026.ipynb）**\n",
        "  - Phase 2 の実行済み出力（Output）が残っている状態で提出すること\n",
        "  - Level 1〜4 の報告事項（✍️ マーク）に対する回答・考察をノートブック内のセルに記入すること\n",
        "  - Level 3 の仮説ラベル、Level 4 の `new_texts` など、自分が追記した部分が明確になっていること\n",
        "- ノートブックでレポートを書きづらい場合には、別途自由形式で作成して提出しても良い。ただし実行結果を保存したノートブックは必ず提出すること。\n",
        "\n",
        "> **注意:** CSVファイルやpickleファイルは提出しないでください。\n"
      ]
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "-6ATKnB8HgjQ"
      },
      "id": "-6ATKnB8HgjQ",
      "execution_count": null,
      "outputs": []
    }
  ]
}