{"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":"# ==============================================================\n# 0. 事前準備\n# ==============================================================\nimport os, gc, warnings, numpy as np, pandas as pd\nfrom sklearn.preprocessing import LabelEncoder\nfrom xgboost import XGBRegressor\nwarnings.filterwarnings(\"ignore\")\n\nSEED      = 71\nDATA_DIR  = \"/kaggle/input/elo-merchant-category-recommendation\"\n\n# --------------------------------------------------------------\n# 1. メインテーブル（train / test）\n# --------------------------------------------------------------\ntrain = pd.read_csv(f\"{DATA_DIR}/train.csv\")\ntest  = pd.read_csv(f\"{DATA_DIR}/test.csv\")\n\ny      = train[\"target\"].copy()\ntrain_ = train.drop(columns=[\"target\"])\ntest_  = test.copy()\n\n# `first_active_month` を Label-Encode して数値化\nfor df in (train_, test_):\n    df[\"first_active_month\"] = df[\"first_active_month\"].fillna(\"Unknown\")\nle = LabelEncoder().fit(pd.concat([train_[\"first_active_month\"], test_[\"first_active_month\"]]))\nfor df in (train_, test_):\n    df[\"fam_label\"] = le.transform(df[\"first_active_month\"])\n    df.drop(columns=[\"first_active_month\"], inplace=True)\n\n# あとで JOIN するので card_id は残す\nbase_cols = [\"card_id\", \"feature_1\", \"feature_2\", \"feature_3\", \"fam_label\"]\n\ntrain_ = train_[base_cols].copy()\ntest_  = test_ [base_cols].copy()\n\n# 使いやすい関数をいくつか定義\ndef range_ptp(x):        # (最大−最小)\n    return x.max() - x.min()\n\ndef quartile25(x):\n    return x.quantile(0.25)\n\ndef quartile75(x):\n    return x.quantile(0.75)\n\n# --------------------------------------------------------------\n# 2. 取引テーブル（historical / new）を読む\n# --------------------------------------------------------------\nusecols = [                # 最小限に絞ってメモリ節約\n    \"card_id\", \"authorized_flag\",\n    \"category_1\", \"category_2\", \"category_3\",\n    \"city_id\", \"state_id\", \"subsector_id\",\n    \"merchant_id\", \"merchant_category_id\",\n    \"purchase_amount\", \"installments\", \"month_lag\",\n    \"purchase_date\"\n]\n\nhist_trans = pd.read_csv(f\"{DATA_DIR}/historical_transactions.csv\",\n                         usecols=usecols, parse_dates=[\"purchase_date\"])\nnew_trans  = pd.read_csv(f\"{DATA_DIR}/new_merchant_transactions.csv\",\n                         usecols=usecols, parse_dates=[\"purchase_date\"])\n\n# 3. 前処理：使いやすい数値型に変換\n# ================================================\nfor df in (hist_trans, new_trans):\n\n    # フラグを 0/1 に\n    df[\"authorized_flag\"] = (df[\"authorized_flag\"] == \"Y\").astype(np.int8)\n    df[\"category_1\"]      = (df[\"category_1\"]      == \"Y\").astype(np.int8)\n\n    # purchase_date → Unix time（秒）\n    df[\"purchase_dt\"] = df[\"purchase_date\"].astype(np.int64) // 10**9\n\n    # category_2 : 数値 (1–5) + NaN\n    df[\"category_2\"] = df[\"category_2\"].fillna(-1).astype(np.int8)\n\n    # ----- ★ 修正ポイント ★ -----\n    # category_3 : 'A','B','C' → 0,1,2 へマッピング\n    cat3_map = {\"A\": 0, \"B\": 1, \"C\": 2}\n    df[\"category_3\"] = df[\"category_3\"].map(cat3_map).fillna(-1).astype(np.int8)\n\n    # installments : -1 を 0（⟷一括払い）とみなす\n    df[\"installments\"] = df[\"installments\"].replace(-1, 0).astype(np.int16)\n\n# --------------------------------------------------------------\n# 4. 集約関数を定義\n#    ※ min / max / count 以外も大量投入！\n# --------------------------------------------------------------\nagg_dict = {\n    # 数値系\n    \"purchase_amount\":  [\"min\", \"max\", \"mean\", \"sum\", \"std\", \"var\", range_ptp,\n                         quartile25, quartile75],\n    \"installments\":     [\"min\", \"max\", \"mean\", \"sum\", \"std\", \"var\"],\n    \"month_lag\":        [\"min\", \"max\", \"mean\", \"std\", skew := \"skew\"],\n    \"purchase_dt\":      [\"min\", \"max\", range_ptp],\n    # カテゴリ系 → 集計すると「多様性」を表せる\n    \"authorized_flag\":  [\"mean\"],                  # 承認率\n    \"category_1\":       [\"mean\"],                  # C1=1 の比率\n    \"category_2\":       [\"nunique\", quartile75],   # 分布の粗さ + 値\n    \"category_3\":       [\"nunique\"],\n    \"merchant_id\":      [\"nunique\"],\n    \"merchant_category_id\": [\"nunique\"],\n    \"city_id\":          [\"nunique\"],\n    \"state_id\":         [\"nunique\"],\n    \"subsector_id\":     [\"nunique\"],\n    \"purchase_date\":    [\"count\"],                 # 取引件数\n}\n\n# --------------------------------------------------------------\n# 5. 「historical」「new」別に集約し、列名に接頭辞を付ける\n# --------------------------------------------------------------\ndef aggregate_transactions(df, prefix):\n    \"\"\"card_id ごとに集約し、列名を prefix_... にする\"\"\"\n    agg = (df\n           .groupby(\"card_id\")\n           .agg(agg_dict))\n    # MultiIndex → 1階層化\n    agg.columns = [f\"{prefix}_{c[0]}_{c[1]}\" for c in agg.columns.to_flat_index()]\n    return agg.reset_index()\n\nhist_agg = aggregate_transactions(hist_trans, \"hist\")\nnew_agg  = aggregate_transactions(new_trans,  \"new\")\n\ndel hist_trans, new_trans\ngc.collect()\n\n# --------------------------------------------------------------\n# 6. メインテーブルと結合   ★修正版★\n# --------------------------------------------------------------\n# train データ\ntrain_ = train_.merge(hist_agg, on=\"card_id\", how=\"left\")\ntrain_ = train_.merge(new_agg,  on=\"card_id\", how=\"left\")\n\n# test データ\ntest_  = test_.merge(hist_agg,  on=\"card_id\", how=\"left\")\ntest_  = test_.merge(new_agg,   on=\"card_id\", how=\"left\")\n\n# 欠損値を -1 で埋める\nfor df in (train_, test_):\n    df.fillna(-1, inplace=True)\n\nprint(\"最終的な特徴量数:\", train_.shape[1] - 1)  # card_id を除いた数\n\n# --------------------------------------------------------------\n# 7. 学習・推論（シンプルなワンショット）\n#    ※ CV／パラメータ調整は必ず行ってください\n# --------------------------------------------------------------\nmodel = XGBRegressor(\n    n_estimators=800,\n    learning_rate=0.03,\n    max_depth=8,\n    subsample=0.9,\n    colsample_bytree=0.8,\n    reg_lambda=3,\n    random_state=SEED,\n    tree_method=\"hist\",\n    eval_metric=\"rmse\"\n)\nmodel.fit(train_.drop(columns=[\"card_id\"]), y)\n\npred = model.predict(test_.drop(columns=[\"card_id\"]))\n\npd.DataFrame({\n    \"card_id\": test_[\"card_id\"],\n    \"target\":  pred\n}).to_csv(\"submission.csv\", index=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-30T12:19:19.457652Z","iopub.execute_input":"2025-06-30T12:19:19.458004Z","iopub.status.idle":"2025-06-30T12:21:40.915712Z","shell.execute_reply.started":"2025-06-30T12:19:19.457969Z","shell.execute_reply":"2025-06-30T12:21:40.913592Z"}},"outputs":[],"execution_count":null}]}