{"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 numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.preprocessing import StandardScaler, OneHotEncoder\nfrom sklearn.decomposition import PCA\nfrom sklearn.manifold import TSNE\nfrom sklearn.model_selection import train_test_split, cross_val_score, KFold, learning_curve, GridSearchCV\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.ensemble import RandomForestRegressor\nfrom sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Configuración de visualización\nplt.style.use('seaborn-v0_8-darkgrid')\nsns.set_palette(\"husl\")\n\n# Cargar los datos\ntrain_path = '/kaggle/input/csiro-biomass/train.csv'\ntest_path = '/kaggle/input/csiro-biomass/test.csv'\n\ntrain_df = pd.read_csv(train_path)\ntest_df = pd.read_csv(test_path)\n\nprint(\"=\" * 80)\nprint(\"1. ANÁLISIS EXPLORATORIO DE DATOS (EDA)\")\nprint(\"=\" * 80)\n\n# Información básica\nprint(\"Dimensiones del conjunto de entrenamiento:\", train_df.shape)\nprint(\"Dimensiones del conjunto de prueba:\", test_df.shape)\nprint(\"\\nPrimeras filas del conjunto de entrenamiento:\")\nprint(train_df.head())\n\nprint(\"\\nInformación de columnas y tipos de datos:\")\nprint(train_df.info())\n\nprint(\"\\nEstadísticas descriptivas de variables numéricas:\")\nprint(train_df.describe())\n\nprint(\"\\nEstadísticas descriptivas de variables categóricas:\")\nprint(train_df[['State', 'Species', 'target_name']].describe())\n\n# Verificar valores faltantes\nprint(\"\\nValores faltantes por columna:\")\nprint(train_df.isnull().sum())\n\n# Análisis de distribuciones\nfig, axes = plt.subplots(2, 3, figsize=(15, 10))\nfig.suptitle('Distribución de Variables Numéricas', fontsize=16)\n\n# Pre_GSHH_NDVI\naxes[0, 0].hist(train_df['Pre_GSHH_NDVI'], bins=30, edgecolor='black')\naxes[0, 0].set_title('Distribución de NDVI')\naxes[0, 0].set_xlabel('NDVI')\naxes[0, 0].set_ylabel('Frecuencia')\n\n# Height_Ave_cm\naxes[0, 1].hist(train_df['Height_Ave_cm'], bins=30, edgecolor='black')\naxes[0, 1].set_title('Distribución de Altura Promedio')\naxes[0, 1].set_xlabel('Altura (cm)')\naxes[0, 1].set_ylabel('Frecuencia')\n\n# target\naxes[0, 2].hist(train_df['target'], bins=30, edgecolor='black')\naxes[0, 2].set_title('Distribución de Target (Biomasa)')\naxes[0, 2].set_xlabel('Biomasa (g)')\naxes[0, 2].set_ylabel('Frecuencia')\n\n# Boxplot por tipo de target\ntarget_types = train_df['target_name'].unique()\ntarget_data = [train_df[train_df['target_name'] == t]['target'] for t in target_types]\naxes[1, 0].boxplot(target_data, labels=target_types)\naxes[1, 0].set_title('Distribución de Biomasa por Tipo')\naxes[1, 0].set_ylabel('Biomasa (g)')\naxes[1, 0].tick_params(axis='x', rotation=45)\n\n# Conteo de especies\nspecies_counts = train_df['Species'].value_counts()\naxes[1, 1].bar(range(len(species_counts)), species_counts.values)\naxes[1, 1].set_title('Conteo por Especie')\naxes[1, 1].set_xlabel('Especie')\naxes[1, 1].set_ylabel('Conteo')\naxes[1, 1].set_xticks(range(len(species_counts)))\naxes[1, 1].set_xticklabels(species_counts.index, rotation=45)\n\n# Conteo por estado\nstate_counts = train_df['State'].value_counts()\naxes[1, 2].bar(range(len(state_counts)), state_counts.values)\naxes[1, 2].set_title('Conteo por Estado')\naxes[1, 2].set_xlabel('Estado')\naxes[1, 2].set_ylabel('Conteo')\naxes[1, 2].set_xticks(range(len(state_counts)))\naxes[1, 2].set_xticklabels(state_counts.index)\n\nplt.tight_layout()\nplt.show()\n\n# Análisis de correlaciones (solo variables numéricas)\nnumeric_cols = ['Pre_GSHH_NDVI', 'Height_Ave_cm', 'target']\ncorrelation_matrix = train_df[numeric_cols].corr()\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0, square=True)\nplt.title('Matriz de Correlación (Variables Numéricas)')\nplt.show()\n\nprint(\"\\nMatriz de correlación:\")\nprint(correlation_matrix)\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"2. REAGRUPAMIENTO Y VISUALIZACIÓN CON PCA/t-SNE\")\nprint(\"=\" * 80)\n\n# Preparar datos para reducción de dimensionalidad\n# Codificar variables categóricas para PCA\ntrain_encoded = pd.get_dummies(train_df[['State', 'Species', 'target_name']], drop_first=True)\nnumeric_features = train_df[['Pre_GSHH_NDVI', 'Height_Ave_cm']].reset_index(drop=True)\nfeatures_for_pca = pd.concat([numeric_features, train_encoded], axis=1)\n\n# Estandarizar características\nscaler = StandardScaler()\nscaled_features = scaler.fit_transform(features_for_pca)\n\n# Aplicar PCA\npca = PCA(n_components=2)\npca_result = pca.fit_transform(scaled_features)\n\n# Aplicar t-SNE (usando una muestra para velocidad)\nsample_size = min(1000, len(scaled_features))\ntsne = TSNE(n_components=2, random_state=42, perplexity=30)\ntsne_result = tsne.fit_transform(scaled_features[:sample_size])\n\n# Visualizar resultados\nfig, axes = plt.subplots(1, 3, figsize=(18, 6))\n\n# PCA por tipo de target\nscatter1 = axes[0].scatter(pca_result[:, 0], pca_result[:, 1], \n                          c=train_df['target'].values, cmap='viridis', alpha=0.6)\naxes[0].set_title('PCA - Coloreado por Valor de Biomasa')\naxes[0].set_xlabel('Componente Principal 1')\naxes[0].set_ylabel('Componente Principal 2')\nplt.colorbar(scatter1, ax=axes[0], label='Biomasa (g)')\n\n# PCA por especie (primeras 5 especies para claridad)\ntop_species = train_df['Species'].value_counts().nlargest(5).index\nspecies_mask = train_df['Species'].isin(top_species)\nfor species in top_species:\n    species_idx = train_df[species_mask & (train_df['Species'] == species)].index\n    axes[1].scatter(pca_result[species_idx, 0], pca_result[species_idx, 1], \n                   label=species, alpha=0.6)\naxes[1].set_title('PCA - Agrupado por Especie (Top 5)')\naxes[1].set_xlabel('Componente Principal 1')\naxes[1].set_ylabel('Componente Principal 2')\naxes[1].legend()\n\n# t-SNE\naxes[2].scatter(tsne_result[:, 0], tsne_result[:, 1], \n               c=train_df['target'].iloc[:sample_size].values, cmap='viridis', alpha=0.6)\naxes[2].set_title('t-SNE (Muestra de 1000 puntos)')\naxes[2].set_xlabel('t-SNE 1')\naxes[2].set_ylabel('t-SNE 2')\n\nplt.tight_layout()\nplt.show()\n\n# Varianza explicada por componentes PCA\npca_full = PCA().fit(scaled_features)\nexplained_variance = np.cumsum(pca_full.explained_variance_ratio_)\n\nplt.figure(figsize=(10, 6))\nplt.plot(range(1, len(explained_variance) + 1), explained_variance, marker='o')\nplt.axhline(y=0.95, color='r', linestyle='--', label='95% de varianza')\nplt.xlabel('Número de Componentes')\nplt.ylabel('Varianza Acumulada Explicada')\nplt.title('Varianza Explicada por Componentes PCA')\nplt.grid(True)\nplt.legend()\nplt.show()\n\nprint(f\"Primeros 5 componentes PCA explican: {explained_variance[4]:.2%} de la varianza\")\nprint(f\"Se necesitan {(explained_variance < 0.95).sum() + 1} componentes para 95% de varianza\")\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"3. PREPROCESAMIENTO DE DATOS Y PIPELINE\")\nprint(\"=\" * 80)\n\n# Preparar características y objetivo\nX = train_df.drop(columns=['sample_id', 'image_path', 'target', 'target_name', 'Sampling_Date'])\ny = train_df['target']\n\n# Identificar columnas categóricas y numéricas\ncategorical_cols = ['State', 'Species']\nnumeric_cols = ['Pre_GSHH_NDVI', 'Height_Ave_cm']\n\n# Crear transformadores para pipeline\nnumeric_transformer = Pipeline(steps=[\n    ('scaler', StandardScaler())\n])\n\ncategorical_transformer = Pipeline(steps=[\n    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))\n])\n\n# Combinar transformadores\npreprocessor = ColumnTransformer(\n    transformers=[\n        ('num', numeric_transformer, numeric_cols),\n        ('cat', categorical_transformer, categorical_cols)\n    ])\n\nprint(\"Pipeline de preprocesamiento creado:\")\nprint(preprocessor)\n\n# Dividir datos en entrenamiento y validación\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)\nprint(f\"\\nTamaño de conjunto de entrenamiento: {X_train.shape}\")\nprint(f\"Tamaño de conjunto de validación: {X_val.shape}\")\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"4. CLASIFICACIÓN VS REGRESIÓN\")\nprint(\"=\" * 80)\n\nprint(\"JUSTIFICACIÓN: Este es claramente un problema de REGRESIÓN porque:\")\nprint(\"1. La variable objetivo 'target' es continua (valores de biomasa en gramos)\")\nprint(\"2. La evaluación se realiza con R² (coeficiente de determinación), métrica típica de regresión\")\nprint(\"3. El enunciado pide 'predecir pasture biomass' (valores numéricos continuos)\")\nprint(\"4. Los pesos aplicados en la evaluación son para diferentes tipos de biomasa, todos continuos\")\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"5. MODELO INICIAL SIMPLE (BASELINE)\")\nprint(\"=\" * 80)\n\n# Crear pipeline con preprocesador y modelo simple\nbaseline_pipeline = Pipeline(steps=[\n    ('preprocessor', preprocessor),\n    ('regressor', LinearRegression())\n])\n\n# Entrenar modelo baseline\nbaseline_pipeline.fit(X_train, y_train)\n\n# Predecir en conjunto de validación\ny_val_pred_baseline = baseline_pipeline.predict(X_val)\n\n# Calcular métricas\nr2_baseline = r2_score(y_val, y_val_pred_baseline)\nmse_baseline = mean_squared_error(y_val, y_val_pred_baseline)\nmae_baseline = mean_absolute_error(y_val, y_val_pred_baseline)\n\nprint(\"Modelo Baseline: Regresión Lineal\")\nprint(f\"R² en validación: {r2_baseline:.4f}\")\nprint(f\"MSE en validación: {mse_baseline:.4f}\")\nprint(f\"MAE en validación: {mae_baseline:.4f}\")\n\n# Visualizar predicciones vs valores reales\nplt.figure(figsize=(10, 6))\nplt.scatter(y_val, y_val_pred_baseline, alpha=0.6)\nplt.plot([y_val.min(), y_val.max()], [y_val.min(), y_val.max()], 'r--', lw=2)\nplt.xlabel('Valores Reales')\nplt.ylabel('Predicciones')\nplt.title('Regresión Lineal: Predicciones vs Valores Reales (Baseline)')\nplt.grid(True)\nplt.show()\n\n# Residuales\nresiduals = y_val - y_val_pred_baseline\nplt.figure(figsize=(10, 6))\nplt.scatter(y_val_pred_baseline, residuals, alpha=0.6)\nplt.axhline(y=0, color='r', linestyle='--')\nplt.xlabel('Predicciones')\nplt.ylabel('Residuales')\nplt.title('Análisis de Residuales - Regresión Lineal')\nplt.grid(True)\nplt.show()\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"6. BÚSQUEDA DE HIPERPARÁMETROS Y MODELO AVANZADO\")\nprint(\"=\" * 80)\n\nprint(\"Selección del modelo: Random Forest Regressor\")\nprint(\"Justificación:\")\nprint(\"1. Maneja bien relaciones no lineales entre características\")\nprint(\"2. Robustez ante outliers y datos no normalizados\")\nprint(\"3. Importancia de características integrada\")\nprint(\"4. Buen desempeño en problemas de regresión con datos tabulares\")\n\n# Crear pipeline con Random Forest\nrf_pipeline = Pipeline(steps=[\n    ('preprocessor', preprocessor),\n    ('regressor', RandomForestRegressor(random_state=42, n_jobs=-1))\n])\n\n# Definir grid de hiperparámetros\nparam_grid = {\n    'regressor__n_estimators': [100, 200],\n    'regressor__max_depth': [None, 10, 20],\n    'regressor__min_samples_split': [2, 5, 10],\n    'regressor__min_samples_leaf': [1, 2, 4]\n}\n\n# Búsqueda en grid con validación cruzada\nprint(\"\\nRealizando búsqueda de hiperparámetros...\")\ngrid_search = GridSearchCV(\n    rf_pipeline, \n    param_grid, \n    cv=3,  # 3-fold CV para velocidad\n    scoring='r2',\n    n_jobs=-1,\n    verbose=1\n)\n\ngrid_search.fit(X_train, y_train)\n\nprint(\"\\nMejores hiperparámetros encontrados:\")\nprint(grid_search.best_params_)\nprint(f\"Mejor score R² (CV): {grid_search.best_score_:.4f}\")\n\n# Usar el mejor modelo\nbest_model = grid_search.best_estimator_\ny_val_pred_best = best_model.predict(X_val)\n\n# Métricas del mejor modelo\nr2_best = r2_score(y_val, y_val_pred_best)\nmse_best = mean_squared_error(y_val, y_val_pred_best)\nmae_best = mean_absolute_error(y_val, y_val_pred_best)\n\nprint(\"\\nMétricas del mejor modelo en validación:\")\nprint(f\"R²: {r2_best:.4f}\")\nprint(f\"MSE: {mse_best:.4f}\")\nprint(f\"MAE: {mae_best:.4f}\")\n\nprint(f\"\\nMejora respecto al baseline: {((r2_best - r2_baseline)/abs(r2_baseline)*100):.1f}% en R²\")\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"7. EVALUACIÓN COMPLETA DEL MODELO\")\nprint(\"=\" * 80)\n\n# 7.1 Validación Cruzada\nprint(\"\\n7.1 Validación Cruzada (5-fold)\")\n\n# Crear pipeline con mejores parámetros\nfinal_pipeline = Pipeline(steps=[\n    ('preprocessor', preprocessor),\n    ('regressor', RandomForestRegressor(\n        n_estimators=grid_search.best_params_['regressor__n_estimators'],\n        max_depth=grid_search.best_params_['regressor__max_depth'],\n        min_samples_split=grid_search.best_params_['regressor__min_samples_split'],\n        min_samples_leaf=grid_search.best_params_['regressor__min_samples_leaf'],\n        random_state=42,\n        n_jobs=-1\n    ))\n])\n\n# Validación cruzada\nkfold = KFold(n_splits=5, shuffle=True, random_state=42)\ncv_scores = cross_val_score(final_pipeline, X, y, cv=kfold, scoring='r2', n_jobs=-1)\n\nprint(f\"Puntuaciones R² por fold: {cv_scores}\")\nprint(f\"R² promedio (CV): {cv_scores.mean():.4f} (±{cv_scores.std():.4f})\")\n\n# 7.2 Curvas de aprendizaje\nprint(\"\\n7.2 Curvas de Aprendizaje\")\n\ntrain_sizes, train_scores, test_scores = learning_curve(\n    final_pipeline, X_train, y_train,\n    cv=3, scoring='r2', n_jobs=-1,\n    train_sizes=np.linspace(0.1, 1.0, 10)\n)\n\ntrain_scores_mean = np.mean(train_scores, axis=1)\ntrain_scores_std = np.std(train_scores, axis=1)\ntest_scores_mean = np.mean(test_scores, axis=1)\ntest_scores_std = np.std(test_scores, axis=1)\n\nplt.figure(figsize=(10, 6))\nplt.fill_between(train_sizes, train_scores_mean - train_scores_std,\n                 train_scores_mean + train_scores_std, alpha=0.1, color=\"r\")\nplt.fill_between(train_sizes, test_scores_mean - test_scores_std,\n                 test_scores_mean + test_scores_std, alpha=0.1, color=\"g\")\nplt.plot(train_sizes, train_scores_mean, 'o-', color=\"r\", label=\"Score entrenamiento\")\nplt.plot(train_sizes, test_scores_mean, 'o-', color=\"g\", label=\"Score validación cruzada\")\nplt.xlabel(\"Tamaño del conjunto de entrenamiento\")\nplt.ylabel(\"R² Score\")\nplt.title(\"Curvas de Aprendizaje - Random Forest\")\nplt.legend(loc=\"best\")\nplt.grid(True)\nplt.show()\n\n# 7.3 Importancia de características\nprint(\"\\n7.3 Importancia de Características\")\n\n# Entrenar modelo final\nfinal_pipeline.fit(X_train, y_train)\n\n# Obtener nombres de características después del preprocesamiento\nfeature_names = []\n# Características numéricas\nfeature_names.extend(numeric_cols)\n# Características categóricas (one-hot encoded)\nif hasattr(preprocessor.named_transformers_['cat'], 'named_steps'):\n    encoder = preprocessor.named_transformers_['cat'].named_steps['onehot']\n    cat_features = encoder.get_feature_names_out(categorical_cols)\n    feature_names.extend(cat_features)\nelse:\n    encoder = preprocessor.named_transformers_['cat']\n    cat_features = encoder.get_feature_names_out(categorical_cols)\n    feature_names.extend(cat_features)\n\n# Obtener importancia de características del Random Forest\nrf_model = final_pipeline.named_steps['regressor']\nimportances = rf_model.feature_importances_\n\n# Crear DataFrame para visualización\nimportance_df = pd.DataFrame({\n    'Feature': feature_names,\n    'Importance': importances\n}).sort_values('Importance', ascending=False)\n\n# Mostrar top 10 características\nprint(\"\\nTop 10 características más importantes:\")\nprint(importance_df.head(10))\n\n# Visualizar importancia de características\nplt.figure(figsize=(12, 8))\ntop_features = importance_df.head(15)\nplt.barh(range(len(top_features)), top_features['Importance'])\nplt.yticks(range(len(top_features)), top_features['Feature'])\nplt.xlabel('Importancia')\nplt.title('Top 15 Características Más Importantes')\nplt.gca().invert_yaxis()\nplt.tight_layout()\nplt.show()\n\n# 7.4 Predicciones vs Valores reales (mejor modelo)\nprint(\"\\n7.4 Análisis de Predicciones del Modelo Final\")\n\nplt.figure(figsize=(12, 5))\n\n# Gráfico de dispersión\nplt.subplot(1, 2, 1)\nplt.scatter(y_val, y_val_pred_best, alpha=0.6)\nplt.plot([y_val.min(), y_val.max()], [y_val.min(), y_val.max()], 'r--', lw=2)\nplt.xlabel('Valores Reales')\nplt.ylabel('Predicciones')\nplt.title('Modelo Final: Predicciones vs Valores Reales')\nplt.grid(True)\n\n# Gráfico de residuales\nplt.subplot(1, 2, 2)\nresiduals_best = y_val - y_val_pred_best\nplt.scatter(y_val_pred_best, residuals_best, alpha=0.6)\nplt.axhline(y=0, color='r', linestyle='--')\nplt.xlabel('Predicciones')\nplt.ylabel('Residuales')\nplt.title('Residuales del Modelo Final')\nplt.grid(True)\n\nplt.tight_layout()\nplt.show()\n\n# 7.5 Distribución de errores\nprint(\"\\n7.5 Distribución de Errores\")\n\nplt.figure(figsize=(10, 6))\nplt.hist(residuals_best, bins=30, edgecolor='black', alpha=0.7)\nplt.axvline(x=0, color='r', linestyle='--', linewidth=2)\nplt.xlabel('Error (Residual)')\nplt.ylabel('Frecuencia')\nplt.title('Distribución de Errores del Modelo Final')\nplt.grid(True, alpha=0.3)\nplt.show()\n\n# Resumen de métricas\nprint(\"\\n\" + \"=\" * 80)\nprint(\"RESUMEN FINAL DEL MODELO\")\nprint(\"=\" * 80)\n\nprint(f\"Modelo seleccionado: Random Forest Regressor\")\nprint(f\"Hiperparámetros óptimos:\")\nfor key, value in grid_search.best_params_.items():\n    print(f\"  {key}: {value}\")\n\nprint(f\"\\nMétricas en conjunto de validación:\")\nprint(f\"  R²: {r2_best:.4f}\")\nprint(f\"  MSE: {mse_best:.4f}\")\nprint(f\"  MAE: {mae_best:.4f}\")\nprint(f\"  RMSE: {np.sqrt(mse_best):.4f}\")\n\nprint(f\"\\nValidación cruzada (5-fold):\")\nprint(f\"  R² promedio: {cv_scores.mean():.4f}\")\nprint(f\"  Desviación estándar: {cv_scores.std():.4f}\")\n\n# Función para calcular R² ponderado según la competencia\ndef weighted_r2_score(y_true, y_pred, sample_weights):\n    \"\"\"Calcula R² ponderado según especificaciones del concurso\"\"\"\n    # Calcular media ponderada\n    weighted_mean = np.sum(sample_weights * y_true) / np.sum(sample_weights)\n    \n    # Calcular SS_res y SS_tot ponderados\n    ss_res = np.sum(sample_weights * (y_true - y_pred) ** 2)\n    ss_tot = np.sum(sample_weights * (y_true - weighted_mean) ** 2)\n    \n    # Calcular R² ponderado\n    r2_weighted = 1 - (ss_res / ss_tot)\n    return r2_weighted\n\n# Mapeo de pesos según tipo de target (según especificaciones del concurso)\nweight_mapping = {\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}\n\n# Aplicar pesos a los datos de validación\nval_weights = X_val.index.map(lambda idx: weight_mapping.get(train_df.loc[idx, 'target_name'], 1.0))\n\n# Calcular R² ponderado\nweighted_r2 = weighted_r2_score(y_val.values, y_val_pred_best, val_weights.values)\nprint(f\"\\nR² ponderado (según especificaciones del concurso): {weighted_r2:.4f}\")\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"8. PREPARACIÓN PARA PREDICCIÓN EN DATOS DE TEST\")\nprint(\"=\" * 80)\n\nprint(\"• Analizando la estructura de los datos de test...\")\nprint(f\"Número de muestras en test: {len(test_df)}\")\nprint(f\"Columnas en test: {list(test_df.columns)}\")\n\n# Verificar si hay características disponibles para test\nprint(\"\\n• Buscando características para imágenes de test...\")\n# Nota: En una competencia real, deberías tener un archivo separado con características\n# Para este ejemplo, crearé características basadas en promedios del train\n\n# Extraer image_id y target_name del sample_id\ntest_df['image_id'] = test_df['sample_id'].str.split('__').str[0]\ntest_df['target_name'] = test_df['sample_id'].str.split('__').str[1]\n\nprint(f\"Número de imágenes únicas en test: {test_df['image_id'].nunique()}\")\nprint(f\"Targets en test: {test_df['target_name'].unique()}\")\n\n# Crear un DataFrame con características para cada imagen de test\n# En realidad, deberías cargar esto desde un archivo separado\ntest_images = test_df[['image_id']].drop_duplicates()\n\nprint(f\"\\n• Creando características para {len(test_images)} imágenes de test...\")\nprint(\"NOTA: Estas son características de ejemplo. Para resultados reales, necesitas\")\nprint(\"      las características reales de las imágenes de test.\")\n\n# Crear características basadas en promedios/moda del train\ntest_features_list = []\n\nfor img_id in test_images['image_id']:\n    # Para cada imagen, usar características promedio del train\n    # ¡EN REALIDAD DEBES USAR LAS CARACTERÍSTICAS REALES!\n    test_features_list.append({\n        'image_id': img_id,\n        'Pre_GSHH_NDVI': train_df['Pre_GSHH_NDVI'].mean(),\n        'Height_Ave_cm': train_df['Height_Ave_cm'].mean(),\n        'State': train_df['State'].mode()[0],\n        'Species': train_df['Species'].mode()[0]\n    })\n\ntest_features_df = pd.DataFrame(test_features_list)\n\n# Unir características con test_df\ntest_df = pd.merge(test_df, test_features_df, on='image_id', how='left')\n\nprint(f\"\\n• Características preparadas para {len(test_features_df)} imágenes\")\n\n# ================================================================\n# 9. ENTRENAR MODELOS PARA CADA TIPO DE TARGET\n# ================================================================\nprint(\"\\n\" + \"=\" * 80)\nprint(\"9. ENTRENANDO MODELOS PARA CADA TIPO DE TARGET\")\nprint(\"=\" * 80)\n\n# Lista de todos los targets\nall_targets = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n\n# Diccionario para almacenar modelos\nmodels_by_target = {}\n\n# Preparar datos de entrenamiento\nX_train_all = train_df.drop(columns=['sample_id', 'image_path', 'target', 'target_name', 'Sampling_Date'])\n\nfor target in all_targets:\n    print(f\"\\n• Entrenando modelo para {target}...\")\n    \n    # Filtrar datos para este target específico\n    mask = train_df['target_name'] == target\n    X_target = X_train_all[mask]\n    y_target = train_df[mask]['target']\n    \n    # Verificar que tenemos datos\n    if len(X_target) == 0:\n        print(f\"  ¡Advertencia! No hay datos para {target}. Usando modelo genérico.\")\n        # Si no hay datos específicos, usar todos los datos\n        X_target = X_train_all\n        y_target = train_df['target']\n    \n    # Dividir en train/val para este target\n    X_train_t, X_val_t, y_train_t, y_val_t = train_test_split(\n        X_target, y_target, test_size=0.2, random_state=42\n    )\n    \n    # Crear pipeline para este target\n    target_pipeline = Pipeline(steps=[\n        ('preprocessor', preprocessor),\n        ('regressor', RandomForestRegressor(\n            n_estimators=100,  # Usar menos árboles para velocidad\n            random_state=42,\n            n_jobs=-1\n        ))\n    ])\n    \n    # Entrenar\n    target_pipeline.fit(X_train_t, y_train_t)\n    models_by_target[target] = target_pipeline\n    \n    # Evaluación rápida\n    y_pred_t = target_pipeline.predict(X_val_t)\n    r2_t = r2_score(y_val_t, y_pred_t)\n    rmse_t = np.sqrt(mean_squared_error(y_val_t, y_pred_t))\n    print(f\"  R² en validación: {r2_t:.4f}\")\n    print(f\"  RMSE en validación: {rmse_t:.4f}\")\n\n# ================================================================\n# 10. GENERAR PREDICCIONES PARA TEST\n# ================================================================\nprint(\"\\n\" + \"=\" * 80)\nprint(\"10. GENERANDO PREDICCIONES PARA DATOS DE TEST\")\nprint(\"=\" * 80)\n\n# Preparar características de test para predicción\nX_test_final = test_df[['Pre_GSHH_NDVI', 'Height_Ave_cm', 'State', 'Species']]\n\nprint(f\"Forma de X_test_final: {X_test_final.shape}\")\nprint(f\"Columnas: {list(X_test_final.columns)}\")\n\n# Hacer predicciones para cada fila en test_df\npredictions_list = []\n\nfor idx, row in test_df.iterrows():\n    sample_id = row['sample_id']\n    target_name = row['target_name']\n    \n    # Obtener características para esta fila\n    X_row = X_test_final.iloc[[idx]]\n    \n    # Verificar que tenemos un modelo para este target\n    if target_name in models_by_target:\n        model = models_by_target[target_name]\n        prediction = model.predict(X_row)[0]\n        # Asegurar que la predicción no sea negativa\n        prediction = max(0.0, prediction)\n    else:\n        # Si no hay modelo, usar 0 como predicción\n        prediction = 0.0\n        print(f\"  ¡Advertencia! No hay modelo para {target_name}, usando 0.0\")\n    \n    predictions_list.append({\n        'sample_id': sample_id,\n        'target': prediction\n    })\n\n# Crear DataFrame de submission\nsubmission_df = pd.DataFrame(predictions_list)\n\n# Verificar el formato\nprint(f\"\\n• Formato del submission generado:\")\nprint(f\"  Número de filas: {len(submission_df)}\")\nprint(f\"  Número de columnas: {len(submission_df.columns)}\")\nprint(f\"  Columnas: {list(submission_df.columns)}\")\n\n# Ordenar por sample_id para consistencia\nsubmission_df = submission_df.sort_values('sample_id')\n\n# Mostrar estadísticas\nprint(f\"\\n• Estadísticas de las predicciones:\")\nprint(f\"  Mínimo: {submission_df['target'].min():.4f}\")\nprint(f\"  Máximo: {submission_df['target'].max():.4f}\")\nprint(f\"  Media: {submission_df['target'].mean():.4f}\")\nprint(f\"  Mediana: {submission_df['target'].median():.4f}\")\n\n# Mostrar algunas predicciones\nprint(f\"\\n• Primeras 10 predicciones:\")\nprint(submission_df.head(10))\n\n# Verificar que no haya valores NaN\nif submission_df['target'].isnull().any():\n    print(f\"\\n¡ADVERTENCIA! Hay {submission_df['target'].isnull().sum()} valores NaN en las predicciones.\")\n    print(\"Rellenando con 0...\")\n    submission_df['target'] = submission_df['target'].fillna(0.0)\n\n# ================================================================\n# 11. GUARDAR ARCHIVO DE SUBMISSION\n# ================================================================\nprint(\"\\n\" + \"=\" * 80)\nprint(\"11. GUARDANDO ARCHIVO DE SUBMISSION\")\nprint(\"=\" * 80)\n\n# Guardar archivo\nsubmission_path = '/kaggle/working/submission.csv'\nsubmission_df.to_csv(submission_path, index=False)\n\nprint(f\"✓ Archivo guardado en: {submission_path}\")\nprint(f\"✓ Tamaño del archivo: {len(submission_df)} filas\")\n\n# Verificar que el archivo se puede leer correctamente\ntry:\n    check_df = pd.read_csv(submission_path)\n    print(f\"✓ Archivo verificado: {len(check_df)} filas leídas correctamente\")\n    \n    # Comparar con el formato esperado\n    if len(check_df.columns) == 2 and list(check_df.columns) == ['sample_id', 'target']:\n        print(\"✓ Formato de columnas CORRECTO\")\n    else:\n        print(f\"✗ Formato de columnas INCORRECTO: {list(check_df.columns)}\")\n        \n    if len(check_df) == len(test_df):\n        print(\"✓ Número de filas CORRECTO\")\n    else:\n        print(f\"✗ Número de filas INCORRECTO: esperado {len(test_df)}, obtenido {len(check_df)}\")\n        \nexcept Exception as e:\n    print(f\"✗ Error al verificar el archivo: {e}\")\n\n# ================================================================\n# 12. ANÁLISIS DE LOS RESULTADOS Y RECOMENDACIONES\n# ================================================================\nprint(\"\\n\" + \"=\" * 80)\nprint(\"12. ANÁLISIS FINAL Y RECOMENDACIONES\")\nprint(\"=\" * 80)\n\nprint(\"\\n1. RESUMEN DEL MODELO:\")\nprint(f\"   • Modelo utilizado: Random Forest Regressor\")\nprint(f\"   • R² en validación: {r2_best:.4f}\")\nprint(f\"   • R² ponderado: {weighted_r2:.4f}\")\nprint(f\"   • RMSE: {np.sqrt(mse_best):.4f}\")\n\nprint(\"\\n2. CARACTERÍSTICAS MÁS IMPORTANTES:\")\nfor i, (_, row) in enumerate(importance_df.head(5).iterrows(), 1):\n    print(f\"   {i}. {row['Feature']}: {row['Importance']:.4f}\")\n\nprint(\"\\n3. LIMITACIONES IDENTIFICADAS:\")\nprint(\"   • No se utilizan las imágenes (solo datos tabulares)\")\nprint(\"   • Para test, se usan características promedio/moda (no las reales)\")\nprint(\"   • Modelos entrenados por separado para cada target, sin considerar correlaciones\")\n\nprint(\"\\n4. MEJORAS POSIBLES PARA INCREMENTAR EL DESEMPEÑO:\")\nprint(\"   • EXTRAER CARACTERÍSTICAS REALES de las imágenes de test\")\nprint(\"   • Utilizar técnicas de procesamiento de imágenes para extraer características visuales\")\nprint(\"   • Incorporar la fecha (Sampling_Date) como característica temporal\")\nprint(\"   • Usar modelos ensemble o stacking para mejorar predicciones\")\nprint(\"   • Realizar feature engineering más avanzado\")\nprint(\"   • Ajustar hiperparámetros de forma más exhaustiva\")\n\nprint(\"\\n5. NOTA CRÍTICA SOBRE LAS PREDICCIONES:\")\nprint(\"   • Las predicciones actuales usan características promedio/moda del train\")\nprint(\"   • Esto dará resultados POCO PRECISOS en la competencia\")\nprint(\"   • Para mejorar, NECESITAS las características reales de las imágenes de test\")\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"PROCESO COMPLETADO\")\nprint(\"=\" * 80)\nprint(\"El archivo 'submission.csv' ha sido generado y está listo para enviar a Kaggle.\")\nprint(\"Recuerda que para obtener buenos resultados necesitas las características reales\")\nprint(\"de las imágenes de test. Revisa la pestaña 'Data' de la competencia para\")\nprint(\"encontrar el archivo con las características de test.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-08T05:23:52.326154Z","iopub.execute_input":"2025-12-08T05:23:52.327706Z","iopub.status.idle":"2025-12-08T05:24:44.646907Z","shell.execute_reply.started":"2025-12-08T05:23:52.327653Z","shell.execute_reply":"2025-12-08T05:24:44.646031Z"}},"outputs":[],"execution_count":null}]}