Skip to content

Sklearn Random Forest: Guía Completa de Clasificación y Regresión en Python

Updated on

Construiste un árbol de decisión que obtiene un 95% de precisión en entrenamiento, luego obtiene un 62% en datos nuevos. Un solo árbol de decisión memoriza el conjunto de entrenamiento -- cada división, cada hoja está ajustada a las muestras exactas que vio. El resultado es un modelo que se ve bien en papel pero falla en producción.

Este problema de sobreajuste no es solo académico. Los equipos despliegan modelos que funcionan bien en notebooks de desarrollo pero generan predicciones no confiables en datos en vivo. Un solo árbol de decisión tiene alta varianza: pequeños cambios en los datos de entrenamiento producen estructuras de árbol completamente diferentes. No puedes confiar en un modelo que es tan sensible a sus datos de entrenamiento.

Random Forest resuelve esto construyendo cientos de árboles de decisión en subconjuntos aleatorios de datos y características, luego combinando sus predicciones mediante votación por mayoría (clasificación) o promedio (regresión). Este enfoque de ensamble reduce dramáticamente la varianza manteniendo la precisión. Los RandomForestClassifier y RandomForestRegressor de Scikit-learn proporcionan una implementación lista para producción con importancia de características integrada, evaluación out-of-bag y entrenamiento paralelo.

📚

¿Qué es Random Forest?

Random Forest es un método de aprendizaje por ensamble que combina múltiples árboles de decisión para producir una predicción única y más robusta. Utiliza una técnica llamada bagging (Bootstrap Aggregating):

  1. Muestreo bootstrap: Crear múltiples subconjuntos aleatorios de los datos de entrenamiento muestreando con reemplazo. Cada subconjunto es aproximadamente el 63% de los datos originales.
  2. Selección aleatoria de características: En cada división de cada árbol, considerar solo un subconjunto aleatorio de características (típicamente sqrt(n_features) para clasificación, n_features/3 para regresión).
  3. Entrenamiento independiente: Entrenar un árbol de decisión en cada muestra bootstrap con la restricción de características aleatorias.
  4. Agregación: Combinar predicciones por voto mayoritario (clasificación) o media (regresión).

La aleatoriedad tanto en el muestreo de datos como en la selección de características asegura que los árboles individuales estén decorrelacionados. Incluso si un árbol se sobreajusta a un patrón particular, la mayoría de los otros árboles no lo harán, y el ensamble promedia el ruido.

Cuándo Usar Random Forest

Escenario¿Random Forest?Por qué
Datos tabulares con tipos de características mixtasManeja características numéricas y categóricas, no necesita escalado
Necesitas rankings de importancia de característicasAtributo feature_importances_ integrado
Datasets pequeños a medianos (hasta ~100K filas)Entrenamiento rápido con procesamiento paralelo
Clasificación desbalanceadaSoporta class_weight='balanced'
Necesitas predicciones interpretablesModeradoLos árboles individuales son interpretables, pero el ensamble menos
Datos dispersos de muy alta dimensionalidad (texto)NoModelos lineales o gradient boosting son típicamente mejores
Inferencia en tiempo real con latencia estrictaCon cuidadoLos bosques grandes pueden ser lentos en tiempo de predicción

RandomForestClassifier: Ejemplo de Clasificación

Aquí está un ejemplo completo de clasificación usando el dataset de vinos:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score
from sklearn.datasets import load_wine
 
# Cargar dataset
wine = load_wine()
X, y = wine.data, wine.target
feature_names = wine.feature_names
 
print(f"Dataset: {X.shape[0]} muestras, {X.shape[1]} características")
print(f"Clases: {wine.target_names}")
 
# Dividir datos
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
 
# Entrenar Random Forest
rf = RandomForestClassifier(
    n_estimators=100,
    max_depth=None,
    min_samples_split=2,
    min_samples_leaf=1,
    random_state=42,
    n_jobs=-1
)
rf.fit(X_train, y_train)
 
# Evaluar
y_pred = rf.predict(X_test)
print(f"\nPrecisión: {accuracy_score(y_test, y_pred):.4f}")
print(f"\nReporte de Clasificación:")
print(classification_report(y_test, y_pred, target_names=wine.target_names))

Parámetros Clave Explicados

ParámetroDefaultDescripciónConsejo de Ajuste
n_estimators100Número de árboles en el bosqueMás árboles = mejor rendimiento pero más lento. 100-500 es típico.
max_depthNoneProfundidad máxima de cada árbolNone significa crecimiento completo. Establecer en 10-30 para reducir sobreajuste.
min_samples_split2Muestras mínimas para dividir un nodoIncrementar a 5-20 para prevenir sobreajuste en datos ruidosos.
min_samples_leaf1Muestras mínimas en un nodo hojaIncrementar a 2-10 para predicciones más suaves.
max_features'sqrt'Características consideradas en cada división'sqrt' para clasificación, 'log2' o una fracción para alternativas.
bootstrapTrueUsar muestreo bootstrapEstablecer False para datasets pequeños para usar todos los datos por árbol.
class_weightNonePesos para cada claseUsar 'balanced' para datasets desbalanceados.
n_jobsNoneNúmero de trabajos paralelosEstablecer en -1 para usar todos los núcleos de CPU.
oob_scoreFalseUsar muestras out-of-bag para evaluaciónEstablecer True para una estimación de validación integrada sin conjunto de holdout.

Puntuación Out-of-Bag (OOB)

Cada árbol es entrenado en aproximadamente el 63% de los datos. El 37% restante (muestras out-of-bag) puede usarse como un conjunto de validación gratuito:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(
    n_estimators=200,
    oob_score=True,
    random_state=42,
    n_jobs=-1
)
rf.fit(X_train, y_train)
 
print(f"Puntuación OOB:  {rf.oob_score_:.4f}")
print(f"Puntuación Test: {rf.score(X_test, y_test):.4f}")

La puntuación OOB te da una estimación de validación sin necesidad de un conjunto de holdout separado. Es especialmente útil cuando los datos son limitados.

RandomForestRegressor: Ejemplo de Regresión

La regresión con Random Forest predice valores continuos promediando las salidas de todos los árboles:

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.datasets import fetch_california_housing
import numpy as np
 
# Cargar dataset de California housing
housing = fetch_california_housing()
X, y = housing.data, housing.target
feature_names = housing.feature_names
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
 
# Entrenar regresor
rf_reg = RandomForestRegressor(
    n_estimators=200,
    max_depth=20,
    min_samples_leaf=5,
    random_state=42,
    n_jobs=-1
)
rf_reg.fit(X_train, y_train)
y_pred = rf_reg.predict(X_test)
 
# Métricas de evaluación
r2 = r2_score(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mae = mean_absolute_error(y_test, y_pred)
 
print(f"R-squared: {r2:.4f}")
print(f"RMSE:      {rmse:.4f}")
print(f"MAE:       {mae:.4f}")

Comparando Regresores

from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.datasets import fetch_california_housing
import numpy as np
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
regressors = {
    'Linear Regression': LinearRegression(),
    'Ridge': Ridge(alpha=1.0),
    'Decision Tree': DecisionTreeRegressor(max_depth=10, random_state=42),
    'Random Forest': RandomForestRegressor(n_estimators=100, max_depth=20, random_state=42, n_jobs=-1),
    'Gradient Boosting': GradientBoostingRegressor(n_estimators=100, max_depth=5, random_state=42),
}
 
print(f"{'Modelo':<25} {'CV R² (media)':>12} {'CV R² (std)':>12}")
print("-" * 52)
 
for name, model in regressors.items():
    scores = cross_val_score(model, X_train, y_train, cv=5, scoring='r2', n_jobs=-1)
    print(f"{name:<25} {scores.mean():>12.4f} {scores.std():>12.4f}")

Random Forest típicamente supera a un solo árbol de decisión y modelos lineales en datasets con relaciones no lineales, siendo competitivo con gradient boosting.

Ajuste de Hiperparámetros

GridSearchCV: Búsqueda Exhaustiva

GridSearchCV prueba todas las combinaciones de valores de parámetros especificados:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.datasets import load_wine
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [None, 10, 20],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
}
 
rf = RandomForestClassifier(random_state=42, n_jobs=-1)
grid_search = GridSearchCV(
    rf,
    param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    verbose=1
)
grid_search.fit(X_train, y_train)
 
print(f"Mejores Parámetros: {grid_search.best_params_}")
print(f"Mejor Puntuación CV:   {grid_search.best_score_:.4f}")
print(f"Puntuación Test:      {grid_search.score(X_test, y_test):.4f}")

RandomizedSearchCV: Búsqueda Eficiente

Cuando el espacio de parámetros es grande, RandomizedSearchCV muestrea un número fijo de combinaciones de parámetros en lugar de probar todas:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import RandomizedSearchCV, train_test_split
from sklearn.datasets import load_wine
from scipy.stats import randint, uniform
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
param_distributions = {
    'n_estimators': randint(50, 500),
    'max_depth': [None, 5, 10, 15, 20, 30],
    'min_samples_split': randint(2, 20),
    'min_samples_leaf': randint(1, 10),
    'max_features': ['sqrt', 'log2', 0.3, 0.5, 0.7],
    'bootstrap': [True, False],
}
 
rf = RandomForestClassifier(random_state=42, n_jobs=-1)
random_search = RandomizedSearchCV(
    rf,
    param_distributions,
    n_iter=100,
    cv=5,
    scoring='accuracy',
    random_state=42,
    n_jobs=-1,
    verbose=1
)
random_search.fit(X_train, y_train)
 
print(f"Mejores Parámetros: {random_search.best_params_}")
print(f"Mejor Puntuación CV:   {random_search.best_score_:.4f}")
print(f"Puntuación Test:      {random_search.score(X_test, y_test):.4f}")

Importancia de Parámetros para Ajuste

No todos los parámetros tienen igual impacto. Enfoca tu presupuesto de ajuste en los parámetros que más importan:

ParámetroImpactoPrioridadNotas
n_estimatorsAlto1roMás árboles casi siempre ayuda hasta retornos decrecientes (~200-500)
max_depthAlto2doControla el sobreajuste directamente. Probar None, 10, 20, 30
min_samples_leafMedio3roSuaviza predicciones. Probar 1, 2, 5, 10
max_featuresMedio4toControla la diversidad de árboles. 'sqrt' suele ser bueno para clasificación
min_samples_splitBajo5toMenos impacto que min_samples_leaf en la práctica
bootstrapBajo6toTrue es casi siempre mejor. Solo probar False en datasets muy pequeños

Importancia de Características

Una de las ventajas más fuertes de Random Forest es la importancia de características integrada. Entender qué características impulsan las predicciones ayuda con la interpretación del modelo, selección de características y conocimiento del dominio.

Importancia de Características Basada en Impureza

El atributo feature_importances_ por defecto mide cuánto disminuye cada característica la impureza (Gini para clasificación, varianza para regresión) a través de todos los árboles:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
 
# Obtener importancias
importances = rf.feature_importances_
feature_names = wine.feature_names
indices = np.argsort(importances)[::-1]
 
# Imprimir características ordenadas
print("Ranking de Características:")
for i, idx in enumerate(indices):
    print(f"  {i+1}. {feature_names[idx]:25s} ({importances[idx]:.4f})")
 
# Gráfico
plt.figure(figsize=(10, 6))
plt.barh(range(len(indices)), importances[indices[::-1]], align='center')
plt.yticks(range(len(indices)), [feature_names[i] for i in indices[::-1]])
plt.xlabel('Importancia de Característica (Gini)')
plt.title('Importancia de Características Random Forest - Dataset Wine')
plt.tight_layout()
plt.savefig('rf_feature_importance.png', dpi=150)
plt.show()

Permutation Importance

La importancia basada en impureza puede estar sesgada hacia características de alta cardinalidad. Permutation importance mide la caída en el rendimiento del modelo cuando los valores de una característica se barajan aleatoriamente:

from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
 
# Calcular permutation importance en el conjunto de test
perm_imp = permutation_importance(
    rf, X_test, y_test,
    n_repeats=30,
    random_state=42,
    n_jobs=-1
)
 
# Ordenar y mostrar
sorted_idx = perm_imp.importances_mean.argsort()[::-1]
 
print("Permutation Importance (conjunto de test):")
for idx in sorted_idx:
    mean = perm_imp.importances_mean[idx]
    std = perm_imp.importances_std[idx]
    print(f"  {wine.feature_names[idx]:25s}: {mean:.4f} +/- {std:.4f}")
 
# Gráfico con barras de error
plt.figure(figsize=(10, 6))
plt.barh(
    range(len(sorted_idx)),
    perm_imp.importances_mean[sorted_idx[::-1]],
    xerr=perm_imp.importances_std[sorted_idx[::-1]],
    align='center'
)
plt.yticks(range(len(sorted_idx)), [wine.feature_names[i] for i in sorted_idx[::-1]])
plt.xlabel('Disminución en Precisión')
plt.title('Permutation Importance - Dataset Wine')
plt.tight_layout()
plt.savefig('rf_permutation_importance.png', dpi=150)
plt.show()

¿Qué Método de Importancia Usar?

MétodoProsContrasMejor Para
Basado en impureza (feature_importances_)Rápido, sin computación extraSesgado hacia características de alta cardinalidadScreening rápido, exploración inicial
Permutation importanceSin sesgo, funciona en datos de testMás lento, afectado por características correlacionadasSelección final de características, reportes

Validación Cruzada con Random Forest

Validación Cruzada Básica

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_wine
 
wine = load_wine()
X, y = wine.data, wine.target
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
scores = cross_val_score(rf, X, y, cv=5, scoring='accuracy')
 
print(f"Precisión CV: {scores.mean():.4f} (+/- {scores.std():.4f})")
print(f"Por fold:    {scores}")

StratifiedKFold para Datos Desbalanceados

StratifiedKFold preserva la distribución de clases en cada fold, lo cual es crítico para datasets desbalanceados:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.datasets import load_wine
import numpy as np
 
wine = load_wine()
X, y = wine.data, wine.target
 
# Validación cruzada estratificada 10-fold
skf = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
 
scores = cross_val_score(rf, X, y, cv=skf, scoring='accuracy')
print(f"Precisión 10-Fold Estratificada: {scores.mean():.4f} (+/- {scores.std():.4f})")
 
# Múltiples métricas
from sklearn.model_selection import cross_validate
 
results = cross_validate(
    rf, X, y, cv=skf,
    scoring=['accuracy', 'f1_weighted', 'precision_weighted', 'recall_weighted'],
    n_jobs=-1
)
 
for metric in ['test_accuracy', 'test_f1_weighted', 'test_precision_weighted', 'test_recall_weighted']:
    vals = results[metric]
    name = metric.replace('test_', '')
    print(f"{name:>20s}: {vals.mean():.4f} (+/- {vals.std():.4f})")

Manejo de Datos Desbalanceados

Cuando una clase tiene muchas más muestras que otras, un modelo puede lograr alta precisión siempre prediciendo la clase mayoritaria. Random Forest proporciona varias herramientas para manejar esto.

Usando class_weight='balanced'

El parámetro class_weight='balanced' ajusta automáticamente los pesos inversamente proporcionales a las frecuencias de clase:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import make_classification
 
# Crear dataset desbalanceado (95% clase 0, 5% clase 1)
X, y = make_classification(
    n_samples=2000,
    n_features=20,
    weights=[0.95, 0.05],
    flip_y=0,
    random_state=42
)
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
 
# Sin peso de clase
rf_default = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf_default.fit(X_train, y_train)
print("=== Sin class_weight ===")
print(classification_report(y_test, rf_default.predict(X_test)))
 
# Con peso de clase balanceado
rf_balanced = RandomForestClassifier(
    n_estimators=200,
    class_weight='balanced',
    random_state=42,
    n_jobs=-1
)
rf_balanced.fit(X_train, y_train)
print("=== Con class_weight='balanced' ===")
print(classification_report(y_test, rf_balanced.predict(X_test)))

Integrando SMOTE para Oversampling

SMOTE (Synthetic Minority Oversampling Technique) crea muestras sintéticas para la clase minoritaria. Úsalo con el pipeline de imblearn:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
 
# Crear dataset desbalanceado
X, y = make_classification(
    n_samples=2000,
    n_features=20,
    weights=[0.95, 0.05],
    flip_y=0,
    random_state=42
)
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
 
# Pipeline SMOTE + Random Forest
pipeline = ImbPipeline([
    ('smote', SMOTE(random_state=42)),
    ('rf', RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1))
])
pipeline.fit(X_train, y_train)
 
print("=== SMOTE + Random Forest ===")
print(classification_report(y_test, pipeline.predict(X_test)))

Evaluación del Modelo

Reporte de Clasificación y Matriz de Confusión

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report, confusion_matrix,
    ConfusionMatrixDisplay, accuracy_score
)
from sklearn.datasets import load_wine
import matplotlib.pyplot as plt
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
 
# Métricas
print(f"Precisión: {accuracy_score(y_test, y_pred):.4f}")
print(f"\n{classification_report(y_test, y_pred, target_names=wine.target_names)}")
 
# Gráfico de matriz de confusión
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=wine.target_names)
disp.plot(cmap='Blues')
plt.title('Random Forest - Clasificación Wine')
plt.tight_layout()
plt.savefig('rf_confusion_matrix.png', dpi=150)
plt.show()

Curva ROC para Clasificación Binaria

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve, roc_auc_score, RocCurveDisplay
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
 
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, test_size=0.2, random_state=42, stratify=cancer.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
 
# Predecir probabilidades
y_prob = rf.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, y_prob)
 
# Graficar curva ROC
RocCurveDisplay.from_estimator(rf, X_test, y_test)
plt.title(f'Curva ROC Random Forest (AUC = {auc:.4f})')
plt.tight_layout()
plt.savefig('rf_roc_curve.png', dpi=150)
plt.show()

Random Forest vs Otros Algoritmos

CaracterísticaRandom ForestXGBoostGradient BoostingDecision Tree
Tipo de EnsambleBagging (paralelo)Boosting (secuencial)Boosting (secuencial)Modelo único
PrecisiónAltaMuy AltaMuy AltaModerada
Velocidad de EntrenamientoRápida (paralelizable)ModeradaLenta (secuencial)Muy Rápida
Velocidad de PredicciónModeradaRápidaModeradaMuy Rápida
Riesgo de SobreajusteBajoBajo (con ajuste)Bajo (con ajuste)Alto
Sensibilidad a HiperparámetrosBajaAltaAltaModerada
Requiere Escalado de CaracterísticasNoNoNoNo
Maneja Valores FaltantesNo (necesita imputación)Sí (integrado)No (necesita imputación)No
Importancia de Características Integrada
InterpretabilidadModeradaBajaBajaAlta
Mejor ParaUso general, primer modeloCompeticiones Kaggle, máxima precisiónDatos tabulares estructuradosLíneas base rápidas, datasets pequeños

Cuándo elegir Random Forest sobre alternativas:

  • Necesitas un modelo base fuerte con ajuste mínimo
  • La velocidad de entrenamiento importa y tienes múltiples núcleos de CPU
  • Quieres estimaciones confiables de importancia de características
  • No estás persiguiendo el último 0.5% de precisión que los métodos de boosting podrían proporcionar

Pipeline del Mundo Real: Ejemplo de Punta a Punta

Este pipeline combina preprocesamiento, ingeniería de características, entrenamiento del modelo, evaluación y predicción en un flujo de trabajo de estilo producción:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.datasets import load_breast_cancer
import numpy as np
import pandas as pd
 
# Cargar y preparar datos
cancer = load_breast_cancer()
df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
df['target'] = cancer.target
 
# Introducir algunos valores faltantes para simular datos reales
np.random.seed(42)
mask = np.random.random(df.shape) < 0.05
df_missing = df.mask(mask.astype(bool))
df_missing['target'] = cancer.target  # Mantener target limpio
 
X = df_missing.drop('target', axis=1)
y = df_missing['target']
 
# División
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
 
# Construir pipeline de preprocesamiento + modelo
numeric_features = X.columns.tolist()
numeric_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler()),
])
 
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
    ]
)
 
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier(
        n_estimators=300,
        max_depth=20,
        min_samples_leaf=2,
        class_weight='balanced',
        random_state=42,
        n_jobs=-1
    ))
])
 
# Validación cruzada
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=skf, scoring='accuracy')
print(f"Precisión validación cruzada: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})")
 
# Entrenar modelo final
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
 
# Evaluación
print(f"\nResultados Conjunto de Test:")
print(classification_report(y_test, y_pred, target_names=cancer.target_names))
 
# Hacer predicciones en nuevos datos
sample = X_test.iloc[:3]
predictions = pipeline.predict(sample)
probabilities = pipeline.predict_proba(sample)
 
print(f"\nPredicciones de Muestra:")
for i, (pred, prob) in enumerate(zip(predictions, probabilities)):
    class_name = cancer.target_names[pred]
    confidence = prob[pred]
    print(f"  Muestra {i+1}: {class_name} (confianza: {confidence:.2%})")

Guardando y Cargando el Modelo

import joblib
 
# Guardar el pipeline entrenado
joblib.dump(pipeline, 'rf_pipeline.joblib')
 
# Cargar y usar después
loaded_pipeline = joblib.load('rf_pipeline.joblib')
new_predictions = loaded_pipeline.predict(X_test[:5])
print(f"Predicciones del modelo cargado: {new_predictions}")

Explorando Resultados con PyGWalker

Después de entrenar tu modelo Random Forest, a menudo necesitas explorar patrones de importancia de características, distribuciones de predicciones y casos de clasificación errónea en detalle. PyGWalker (opens in a new tab) te permite convertir tu DataFrame de resultados en una interfaz de exploración interactiva tipo Tableau directamente en Jupyter:

import pandas as pd
import pygwalker as pyg
 
# Construir DataFrame de resultados
results = pd.DataFrame(X_test.values, columns=cancer.feature_names)
results['actual'] = y_test.values
results['predicted'] = y_pred
results['correct'] = y_test.values == y_pred
results['prob_malignant'] = pipeline.predict_proba(X_test)[:, 0]
results['prob_benign'] = pipeline.predict_proba(X_test)[:, 1]
 
# Lanzar exploración interactiva
walker = pyg.walk(results)

Arrastra características a los ejes, filtra por muestras mal clasificadas y codifica por color según la confianza de predicción para identificar donde el modelo tiene dificultades. Este tipo de análisis visual te ayuda a decidir qué características ingenierizar o qué muestras necesitan inspección más cercana.

Para ejecutar tu flujo de trabajo completo de experimentación de ML -- desde carga de datos hasta comparación de modelos hasta evaluación final -- RunCell (opens in a new tab) proporciona un entorno Jupyter potenciado por IA que te ayuda a iterar más rápido en experimentos, auto-generar código de evaluación y gestionar tu flujo de trabajo de notebooks.

Preguntas Frecuentes

¿Cuántos árboles debo usar en un Random Forest?

Comienza con 100-200 árboles. La precisión generalmente mejora con más árboles pero se estabiliza después de cierto punto. Usa validación cruzada para encontrar el punto óptimo. Más allá de 500 árboles, las ganancias son usualmente insignificantes mientras el tiempo de entrenamiento aumenta. Monitorea la puntuación OOB a medida que incrementas n_estimators -- cuando deja de mejorar, tienes suficientes árboles.

¿Random Forest necesita escalado de características?

No. Random Forest hace divisiones basadas en umbrales de valores de características, por lo que la escala absoluta de las características no afecta las decisiones de división. A diferencia de regresión logística, SVM o redes neuronales, Random Forest maneja naturalmente características con diferentes rangos. Sin embargo, si tu pipeline incluye otros componentes (como PCA o preprocesamiento basado en distancia), el escalado puede ser requerido para esos pasos.

¿Cómo maneja Random Forest los valores faltantes?

Los RandomForestClassifier y RandomForestRegressor de Scikit-learn no manejan valores faltantes nativamente. Debes imputar datos faltantes antes del entrenamiento -- usa SimpleImputer con estrategia de mediana o media para características numéricas, o usa métodos de imputación más avanzados como IterativeImputer. Algunas otras implementaciones como H2O o LightGBM pueden manejar valores faltantes directamente.

¿Cuál es la diferencia entre Random Forest y Gradient Boosting?

Random Forest construye árboles independientemente en paralelo (bagging), mientras que Gradient Boosting construye árboles secuencialmente donde cada árbol corrige los errores del anterior (boosting). Random Forest reduce varianza, Gradient Boosting reduce sesgo. En la práctica, Gradient Boosting (especialmente XGBoost) a menudo logra ligeramente mayor precisión, pero Random Forest es más fácil de ajustar y menos propenso al sobreajuste.

¿Puede Random Forest usarse para selección de características?

Sí. Usa feature_importances_ para un ranking rápido o permutation_importance para una estimación más confiable. Luego puedes eliminar características de baja importancia y reentrenar. Alternativamente, usa SelectFromModel con un estimador Random Forest dentro de un pipeline para seleccionar automáticamente características sobre un umbral.

Conclusión

Random Forest es uno de los algoritmos más confiables y versátiles en machine learning. Reduce el sobreajuste combinando cientos de árboles de decisión decorrelacionados, maneja tanto tareas de clasificación como regresión sin escalado de características, y proporciona rankings integrados de importancia de características. Para la mayoría de problemas de datos tabulares, sirve como un excelente primer modelo que a menudo funciona lo suficientemente bien para uso en producción.

Comienza con RandomForestClassifier o RandomForestRegressor con parámetros por defecto como tu línea base. Ajusta n_estimators primero para análisis de retornos decrecientes, luego max_depth y min_samples_leaf para controlar el sobreajuste. Usa class_weight='balanced' para datos desbalanceados, permutation importance para rankings confiables de características, y validación cruzada StratifiedKFold para evaluación robusta. Cuando necesites la precisión absolutamente más alta en datos estructurados, considera Gradient Boosting o XGBoost, pero Random Forest permanece como la elección por defecto más segura que raramente falla gravemente.

📚