{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"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\n# for 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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:12.309502Z","iopub.execute_input":"2025-12-12T05:27:12.309806Z","iopub.status.idle":"2025-12-12T05:27:12.316571Z","shell.execute_reply.started":"2025-12-12T05:27:12.309784Z","shell.execute_reply":"2025-12-12T05:27:12.314658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.multioutput import MultiOutputRegressor\nfrom sklearn.model_selection import KFold\nfrom catboost import CatBoostRegressor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:44:03.237333Z","iopub.execute_input":"2025-12-12T05:44:03.237639Z","iopub.status.idle":"2025-12-12T05:44:04.554197Z","shell.execute_reply.started":"2025-12-12T05:44:03.237618Z","shell.execute_reply":"2025-12-12T05:44:04.553252Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CONFIG","metadata":{}},{"cell_type":"code","source":"TRAIN_PATH = '/kaggle/input/csiro-biomass/train.csv'\nTEST_PATH  = '/kaggle/input/csiro-biomass/test.csv'\nSUB_PATH   = \"submission.csv\"\nRANDOM_STATE = 42\nN_FOLDS = 5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:27.315890Z","iopub.execute_input":"2025-12-12T05:27:27.316200Z","iopub.status.idle":"2025-12-12T05:27:27.321563Z","shell.execute_reply.started":"2025-12-12T05:27:27.316180Z","shell.execute_reply":"2025-12-12T05:27:27.320170Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGETS = [\"Dry_Clover_g\", \"Dry_Dead_g\", \"Dry_Green_g\", \"Dry_Total_g\", \"GDM_g\"]\nTARGET_WEIGHTS = {\n    \"Dry_Green_g\": 0.1,\n    \"Dry_Dead_g\": 0.1,\n    \"Dry_Clover_g\": 0.1,\n    \"GDM_g\": 0.2,\n    \"Dry_Total_g\": 0.5,\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:32.220365Z","iopub.execute_input":"2025-12-12T05:27:32.221126Z","iopub.status.idle":"2025-12-12T05:27:32.226553Z","shell.execute_reply.started":"2025-12-12T05:27:32.221096Z","shell.execute_reply":"2025-12-12T05:27:32.225275Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1. HELPER: SEASON (SOUTHERN HEMISPHERE)","metadata":{}},{"cell_type":"code","source":"def get_season_southern(month: int) -> str:\n    if month in [12, 1, 2]:\n        return \"Summer\"\n    elif month in [3, 4, 5]:\n        return \"Fall\"\n    elif month in [6, 7, 8]:\n        return \"Winter\"\n    else:  # 9, 10, 11\n        return \"Spring\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:34.801483Z","iopub.execute_input":"2025-12-12T05:27:34.801786Z","iopub.status.idle":"2025-12-12T05:27:34.807314Z","shell.execute_reply.started":"2025-12-12T05:27:34.801766Z","shell.execute_reply":"2025-12-12T05:27:34.806235Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. HELPER: CLIMATE (TEMP + RAIN) – BOM-BASED","metadata":{}},{"cell_type":"code","source":"# Official-like seasonal temps (°C) for state capitals (BOM norms, rounded)\nstate_season_temp = {\n    (\"NSW\",\"Winter\"):12.3, (\"NSW\",\"Spring\"):16.9, (\"NSW\",\"Summer\"):22.1, (\"NSW\",\"Fall\"):17.3,\n    (\"Vic\",\"Winter\"):10.0, (\"Vic\",\"Spring\"):14.3, (\"Vic\",\"Summer\"):20.4, (\"Vic\",\"Fall\"):15.6,\n    (\"QLD\",\"Winter\"):15.7, (\"QLD\",\"Spring\"):20.8, (\"QLD\",\"Summer\"):25.6, (\"QLD\",\"Fall\"):21.2,\n    (\"SA\", \"Winter\"):12.0, (\"SA\", \"Spring\"):16.3, (\"SA\", \"Summer\"):22.8, (\"SA\", \"Fall\"):17.4,\n    (\"WA\", \"Winter\"):13.1, (\"WA\", \"Spring\"):17.4, (\"WA\", \"Summer\"):23.7, (\"WA\", \"Fall\"):18.2,\n    (\"Tas\",\"Winter\"): 8.6, (\"Tas\",\"Spring\"):12.5, (\"Tas\",\"Summer\"):17.5, (\"Tas\",\"Fall\"):13.0,\n    (\"NT\", \"Winter\"):25.4, (\"NT\",\"Spring\"):28.5, (\"NT\",\"Summer\"):29.3, (\"NT\",\"Fall\"):27.7,\n    (\"ACT\",\"Winter\"): 6.5, (\"ACT\",\"Spring\"):12.1, (\"ACT\",\"Summer\"):20.2, (\"ACT\",\"Fall\"):14.7,\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:37.567399Z","iopub.execute_input":"2025-12-12T05:27:37.568103Z","iopub.status.idle":"2025-12-12T05:27:37.576746Z","shell.execute_reply.started":"2025-12-12T05:27:37.568073Z","shell.execute_reply":"2025-12-12T05:27:37.575315Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fallback seasonal mean temp (if unknown state)\nseason_mean_temp = {\n    \"Winter\": 12.0,\n    \"Spring\": 17.0,\n    \"Summer\": 23.0,\n    \"Fall\":   16.0,\n}\n\ndef map_state_season_temp(row):\n    key = (row[\"State\"], row[\"Season\"])\n    if key in state_season_temp:\n        return state_season_temp[key]\n    return season_mean_temp[row[\"Season\"]]\n\n# Seasonal rainfall (mm) for capitals (BOM norms, rounded)\nstate_season_rain = {\n    (\"NSW\",\"Winter\"):342, (\"NSW\",\"Spring\"):267, (\"NSW\",\"Summer\"):275, (\"NSW\",\"Fall\"):367,\n    (\"Vic\",\"Winter\"):132, (\"Vic\",\"Spring\"):167, (\"Vic\",\"Summer\"):136, (\"Vic\",\"Fall\"):151,\n    (\"QLD\",\"Winter\"): 56, (\"QLD\",\"Spring\"):171, (\"QLD\",\"Summer\"):420, (\"QLD\",\"Fall\"):246,\n    (\"SA\", \"Winter\"):217, (\"SA\", \"Spring\"):124, (\"SA\", \"Summer\"): 56, (\"SA\", \"Fall\"):115,\n    (\"WA\", \"Winter\"):476, (\"WA\", \"Spring\"):233, (\"WA\", \"Summer\"): 40, (\"WA\", \"Fall\"):113,\n    (\"Tas\",\"Winter\"):158, (\"Tas\",\"Spring\"):170, (\"Tas\",\"Summer\"):150, (\"Tas\",\"Fall\"):168,\n    (\"NT\", \"Winter\"):  2, (\"NT\",\"Spring\"): 70, (\"NT\",\"Summer\"):1075,(\"NT\",\"Fall\"):320,\n    (\"ACT\",\"Winter\"):108, (\"ACT\",\"Spring\"):174, (\"ACT\",\"Summer\"):155, (\"ACT\",\"Fall\"):146,\n}\n\nseason_mean_rain = {\n    \"Winter\": 175,\n    \"Spring\": 180,\n    \"Summer\": 250,\n    \"Fall\":   185,\n}\n\ndef map_state_season_rain(row):\n    key = (row[\"State\"], row[\"Season\"])\n    if key in state_season_rain:\n        return state_season_rain[key]\n    return season_mean_rain[row[\"Season\"]]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:38.383912Z","iopub.execute_input":"2025-12-12T05:27:38.384230Z","iopub.status.idle":"2025-12-12T05:27:38.394302Z","shell.execute_reply.started":"2025-12-12T05:27:38.384207Z","shell.execute_reply":"2025-12-12T05:27:38.393151Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Seasonal rainfall (mm) for capitals (BOM norms, rounded)\nstate_season_rain = {\n    (\"NSW\",\"Winter\"):342, (\"NSW\",\"Spring\"):267, (\"NSW\",\"Summer\"):275, (\"NSW\",\"Fall\"):367,\n    (\"Vic\",\"Winter\"):132, (\"Vic\",\"Spring\"):167, (\"Vic\",\"Summer\"):136, (\"Vic\",\"Fall\"):151,\n    (\"QLD\",\"Winter\"): 56, (\"QLD\",\"Spring\"):171, (\"QLD\",\"Summer\"):420, (\"QLD\",\"Fall\"):246,\n    (\"SA\", \"Winter\"):217, (\"SA\", \"Spring\"):124, (\"SA\", \"Summer\"): 56, (\"SA\", \"Fall\"):115,\n    (\"WA\", \"Winter\"):476, (\"WA\", \"Spring\"):233, (\"WA\", \"Summer\"): 40, (\"WA\", \"Fall\"):113,\n    (\"Tas\",\"Winter\"):158, (\"Tas\",\"Spring\"):170, (\"Tas\",\"Summer\"):150, (\"Tas\",\"Fall\"):168,\n    (\"NT\", \"Winter\"):  2, (\"NT\",\"Spring\"): 70, (\"NT\",\"Summer\"):1075,(\"NT\",\"Fall\"):320,\n    (\"ACT\",\"Winter\"):108, (\"ACT\",\"Spring\"):174, (\"ACT\",\"Summer\"):155, (\"ACT\",\"Fall\"):146,\n}\n\nseason_mean_rain = {\n    \"Winter\": 175,\n    \"Spring\": 180,\n    \"Summer\": 250,\n    \"Fall\":   185,\n}\n\ndef map_state_season_rain(row):\n    key = (row[\"State\"], row[\"Season\"])\n    if key in state_season_rain:\n        return state_season_rain[key]\n    return season_mean_rain[row[\"Season\"]]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:40.779257Z","iopub.execute_input":"2025-12-12T05:27:40.779671Z","iopub.status.idle":"2025-12-12T05:27:40.789617Z","shell.execute_reply.started":"2025-12-12T05:27:40.779644Z","shell.execute_reply":"2025-12-12T05:27:40.787992Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. HELPER: WEIGHTED GLOBAL R²","metadata":{}},{"cell_type":"code","source":"def weighted_global_r2(y_true: np.ndarray,\n                       y_pred: np.ndarray,\n                       target_names,\n                       weights_dict) -> float:\n    \"\"\"\n    y_true, y_pred: shape (n_samples, n_targets) in ORIGINAL SCALE (grams)\n    target_names: list of target column names in order\n    weights_dict: mapping target_name -> weight\n    \"\"\"\n    y_true = np.asarray(y_true)\n    y_pred = np.asarray(y_pred)\n    \n    n_samples, n_targets = y_true.shape\n    w_per_target = np.array([weights_dict[t] for t in target_names])  # shape (n_targets,)\n    # expand to per-row weights\n    W = np.tile(w_per_target, n_samples)                # shape (n_samples * n_targets,)\n    y_true_flat = y_true.reshape(-1)\n    y_pred_flat = y_pred.reshape(-1)\n\n    # weighted mean of y_true\n    y_mean_w = np.sum(W * y_true_flat) / np.sum(W)\n\n    rss = np.sum(W * (y_true_flat - y_pred_flat) ** 2)\n    tss = np.sum(W * (y_true_flat - y_mean_w) ** 2)\n\n    return 1.0 - rss / tss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:55:43.793349Z","iopub.execute_input":"2025-12-12T05:55:43.794055Z","iopub.status.idle":"2025-12-12T05:55:43.801810Z","shell.execute_reply.started":"2025-12-12T05:55:43.794020Z","shell.execute_reply":"2025-12-12T05:55:43.800445Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. LOAD RAW DATA (LONG FORMAT)","metadata":{}},{"cell_type":"code","source":"train_long = pd.read_csv(TRAIN_PATH)\ntest_long  = pd.read_csv(TEST_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:54.440687Z","iopub.execute_input":"2025-12-12T05:27:54.441089Z","iopub.status.idle":"2025-12-12T05:27:54.459258Z","shell.execute_reply.started":"2025-12-12T05:27:54.441063Z","shell.execute_reply":"2025-12-12T05:27:54.458255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train_long.shape)\ntrain_long.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:27:56.926521Z","iopub.execute_input":"2025-12-12T05:27:56.928205Z","iopub.status.idle":"2025-12-12T05:27:56.945727Z","shell.execute_reply.started":"2025-12-12T05:27:56.928155Z","shell.execute_reply":"2025-12-12T05:27:56.943962Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(test_long.shape)\ntest_long.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:01.501443Z","iopub.execute_input":"2025-12-12T05:28:01.502175Z","iopub.status.idle":"2025-12-12T05:28:01.513763Z","shell.execute_reply.started":"2025-12-12T05:28:01.502131Z","shell.execute_reply":"2025-12-12T05:28:01.512290Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# image_id from sample_id: \"IDxxxx__Dry_Clover_g\" -> \"IDxxxx\"\ntrain_long[\"image_id\"] = train_long[\"sample_id\"].str.split(\"__\").str[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:05.469292Z","iopub.execute_input":"2025-12-12T05:28:05.469609Z","iopub.status.idle":"2025-12-12T05:28:05.478165Z","shell.execute_reply.started":"2025-12-12T05:28:05.469587Z","shell.execute_reply":"2025-12-12T05:28:05.476844Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5. PIVOT TO WIDE FORMAT (1 row per image)","metadata":{}},{"cell_type":"code","source":"# Common index columns (metadata)\nindex_cols = [\n    \"image_id\", \"image_path\", \"Sampling_Date\", \"State\", \"Species\",\n    \"Pre_GSHH_NDVI\", \"Height_Ave_cm\"\n]\n\n# TRAIN: pivot targets\ntrain_wide = train_long.pivot_table(\n    index=index_cols,\n    columns=\"target_name\",\n    values=\"target\"\n).reset_index()\n\n# Ensure target columns order\ntrain_wide = train_wide[index_cols + TARGETS]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:07.548198Z","iopub.execute_input":"2025-12-12T05:28:07.548525Z","iopub.status.idle":"2025-12-12T05:28:07.571433Z","shell.execute_reply.started":"2025-12-12T05:28:07.548501Z","shell.execute_reply":"2025-12-12T05:28:07.570130Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_wide.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:09.730164Z","iopub.execute_input":"2025-12-12T05:28:09.730507Z","iopub.status.idle":"2025-12-12T05:28:09.746573Z","shell.execute_reply.started":"2025-12-12T05:28:09.730482Z","shell.execute_reply":"2025-12-12T05:28:09.745591Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6. FEATURE ENGINEERING (TIME + CLIMATE + INTERACTIONS)","metadata":{}},{"cell_type":"code","source":"def add_features(df):\n    # parse date\n    df[\"Sampling_Date\"] = pd.to_datetime(df[\"Sampling_Date\"])\n    df[\"Month\"] = df[\"Sampling_Date\"].dt.month\n    df[\"DayOfYear\"] = df[\"Sampling_Date\"].dt.dayofyear\n\n    # Season (Southern hemisphere)\n    df[\"Season\"] = df[\"Month\"].apply(get_season_southern)\n\n    # Temperature features\n    df[\"State_Season_temp\"] = df.apply(map_state_season_temp, axis=1)\n    df[\"Season_temp_global\"] = df[\"Season\"].map(season_mean_temp)\n\n    # Rainfall features\n    df[\"State_Season_rain\"] = df.apply(map_state_season_rain, axis=1)\n    df[\"Season_rain_global\"] = df[\"Season\"].map(season_mean_rain)\n\n    # Cyclic encodings\n    df[\"Month_sin\"] = np.sin(2 * np.pi * df[\"Month\"] / 12)\n    df[\"Month_cos\"] = np.cos(2 * np.pi * df[\"Month\"] / 12)\n    df[\"Day_sin\"]   = np.sin(2 * np.pi * df[\"DayOfYear\"] / 365)\n    df[\"Day_cos\"]   = np.cos(2 * np.pi * df[\"DayOfYear\"] / 365)\n\n    # Interactions\n    df[\"Temp_NDVI\"]   = df[\"State_Season_temp\"] * df[\"Pre_GSHH_NDVI\"]\n    df[\"Temp_Height\"] = df[\"State_Season_temp\"] * df[\"Height_Ave_cm\"]\n    df[\"Rain_NDVI\"]   = df[\"State_Season_rain\"] * df[\"Pre_GSHH_NDVI\"]\n    df[\"Rain_Height\"] = df[\"State_Season_rain\"] * df[\"Height_Ave_cm\"]\n    df[\"Rain_Temp\"]   = df[\"State_Season_rain\"] * df[\"State_Season_temp\"]\n\n    return df\n\ntrain_wide = add_features(train_wide)\n\n# Optional: Z-score within state (requires enough samples per state)\nfor col in [\"Height_Ave_cm\", \"Pre_GSHH_NDVI\"]:\n    train_wide[f\"{col}_state_z\"] = (\n        train_wide.groupby(\"State\")[col].transform(\n            lambda x: (x - x.mean()) / (x.std(ddof=0) + 1e-6)\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:12.496136Z","iopub.execute_input":"2025-12-12T05:28:12.496974Z","iopub.status.idle":"2025-12-12T05:28:12.545689Z","shell.execute_reply.started":"2025-12-12T05:28:12.496942Z","shell.execute_reply":"2025-12-12T05:28:12.544410Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_wide.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:17.478350Z","iopub.execute_input":"2025-12-12T05:28:17.478703Z","iopub.status.idle":"2025-12-12T05:28:17.517663Z","shell.execute_reply.started":"2025-12-12T05:28:17.478679Z","shell.execute_reply":"2025-12-12T05:28:17.515977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_wide.columns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:20.573339Z","iopub.execute_input":"2025-12-12T05:28:20.573699Z","iopub.status.idle":"2025-12-12T05:28:20.582253Z","shell.execute_reply.started":"2025-12-12T05:28:20.573677Z","shell.execute_reply":"2025-12-12T05:28:20.579957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUM_FEATS = [\n    \"Pre_GSHH_NDVI\",\n    \"Height_Ave_cm\",\n    \"Month\",\n    \"DayOfYear\",\n    \"State_Season_temp\",\n    \"Season_temp_global\",\n    \"State_Season_rain\",\n    \"Season_rain_global\",\n    \"Month_sin\",\n    \"Month_cos\",\n    \"Day_sin\",\n    \"Day_cos\",\n    \"Temp_NDVI\",\n    \"Temp_Height\",\n    \"Rain_NDVI\",\n    \"Rain_Height\",\n    \"Rain_Temp\",\n    \"Height_Ave_cm_state_z\",\n    \"Pre_GSHH_NDVI_state_z\",\n]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:42.500141Z","iopub.execute_input":"2025-12-12T05:28:42.500573Z","iopub.status.idle":"2025-12-12T05:28:42.507435Z","shell.execute_reply.started":"2025-12-12T05:28:42.500545Z","shell.execute_reply":"2025-12-12T05:28:42.505881Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 7. LOG-TRANSFORM TARGETS","metadata":{}},{"cell_type":"code","source":"for t in TARGETS:\n    train_wide[t + \"_log\"] = np.log1p(train_wide[t])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:28:51.750994Z","iopub.execute_input":"2025-12-12T05:28:51.751305Z","iopub.status.idle":"2025-12-12T05:28:51.760144Z","shell.execute_reply.started":"2025-12-12T05:28:51.751284Z","shell.execute_reply":"2025-12-12T05:28:51.758945Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 8. CATEGORICALS Features\n","metadata":{}},{"cell_type":"code","source":"CAT_FEATS = [\"State\", \"Season\", \"Species\"]\n# Ensure categoricals are strings (CatBoost likes that)\nfor c in CAT_FEATS:\n    train_wide[c] = train_wide[c].astype(str)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:29:12.981333Z","iopub.execute_input":"2025-12-12T05:29:12.981683Z","iopub.status.idle":"2025-12-12T05:29:12.988014Z","shell.execute_reply.started":"2025-12-12T05:29:12.981660Z","shell.execute_reply":"2025-12-12T05:29:12.987044Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 9. BUILD X, Y","metadata":{}},{"cell_type":"code","source":"# Exclude target columns and raw date/string columns\ndrop_cols = TARGETS + [t + \"_log\" for t in TARGETS] + [\n    \"Sampling_Date\", \"image_path\", 'image_id', \n]\n\nfeature_cols = [c for c in train_wide.columns if c not in drop_cols]\n\nX = train_wide[NUM_FEATS + CAT_FEATS].copy()\nY_log = train_wide[[t + \"_log\" for t in TARGETS]].copy()\nY_orig = train_wide[TARGETS].copy()   # for metric only","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:32:15.060234Z","iopub.execute_input":"2025-12-12T05:32:15.060638Z","iopub.status.idle":"2025-12-12T05:32:15.076602Z","shell.execute_reply.started":"2025-12-12T05:32:15.060611Z","shell.execute_reply":"2025-12-12T05:32:15.075175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cat_idx = [X.columns.get_loc(c) for c in CAT_FEATS]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:45:59.334982Z","iopub.execute_input":"2025-12-12T05:45:59.335321Z","iopub.status.idle":"2025-12-12T05:45:59.340584Z","shell.execute_reply.started":"2025-12-12T05:45:59.335298Z","shell.execute_reply":"2025-12-12T05:45:59.339348Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cat_idx","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:46:02.978927Z","iopub.execute_input":"2025-12-12T05:46:02.979257Z","iopub.status.idle":"2025-12-12T05:46:02.985092Z","shell.execute_reply.started":"2025-12-12T05:46:02.979235Z","shell.execute_reply":"2025-12-12T05:46:02.984277Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 10. K-FOLD CV WITH WEIGHTED GLOBAL R²","metadata":{}},{"cell_type":"code","source":"kf = KFold(n_splits=N_FOLDS, shuffle=True, random_state=RANDOM_STATE)\noof_pred = np.zeros_like(Y_orig, dtype=float)\nfold_scores = []\nmodels = []\n\nfor fold, (trn_idx, val_idx) in enumerate(kf.split(X), 1):\n    X_tr, X_val = X.iloc[trn_idx], X.iloc[val_idx]\n    y_tr, y_val = Y_log.iloc[trn_idx], Y_log.iloc[val_idx]\n    y_val_orig = Y_orig.iloc[val_idx]\n\n    model = CatBoostRegressor(\n        loss_function=\"MultiRMSE\",\n        iterations=4000,\n        learning_rate=0.03,\n        depth=6,\n        random_seed=RANDOM_STATE + fold,\n        verbose=200,\n        od_type=\"Iter\",\n        od_wait=300,\n        # good defaults for small tabular:\n        l2_leaf_reg=3.0,\n        grow_policy=\"SymmetricTree\",\n    )\n\n    model.fit(\n        X_tr, y_tr,\n        cat_features=cat_idx,\n        eval_set=(X_val, y_val),\n        use_best_model=True\n    )\n\n    pred_log = model.predict(X_val)          # shape (n_val, 5)\n    pred = np.expm1(pred_log)                # back to grams\n\n    oof_pred[val_idx, :] = pred\n\n    r2 = weighted_global_r2(\n        y_true=y_val_orig,\n        y_pred=pred,\n        target_names=TARGETS,\n        weights_dict=TARGET_WEIGHTS\n    )\n    fold_scores.append(r2)\n    print(f\"Fold {fold} weighted global R²: {r2:.5f}\")\n\n    models.append(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T05:55:55.788535Z","iopub.execute_input":"2025-12-12T05:55:55.788894Z","iopub.status.idle":"2025-12-12T05:58:09.288495Z","shell.execute_reply.started":"2025-12-12T05:55:55.788871Z","shell.execute_reply":"2025-12-12T05:58:09.287318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"overall_r2 = weighted_global_r2(\n    y_true=Y_orig,\n    y_pred=oof_pred,\n    target_names=TARGETS,\n    weights_dict=TARGET_WEIGHTS\n)\n\nprint(\"\\n===== CV RESULTS =====\")\nprint(\"Fold scores:\", [f\"{s:.5f}\" for s in fold_scores])\nprint(f\"Mean CV weighted global R²: {np.mean(fold_scores):.5f}\")\nprint(f\"OOF weighted global R²:      {overall_r2:.5f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-12T06:00:22.228123Z","iopub.execute_input":"2025-12-12T06:00:22.228527Z","iopub.status.idle":"2025-12-12T06:00:22.237386Z","shell.execute_reply.started":"2025-12-12T06:00:22.228505Z","shell.execute_reply":"2025-12-12T06:00:22.235912Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}