{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:20.552966Z","iopub.execute_input":"2026-07-25T12:36:20.553859Z","iopub.status.idle":"2026-07-25T12:36:21.929224Z","shell.execute_reply.started":"2026-07-25T12:36:20.553784Z","shell.execute_reply":"2026-07-25T12:36:21.928358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.preprocessing import OneHotEncoder\nfrom sklearn.metrics import mean_squared_log_error\n#from xgboost import XGBRegressor\n\nfrom lightgbm import LGBMRegressor\nimport lightgbm as lgb","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:21.930517Z","iopub.execute_input":"2026-07-25T12:36:21.931022Z","iopub.status.idle":"2026-07-25T12:36:26.750934Z","shell.execute_reply.started":"2026-07-25T12:36:21.930995Z","shell.execute_reply":"2026-07-25T12:36:26.750073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 1. データの読み込み\n# ============================================================\n\npath = \"/kaggle/input/competitions/Store-Sales-Forecasting/\"\n\ntrain = pd.read_csv(path + \"train.csv\")\ntest = pd.read_csv(path + \"test.csv\")\nstores = pd.read_csv(path + \"stores.csv\")\n\nprint(\"train:\", train.shape)\nprint(\"test :\", test.shape)\nprint(\"stores:\", stores.shape)\n\ndisplay(train.head())\ndisplay(stores.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:26.752007Z","iopub.execute_input":"2026-07-25T12:36:26.752285Z","iopub.status.idle":"2026-07-25T12:36:30.332726Z","shell.execute_reply.started":"2026-07-25T12:36:26.752260Z","shell.execute_reply":"2026-07-25T12:36:30.331856Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 2. 日付特徴量の作成\n# ============================================================\n\ndef add_date_features(df):\n    \"\"\"\n    date列から、曜日・年月日などの特徴量を作る関数\n    \"\"\"\n    df = df.copy()\n\n    df[\"date\"] = pd.to_datetime(df[\"date\"])\n\n    # 0=月曜日、6=日曜日\n    df[\"dayofweek\"] = df[\"date\"].dt.dayofweek\n\n    # グラフ表示用の曜日名\n    df[\"day_name\"] = df[\"date\"].dt.day_name()\n\n    df[\"year\"] = df[\"date\"].dt.year\n    df[\"month\"] = df[\"date\"].dt.month\n    df[\"day\"] = df[\"date\"].dt.day\n\n    # 土曜日・日曜日なら1\n    df[\"is_weekend\"] = (df[\"dayofweek\"] >= 5).astype(int)\n\n    return df\n\n\ntrain = add_date_features(train)\ntest = add_date_features(test)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:30.334485Z","iopub.execute_input":"2026-07-25T12:36:30.334789Z","iopub.status.idle":"2026-07-25T12:36:31.818619Z","shell.execute_reply.started":"2026-07-25T12:36:30.334760Z","shell.execute_reply":"2026-07-25T12:36:31.817735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 3. stores.csvを結合\n# ============================================================\n\ntrain_stores = train.merge(\n    stores,\n    on=\"store_nbr\",\n    how=\"left\"\n)\n\ntest_stores = test.merge(\n    stores,\n    on=\"store_nbr\",\n    how=\"left\"\n)\n\nprint(\"店舗情報結合後:\", train_stores.shape)\n\ndisplay(\n    train_stores[\n        [\n            \"date\",\n            \"store_nbr\",\n            \"family\",\n            \"sales\",\n            \"city\",\n            \"state\",\n            \"type\",\n            \"cluster\"\n        ]\n    ].head()\n)\n\n\n# 結合後に欠損値がないか確認\nprint(\"\\n店舗属性の欠損値\")\nprint(\n    train_stores[\n        [\"city\", \"state\", \"type\", \"cluster\"]\n    ].isnull().sum()\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:31.819750Z","iopub.execute_input":"2026-07-25T12:36:31.820081Z","iopub.status.idle":"2026-07-25T12:36:33.499180Z","shell.execute_reply.started":"2026-07-25T12:36:31.820048Z","shell.execute_reply":"2026-07-25T12:36:33.498264Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 4. EDA：曜日・店舗属性ごとの平均売上\n# ============================================================\n\n# ------------------------------------------------------------\n# 4-1. 曜日ごとの平均売上\n# ------------------------------------------------------------\n\nday_order = [\n    \"Monday\",\n    \"Tuesday\",\n    \"Wednesday\",\n    \"Thursday\",\n    \"Friday\",\n    \"Saturday\",\n    \"Sunday\"\n]\n\nweekday_sales = (\n    train_stores\n    .groupby(\"day_name\")[\"sales\"]\n    .mean()\n    .reindex(day_order)\n)\n\nplt.figure(figsize=(10, 6))\nweekday_sales.plot(kind=\"bar\")\n\nplt.title(\"Average Sales by Day of Week\")\nplt.xlabel(\"Day of Week\")\nplt.ylabel(\"Average Sales\")\nplt.xticks(rotation=45)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:33.500347Z","iopub.execute_input":"2026-07-25T12:36:33.500704Z","iopub.status.idle":"2026-07-25T12:36:34.012846Z","shell.execute_reply.started":"2026-07-25T12:36:33.500677Z","shell.execute_reply":"2026-07-25T12:36:34.011979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------------------------------------------------\n# 4-2. 店舗タイプごとの平均売上\n# ------------------------------------------------------------\n\ntype_sales = (\n    train_stores\n    .groupby(\"type\")[\"sales\"]\n    .mean()\n    .sort_values(ascending=False)\n)\n\nplt.figure(figsize=(8, 5))\ntype_sales.plot(kind=\"bar\")\n\nplt.title(\"Average Sales by Store Type\")\nplt.xlabel(\"Store Type\")\nplt.ylabel(\"Average Sales\")\nplt.xticks(rotation=0)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:34.013901Z","iopub.execute_input":"2026-07-25T12:36:34.014141Z","iopub.status.idle":"2026-07-25T12:36:34.324926Z","shell.execute_reply.started":"2026-07-25T12:36:34.014118Z","shell.execute_reply":"2026-07-25T12:36:34.324090Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------------------------------------------------\n# 4-3. クラスターごとの平均売上\n# ------------------------------------------------------------\n\ncluster_sales = (\n    train_stores\n    .groupby(\"cluster\")[\"sales\"]\n    .mean()\n    .sort_values(ascending=False)\n)\n\nplt.figure(figsize=(11, 5))\ncluster_sales.plot(kind=\"bar\")\n\nplt.title(\"Average Sales by Store Cluster\")\nplt.xlabel(\"Cluster\")\nplt.ylabel(\"Average Sales\")\nplt.xticks(rotation=0)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:34.325979Z","iopub.execute_input":"2026-07-25T12:36:34.326311Z","iopub.status.idle":"2026-07-25T12:36:34.595611Z","shell.execute_reply.started":"2026-07-25T12:36:34.326282Z","shell.execute_reply":"2026-07-25T12:36:34.594881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------------------------------------------------\n# 4-4. 都市ごとの平均売上\n# ------------------------------------------------------------\n\ncity_sales = (\n    train_stores\n    .groupby(\"city\")[\"sales\"]\n    .mean()\n    .sort_values(ascending=False)\n)\n\nplt.figure(figsize=(13, 6))\ncity_sales.plot(kind=\"bar\")\n\nplt.title(\"Average Sales by City\")\nplt.xlabel(\"City\")\nplt.ylabel(\"Average Sales\")\nplt.xticks(rotation=70)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:34.596564Z","iopub.execute_input":"2026-07-25T12:36:34.596880Z","iopub.status.idle":"2026-07-25T12:36:35.052319Z","shell.execute_reply.started":"2026-07-25T12:36:34.596843Z","shell.execute_reply":"2026-07-25T12:36:35.051297Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------------------------------------------------\n# 4-5. 州ごとの平均売上\n# ------------------------------------------------------------\n\nstate_sales = (\n    train_stores\n    .groupby(\"state\")[\"sales\"]\n    .mean()\n    .sort_values(ascending=False)\n)\n\nplt.figure(figsize=(12, 6))\nstate_sales.plot(kind=\"bar\")\n\nplt.title(\"Average Sales by State\")\nplt.xlabel(\"State\")\nplt.ylabel(\"Average Sales\")\nplt.xticks(rotation=70)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:35.055024Z","iopub.execute_input":"2026-07-25T12:36:35.055329Z","iopub.status.idle":"2026-07-25T12:36:35.483713Z","shell.execute_reply.started":"2026-07-25T12:36:35.055300Z","shell.execute_reply":"2026-07-25T12:36:35.482855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------------------------------------------------\n# 4-6. 店舗ごとの売上一覧\n# ------------------------------------------------------------\n\nstore_summary = (\n    train_stores\n    .groupby(\"store_nbr\")\n    .agg(\n        total_sales=(\"sales\", \"sum\"),\n        avg_sales=(\"sales\", \"mean\"),\n        city=(\"city\", \"first\"),\n        state=(\"state\", \"first\"),\n        type=(\"type\", \"first\"),\n        cluster=(\"cluster\", \"first\")\n    )\n    .reset_index()\n    .sort_values(\"total_sales\", ascending=False)\n)\n\ndisplay(store_summary.head(15))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:35.484831Z","iopub.execute_input":"2026-07-25T12:36:35.485151Z","iopub.status.idle":"2026-07-25T12:36:36.215065Z","shell.execute_reply.started":"2026-07-25T12:36:35.485115Z","shell.execute_reply":"2026-07-25T12:36:36.214173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 5. 学習用・検証用データに時系列で分割\n# ============================================================\n\n# 最後の60日間を検証データにする\nvalidation_days = 60\n\nvalidation_start = (\n    train_stores[\"date\"].max()\n    - pd.Timedelta(days=validation_days - 1)\n)\n\ntrain_part = train_stores[\n    train_stores[\"date\"] < validation_start\n].copy()\n\nvalid_part = train_stores[\n    train_stores[\"date\"] >= validation_start\n].copy()\n\nprint(\"学習期間:\")\nprint(train_part[\"date\"].min(), \"～\", train_part[\"date\"].max())\n\nprint(\"\\n検証期間:\")\nprint(valid_part[\"date\"].min(), \"～\", valid_part[\"date\"].max())\n\nprint(\"\\n学習データ数:\", len(train_part))\nprint(\"検証データ数:\", len(valid_part))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:36.216298Z","iopub.execute_input":"2026-07-25T12:36:36.216921Z","iopub.status.idle":"2026-07-25T12:36:37.537919Z","shell.execute_reply.started":"2026-07-25T12:36:36.216879Z","shell.execute_reply":"2026-07-25T12:36:37.537140Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 計算量削減\n# ============================================================\n\n# train.csvは約300万行あるため、\n# 初回の実験では学習データを最大80万行に制限する\n#\n# PCやKaggleのメモリに余裕がある場合は、\n# MAX_TRAIN_ROWS = None に変更する\n\nMAX_TRAIN_ROWS = 800_000\n\nif (\n    MAX_TRAIN_ROWS is not None\n    and len(train_part) > MAX_TRAIN_ROWS\n):\n    train_part_model = train_part.sample(\n        n=MAX_TRAIN_ROWS,\n        random_state=42\n    )\nelse:\n    train_part_model = train_part.copy()\n\nprint(\"実際にモデル学習に使う行数:\", len(train_part_model))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:37.538934Z","iopub.execute_input":"2026-07-25T12:36:37.539234Z","iopub.status.idle":"2026-07-25T12:36:38.249219Z","shell.execute_reply.started":"2026-07-25T12:36:37.539209Z","shell.execute_reply":"2026-07-25T12:36:38.248439Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 6. RMSLEを計算する関数\n# ============================================================\n\ndef calculate_rmsle(y_true, y_pred):\n    \"\"\"\n    RMSLEを計算する関数\n    予測値が負になった場合は0に直す\n    \"\"\"\n    y_pred = np.clip(y_pred, 0, None)\n\n    return np.sqrt(\n        mean_squared_log_error(y_true, y_pred)\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:38.250355Z","iopub.execute_input":"2026-07-25T12:36:38.250754Z","iopub.status.idle":"2026-07-25T12:36:38.255158Z","shell.execute_reply.started":"2026-07-25T12:36:38.250716Z","shell.execute_reply":"2026-07-25T12:36:38.254464Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 7. モデルを学習・評価する関数\n# ============================================================\n\ndef train_and_evaluate_model(\n    train_df,\n    valid_df,\n    numeric_features,\n    categorical_features,\n    model_name\n):\n    \"\"\"\n    One-Hot Encoding\n        ↓\n    LightGBMによる学習\n        ↓\n    RMSLE計算\n    \"\"\"\n\n    feature_columns = (\n        numeric_features\n        + categorical_features\n    )\n\n    X_train = train_df[feature_columns]\n    X_valid = valid_df[feature_columns]\n\n    # 売上にlog1pを適用\n    y_train_log = np.log1p(\n        train_df[\"sales\"]\n    )\n\n    y_valid = valid_df[\"sales\"]\n\n    # カテゴリ変数をOne-Hot Encoding\n    preprocessor = ColumnTransformer(\n        transformers=[\n            (\n                \"category\",\n                OneHotEncoder(\n                    handle_unknown=\"ignore\",\n                    sparse_output=True\n                ),\n                categorical_features\n            ),\n            (\n                \"number\",\n                \"passthrough\",\n                numeric_features\n            )\n        ]\n    )\n\n    print(f\"\\n{model_name}: 前処理中\")\n\n    X_train_encoded = (\n        preprocessor.fit_transform(X_train)\n    )\n\n    X_valid_encoded = (\n        preprocessor.transform(X_valid)\n    )\n\n    print(\n        \"変換後の学習データ形状:\",\n        X_train_encoded.shape\n    )\n\n    # LightGBMモデル\n    model = LGBMRegressor(\n        objective=\"regression\",\n\n        n_estimators=1000,\n        learning_rate=0.05,\n\n        num_leaves=31,\n        max_depth=-1,\n        min_child_samples=20,\n\n        subsample=0.8,\n        colsample_bytree=0.8,\n\n        reg_alpha=0.0,\n        reg_lambda=0.0,\n\n        random_state=42,\n        n_jobs=-1,\n        verbosity=-1\n    )\n\n    print(f\"{model_name}: 学習中\")\n\n    model.fit(\n        X_train_encoded,\n        y_train_log,\n\n        eval_set=[\n            (\n                X_valid_encoded,\n                np.log1p(y_valid)\n            )\n        ],\n\n        eval_metric=\"rmse\",\n\n        callbacks=[\n            lgb.early_stopping(\n                stopping_rounds=50,\n                verbose=False\n            ),\n            lgb.log_evaluation(\n                period=0\n            )\n        ]\n    )\n\n    # log予測値を元の売上に戻す\n    valid_pred_log = model.predict(\n        X_valid_encoded\n    )\n\n    valid_pred = np.expm1(\n        valid_pred_log\n    )\n\n    # 負の売上予測を0にする\n    valid_pred = np.clip(\n        valid_pred,\n        0,\n        None\n    )\n\n    rmsle = calculate_rmsle(\n        y_valid,\n        valid_pred\n    )\n\n    print(\n        f\"{model_name} RMSLE: \"\n        f\"{rmsle:.5f}\"\n    )\n\n    print(\n        \"最適な木の本数:\",\n        model.best_iteration_\n    )\n\n    return {\n        \"name\": model_name,\n        \"model\": model,\n        \"preprocessor\": preprocessor,\n        \"prediction\": valid_pred,\n        \"rmsle\": rmsle,\n        \"numeric_features\": numeric_features,\n        \"categorical_features\": categorical_features\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:38.256432Z","iopub.execute_input":"2026-07-25T12:36:38.256890Z","iopub.status.idle":"2026-07-25T12:36:38.276057Z","shell.execute_reply.started":"2026-07-25T12:36:38.256842Z","shell.execute_reply":"2026-07-25T12:36:38.275174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 8. モデル1：曜日情報だけ\n# ============================================================\n\nweekday_numeric_features = [\n    \"dayofweek\",\n    \"month\",\n    \"day\",\n    \"is_weekend\",\n    \"onpromotion\"\n]\n\n# 商品カテゴリと店舗番号は、\n# 予測対象を区別するために最低限入れる\nweekday_categorical_features = [\n    \"store_nbr\",\n    \"family\"\n]\n\nweekday_result = train_and_evaluate_model(\n    train_df=train_part_model,\n    valid_df=valid_part,\n    numeric_features=weekday_numeric_features,\n    categorical_features=weekday_categorical_features,\n    model_name=\"曜日のみモデル\"\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:38.277206Z","iopub.execute_input":"2026-07-25T12:36:38.277734Z","iopub.status.idle":"2026-07-25T12:36:59.867189Z","shell.execute_reply.started":"2026-07-25T12:36:38.277691Z","shell.execute_reply":"2026-07-25T12:36:59.866339Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 9. モデル2：曜日＋stores.csvの店舗情報\n# ============================================================\n\nstore_numeric_features = [\n    \"dayofweek\",\n    \"month\",\n    \"day\",\n    \"is_weekend\",\n    \"onpromotion\"\n]\n\nstore_categorical_features = [\n    \"store_nbr\",\n    \"family\",\n    \"city\",\n    \"state\",\n    \"type\",\n    \"cluster\"\n]\n\nstore_result = train_and_evaluate_model(\n    train_df=train_part_model,\n    valid_df=valid_part,\n    numeric_features=store_numeric_features,\n    categorical_features=store_categorical_features,\n    model_name=\"曜日＋店舗属性モデル\"\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:36:59.868428Z","iopub.execute_input":"2026-07-25T12:36:59.868787Z","iopub.status.idle":"2026-07-25T12:37:25.165572Z","shell.execute_reply.started":"2026-07-25T12:36:59.868758Z","shell.execute_reply":"2026-07-25T12:37:25.164640Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 10. 2モデルのRMSLE比較\n# ============================================================\n\nscore_comparison = pd.DataFrame({\n    \"model\": [\n        weekday_result[\"name\"],\n        store_result[\"name\"]\n    ],\n    \"RMSLE\": [\n        weekday_result[\"rmsle\"],\n        store_result[\"rmsle\"]\n    ]\n})\n\ndisplay(score_comparison)\n\nplt.figure(figsize=(8, 5))\nplt.bar(\n    score_comparison[\"model\"],\n    score_comparison[\"RMSLE\"]\n)\n\nplt.title(\"RMSLE Comparison\")\nplt.xlabel(\"Model\")\nplt.ylabel(\"RMSLE\")\nplt.xticks(rotation=15)\nplt.tight_layout()\nplt.show()\n\n\ndifference = (\n    weekday_result[\"rmsle\"]\n    - store_result[\"rmsle\"]\n)\n\nimprovement_rate = (\n    difference\n    / weekday_result[\"rmsle\"]\n    * 100\n)\n\nprint(\"RMSLEの差:\", round(difference, 5))\nprint(\"改善率:\", round(improvement_rate, 2), \"%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:37:25.166716Z","iopub.execute_input":"2026-07-25T12:37:25.167087Z","iopub.status.idle":"2026-07-25T12:37:25.323104Z","shell.execute_reply.started":"2026-07-25T12:37:25.167059Z","shell.execute_reply":"2026-07-25T12:37:25.322218Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 11. 全特徴量の重要度を取得\n# ============================================================\n\npreprocessor = store_result[\"preprocessor\"]\nmodel = store_result[\"model\"]\n\nfeature_names = preprocessor.get_feature_names_out()\nfeature_importances = model.feature_importances_\n\nimportance_df = pd.DataFrame({\n    \"feature\": feature_names,\n    \"importance\": feature_importances\n})\n\nimportance_df = importance_df.sort_values(\n    \"importance\",\n    ascending=False\n).reset_index(drop=True)\n\nprint(\"特徴量重要度 上位30個\")\ndisplay(importance_df.head(30))\n\n\n# 上位30個をグラフ表示\ntop_n = 30\n\ntop_importance = (\n    importance_df\n    .head(top_n)\n    .sort_values(\"importance\")\n)\n\nplt.figure(figsize=(10, 10))\n\nplt.barh(\n    top_importance[\"feature\"],\n    top_importance[\"importance\"]\n)\n\nplt.title(\"Top 30 Feature Importances\")\nplt.xlabel(\"Importance\")\nplt.ylabel(\"Feature\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:37:25.324197Z","iopub.execute_input":"2026-07-25T12:37:25.324575Z","iopub.status.idle":"2026-07-25T12:37:25.737623Z","shell.execute_reply.started":"2026-07-25T12:37:25.324548Z","shell.execute_reply":"2026-07-25T12:37:25.736676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 12. stores.csvの各指標ごとに重要度を合計\n# ============================================================\n\ndef classify_store_feature(feature_name):\n    \"\"\"\n    One-Hot Encoding後の列名を、\n    city・state・type・clusterなどの元の指標に戻す\n    \"\"\"\n\n    if \"city_\" in feature_name:\n        return \"city\"\n\n    elif \"state_\" in feature_name:\n        return \"state\"\n\n    elif \"type_\" in feature_name:\n        return \"type\"\n\n    elif \"cluster_\" in feature_name:\n        return \"cluster\"\n\n    elif \"store_nbr_\" in feature_name:\n        return \"store_nbr\"\n\n    elif \"family_\" in feature_name:\n        return \"family\"\n\n    elif \"dayofweek\" in feature_name:\n        return \"dayofweek\"\n\n    elif \"month\" in feature_name:\n        return \"month\"\n\n    elif \"is_weekend\" in feature_name:\n        return \"is_weekend\"\n\n    elif \"onpromotion\" in feature_name:\n        return \"onpromotion\"\n\n    elif feature_name.endswith(\"day\"):\n        return \"day\"\n\n    else:\n        return \"other\"\n\n\nimportance_df[\"feature_group\"] = (\n    importance_df[\"feature\"]\n    .apply(classify_store_feature)\n)\n\ngroup_importance = (\n    importance_df\n    .groupby(\"feature_group\", as_index=False)[\"importance\"]\n    .sum()\n    .sort_values(\"importance\", ascending=False)\n)\n\nprint(\"元の指標ごとに合計した特徴量重要度\")\ndisplay(group_importance)\n\n\n# stores.csvの指標だけ取り出す\nstore_attribute_importance = (\n    group_importance[\n        group_importance[\"feature_group\"].isin(\n            [\n                \"city\",\n                \"state\",\n                \"type\",\n                \"cluster\"\n            ]\n        )\n    ]\n    .sort_values(\"importance\", ascending=False)\n)\n\ndisplay(store_attribute_importance)\n\nplt.figure(figsize=(8, 5))\n\nplt.bar(\n    store_attribute_importance[\"feature_group\"],\n    store_attribute_importance[\"importance\"]\n)\n\nplt.title(\"Importance of Store Attributes\")\nplt.xlabel(\"Store Attribute\")\nplt.ylabel(\"Total Importance\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:37:25.738872Z","iopub.execute_input":"2026-07-25T12:37:25.739228Z","iopub.status.idle":"2026-07-25T12:37:25.906933Z","shell.execute_reply.started":"2026-07-25T12:37:25.739190Z","shell.execute_reply":"2026-07-25T12:37:25.906146Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 13. city・state・type・clusterの各カテゴリを確認\n# ============================================================\n\ndef show_category_importance(\n    importance_data,\n    category_name,\n    top_n=20\n):\n    \"\"\"\n    例：\n    city_Quito、city_Guayaquilなど、\n    カテゴリ内の重要度を表示する\n    \"\"\"\n\n    category_data = importance_data[\n        importance_data[\"feature\"]\n        .str.contains(\n            f\"{category_name}_\",\n            regex=False\n        )\n    ].copy()\n\n    category_data = (\n        category_data\n        .sort_values(\"importance\", ascending=False)\n        .head(top_n)\n    )\n\n    display(category_data)\n\n    category_plot = category_data.sort_values(\n        \"importance\"\n    )\n\n    plt.figure(figsize=(10, 7))\n\n    plt.barh(\n        category_plot[\"feature\"],\n        category_plot[\"importance\"]\n    )\n\n    plt.title(\n        f\"Feature Importance: {category_name}\"\n    )\n    plt.xlabel(\"Importance\")\n    plt.ylabel(\"Feature\")\n    plt.tight_layout()\n    plt.show()\n\n\n# 店舗タイプ別\nshow_category_importance(\n    importance_df,\n    \"type\",\n    top_n=10\n)\n\n# クラスター別\nshow_category_importance(\n    importance_df,\n    \"cluster\",\n    top_n=17\n)\n\n# 都市別\nshow_category_importance(\n    importance_df,\n    \"city\",\n    top_n=22\n)\n\n# 州別\nshow_category_importance(\n    importance_df,\n    \"state\",\n    top_n=16\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:37:25.908033Z","iopub.execute_input":"2026-07-25T12:37:25.908378Z","iopub.status.idle":"2026-07-25T12:37:27.040120Z","shell.execute_reply.started":"2026-07-25T12:37:25.908342Z","shell.execute_reply":"2026-07-25T12:37:27.039383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 14. 検証データの実測値と予測値を比較\n# ============================================================\n\nprediction_comparison = valid_part[\n    [\n        \"date\",\n        \"store_nbr\",\n        \"family\",\n        \"sales\"\n    ]\n].copy()\n\nprediction_comparison[\"weekday_prediction\"] = (\n    weekday_result[\"prediction\"]\n)\n\nprediction_comparison[\"store_prediction\"] = (\n    store_result[\"prediction\"]\n)\n\ndisplay(prediction_comparison.head(20))\n\n\n# 日付ごとに合計して比較\ndaily_prediction = (\n    prediction_comparison\n    .groupby(\"date\")\n    .agg(\n        actual_sales=(\"sales\", \"sum\"),\n        weekday_prediction=(\n            \"weekday_prediction\",\n            \"sum\"\n        ),\n        store_prediction=(\n            \"store_prediction\",\n            \"sum\"\n        )\n    )\n    .reset_index()\n)\n\nplt.figure(figsize=(15, 6))\n\nplt.plot(\n    daily_prediction[\"date\"],\n    daily_prediction[\"actual_sales\"],\n    label=\"Actual\"\n)\n\nplt.plot(\n    daily_prediction[\"date\"],\n    daily_prediction[\"weekday_prediction\"],\n    label=\"Weekday Model\"\n)\n\nplt.plot(\n    daily_prediction[\"date\"],\n    daily_prediction[\"store_prediction\"],\n    label=\"Weekday + Store Model\"\n)\n\nplt.title(\"Actual Sales and Predicted Sales\")\nplt.xlabel(\"Date\")\nplt.ylabel(\"Total Sales\")\nplt.legend()\nplt.grid()\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:37:27.041165Z","iopub.execute_input":"2026-07-25T12:37:27.041602Z","iopub.status.idle":"2026-07-25T12:37:27.375200Z","shell.execute_reply.started":"2026-07-25T12:37:27.041574Z","shell.execute_reply":"2026-07-25T12:37:27.374150Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 15. 提出用LightGBMモデルを全期間データで再学習\n# ============================================================\nfrom lightgbm import LGBMRegressor\n# 使用する学習データの最大行数\n# 全データを使う場合は None に変更\nMAX_FINAL_TRAIN_ROWS = 1_000_000\n\nif (\n    MAX_FINAL_TRAIN_ROWS is not None\n    and len(train_stores) > MAX_FINAL_TRAIN_ROWS\n):\n    final_train = train_stores.sample(\n        n=MAX_FINAL_TRAIN_ROWS,\n        random_state=42\n    ).copy()\nelse:\n    final_train = train_stores.copy()\n\n\n# 使用する特徴量\nfinal_numeric_features = store_numeric_features\n\nfinal_categorical_features = store_categorical_features\n\nfinal_feature_columns = (\n    final_numeric_features\n    + final_categorical_features\n)\n\n\n# 学習用の説明変数\nX_final_train = final_train[\n    final_feature_columns\n]\n\n\n# 目的変数\n# RMSLEに合わせてlog1p変換する\ny_final_train_log = np.log1p(\n    final_train[\"sales\"]\n)\n\n\n# test.csvの説明変数\nX_test = test_stores[\n    final_feature_columns\n]\n\n\n# ------------------------------------------------------------\n# One-Hot Encoding\n# ------------------------------------------------------------\n\nfinal_preprocessor = ColumnTransformer(\n    transformers=[\n        (\n            \"category\",\n            OneHotEncoder(\n                handle_unknown=\"ignore\",\n                sparse_output=True\n            ),\n            final_categorical_features\n        ),\n        (\n            \"number\",\n            \"passthrough\",\n            final_numeric_features\n        )\n    ]\n)\n\n\nprint(\"提出用データの前処理中\")\n\n\n# 学習データでOne-Hot Encodingを学習\nX_final_encoded = (\n    final_preprocessor.fit_transform(\n        X_final_train\n    )\n)\n\n\n# 学習データと同じ変換をtestデータに適用\nX_test_encoded = (\n    final_preprocessor.transform(\n        X_test\n    )\n)\n\n\nprint(\n    \"変換後の学習データ形状:\",\n    X_final_encoded.shape\n)\n\nprint(\n    \"変換後のテストデータ形状:\",\n    X_test_encoded.shape\n)\n\n\n# ------------------------------------------------------------\n# 検証時に求めた最適な木の本数を取得\n# ------------------------------------------------------------\n\nbest_n_estimators = getattr(\n    store_result[\"model\"],\n    \"best_iteration_\",\n    None\n)\n\n\n# best_iteration_が取得できなかった場合\nif (\n    best_n_estimators is None\n    or best_n_estimators <= 0\n):\n    best_n_estimators = 600\n\n\nprint(\n    \"使用する木の本数:\",\n    best_n_estimators\n)\n\n\n# ------------------------------------------------------------\n# LightGBMモデル\n# ------------------------------------------------------------\n\nfinal_model = LGBMRegressor(\n    objective=\"regression\",\n\n    n_estimators=best_n_estimators,\n    learning_rate=0.05,\n\n    # 木の複雑さ\n    num_leaves=31,\n    max_depth=-1,\n    min_child_samples=20,\n\n    # 過学習を抑える\n    subsample=0.8,\n    subsample_freq=1,\n    colsample_bytree=0.8,\n\n    reg_alpha=0.0,\n    reg_lambda=0.0,\n\n    # 特徴量重要度をgainで計算\n    importance_type=\"gain\",\n\n    random_state=42,\n    n_jobs=-1,\n    verbosity=-1\n)\n\n\nprint(\"提出用LightGBMモデルを学習中\")\n\n\n# 全期間データで学習\nfinal_model.fit(\n    X_final_encoded,\n    y_final_train_log\n)\n\n\nprint(\"提出用LightGBMモデルの学習完了\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:37:27.376281Z","iopub.execute_input":"2026-07-25T12:37:27.376631Z","iopub.status.idle":"2026-07-25T12:38:01.680249Z","shell.execute_reply.started":"2026-07-25T12:37:27.376601Z","shell.execute_reply":"2026-07-25T12:38:01.679479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 16. test.csvの売上を予測\n# ============================================================\n\n# log変換された売上を予測\ntest_prediction_log = final_model.predict(\n    X_test_encoded\n)\n\n\n# log1p変換を元に戻す\ntest_prediction = np.expm1(\n    test_prediction_log\n)\n\n\n# 売上が負の値にならないように0以上に制限\ntest_prediction = np.clip(\n    test_prediction,\n    0,\n    None\n)\n\n\nprint(\"予測完了\")\n\nprint(\n    \"予測件数:\",\n    len(test_prediction)\n)\n\nprint(\n    \"予測値の最小値:\",\n    test_prediction.min()\n)\n\nprint(\n    \"予測値の最大値:\",\n    test_prediction.max()\n)\n\nprint(\n    \"予測値の平均:\",\n    test_prediction.mean()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:38:01.681380Z","iopub.execute_input":"2026-07-25T12:38:01.681683Z","iopub.status.idle":"2026-07-25T12:38:02.428028Z","shell.execute_reply.started":"2026-07-25T12:38:01.681656Z","shell.execute_reply":"2026-07-25T12:38:02.427287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 17. submission.csvを作成\n# ============================================================\n\nsubmission = pd.DataFrame({\n    \"id\": test[\"id\"],\n    \"sales\": test_prediction\n})\n\n\n# Kaggleの作業フォルダに保存\nsubmission_path = (\n    \"/kaggle/working/submission.csv\"\n)\n\nsubmission.to_csv(\n    submission_path,\n    index=False\n)\n\n\nprint(\n    \"提出ファイルを作成しました:\",\n    submission_path\n)\n\nprint(\n    \"提出ファイルの形状:\",\n    submission.shape\n)\n\n\n# 最初の5行を確認\ndisplay(\n    submission.head()\n)\n\n\n# 欠損値や負の値がないか確認\nprint(\"\\n欠損値の数\")\nprint(\n    submission.isnull().sum()\n)\n\nprint(\n    \"\\n負の予測値の数:\",\n    (submission[\"sales\"] < 0).sum()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-25T12:38:02.429007Z","iopub.execute_input":"2026-07-25T12:38:02.429285Z","iopub.status.idle":"2026-07-25T12:38:02.514278Z","shell.execute_reply.started":"2026-07-25T12:38:02.429258Z","shell.execute_reply":"2026-07-25T12:38:02.513477Z"}},"outputs":[],"execution_count":null}]}