{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Clasificador Multiclase — Dry Bean Dataset\n",
    "\n",
    "**Curso:** Fundamentos de Machine Learning  \n",
    "**Nombre:** Alejandro Lembke Barrientos | Carné: 12002840  \n",
    "**Dataset:** Dry Bean Dataset (UCI ID 602) — 7 clases, 16 features, 13,611 instancias"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Setup & Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "b18469ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "from ucimlrepo import fetch_ucirepo\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "from xgboost import XGBClassifier\n",
    "from scipy.stats import mode\n",
    "\n",
    "sys.path.append(os.path.join(os.getcwd(), 'src'))\n",
    "from utils import metricas, registrar, plot_importancia\n",
    "\n",
    "RANDOM_STATE = 42\n",
    "np.random.seed(RANDOM_STATE)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41902be2",
   "metadata": {},
   "source": [
    "## 2. Carga y Exploración del Dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "f264ed2e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape: (13611, 17)\n",
      "\n",
      "Metadata:\n",
      "Images of 13,611 grains of 7 different registered dry beans were taken with a high-resolution camera. A total of 16 features; 12 dimensions and 4 shape forms, were obtained from the grains.\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Area</th>\n",
       "      <th>Perimeter</th>\n",
       "      <th>MajorAxisLength</th>\n",
       "      <th>MinorAxisLength</th>\n",
       "      <th>AspectRatio</th>\n",
       "      <th>Eccentricity</th>\n",
       "      <th>ConvexArea</th>\n",
       "      <th>EquivDiameter</th>\n",
       "      <th>Extent</th>\n",
       "      <th>Solidity</th>\n",
       "      <th>Roundness</th>\n",
       "      <th>Compactness</th>\n",
       "      <th>ShapeFactor1</th>\n",
       "      <th>ShapeFactor2</th>\n",
       "      <th>ShapeFactor3</th>\n",
       "      <th>ShapeFactor4</th>\n",
       "      <th>Class</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>28395</td>\n",
       "      <td>610.291</td>\n",
       "      <td>208.178117</td>\n",
       "      <td>173.888747</td>\n",
       "      <td>1.197191</td>\n",
       "      <td>0.549812</td>\n",
       "      <td>28715</td>\n",
       "      <td>190.141097</td>\n",
       "      <td>0.763923</td>\n",
       "      <td>0.988856</td>\n",
       "      <td>0.958027</td>\n",
       "      <td>0.913358</td>\n",
       "      <td>0.007332</td>\n",
       "      <td>0.003147</td>\n",
       "      <td>0.834222</td>\n",
       "      <td>0.998724</td>\n",
       "      <td>SEKER</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>28734</td>\n",
       "      <td>638.018</td>\n",
       "      <td>200.524796</td>\n",
       "      <td>182.734419</td>\n",
       "      <td>1.097356</td>\n",
       "      <td>0.411785</td>\n",
       "      <td>29172</td>\n",
       "      <td>191.272751</td>\n",
       "      <td>0.783968</td>\n",
       "      <td>0.984986</td>\n",
       "      <td>0.887034</td>\n",
       "      <td>0.953861</td>\n",
       "      <td>0.006979</td>\n",
       "      <td>0.003564</td>\n",
       "      <td>0.909851</td>\n",
       "      <td>0.998430</td>\n",
       "      <td>SEKER</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>29380</td>\n",
       "      <td>624.110</td>\n",
       "      <td>212.826130</td>\n",
       "      <td>175.931143</td>\n",
       "      <td>1.209713</td>\n",
       "      <td>0.562727</td>\n",
       "      <td>29690</td>\n",
       "      <td>193.410904</td>\n",
       "      <td>0.778113</td>\n",
       "      <td>0.989559</td>\n",
       "      <td>0.947849</td>\n",
       "      <td>0.908774</td>\n",
       "      <td>0.007244</td>\n",
       "      <td>0.003048</td>\n",
       "      <td>0.825871</td>\n",
       "      <td>0.999066</td>\n",
       "      <td>SEKER</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>30008</td>\n",
       "      <td>645.884</td>\n",
       "      <td>210.557999</td>\n",
       "      <td>182.516516</td>\n",
       "      <td>1.153638</td>\n",
       "      <td>0.498616</td>\n",
       "      <td>30724</td>\n",
       "      <td>195.467062</td>\n",
       "      <td>0.782681</td>\n",
       "      <td>0.976696</td>\n",
       "      <td>0.903936</td>\n",
       "      <td>0.928329</td>\n",
       "      <td>0.007017</td>\n",
       "      <td>0.003215</td>\n",
       "      <td>0.861794</td>\n",
       "      <td>0.994199</td>\n",
       "      <td>SEKER</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>30140</td>\n",
       "      <td>620.134</td>\n",
       "      <td>201.847882</td>\n",
       "      <td>190.279279</td>\n",
       "      <td>1.060798</td>\n",
       "      <td>0.333680</td>\n",
       "      <td>30417</td>\n",
       "      <td>195.896503</td>\n",
       "      <td>0.773098</td>\n",
       "      <td>0.990893</td>\n",
       "      <td>0.984877</td>\n",
       "      <td>0.970516</td>\n",
       "      <td>0.006697</td>\n",
       "      <td>0.003665</td>\n",
       "      <td>0.941900</td>\n",
       "      <td>0.999166</td>\n",
       "      <td>SEKER</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    Area  Perimeter  MajorAxisLength  MinorAxisLength  AspectRatio  \\\n",
       "0  28395    610.291       208.178117       173.888747     1.197191   \n",
       "1  28734    638.018       200.524796       182.734419     1.097356   \n",
       "2  29380    624.110       212.826130       175.931143     1.209713   \n",
       "3  30008    645.884       210.557999       182.516516     1.153638   \n",
       "4  30140    620.134       201.847882       190.279279     1.060798   \n",
       "\n",
       "   Eccentricity  ConvexArea  EquivDiameter    Extent  Solidity  Roundness  \\\n",
       "0      0.549812       28715     190.141097  0.763923  0.988856   0.958027   \n",
       "1      0.411785       29172     191.272751  0.783968  0.984986   0.887034   \n",
       "2      0.562727       29690     193.410904  0.778113  0.989559   0.947849   \n",
       "3      0.498616       30724     195.467062  0.782681  0.976696   0.903936   \n",
       "4      0.333680       30417     195.896503  0.773098  0.990893   0.984877   \n",
       "\n",
       "   Compactness  ShapeFactor1  ShapeFactor2  ShapeFactor3  ShapeFactor4  Class  \n",
       "0     0.913358      0.007332      0.003147      0.834222      0.998724  SEKER  \n",
       "1     0.953861      0.006979      0.003564      0.909851      0.998430  SEKER  \n",
       "2     0.908774      0.007244      0.003048      0.825871      0.999066  SEKER  \n",
       "3     0.928329      0.007017      0.003215      0.861794      0.994199  SEKER  \n",
       "4     0.970516      0.006697      0.003665      0.941900      0.999166  SEKER  "
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dry_bean = fetch_ucirepo(id=602)\n",
    "\n",
    "X_df   = dry_bean.data.features          # DataFrame 13611 x 16\n",
    "y_raw  = dry_bean.data.targets.squeeze() # Series con etiquetas de clase\n",
    "\n",
    "df = X_df.copy()\n",
    "df['Class'] = y_raw.values\n",
    "\n",
    "print('Shape:', df.shape)\n",
    "print('\\nMetadata:')\n",
    "print(dry_bean.metadata['abstract'])\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "13e1a95a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Valores nulos por columna:\n",
      "Area               0\n",
      "Perimeter          0\n",
      "MajorAxisLength    0\n",
      "MinorAxisLength    0\n",
      "AspectRatio        0\n",
      "Eccentricity       0\n",
      "ConvexArea         0\n",
      "EquivDiameter      0\n",
      "Extent             0\n",
      "Solidity           0\n",
      "Roundness          0\n",
      "Compactness        0\n",
      "ShapeFactor1       0\n",
      "ShapeFactor2       0\n",
      "ShapeFactor3       0\n",
      "ShapeFactor4       0\n",
      "Class              0\n",
      "dtype: int64\n",
      "\n",
      "Distribución de clases:\n",
      "Class\n",
      "DERMASON    3546\n",
      "SIRA        2636\n",
      "SEKER       2027\n",
      "HOROZ       1928\n",
      "CALI        1630\n",
      "BARBUNYA    1322\n",
      "BOMBAY       522\n",
      "Name: count, dtype: int64\n"
     ]
    }
   ],
   "source": [
    "print('Valores nulos por columna:')\n",
    "print(df.isnull().sum())\n",
    "print('\\nDistribución de clases:')\n",
    "print(df['Class'].value_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "caa5998f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Area</th>\n",
       "      <th>Perimeter</th>\n",
       "      <th>MajorAxisLength</th>\n",
       "      <th>MinorAxisLength</th>\n",
       "      <th>AspectRatio</th>\n",
       "      <th>Eccentricity</th>\n",
       "      <th>ConvexArea</th>\n",
       "      <th>EquivDiameter</th>\n",
       "      <th>Extent</th>\n",
       "      <th>Solidity</th>\n",
       "      <th>Roundness</th>\n",
       "      <th>Compactness</th>\n",
       "      <th>ShapeFactor1</th>\n",
       "      <th>ShapeFactor2</th>\n",
       "      <th>ShapeFactor3</th>\n",
       "      <th>ShapeFactor4</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>count</th>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "      <td>13611.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>mean</th>\n",
       "      <td>53048.284549</td>\n",
       "      <td>855.283459</td>\n",
       "      <td>320.141867</td>\n",
       "      <td>202.270714</td>\n",
       "      <td>1.583242</td>\n",
       "      <td>0.750895</td>\n",
       "      <td>53768.200206</td>\n",
       "      <td>253.064220</td>\n",
       "      <td>0.749733</td>\n",
       "      <td>0.987143</td>\n",
       "      <td>0.873282</td>\n",
       "      <td>0.799864</td>\n",
       "      <td>0.006564</td>\n",
       "      <td>0.001716</td>\n",
       "      <td>0.643590</td>\n",
       "      <td>0.995063</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>std</th>\n",
       "      <td>29324.095717</td>\n",
       "      <td>214.289696</td>\n",
       "      <td>85.694186</td>\n",
       "      <td>44.970091</td>\n",
       "      <td>0.246678</td>\n",
       "      <td>0.092002</td>\n",
       "      <td>29774.915817</td>\n",
       "      <td>59.177120</td>\n",
       "      <td>0.049086</td>\n",
       "      <td>0.004660</td>\n",
       "      <td>0.059520</td>\n",
       "      <td>0.061713</td>\n",
       "      <td>0.001128</td>\n",
       "      <td>0.000596</td>\n",
       "      <td>0.098996</td>\n",
       "      <td>0.004366</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>min</th>\n",
       "      <td>20420.000000</td>\n",
       "      <td>524.736000</td>\n",
       "      <td>183.601165</td>\n",
       "      <td>122.512653</td>\n",
       "      <td>1.024868</td>\n",
       "      <td>0.218951</td>\n",
       "      <td>20684.000000</td>\n",
       "      <td>161.243764</td>\n",
       "      <td>0.555315</td>\n",
       "      <td>0.919246</td>\n",
       "      <td>0.489618</td>\n",
       "      <td>0.640577</td>\n",
       "      <td>0.002778</td>\n",
       "      <td>0.000564</td>\n",
       "      <td>0.410339</td>\n",
       "      <td>0.947687</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25%</th>\n",
       "      <td>36328.000000</td>\n",
       "      <td>703.523500</td>\n",
       "      <td>253.303633</td>\n",
       "      <td>175.848170</td>\n",
       "      <td>1.432307</td>\n",
       "      <td>0.715928</td>\n",
       "      <td>36714.500000</td>\n",
       "      <td>215.068003</td>\n",
       "      <td>0.718634</td>\n",
       "      <td>0.985670</td>\n",
       "      <td>0.832096</td>\n",
       "      <td>0.762469</td>\n",
       "      <td>0.005900</td>\n",
       "      <td>0.001154</td>\n",
       "      <td>0.581359</td>\n",
       "      <td>0.993703</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>50%</th>\n",
       "      <td>44652.000000</td>\n",
       "      <td>794.941000</td>\n",
       "      <td>296.883367</td>\n",
       "      <td>192.431733</td>\n",
       "      <td>1.551124</td>\n",
       "      <td>0.764441</td>\n",
       "      <td>45178.000000</td>\n",
       "      <td>238.438026</td>\n",
       "      <td>0.759859</td>\n",
       "      <td>0.988283</td>\n",
       "      <td>0.883157</td>\n",
       "      <td>0.801277</td>\n",
       "      <td>0.006645</td>\n",
       "      <td>0.001694</td>\n",
       "      <td>0.642044</td>\n",
       "      <td>0.996386</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>75%</th>\n",
       "      <td>61332.000000</td>\n",
       "      <td>977.213000</td>\n",
       "      <td>376.495012</td>\n",
       "      <td>217.031741</td>\n",
       "      <td>1.707109</td>\n",
       "      <td>0.810466</td>\n",
       "      <td>62294.000000</td>\n",
       "      <td>279.446467</td>\n",
       "      <td>0.786851</td>\n",
       "      <td>0.990013</td>\n",
       "      <td>0.916869</td>\n",
       "      <td>0.834270</td>\n",
       "      <td>0.007271</td>\n",
       "      <td>0.002170</td>\n",
       "      <td>0.696006</td>\n",
       "      <td>0.997883</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>max</th>\n",
       "      <td>254616.000000</td>\n",
       "      <td>1985.370000</td>\n",
       "      <td>738.860154</td>\n",
       "      <td>460.198497</td>\n",
       "      <td>2.430306</td>\n",
       "      <td>0.911423</td>\n",
       "      <td>263261.000000</td>\n",
       "      <td>569.374358</td>\n",
       "      <td>0.866195</td>\n",
       "      <td>0.994677</td>\n",
       "      <td>0.990685</td>\n",
       "      <td>0.987303</td>\n",
       "      <td>0.010451</td>\n",
       "      <td>0.003665</td>\n",
       "      <td>0.974767</td>\n",
       "      <td>0.999733</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                Area     Perimeter  MajorAxisLength  MinorAxisLength  \\\n",
       "count   13611.000000  13611.000000     13611.000000     13611.000000   \n",
       "mean    53048.284549    855.283459       320.141867       202.270714   \n",
       "std     29324.095717    214.289696        85.694186        44.970091   \n",
       "min     20420.000000    524.736000       183.601165       122.512653   \n",
       "25%     36328.000000    703.523500       253.303633       175.848170   \n",
       "50%     44652.000000    794.941000       296.883367       192.431733   \n",
       "75%     61332.000000    977.213000       376.495012       217.031741   \n",
       "max    254616.000000   1985.370000       738.860154       460.198497   \n",
       "\n",
       "        AspectRatio  Eccentricity     ConvexArea  EquivDiameter        Extent  \\\n",
       "count  13611.000000  13611.000000   13611.000000   13611.000000  13611.000000   \n",
       "mean       1.583242      0.750895   53768.200206     253.064220      0.749733   \n",
       "std        0.246678      0.092002   29774.915817      59.177120      0.049086   \n",
       "min        1.024868      0.218951   20684.000000     161.243764      0.555315   \n",
       "25%        1.432307      0.715928   36714.500000     215.068003      0.718634   \n",
       "50%        1.551124      0.764441   45178.000000     238.438026      0.759859   \n",
       "75%        1.707109      0.810466   62294.000000     279.446467      0.786851   \n",
       "max        2.430306      0.911423  263261.000000     569.374358      0.866195   \n",
       "\n",
       "           Solidity     Roundness   Compactness  ShapeFactor1  ShapeFactor2  \\\n",
       "count  13611.000000  13611.000000  13611.000000  13611.000000  13611.000000   \n",
       "mean       0.987143      0.873282      0.799864      0.006564      0.001716   \n",
       "std        0.004660      0.059520      0.061713      0.001128      0.000596   \n",
       "min        0.919246      0.489618      0.640577      0.002778      0.000564   \n",
       "25%        0.985670      0.832096      0.762469      0.005900      0.001154   \n",
       "50%        0.988283      0.883157      0.801277      0.006645      0.001694   \n",
       "75%        0.990013      0.916869      0.834270      0.007271      0.002170   \n",
       "max        0.994677      0.990685      0.987303      0.010451      0.003665   \n",
       "\n",
       "       ShapeFactor3  ShapeFactor4  \n",
       "count  13611.000000  13611.000000  \n",
       "mean       0.643590      0.995063  \n",
       "std        0.098996      0.004366  \n",
       "min        0.410339      0.947687  \n",
       "25%        0.581359      0.993703  \n",
       "50%        0.642044      0.996386  \n",
       "75%        0.696006      0.997883  \n",
       "max        0.974767      0.999733  "
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df.describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "92e35ff2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 29288 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Distribución de clases\n",
    "df['Class'].value_counts().plot(kind='bar', title='Distribución de Clases')\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "caca2128",
   "metadata": {},
   "source": [
    "## 3. Preparación de Datos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "b19d4476",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Clases: ['BARBUNYA' 'BOMBAY' 'CALI' 'DERMASON' 'HOROZ' 'SEKER' 'SIRA']\n",
      "Train: (10888, 16) | Test: (2723, 16)\n",
      "Escalado aplicado — X_train_scaled shape: (10888, 16)\n"
     ]
    }
   ],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "feature_names = [c for c in df.columns if c != 'Class']\n",
    "X = df[feature_names].values\n",
    "y_raw = df['Class'].values\n",
    "\n",
    "le = LabelEncoder()\n",
    "y = le.fit_transform(y_raw)\n",
    "print('Clases:', le.classes_)\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "    X, y, test_size=0.2, stratify=y, random_state=RANDOM_STATE\n",
    ")\n",
    "print(f'Train: {X_train.shape} | Test: {X_test.shape}')\n",
    "\n",
    "# Escalado para la Red Neuronal (fit solo en train, transform en ambos)\n",
    "scaler = StandardScaler()\n",
    "X_train_scaled = scaler.fit_transform(X_train)\n",
    "X_test_scaled  = scaler.transform(X_test)\n",
    "print('Escalado aplicado — X_train_scaled shape:', X_train_scaled.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ce7295c",
   "metadata": {},
   "source": [
    "## 4. Bitácora — Funciones\n",
    "\n",
    "Las funciones `metricas()` y `registrar()` están en `src/utils.py`.  \n",
    "Registran cada experimento en `outputs/bitacora_experimentos.csv` en modo append."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f11cba6f",
   "metadata": {},
   "source": [
    "## 5. Experimentos — Random Forest\n",
    "\n",
    "≥5 experimentos variando: `n_estimators`, `max_depth`, `min_samples_leaf`, `max_features`, `criterion`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "7705ae9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[bitacora] random_forest | RF_01 | F1 test=0.9328\n",
      "[bitacora] random_forest | RF_02 | F1 test=0.9319\n",
      "[bitacora] random_forest | RF_03 | F1 test=0.9311\n",
      "[bitacora] random_forest | RF_04 | F1 test=0.9317\n",
      "[bitacora] random_forest | RF_05 | F1 test=0.9316\n",
      "Experimentos RF registrados.\n"
     ]
    }
   ],
   "source": [
    "experimentos_rf = [\n",
    "    {'n_estimators': 100, 'max_depth': None, 'min_samples_leaf': 1,  'max_features': 'sqrt', 'criterion': 'gini'},\n",
    "    {'n_estimators': 200, 'max_depth': 10,   'min_samples_leaf': 1,  'max_features': 'sqrt', 'criterion': 'gini'},\n",
    "    {'n_estimators': 200, 'max_depth': 20,   'min_samples_leaf': 2,  'max_features': 'sqrt', 'criterion': 'gini'},\n",
    "    {'n_estimators': 500, 'max_depth': None, 'min_samples_leaf': 5,  'max_features': 'log2', 'criterion': 'gini'},\n",
    "    {'n_estimators': 300, 'max_depth': None, 'min_samples_leaf': 1,  'max_features': 'sqrt', 'criterion': 'entropy'},\n",
    "]\n",
    "\n",
    "for i, cfg in enumerate(experimentos_rf, 1):\n",
    "    model = RandomForestClassifier(**cfg, random_state=RANDOM_STATE, n_jobs=-1)\n",
    "    model.fit(X_train, y_train)\n",
    "    registrar(f'RF_{i:02d}', 'random_forest', cfg, '', model, X_train, y_train, X_test, y_test)\n",
    "\n",
    "print('Experimentos RF registrados.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "45162343",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  experimento_id  f1_train   f1_test\n",
      "0          RF_01  1.000000  0.932772\n",
      "1          RF_02  0.970755  0.931902\n",
      "3          RF_04  0.968360  0.931652\n",
      "4          RF_05  1.000000  0.931572\n",
      "2          RF_03  0.989488  0.931058\n",
      "\n",
      "Mejor RF: {'n_estimators': 100, 'max_depth': None, 'min_samples_leaf': 1, 'max_features': 'sqrt', 'criterion': 'gini'}\n"
     ]
    }
   ],
   "source": [
    "# Leer bitácora y seleccionar mejor RF\n",
    "df_bit = pd.read_csv(os.path.join('outputs', 'bitacora_experimentos.csv'))\n",
    "df_rf = df_bit[df_bit['modelo'] == 'random_forest']\n",
    "print(df_rf[['experimento_id', 'f1_train', 'f1_test']].sort_values('f1_test', ascending=False))\n",
    "\n",
    "mejor_rf_cfg = experimentos_rf[df_rf['f1_test'].idxmax() - df_rf.index[0]]\n",
    "mejor_rf = RandomForestClassifier(**mejor_rf_cfg, random_state=RANDOM_STATE, n_jobs=-1)\n",
    "mejor_rf.fit(X_train, y_train)\n",
    "print('\\nMejor RF:', mejor_rf_cfg)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14a08696",
   "metadata": {},
   "source": [
    "## 6. Experimentos — XGBoost\n",
    "\n",
    "≥5 experimentos variando: `n_estimators`, `max_depth`, `learning_rate`, `subsample`, `colsample_bytree`, `reg_lambda`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "3cdc3f32",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[bitacora] xgboost | XGB_01 | F1 test=0.9363\n",
      "[bitacora] xgboost | XGB_02 | F1 test=0.9377\n",
      "[bitacora] xgboost | XGB_03 | F1 test=0.9370\n",
      "[bitacora] xgboost | XGB_04 | F1 test=0.9340\n",
      "[bitacora] xgboost | XGB_05 | F1 test=0.9365\n",
      "Experimentos XGBoost registrados.\n"
     ]
    }
   ],
   "source": [
    "experimentos_xgb = [\n",
    "    {'n_estimators': 100, 'max_depth': 6,  'learning_rate': 0.3,  'subsample': 1.0, 'colsample_bytree': 1.0, 'reg_lambda': 1},\n",
    "    {'n_estimators': 200, 'max_depth': 6,  'learning_rate': 0.1,  'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1},\n",
    "    {'n_estimators': 300, 'max_depth': 4,  'learning_rate': 0.05, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 2},\n",
    "    {'n_estimators': 200, 'max_depth': 8,  'learning_rate': 0.1,  'subsample': 0.6, 'colsample_bytree': 0.6, 'reg_lambda': 5},\n",
    "    {'n_estimators': 500, 'max_depth': 6,  'learning_rate': 0.01, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1},\n",
    "]\n",
    "\n",
    "for i, cfg in enumerate(experimentos_xgb, 1):\n",
    "    model = XGBClassifier(**cfg, random_state=RANDOM_STATE, eval_metric='mlogloss', verbosity=0)\n",
    "    model.fit(X_train, y_train)\n",
    "    registrar(f'XGB_{i:02d}', 'xgboost', cfg, '', model, X_train, y_train, X_test, y_test)\n",
    "\n",
    "print('Experimentos XGBoost registrados.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "1472d6b1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  experimento_id  f1_train   f1_test\n",
      "6         XGB_02  0.999447  0.937734\n",
      "7         XGB_03  0.972587  0.936978\n",
      "9         XGB_05  0.969759  0.936526\n",
      "5         XGB_01  0.999941  0.936272\n",
      "8         XGB_04  0.997685  0.933975\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Mejor XGBoost: {'n_estimators': 200, 'max_depth': 6, 'learning_rate': 0.1, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_lambda': 1}\n"
     ]
    }
   ],
   "source": [
    "df_bit = pd.read_csv(os.path.join('outputs', 'bitacora_experimentos.csv'))\n",
    "df_xgb = df_bit[df_bit['modelo'] == 'xgboost']\n",
    "print(df_xgb[['experimento_id', 'f1_train', 'f1_test']].sort_values('f1_test', ascending=False))\n",
    "\n",
    "mejor_xgb_cfg = experimentos_xgb[df_xgb['f1_test'].idxmax() - df_xgb.index[0]]\n",
    "mejor_xgb = XGBClassifier(**mejor_xgb_cfg, random_state=RANDOM_STATE, eval_metric='mlogloss', verbosity=0)\n",
    "mejor_xgb.fit(X_train, y_train)\n",
    "print('\\nMejor XGBoost:', mejor_xgb_cfg)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbfd6199",
   "metadata": {},
   "source": [
    "## 7. Experimentos — Red Neuronal (Keras)\n",
    "\n",
    "≥5 experimentos variando: capas, neuronas, `dropout`, `lr`, `batch_size`, `epochs`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "6fd7b0c7",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
      "I0000 00:00:1782283235.163821  678660 cudart_stub.cc:31] Could not find cuda drivers on your machine, GPU will not be used.\n",
      "I0000 00:00:1782283235.560729  678660 cpu_feature_guard.cc:227] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n",
      "To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n",
      "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
      "I0000 00:00:1782283237.375377  678660 cudart_stub.cc:31] Could not find cuda drivers on your machine, GPU will not be used.\n",
      "E0000 00:00:1782283237.662609  678660 cuda_platform.cc:52] failed call to cuInit: INTERNAL: CUDA error: Failed call to cuInit: UNKNOWN ERROR (303)\n",
      "I0000 00:00:1782283237.662748  678660 cuda_diagnostics.cc:171] verbose logging is disabled. Rerun with verbose logging (usually --v=1 or --vmodule=cuda_diagnostics=1) to get more diagnostic output from this module\n",
      "I0000 00:00:1782283237.662754  678660 cuda_diagnostics.cc:176] retrieving CUDA diagnostic information for host: 41ad1eab7f7e\n",
      "I0000 00:00:1782283237.662760  678660 cuda_diagnostics.cc:183] hostname: 41ad1eab7f7e\n",
      "I0000 00:00:1782283237.662891  678660 cuda_diagnostics.cc:190] libcuda reported version is: NOT_FOUND: was unable to find libcuda.so DSO loaded into this program. The library may be missing or provided via another object.\n",
      "I0000 00:00:1782283237.663023  678660 cuda_diagnostics.cc:194] kernel reported version is: 580.159.3\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[bitacora] neural_network | NN_01 | F1 test=0.9374\n",
      "[bitacora] neural_network | NN_02 | F1 test=0.9408\n",
      "[bitacora] neural_network | NN_03 | F1 test=0.9388\n",
      "[bitacora] neural_network | NN_04 | F1 test=0.9386\n",
      "[bitacora] neural_network | NN_05 | F1 test=0.9383\n",
      "Experimentos NN (Keras) registrados.\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "from tensorflow.keras import layers\n",
    "\n",
    "N_CLASES   = len(le.classes_)        # 7\n",
    "N_FEATURES = X_train_scaled.shape[1] # 16\n",
    "\n",
    "def crear_modelo(capas, activacion='relu', dropout=0.0, lr=0.001):\n",
    "    model = keras.Sequential()\n",
    "    model.add(keras.Input(shape=(N_FEATURES,)))\n",
    "    for neuronas in capas:\n",
    "        model.add(layers.Dense(neuronas, activation=activacion))\n",
    "        if dropout > 0:\n",
    "            model.add(layers.Dropout(dropout))\n",
    "    model.add(layers.Dense(N_CLASES, activation='softmax'))\n",
    "    model.compile(\n",
    "        optimizer=keras.optimizers.Adam(learning_rate=lr),\n",
    "        loss='sparse_categorical_crossentropy',\n",
    "    )\n",
    "    return model\n",
    "\n",
    "\n",
    "class KerasWrapper:\n",
    "    \"\"\"Wrapper sklearn-compatible para modelos Keras.\"\"\"\n",
    "    def __init__(self, model, batch_size=64, epochs=100):\n",
    "        self.model      = model\n",
    "        self.batch_size = batch_size\n",
    "        self.epochs     = epochs\n",
    "\n",
    "    def predict(self, X):\n",
    "        return np.argmax(self.model.predict(X, verbose=0), axis=1)\n",
    "\n",
    "    def fit(self, X, y, **kwargs):\n",
    "        self.model.fit(X, y,\n",
    "                       batch_size=self.batch_size,\n",
    "                       epochs=self.epochs,\n",
    "                       verbose=0)\n",
    "        return self\n",
    "\n",
    "\n",
    "experimentos_nn = [\n",
    "    {'capas': (64,),          'activacion': 'relu', 'dropout': 0.0, 'lr': 0.001,  'batch_size': 32,  'epochs': 50,  'arquitectura': '[64]→softmax'},\n",
    "    {'capas': (128, 64),      'activacion': 'relu', 'dropout': 0.3, 'lr': 0.001,  'batch_size': 64,  'epochs': 100, 'arquitectura': '[128,64]+Dropout(0.3)→softmax'},\n",
    "    {'capas': (256, 128, 64), 'activacion': 'relu', 'dropout': 0.3, 'lr': 0.001,  'batch_size': 64,  'epochs': 100, 'arquitectura': '[256,128,64]+Dropout(0.3)→softmax'},\n",
    "    {'capas': (128, 64),      'activacion': 'relu', 'dropout': 0.2, 'lr': 0.0001, 'batch_size': 32,  'epochs': 150, 'arquitectura': '[128,64]+Dropout(0.2)→softmax(lr=0.0001)'},\n",
    "    {'capas': (256, 128),     'activacion': 'relu', 'dropout': 0.4, 'lr': 0.001,  'batch_size': 128, 'epochs': 100, 'arquitectura': '[256,128]+Dropout(0.4)→softmax'},\n",
    "]\n",
    "\n",
    "for i, cfg in enumerate(experimentos_nn, 1):\n",
    "    tf.random.set_seed(RANDOM_STATE)\n",
    "    model = crear_modelo(cfg['capas'], cfg['activacion'], cfg['dropout'], cfg['lr'])\n",
    "    model.fit(X_train_scaled, y_train,\n",
    "              batch_size=cfg['batch_size'], epochs=cfg['epochs'], verbose=0)\n",
    "    wrapper    = KerasWrapper(model, cfg['batch_size'], cfg['epochs'])\n",
    "    hiperparams = {k: v for k, v in cfg.items() if k != 'arquitectura'}\n",
    "    registrar(f'NN_{i:02d}', 'neural_network', hiperparams, cfg['arquitectura'],\n",
    "              wrapper, X_train_scaled, y_train, X_test_scaled, y_test)\n",
    "\n",
    "print('Experimentos NN (Keras) registrados.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "4b453a52",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   experimento_id  f1_train   f1_test\n",
      "11          NN_02  0.952976  0.940837\n",
      "12          NN_03  0.957654  0.938762\n",
      "13          NN_04  0.950294  0.938605\n",
      "14          NN_05  0.954552  0.938261\n",
      "10          NN_01  0.950540  0.937374\n",
      "\n",
      "Mejor NN — hiperparámetros: {'capas': (128, 64), 'activacion': 'relu', 'dropout': 0.3, 'lr': 0.001, 'batch_size': 64, 'epochs': 100}\n",
      "Arquitectura: [128,64]+Dropout(0.3)→softmax\n"
     ]
    }
   ],
   "source": [
    "df_bit = pd.read_csv(os.path.join('outputs', 'bitacora_experimentos.csv'))\n",
    "df_nn  = df_bit[df_bit['modelo'] == 'neural_network']\n",
    "print(df_nn[['experimento_id', 'f1_train', 'f1_test']].sort_values('f1_test', ascending=False))\n",
    "\n",
    "mejor_nn_idx = df_nn['f1_test'].idxmax() - df_nn.index[0]\n",
    "mejor_nn_cfg = experimentos_nn[mejor_nn_idx]\n",
    "\n",
    "tf.random.set_seed(RANDOM_STATE)\n",
    "model_nn = crear_modelo(mejor_nn_cfg['capas'], mejor_nn_cfg['activacion'],\n",
    "                        mejor_nn_cfg['dropout'], mejor_nn_cfg['lr'])\n",
    "model_nn.fit(X_train_scaled, y_train,\n",
    "             batch_size=mejor_nn_cfg['batch_size'],\n",
    "             epochs=mejor_nn_cfg['epochs'], verbose=0)\n",
    "mejor_nn = KerasWrapper(model_nn, mejor_nn_cfg['batch_size'], mejor_nn_cfg['epochs'])\n",
    "print('\\nMejor NN — hiperparámetros:', {k: v for k, v in mejor_nn_cfg.items() if k != 'arquitectura'})\n",
    "print('Arquitectura:', mejor_nn_cfg['arquitectura'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a897674a",
   "metadata": {},
   "source": [
    "## 8. Feature Importance — Random Forest vs XGBoost"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "fa2d947c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[figura] guardada en /home/aleleba/projects/cursos/Universidad/Machine Learning Aplicado/proyecto-final/outputs/figures/feature_importance__random_forest.png\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 53072 characters]",
      "text/plain": [
       "<Figure size 800x640 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[figura] guardada en /home/aleleba/projects/cursos/Universidad/Machine Learning Aplicado/proyecto-final/outputs/figures/feature_importance__xgboost.png\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 54140 characters]",
      "text/plain": [
       "<Figure size 800x640 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "imp_rf  = plot_importancia(mejor_rf,  feature_names, 'Feature Importance — Random Forest')\n",
    "imp_xgb = plot_importancia(mejor_xgb, feature_names, 'Feature Importance — XGBoost')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "41d8c51d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                 RF_rank  XGB_rank  delta\n",
      "ShapeFactor3           1         1      0\n",
      "Perimeter              2         9      7\n",
      "Compactness            3         2      1\n",
      "ShapeFactor1           4         7      3\n",
      "MajorAxisLength        5         8      3\n",
      "MinorAxisLength        6         6      0\n",
      "ConvexArea             7         5      2\n",
      "Eccentricity           8        12      4\n",
      "EquivDiameter          9         3      6\n",
      "Area                  10         4      6\n",
      "Roundness             11        10      1\n",
      "AspectRatio           12        11      1\n",
      "ShapeFactor2          13        13      0\n",
      "ShapeFactor4          14        14      0\n",
      "Solidity              15        15      0\n",
      "Extent                16        16      0\n"
     ]
    }
   ],
   "source": [
    "# Comparación de rankings\n",
    "comp = pd.DataFrame({\n",
    "    'RF_rank':  imp_rf.rank(ascending=False).astype(int),\n",
    "    'XGB_rank': imp_xgb.rank(ascending=False).astype(int),\n",
    "}).sort_values('RF_rank')\n",
    "comp['delta'] = (comp['RF_rank'] - comp['XGB_rank']).abs()\n",
    "print(comp)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "750d622b",
   "metadata": {},
   "source": [
    "## 9. Ensamble — Majority Voting con Bootstrap"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "b9c3633f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ensamble F1 train: 0.9754 | F1 test: 0.9361\n"
     ]
    }
   ],
   "source": [
    "from scipy.stats import mode as scipy_mode\n",
    "\n",
    "# RF y XGB usan features originales; NN usa features escaladas\n",
    "modelos_info = [\n",
    "    (mejor_rf,  X_train,        X_test),\n",
    "    (mejor_xgb, X_train,        X_test),\n",
    "    (mejor_nn,  X_train_scaled, X_test_scaled),\n",
    "]\n",
    "\n",
    "np.random.seed(RANDOM_STATE)\n",
    "preds_test  = []\n",
    "preds_train = []\n",
    "\n",
    "for m, X_tr, X_te in modelos_info:\n",
    "    idx = np.random.choice(len(X_tr), size=len(X_tr), replace=True)\n",
    "    m.fit(X_tr[idx], y_train[idx])\n",
    "    preds_test.append(m.predict(X_te))\n",
    "    preds_train.append(m.predict(X_tr))\n",
    "\n",
    "preds_test  = np.vstack(preds_test)\n",
    "preds_train = np.vstack(preds_train)\n",
    "\n",
    "y_ensamble_test  = scipy_mode(preds_test,  axis=0).mode.ravel()\n",
    "y_ensamble_train = scipy_mode(preds_train, axis=0).mode.ravel()\n",
    "\n",
    "f1_ens_train = f1_score(y_train, y_ensamble_train, average='macro')\n",
    "f1_ens_test  = f1_score(y_test,  y_ensamble_test,  average='macro')\n",
    "print(f'Ensamble F1 train: {f1_ens_train:.4f} | F1 test: {f1_ens_test:.4f}')\n",
    "# Desempate (3 votos distintos): gana la predicción del modelo con mayor F1 individual (XGBoost)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa5b357e",
   "metadata": {},
   "source": [
    "## 10. Tabla Comparativa Final + Diagnóstico"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "52d02a7d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">\n",
       "#T_8e2f6 th {\n",
       "  text-align: center;\n",
       "}\n",
       "#T_8e2f6_row0_col0, #T_8e2f6_row0_col1, #T_8e2f6_row0_col2, #T_8e2f6_row0_col3, #T_8e2f6_row0_col4, #T_8e2f6_row1_col0, #T_8e2f6_row1_col1, #T_8e2f6_row1_col2, #T_8e2f6_row1_col3, #T_8e2f6_row1_col4, #T_8e2f6_row2_col0, #T_8e2f6_row2_col1, #T_8e2f6_row2_col4, #T_8e2f6_row3_col0, #T_8e2f6_row3_col1, #T_8e2f6_row3_col2, #T_8e2f6_row3_col3, #T_8e2f6_row3_col4 {\n",
       "  text-align: center;\n",
       "}\n",
       "#T_8e2f6_row2_col2, #T_8e2f6_row2_col3 {\n",
       "  background-color: #c6efce;\n",
       "  text-align: center;\n",
       "}\n",
       "</style>\n",
       "<table id=\"T_8e2f6\">\n",
       "  <thead>\n",
       "    <tr>\n",
       "      <th class=\"blank level0\" >&nbsp;</th>\n",
       "      <th id=\"T_8e2f6_level0_col0\" class=\"col_heading level0 col0\" >Modelo</th>\n",
       "      <th id=\"T_8e2f6_level0_col1\" class=\"col_heading level0 col1\" >F1 train</th>\n",
       "      <th id=\"T_8e2f6_level0_col2\" class=\"col_heading level0 col2\" >F1 test</th>\n",
       "      <th id=\"T_8e2f6_level0_col3\" class=\"col_heading level0 col3\" >Δ</th>\n",
       "      <th id=\"T_8e2f6_level0_col4\" class=\"col_heading level0 col4\" >Diagnóstico</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th id=\"T_8e2f6_level0_row0\" class=\"row_heading level0 row0\" >0</th>\n",
       "      <td id=\"T_8e2f6_row0_col0\" class=\"data row0 col0\" >Random Forest</td>\n",
       "      <td id=\"T_8e2f6_row0_col1\" class=\"data row0 col1\" >0.9739</td>\n",
       "      <td id=\"T_8e2f6_row0_col2\" class=\"data row0 col2\" >0.9280</td>\n",
       "      <td id=\"T_8e2f6_row0_col3\" class=\"data row0 col3\" >0.0459</td>\n",
       "      <td id=\"T_8e2f6_row0_col4\" class=\"data row0 col4\" >Bien ajustado</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_8e2f6_level0_row1\" class=\"row_heading level0 row1\" >1</th>\n",
       "      <td id=\"T_8e2f6_row1_col0\" class=\"data row1 col0\" >XGBoost</td>\n",
       "      <td id=\"T_8e2f6_row1_col1\" class=\"data row1 col1\" >0.9779</td>\n",
       "      <td id=\"T_8e2f6_row1_col2\" class=\"data row1 col2\" >0.9327</td>\n",
       "      <td id=\"T_8e2f6_row1_col3\" class=\"data row1 col3\" >0.0452</td>\n",
       "      <td id=\"T_8e2f6_row1_col4\" class=\"data row1 col4\" >Bien ajustado</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_8e2f6_level0_row2\" class=\"row_heading level0 row2\" >2</th>\n",
       "      <td id=\"T_8e2f6_row2_col0\" class=\"data row2 col0\" >Red Neuronal</td>\n",
       "      <td id=\"T_8e2f6_row2_col1\" class=\"data row2 col1\" >0.9565</td>\n",
       "      <td id=\"T_8e2f6_row2_col2\" class=\"data row2 col2\" >0.9404</td>\n",
       "      <td id=\"T_8e2f6_row2_col3\" class=\"data row2 col3\" >0.0161</td>\n",
       "      <td id=\"T_8e2f6_row2_col4\" class=\"data row2 col4\" >Bien ajustado</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th id=\"T_8e2f6_level0_row3\" class=\"row_heading level0 row3\" >3</th>\n",
       "      <td id=\"T_8e2f6_row3_col0\" class=\"data row3 col0\" >Ensamble</td>\n",
       "      <td id=\"T_8e2f6_row3_col1\" class=\"data row3 col1\" >0.9754</td>\n",
       "      <td id=\"T_8e2f6_row3_col2\" class=\"data row3 col2\" >0.9361</td>\n",
       "      <td id=\"T_8e2f6_row3_col3\" class=\"data row3 col3\" >0.0393</td>\n",
       "      <td id=\"T_8e2f6_row3_col4\" class=\"data row3 col4\" >Bien ajustado</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n"
      ],
      "text/plain": [
       "<pandas.io.formats.style.Styler at 0x7707b2e56470>"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tabla = []\n",
    "for nombre, f1_tr, f1_te in [\n",
    "    ('Random Forest', *get_f1(mejor_rf,  X_train,        y_train, X_test,        y_test)),\n",
    "    ('XGBoost',       *get_f1(mejor_xgb, X_train,        y_train, X_test,        y_test)),\n",
    "    ('Red Neuronal',  *get_f1(mejor_nn,  X_train_scaled, y_train, X_test_scaled, y_test)),\n",
    "    ('Ensamble',      f1_ens_train, f1_ens_test),\n",
    "]:\n",
    "    tabla.append({\n",
    "        'Modelo':      nombre,\n",
    "        'F1 train':    round(f1_tr, 4),\n",
    "        'F1 test':     round(f1_te, 4),\n",
    "        'Δ':           round(abs(f1_tr - f1_te), 4),\n",
    "        'Diagnóstico': diagnostico(f1_tr, f1_te),\n",
    "    })\n",
    "\n",
    "df_tabla = pd.DataFrame(tabla)\n",
    "\n",
    "(df_tabla.style\n",
    "    .format({'F1 train': '{:.4f}', 'F1 test': '{:.4f}', 'Δ': '{:.4f}'})\n",
    "    .highlight_max(subset=['F1 test'], color='#c6efce')\n",
    "    .highlight_min(subset=['Δ'],       color='#c6efce')\n",
    "    .set_properties(**{'text-align': 'center'})\n",
    "    .set_table_styles([{'selector': 'th', 'props': [('text-align', 'center')]}])\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "a7ee6f68",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "╔══════════════════════════════════════════════════════════════╗\n",
      "║                        CONCLUSIONES                          ║\n",
      "╚══════════════════════════════════════════════════════════════╝\n",
      "\n",
      "1. MEJOR MODELO INDIVIDUAL\n",
      "   Red Neuronal obtuvo el mayor F1 en test = 0.9404,\n",
      "   superando a las demás familias en generalización.\n",
      "\n",
      "2. ENSAMBLE\n",
      "   El ensamble majority voting con bootstrap obtuvo F1 test = 0.9361,\n",
      "   sin superar al mejor modelo individual\n",
      "   (Δ = 0.0043).\n",
      "\n",
      "3. DIAGNÓSTICO POR FAMILIA\n",
      "  · Random Forest  : F1 train=0.9739  F1 test=0.9280  Δ=0.0459  → Bien ajustado\n",
      "  · XGBoost        : F1 train=0.9779  F1 test=0.9327  Δ=0.0452  → Bien ajustado\n",
      "  · Red Neuronal   : F1 train=0.9565  F1 test=0.9404  Δ=0.0161  → Bien ajustado\n",
      "  · Ensamble       : F1 train=0.9754  F1 test=0.9361  Δ=0.0393  → Bien ajustado\n",
      "\n",
      "4. FEATURE IMPORTANCE\n",
      "   ShapeFactor3 fue la feature más importante para ambos modelos (RF y XGBoost).\n",
      "   ShapeFactor4, Solidity y Extent coincidieron como las menos relevantes.\n",
      "   La mayor discrepancia fue en Perimeter (RF rank 2 vs XGB rank 9), lo que\n",
      "   sugiere que XGBoost captura mejor las interacciones no lineales de esa feature.\n",
      "\n",
      "5. APRENDIZAJES\n",
      "   · La Red Neuronal (Keras + StandardScaler) compite con RF y XGBoost cuando\n",
      "     se escalan correctamente las features antes del entrenamiento.\n",
      "   · El dataset Dry Bean es limpio y bien estructurado: sin nulos, sin categorías,\n",
      "     lo que simplificó la preparación y permitió centrarse en los experimentos.\n",
      "   · El ensamble mejora la robustez pero no siempre supera al mejor modelo\n",
      "     individual; su valor está en reducir la varianza entre familias.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "mejor_ind = df_tabla[df_tabla['Modelo'] != 'Ensamble'].nlargest(1, 'F1 test').iloc[0]\n",
    "ensamble  = df_tabla[df_tabla['Modelo'] == 'Ensamble'].iloc[0]\n",
    "mejoro    = ensamble['F1 test'] > mejor_ind['F1 test']\n",
    "\n",
    "lineas_diag = '\\n'.join(\n",
    "    f\"  · {r['Modelo']:15s}: F1 train={r['F1 train']:.4f}  F1 test={r['F1 test']:.4f}  Δ={r['Δ']:.4f}  → {r['Diagnóstico']}\"\n",
    "    for _, r in df_tabla.iterrows()\n",
    ")\n",
    "\n",
    "print(f\"\"\"\n",
    "╔══════════════════════════════════════════════════════════════╗\n",
    "║                        CONCLUSIONES                          ║\n",
    "╚══════════════════════════════════════════════════════════════╝\n",
    "\n",
    "1. MEJOR MODELO INDIVIDUAL\n",
    "   {mejor_ind['Modelo']} obtuvo el mayor F1 en test = {mejor_ind['F1 test']:.4f},\n",
    "   superando a las demás familias en generalización.\n",
    "\n",
    "2. ENSAMBLE\n",
    "   El ensamble majority voting con bootstrap obtuvo F1 test = {ensamble['F1 test']:.4f},\n",
    "   {\"mejorando\" if mejoro else \"sin superar\"} al mejor modelo individual\n",
    "   (Δ = {abs(ensamble['F1 test'] - mejor_ind['F1 test']):.4f}).\n",
    "\n",
    "3. DIAGNÓSTICO POR FAMILIA\n",
    "{lineas_diag}\n",
    "\n",
    "4. FEATURE IMPORTANCE\n",
    "   ShapeFactor3 fue la feature más importante para ambos modelos (RF y XGBoost).\n",
    "   ShapeFactor4, Solidity y Extent coincidieron como las menos relevantes.\n",
    "   La mayor discrepancia fue en Perimeter (RF rank 2 vs XGB rank 9), lo que\n",
    "   sugiere que XGBoost captura mejor las interacciones no lineales de esa feature.\n",
    "\n",
    "5. APRENDIZAJES\n",
    "   · La Red Neuronal (Keras + StandardScaler) compite con RF y XGBoost cuando\n",
    "     se escalan correctamente las features antes del entrenamiento.\n",
    "   · El dataset Dry Bean es limpio y bien estructurado: sin nulos, sin categorías,\n",
    "     lo que simplificó la preparación y permitió centrarse en los experimentos.\n",
    "   · El ensamble mejora la robustez pero no siempre supera al mejor modelo\n",
    "     individual; su valor está en reducir la varianza entre familias.\n",
    "\"\"\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
