{"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","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.11"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":10445,"databundleVersionId":200747,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom sklearn.preprocessing import LabelEncoder\nfrom xgboost import XGBRegressor\nimport matplotlib.pyplot as plt\n\nrseed = 71","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-30T08:51:14.719570Z","iopub.execute_input":"2025-06-30T08:51:14.719895Z","iopub.status.idle":"2025-06-30T08:51:14.725084Z","shell.execute_reply.started":"2025-06-30T08:51:14.719875Z","shell.execute_reply":"2025-06-30T08:51:14.724045Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------------------------------\n# データ読み込み\n# ----------------------------------------------\nDATA_DIR = \"/kaggle/input/elo-merchant-category-recommendation\"\ntrain = pd.read_csv(f\"{DATA_DIR}/train.csv\")\ntest  = pd.read_csv(f\"{DATA_DIR}/test.csv\")\n\n# 元テーブルからID列と目的変数をコピー\ntrain_card_id = train['card_id'].copy()\ntest_card_id  = test['card_id'].copy()\ntrain_y       = train['target'].copy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-30T08:51:14.726850Z","iopub.execute_input":"2025-06-30T08:51:14.727158Z","iopub.status.idle":"2025-06-30T08:51:15.067626Z","shell.execute_reply.started":"2025-06-30T08:51:14.727137Z","shell.execute_reply":"2025-06-30T08:51:15.066833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------------------------------\n# メインテーブルの特徴量作成\n# ・年月文字列は欠損補完・Label Encoding\n# ・他はそのまま\n# ----------------------------------------------\n\n# 元テーブルから特徴量をコピー（目的変数は削除）\ntrain_x = train.copy().drop(columns=[\"target\"])\ntest_x  = test.copy()\n\n# 年月文字列に欠損があるので補完\ntrain_x[\"first_active_month\"] = train_x[\"first_active_month\"].fillna(\"Unknown\")\ntest_x[\"first_active_month\"]  = test_x[\"first_active_month\"].fillna(\"Unknown\")\n\n# 年月文字列をLabel Encoding\nle = LabelEncoder()\nall_months = pd.concat([train_x[\"first_active_month\"], test_x[\"first_active_month\"]])\nle.fit(all_months)\ntrain_x[\"fam_label\"] = le.transform(train_x[\"first_active_month\"])\ntest_x[\"fam_label\"]  = le.transform(test_x[\"first_active_month\"])\n\n# 変換前の年月文字列を削除\ntrain_x = train_x.drop(columns=[\"first_active_month\"])\ntest_x  = test_x.drop(columns=[\"first_active_month\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-30T08:51:15.068503Z","iopub.execute_input":"2025-06-30T08:51:15.068834Z","iopub.status.idle":"2025-06-30T08:51:15.199886Z","shell.execute_reply.started":"2025-06-30T08:51:15.068806Z","shell.execute_reply":"2025-06-30T08:51:15.198907Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------------------------------\n# 追加テーブルの特徴量作成\n# ----------------------------------------------\nimport numpy as np\n\n# 追加テーブルの読み込み\nnew_trans = pd.read_csv(f\"{DATA_DIR}/historical_transactions.csv\")\n#new_trans = pd.read_csv(f\"{DATA_DIR}/new_merchant_transactions.csv\")\n#print(new_trans.describe())\n\n# リーク確認\nmerged = new_trans.merge(train[['card_id', 'first_active_month']], on='card_id', how='left')\nleak_mask = merged['purchase_date'] > merged['first_active_month']\nprint(\"リーク件数:\", leak_mask.sum())\n\n# データの数値化\nnew_trans = new_trans[new_trans['month_lag'] < 0]  # リーク防止のため、month_lagが負の行は削除\nnew_trans['authorized_flag'] = new_trans['authorized_flag'].map({'Y': 1, 'N': 0})\nnew_trans['category_1']      = new_trans['category_1'].map({'Y': 1, 'N': 0})\n\n# 集約特徴の作成パターンを辞書化\nagg_funcs = {\n    # 正規化済みの購入金額（負値 = 顧客の支出）\n    \"purchase_amount\": [\"mean\", \"std\", \"min\", \"max\"], \n    # 分割払いの分割回数\n    \"installments\"   : [\"min\", \"max\", \"first\", \"last\"], \n    # 基準日からの経過月数\n    \"month_lag\"      : [\"min\", \"max\", \"first\", \"last\"], \n    # 購入日時（countで空でない行を数える ⇔ 購入回数）  \n    #\"purchase_date\"  : [\"count\"],    \n    # 使用認証の有無  \n    \"authorized_flag\": [\"mean\", \"sum\", \"count\", \"nunique\"],    \n    # カテゴリ１  \n    \"category_1\"     : [\"mean\", \"sum\", \"count\"],\n    # カテゴリ２  \n    \"category_2\"     : [\"mean\", \"sum\", \"skew\"],\n}\n# 例）\"month_lag\": [\"min\", \"mean\", \"max\"],\n# 　　⇔　変数 \"month_lag\" について，同一IDの中で\n# 　　　　最小値，平均値，最大値を取って集約特徴に追加する\n\n# 全ての行を card_id の値でグループ化\n# ⇒　上の集約パターンに従って実際に特徴を集約\nagg_new = new_trans.groupby(\"card_id\").agg(agg_funcs)\n\n# 備考）上の集約特徴のテーブルは，列が MultiIndex の状態になっている\n# 　　　⇒　列名のリストを表示して確認してみよう\nprint(agg_new.columns.to_list())\n# 1列を指定するために「変数名と集約関数」をペアで指定する必要があることがわかる\n\n# 新特徴の名前（列名）を機械的に生成してリスト化\n# 　　命名規則：元変数名_集約方法（「purchase_amount_mean」など）\nflat_cols = []\nfor col_name, func_name in agg_new.columns.to_flat_index():\n    new_name = f\"new_{col_name}_{func_name}\"\n    flat_cols.append(new_name)\n\n# MultiIndex状態の列を通常の状態に変換（列方向に平坦化）\nagg_new.columns = flat_cols\n#print(agg_new.columns.to_list())\n\n# groupby によるグループ化を解除（card_idを通常の列に戻す）\nagg_new = agg_new.reset_index()\n\n# メインテーブルの特徴と結合\ntrain_x = train_x.merge(agg_new, on=\"card_id\", how=\"left\")\ntest_x  = test_x.merge(agg_new, on=\"card_id\", how=\"left\")\n\n# Fill missing values created by the left join\ntrain_x = train_x.fillna(-1)\ntest_x  = test_x.fillna(-1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-30T08:51:15.202083Z","iopub.execute_input":"2025-06-30T08:51:15.202406Z","iopub.status.idle":"2025-06-30T08:53:05.355481Z","shell.execute_reply.started":"2025-06-30T08:51:15.202386Z","shell.execute_reply":"2025-06-30T08:53:05.354484Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ID列を削除\ntrain_x = train_x.drop(columns=[\"card_id\"])\ntest_x  = test_x.drop(columns=[\"card_id\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-30T08:53:05.356572Z","iopub.execute_input":"2025-06-30T08:53:05.356857Z","iopub.status.idle":"2025-06-30T08:53:05.387956Z","shell.execute_reply.started":"2025-06-30T08:53:05.356836Z","shell.execute_reply":"2025-06-30T08:53:05.386739Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------------------------------\n# 使用する特徴量の可視化\n# ----------------------------------------------\n\n# 各特徴の統計量\n#print(pd.concat([train_x, train_y], axis=1).describe())\n\n# 各特徴のヒストグラム\ntrain_x.hist(bins=100, color=\"blue\", grid=True, label='pandas')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-30T08:53:05.389048Z","iopub.execute_input":"2025-06-30T08:53:05.389342Z","iopub.status.idle":"2025-06-30T08:53:11.210562Z","shell.execute_reply.started":"2025-06-30T08:53:05.389321Z","shell.execute_reply":"2025-06-30T08:53:11.209673Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------------------\n# XGBoostの学習・推論・submit\n# ------------------------------\nmodel = XGBRegressor(random_state=rseed)\nmodel.fit(train_x, train_y)\npreds = model.predict(test_x)\n\n\n# # 学習データ予測 & ValidationRMSE算出（過学習の心配があるためあくまで参考程度）\n# import numpy as np\n# from sklearn.metrics import mean_squared_error\n# train_pred = model.predict(train_x)\n# rmse = np.sqrt(mean_squared_error(train_y, train_pred))\n# print(f\"Train RMSE: {rmse:.5f}\")  # Train RMSE: 3.64505\n\n# 学習データ予測 & TrainRMSE算出\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error\nfrom lightgbm import LGBMRegressor\n# データ分割\nX_train, X_valid, y_train, y_valid = train_test_split(\n    train_x, train_y, test_size=0.2, random_state=rseed\n)\n# モデル構築（n_estimatorsは700にする）\nmodel = LGBMRegressor(\n    objective='regression',\n    learning_rate=0.01,\n    num_leaves=31,\n    n_estimators=700,  \n    random_state=rseed,\n    force_row_wise=True,\n    verbose=-1\n)\n# 学習\nmodel.fit(X_train, y_train)\n# 検証予測 & RMSE算出\ny_pred = model.predict(X_valid)\nrmse = np.sqrt(mean_squared_error(y_valid, y_pred))\nprint(f\"Validation RMSE: {rmse:.5f}\")   # RMSE: 3.80265, MinRMSE: 3.79886\n\n\nsubmission = pd.DataFrame({\n    \"card_id\": test_card_id,\n    \"target\":  preds\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\n#Validation RMSE: 3.80026\n#Validation RMSE: 3.77630","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true,"execution":{"iopub.status.busy":"2025-06-30T08:53:11.211861Z","iopub.execute_input":"2025-06-30T08:53:11.212259Z","iopub.status.idle":"2025-06-30T08:53:20.691881Z","shell.execute_reply.started":"2025-06-30T08:53:11.212228Z","shell.execute_reply":"2025-06-30T08:53:20.690791Z"}},"outputs":[],"execution_count":null}]}