{"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":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom pathlib import Path\nfrom PIL import Image\nfrom IPython.display import display\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.ensemble import RandomForestRegressor\n\npd.set_option(\"display.max_columns\", 100)\npd.set_option(\"display.width\", 200)\n\n# Ruta fija de la competencia (ajústala si el nombre del dataset es otro)\nINPUT_DIR = Path(\"/kaggle/input/csiro-biomass\")\n\n# Si dentro hay una sola subcarpeta (p.ej. csiro-biomass-public), usarla\nsubdirs = [p for p in INPUT_DIR.iterdir() if p.is_dir()]\nCOMP_DIR = subdirs[0] if len(subdirs) == 1 else INPUT_DIR\n\nprint(\"Usando carpeta de datos:\", COMP_DIR)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = pd.read_csv(COMP_DIR / \"train.csv\")\ntest  = pd.read_csv(COMP_DIR / \"test.csv\")\n\nprint(\"train shape:\", train.shape)\nprint(\"test shape:\", test.shape)\n\ndisplay(train.head())\ndisplay(test.head())\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lista de variables objetivo\ntarget_names = sorted(train[\"target_name\"].unique())\nprint(\"Targets (target_name):\", target_names)\nprint(\"Número de targets:\", len(target_names))\n\n# Pivot: filas = image_path, columnas = target_name, valores = target\ny_wide = train.pivot(\n    index=\"image_path\",\n    columns=\"target_name\",\n    values=\"target\"\n).loc[:, target_names]\n\nprint(\"y_wide shape:\", y_wide.shape)\ndisplay(y_wide.head())\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_image_features(rel_path: str) -> dict:\n    \"\"\"\n    rel_path: ruta relativa tal como viene en image_path, ej. 'train/ID1001187975.jpg'\n    \"\"\"\n    img_path = COMP_DIR / rel_path  # COMP_DIR/train/ID....jpg\n\n    # Valores por defecto por si hay problemas con la imagen\n    feats = {\n        \"mean_R\": 0.0,\n        \"mean_G\": 0.0,\n        \"mean_B\": 0.0,\n        \"std_R\": 0.0,\n        \"std_G\": 0.0,\n        \"std_B\": 0.0,\n        \"excess_green\": 0.0,\n    }\n\n    try:\n        with Image.open(img_path) as img:\n            img = img.convert(\"RGB\")\n            arr = np.array(img).astype(np.float32)\n\n        R = arr[:, :, 0]\n        G = arr[:, :, 1]\n        B = arr[:, :, 2]\n\n        feats[\"mean_R\"] = R.mean()\n        feats[\"mean_G\"] = G.mean()\n        feats[\"mean_B\"] = B.mean()\n        feats[\"std_R\"] = R.std()\n        feats[\"std_G\"] = G.std()\n        feats[\"std_B\"] = B.std()\n        feats[\"excess_green\"] = (2*G - R - B).mean()\n\n    except Exception as e:\n        # No hacemos raise; mantenemos los valores por defecto\n        # print(f\"Error leyendo {img_path}: {e}\")  # útil para debug local\n        pass\n\n    return feats\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def safe_fill(df: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Reemplaza inf/NaN por valores seguros.\n    \"\"\"\n    df = df.replace([np.inf, -np.inf], np.nan)\n    df = df.fillna(df.mean()).fillna(0)\n    return df\n\n\ndef build_model_and_submission(train: pd.DataFrame,\n                               test: pd.DataFrame,\n                               target_names,\n                               y_wide: pd.DataFrame) -> pd.DataFrame:\n    \"\"\"\n    Construye el modelo, predice sobre test y devuelve un DataFrame\n    con columnas ['sample_id', 'target'], SIN usar sample_submission.csv.\n    \"\"\"\n\n    # =========================\n    # 1) Features de TRAIN\n    # =========================\n    train_images = y_wide.index.tolist()\n    print(\"Número de imágenes de train:\", len(train_images))\n\n    features_list = []\n    for rel_path in train_images:\n        feats = extract_image_features(rel_path)\n        feats[\"image_path\"] = rel_path\n        features_list.append(feats)\n\n    X_train_df = pd.DataFrame(features_list).set_index(\"image_path\")\n    print(\"X_train_df shape (antes de safe_fill):\", X_train_df.shape)\n    display(X_train_df.head())\n\n    X_train_df = safe_fill(X_train_df)\n    print(\"NaN en X_train_df después de safe_fill:\",\n          X_train_df.isna().sum().sum())\n\n    X = X_train_df.values\n    y = y_wide.values\n    print(\"X shape:\", X.shape, \"| y shape:\", y.shape)\n\n    # =========================\n    # 2) CV rápido con RandomForest\n    # =========================\n    kf = KFold(n_splits=5, shuffle=True, random_state=42)\n    fold_rmse = []\n\n    for fold, (tr_idx, va_idx) in enumerate(kf.split(X)):\n        X_tr, X_va = X[tr_idx], X[va_idx]\n        y_tr, y_va = y[tr_idx], y[va_idx]\n\n        model = RandomForestRegressor(\n            n_estimators=400,\n            random_state=42,\n            n_jobs=-1\n        )\n        model.fit(X_tr, y_tr)\n\n        preds = model.predict(X_va)\n        rmse_per_target = np.sqrt(((preds - y_va) ** 2).mean(axis=0))\n        fold_rmse.append(rmse_per_target)\n\n        rmse_dict = {t: r for t, r in zip(target_names, rmse_per_target)}\n        print(f\"Fold {fold} RMSE:\", rmse_dict)\n\n    print(\"\\nRMSE promedio por target en CV:\")\n    mean_rmse = np.mean(fold_rmse, axis=0)\n    for t, r in zip(target_names, mean_rmse):\n        print(f\"{t}: {r:.4f}\")\n\n    # =========================\n    # 3) Entrenar modelo final\n    # =========================\n    final_model = RandomForestRegressor(\n        n_estimators=400,\n        random_state=42,\n        n_jobs=-1\n    )\n    final_model.fit(X, y)\n\n    # =========================\n    # 4) Features de TEST\n    # =========================\n    test_images = test[\"image_path\"].unique().tolist()\n    print(\"Número de imágenes de test:\", len(test_images))\n\n    test_features_list = []\n    for rel_path in test_images:\n        feats = extract_image_features(rel_path)\n        feats[\"image_path\"] = rel_path\n        test_features_list.append(feats)\n\n    X_test_df = pd.DataFrame(test_features_list).set_index(\"image_path\")\n    print(\"X_test_df shape (antes de safe_fill):\", X_test_df.shape)\n    display(X_test_df.head())\n\n    X_test_df = safe_fill(X_test_df)\n    print(\"NaN en X_test_df después de safe_fill:\",\n          X_test_df.isna().sum().sum())\n\n    X_test = X_test_df.values\n\n    test_preds = final_model.predict(X_test)\n    preds_test_df = pd.DataFrame(\n        test_preds,\n        index=test_images,\n        columns=target_names\n    )\n    print(\"Predicciones por imagen (head):\")\n    display(preds_test_df.head())\n\n    # =========================\n    # 5) Pasar a formato largo y unir con test\n    # =========================\n    preds_long = (\n        preds_test_df\n        .reset_index()\n        .melt(id_vars=\"index\", var_name=\"target_name\", value_name=\"pred\")\n        .rename(columns={\"index\": \"image_path\"})\n    )\n\n    print(\"preds_long (head):\")\n    display(preds_long.head())\n\n    test_pred = test.merge(\n        preds_long,\n        on=[\"image_path\", \"target_name\"],\n        how=\"left\"\n    )\n\n    print(\"test_pred shape:\", test_pred.shape)\n    display(test_pred.head())\n\n    # =========================\n    # 6) Construir submission SOLO desde test\n    # =========================\n    # test_pred tiene las mismas filas que test y la columna 'pred'\n    submission = (\n        test_pred[[\"sample_id\", \"pred\"]]\n        .rename(columns={\"pred\": \"target\"})\n    )\n\n    # Sanity check\n    assert submission.shape[0] == test.shape[0], \\\n        \"El submission no tiene el mismo número de filas que test\"\n\n    print(\"submission shape:\", submission.shape)\n    display(submission.head())\n\n    return submission\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# BLOQUE PRINCIPAL ROBUSTO\n# =========================\n\ntry:\n    print(\"Ejecutando pipeline completo…\")\n    submission = build_model_and_submission(\n        train=train,\n        test=test,\n        target_names=target_names,\n        y_wide=y_wide\n    )\n    print(\"Pipeline completo OK.\")\nexcept Exception as e:\n    print(\"ERROR en pipeline, usando fallback basado en test.\")\n    print(\"Detalle del error (solo visible en el public run):\", repr(e))\n\n    # Fallback: usar test y poner un valor fijo o la media de train\n    submission = test[[\"sample_id\"]].copy()\n    # por ejemplo, usar la media global de los targets de train:\n    global_mean = train[\"target\"].mean()\n    submission[\"target\"] = global_mean\n\n# Guardar SIEMPRE submission.csv\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"submission.csv guardado.\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}