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):
- 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.
- 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/3para regresión). - Entrenamiento independiente: Entrenar un árbol de decisión en cada muestra bootstrap con la restricción de características aleatorias.
- 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 mixtas | Sí | Maneja características numéricas y categóricas, no necesita escalado |
| Necesitas rankings de importancia de características | Sí | Atributo feature_importances_ integrado |
| Datasets pequeños a medianos (hasta ~100K filas) | Sí | Entrenamiento rápido con procesamiento paralelo |
| Clasificación desbalanceada | Sí | Soporta class_weight='balanced' |
| Necesitas predicciones interpretables | Moderado | Los árboles individuales son interpretables, pero el ensamble menos |
| Datos dispersos de muy alta dimensionalidad (texto) | No | Modelos lineales o gradient boosting son típicamente mejores |
| Inferencia en tiempo real con latencia estricta | Con cuidado | Los 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ámetro | Default | Descripción | Consejo de Ajuste |
|---|---|---|---|
n_estimators | 100 | Número de árboles en el bosque | Más árboles = mejor rendimiento pero más lento. 100-500 es típico. |
max_depth | None | Profundidad máxima de cada árbol | None significa crecimiento completo. Establecer en 10-30 para reducir sobreajuste. |
min_samples_split | 2 | Muestras mínimas para dividir un nodo | Incrementar a 5-20 para prevenir sobreajuste en datos ruidosos. |
min_samples_leaf | 1 | Muestras mínimas en un nodo hoja | Incrementar 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. |
bootstrap | True | Usar muestreo bootstrap | Establecer False para datasets pequeños para usar todos los datos por árbol. |
class_weight | None | Pesos para cada clase | Usar 'balanced' para datasets desbalanceados. |
n_jobs | None | Número de trabajos paralelos | Establecer en -1 para usar todos los núcleos de CPU. |
oob_score | False | Usar muestras out-of-bag para evaluación | Establecer 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ámetro | Impacto | Prioridad | Notas |
|---|---|---|---|
n_estimators | Alto | 1ro | Más árboles casi siempre ayuda hasta retornos decrecientes (~200-500) |
max_depth | Alto | 2do | Controla el sobreajuste directamente. Probar None, 10, 20, 30 |
min_samples_leaf | Medio | 3ro | Suaviza predicciones. Probar 1, 2, 5, 10 |
max_features | Medio | 4to | Controla la diversidad de árboles. 'sqrt' suele ser bueno para clasificación |
min_samples_split | Bajo | 5to | Menos impacto que min_samples_leaf en la práctica |
bootstrap | Bajo | 6to | True 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étodo | Pros | Contras | Mejor Para |
|---|---|---|---|
Basado en impureza (feature_importances_) | Rápido, sin computación extra | Sesgado hacia características de alta cardinalidad | Screening rápido, exploración inicial |
| Permutation importance | Sin sesgo, funciona en datos de test | Más lento, afectado por características correlacionadas | Selecció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ística | Random Forest | XGBoost | Gradient Boosting | Decision Tree |
|---|---|---|---|---|
| Tipo de Ensamble | Bagging (paralelo) | Boosting (secuencial) | Boosting (secuencial) | Modelo único |
| Precisión | Alta | Muy Alta | Muy Alta | Moderada |
| Velocidad de Entrenamiento | Rápida (paralelizable) | Moderada | Lenta (secuencial) | Muy Rápida |
| Velocidad de Predicción | Moderada | Rápida | Moderada | Muy Rápida |
| Riesgo de Sobreajuste | Bajo | Bajo (con ajuste) | Bajo (con ajuste) | Alto |
| Sensibilidad a Hiperparámetros | Baja | Alta | Alta | Moderada |
| Requiere Escalado de Características | No | No | No | No |
| Maneja Valores Faltantes | No (necesita imputación) | Sí (integrado) | No (necesita imputación) | No |
| Importancia de Características Integrada | Sí | Sí | Sí | Sí |
| Interpretabilidad | Moderada | Baja | Baja | Alta |
| Mejor Para | Uso general, primer modelo | Competiciones Kaggle, máxima precisión | Datos tabulares estructurados | Lí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.