1. Árboles de decisión¶
Un árbol de decisión es un algoritmo de aprendizaje supervisado que se usa tanto en tareas de clasificación como de regresión. Es una estructura tipo diagrama de flujo donde:
- los nodos internos representan pruebas sobre las características (atributos),
- las ramas representan los resultados de esas pruebas, y
- los nodos hoja representan la salida o decisión final (como una etiqueta de clase o un valor predicho).
Cada nodo del árbol divide los datos en subconjuntos según los valores de las características que minimizan cierta función de costo, como la impureza de Gini o la entropía para clasificación, y el error cuadrático medio para regresión.
2. Cómo funcionan los árboles de decisión
División: El algoritmo comienza en el nodo raíz seleccionando la característica que mejor divide el conjunto de datos según un criterio elegido. Para clasificación, las métricas comunes son la impureza de Gini o la ganancia de información.
Particionamiento recursivo: Este proceso se aplica de manera recursiva a los subconjuntos resultantes, creando las ramas del árbol. Cada decisión busca simplificar el problema reduciendo la incertidumbre sobre el resultado.
Criterios de parada: El algoritmo continúa hasta alcanzar una condición de parada, que puede ser un tamaño mínimo de nodo o una profundidad máxima del árbol, para prevenir el sobreajuste.
Predicción: Una vez entrenado el árbol, puede clasificar nuevos puntos de datos recorriéndolo desde la raíz hasta una hoja, siguiendo las decisiones en cada nodo.
3. ¿Por qué son relevantes los árboles de decisión en la investigación geocientífica?
Los árboles de decisión son particularmente útiles en la investigación geocientífica por varias razones:
Interpretabilidad: Los árboles de decisión ofrecen una representación clara y visual de cómo se toman las decisiones. En las geociencias, donde los modelos a menudo deben ser explicables ante una gama amplia de interesados (por ejemplo, autoridades y grupos ambientales), los árboles de decisión permiten a los usuarios entender las predicciones y confiar en ellas.
Manejo de relaciones complejas y no lineales: Los datos geocientíficos, como los ambientales o los climáticos, exhiben a menudo relaciones complejas entre variables. Los árboles de decisión capturan de manera natural estas relaciones no lineales sin necesidad de un preprocesamiento pesado de los datos.
Manejo de datos faltantes: Los árboles de decisión pueden manejar conjuntos de datos con valores faltantes, algo común en las geociencias por los desafíos del monitoreo continuo en entornos hostiles o lugares remotos.
Importancia de las características: Los árboles de decisión ordenan la importancia de cada característica según su contribución al modelo. Esto es valioso en las geociencias para identificar qué factores (por ejemplo, la temperatura, la precipitación o la actividad sísmica) tienen mayor impacto sobre un resultado particular (por ejemplo, predecir deslizamientos o sismos).
Escalabilidad: Los árboles de decisión pueden aplicarse a conjuntos de datos grandes, frecuentes en las geociencias, con una sobrecarga computacional mínima. Pueden adaptarse a marcos de cómputo distribuido para manejar conjuntos de datos geoespaciales vastos.
4. Aplicaciones geocientíficas
Clasificación de eventos sísmicos: Los árboles de decisión pueden clasificar eventos sísmicos (por ejemplo, sismos o erupciones volcánicas) a partir de las formas de onda, el contenido de frecuencias y otras características.
Predicción del riesgo de deslizamientos: Pueden modelar interacciones complejas entre variables ambientales como la pendiente, el tipo de suelo y la lluvia, identificando las zonas con mayor riesgo de deslizamientos.
Clasificación y predicción climática: Los árboles de decisión pueden usarse para clasificar zonas climáticas a partir de variables ambientales, o para predecir patrones climáticos futuros analizando datos meteorológicos históricos.
2. Bosque aleatorio¶
2.1 Conceptos¶
1. ¿Qué son los bosques aleatorios?
Un bosque aleatorio (random forest) es un método de aprendizaje por ensamble que se construye sobre la base de los árboles de decisión. En lugar de depender de un solo árbol de decisión, los bosques aleatorios combinan las predicciones de muchos árboles de decisión para mejorar la exactitud, ganar robustez y reducir el sobreajuste. Cada árbol del bosque hace su predicción de forma independiente, y el bosque agrega esas predicciones — típicamente por votación de mayoría en las tareas de clasificación o por promedio en las de regresión — para tomar la decisión final.
2. ¿Cómo funcionan los bosques aleatorios?
Agregación bootstrap (bagging): Cada árbol de decisión de un bosque aleatorio se entrena sobre un subconjunto distinto del conjunto de datos original. Esto se logra mediante el remuestreo bootstrap, donde para cada árbol se extraen muestras aleatorias (con reemplazo) del conjunto de datos. Esta diversidad en los datos de entrenamiento permite que los árboles desarrollen modelos ligeramente distintos, reduciendo la probabilidad de que todos cometan los mismos errores.
Aleatoriedad en las características: En cada división se elige un subconjunto aleatorio de características a considerar, en lugar del conjunto completo de características. Esta aleatoriedad decorrelaciona los árboles entre sí, reduciendo aún más la posibilidad de sobreajuste y mejorando la generalización.
Agregación de las predicciones: Una vez que todos los árboles del bosque han hecho sus predicciones, el bosque aleatorio las agrega para producir la salida final. Para clasificación, suele ser una votación de mayoría entre los árboles; para regresión, la salida final es el promedio de las predicciones de los árboles.
3. Por qué los bosques aleatorios son valiosos en la investigación geocientífica
Los bosques aleatorios atienden algunas limitaciones de los árboles de decisión individuales, lo que los hace particularmente útiles en aplicaciones geocientíficas:
Mayor exactitud y menor sobreajuste: El enfoque de ensamble de los bosques aleatorios típicamente mejora la exactitud de la predicción respecto de un solo árbol de decisión. Esto importa en las geociencias, donde los datos pueden ser ruidosos y se requieren predicciones exactas para aplicaciones como la evaluación de peligros.
Mejor generalización: Los bosques aleatorios generalizan mejor que los árboles individuales gracias a la diversidad del ensamble. Esto significa que es más probable que se desempeñen bien sobre datos nuevos, no vistos, algo vital en contextos geocientíficos dinámicos (por ejemplo, patrones climáticos cambiantes o condiciones geológicas en evolución).
Importancia de las características e interpretabilidad: Como los árboles de decisión, los bosques aleatorios proporcionan scores de importancia de las características, que indican cuáles contribuyen más a las predicciones. Esto ayuda a los geocientíficos a identificar los factores clave de un fenómeno, como los que conducen a deslizamientos o los que influyen en la actividad sísmica.
Escalabilidad: Los bosques aleatorios son altamente escalables y pueden distribuirse entre múltiples procesadores, lo que los hace adecuados para los conjuntos de datos geoespaciales grandes comunes en las geociencias, como los datos de percepción remota.
2.2 Práctica¶
Entrenaremos un bosque aleatorio para predecir la temperatura máxima de hoy en un sitio de tipo Seattle a partir de unas pocas características simples: la temperatura de ayer, la de hace dos días, el promedio histórico para ese día del calendario y la fecha misma.
Las ediciones anteriores de este libro descargaban un archivo temps.csv desde un enlace de Google Docs. Ese enlace está muerto, y el archivo traía una columna friend puesta en broma (una conjetura aleatoria dentro de un margen de 20 grados alrededor del promedio). La edición 2026 genera un registro diario equivalente dentro del propio cuaderno, así que conocemos su estructura exacta: un ciclo estacional, una tendencia de calentamiento débil y ruido meteorológico autocorrelacionado. El mismo conjunto de datos regresa en la lección 3.10, así que los resultados son directamente comparables entre las dos lecciones.
El flujo de trabajo siguiente sigue el espíritu del tutorial de bosques aleatorios de Will Koehrsen, actualizado a la práctica actual.
import numpy as np
import pandas as pd
def make_daily_temps(start="2012-01-01", end="2019-12-31", seed=42):
"""Synthetic Seattle-like daily maximum temperature record (degrees F).
Seasonal climatology + a weak warming trend + AR(1) weather noise.
Generated in-notebook so the lesson does not depend on a remote file.
"""
rng = np.random.default_rng(seed)
dates = pd.date_range(start, end, freq="D")
day_of_year = dates.dayofyear.to_numpy()
climatology = 62.0 - 15.0 * np.cos(2 * np.pi * (day_of_year - 203) / 365.25)
trend = 0.05 * np.arange(len(dates)) / 365.25
noise = np.zeros(len(dates))
for i in range(1, len(dates)):
noise[i] = 0.65 * noise[i - 1] + rng.normal(0.0, 3.0)
df = pd.DataFrame(
{
"date": dates,
"average": np.round(climatology, 1), # historical average for that calendar day
"actual": np.round(climatology + trend + noise, 1),
}
)
df["temp_1"] = df["actual"].shift(1) # yesterday's max
df["temp_2"] = df["actual"].shift(2) # two days ago
df["month"] = df["date"].dt.month
df["day"] = df["date"].dt.day
df["doy_sin"] = np.sin(2 * np.pi * day_of_year / 365.25)
df["doy_cos"] = np.cos(2 * np.pi * day_of_year / 365.25)
return df.dropna().reset_index(drop=True)
df = make_daily_temps()
print(df.shape)
df.head()(2920, 9)
# Descriptive statistics for each column
df.describe()Explorar los datos¶
Antes de cualquier modelado, mire la serie. Graficamos el objetivo (actual), la característica de rezago más fuerte (temp_1) y la climatología (average). El ciclo estacional domina; el ruido meteorológico cabalga encima de él.
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(10, 8), sharex=True)
axes[0].plot(df["date"], df["actual"], lw=0.6, color="tab:blue")
axes[0].set_ylabel("Temperature (F)")
axes[0].set_title("Actual daily max temperature")
axes[1].plot(df["date"], df["temp_1"], lw=0.6, color="tab:orange")
axes[1].set_ylabel("Temperature (F)")
axes[1].set_title("Yesterday's max temperature (temp_1)")
axes[2].plot(df["date"], df["average"], lw=0.8, color="tab:green")
axes[2].set_ylabel("Temperature (F)")
axes[2].set_title("Historical average for the calendar day")
axes[2].set_xlabel("Date")
plt.tight_layout()
División en conjuntos de entrenamiento y prueba¶
El objetivo es actual. Las características son los dos rezagos, la climatología y la fecha codificada como mes, día y un par seno/coseno para el día del año.
from sklearn.model_selection import train_test_split
features = ["temp_1", "temp_2", "average", "month", "day", "doy_sin", "doy_cos"]
X = df[features]
y = df["actual"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
print("Training features:", X_train.shape)
print("Testing features: ", X_test.shape)Training features: (2190, 7)
Testing features: (730, 7)
El modelo de referencia: la climatología¶
from sklearn.metrics import mean_absolute_error, root_mean_squared_error, r2_score
baseline_pred = X_test["average"]
baseline_mae = mean_absolute_error(y_test, baseline_pred)
baseline_rmse = root_mean_squared_error(y_test, baseline_pred)
print(f"Climatology baseline: MAE = {baseline_mae:.2f} F, RMSE = {baseline_rmse:.2f} F")Climatology baseline: MAE = 3.25 F, RMSE = 4.10 F
Ajustar un bosque aleatorio¶
Ajustamos un bosque de 300 árboles sobre el conjunto de entrenamiento y lo calificamos sobre el conjunto de prueba apartado.
import time
from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(n_estimators=300, random_state=42)
t0 = time.perf_counter()
rf.fit(X_train, y_train)
rf_fit_time = time.perf_counter() - t0
rf_pred = rf.predict(X_test)
rf_mae = mean_absolute_error(y_test, rf_pred)
rf_rmse = root_mean_squared_error(y_test, rf_pred)
rf_r2 = r2_score(y_test, rf_pred)
print(f"Random forest (test): MAE = {rf_mae:.2f} F, RMSE = {rf_rmse:.2f} F, R2 = {rf_r2:.3f}")
print(f"Fit time: {rf_fit_time:.2f} s")Random forest (test): MAE = 2.55 F, RMSE = 3.21 F, R2 = 0.923
Fit time: 1.40 s
Validación cruzada en lugar de una sola división¶
Una sola división entrenamiento/prueba es una sola extracción aleatoria; la validación cruzada muestra la dispersión. Corremos una validación cruzada de 5 pliegues solo sobre los datos de entrenamiento, dejando intacto el conjunto de prueba.
from sklearn.model_selection import KFold, cross_val_score
cv = KFold(n_splits=5, shuffle=True, random_state=42)
rf_cv_scores = -cross_val_score(
RandomForestRegressor(n_estimators=300, random_state=42),
X_train, y_train, cv=cv, scoring="neg_mean_absolute_error",
)
print(f"Random forest 5-fold CV MAE: {rf_cv_scores.mean():.2f} +/- {rf_cv_scores.std():.2f} F")Random forest 5-fold CV MAE: 2.59 +/- 0.03 F
Importancias de las características¶
Los bosques aleatorios ordenan las características según cuánto reducen el criterio de división a través de todos los árboles. Es una vista rápida, interna al modelo, de qué entradas importan — calculada sobre los datos de entrenamiento, con sesgos que examinamos abajo.
importances = rf.feature_importances_
# Print features sorted by importance
for name, imp in sorted(zip(features, importances), key=lambda pair: pair[1], reverse=True):
print(f"{name:10s} importance: {imp:.3f}")
fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(features, importances)
ax.set_ylabel("Importance")
ax.set_xlabel("Feature")
ax.set_title("Random forest feature importances")
plt.xticks(rotation=45)
plt.tight_layout()temp_1 importance: 0.929
average importance: 0.025
temp_2 importance: 0.018
day importance: 0.010
doy_cos importance: 0.009
doy_sin importance: 0.008
month importance: 0.001

# Retrain using only the two most important features, chosen programmatically
top2 = [features[i] for i in np.argsort(importances)[::-1][:2]]
print("Top-2 features:", top2)
rf_top2 = RandomForestRegressor(n_estimators=300, random_state=42)
rf_top2.fit(X_train[top2], y_train)
top2_pred = rf_top2.predict(X_test[top2])
top2_mae = mean_absolute_error(y_test, top2_pred)
print(f"Full model (7 features) test MAE: {rf_mae:.2f} F")
print(f"Top-2 model test MAE: {top2_mae:.2f} F")Top-2 features: ['temp_1', 'average']
Full model (7 features) test MAE: 2.55 F
Top-2 model test MAE: 2.69 F
Dos características recuperan la mayor parte del skill (habilidad predictiva) de las siete: cuando un modelo compacto rinde casi igual, prefiéralo — es más barato de correr, más fácil de explicar y menos propenso a sobreajustar.
Interpretar las importancias¶
Las importancias por impureza de arriba son una propiedad de los árboles ajustados, no del mundo. Se calculan sobre los datos de entrenamiento, y cuando las características están correlacionadas los árboles reparten el crédito entre ellas según cuál haya resultado elegida en cada nodo. La importancia por permutación hace una pregunta distinta: ¿cuánto se degrada el score sobre los datos apartados cuando se barajan los valores de una característica? Calculada sobre el conjunto de prueba, mide de qué depende realmente el modelo para los datos nuevos.
from sklearn.inspection import permutation_importance
perm = permutation_importance(
rf, X_test, y_test, n_repeats=20, random_state=42,
scoring="neg_mean_absolute_error",
)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
order = np.argsort(importances)
ax1.barh(np.array(features)[order], importances[order])
ax1.set_title("Impurity importance (training)")
order = np.argsort(perm.importances_mean)
ax2.barh(np.array(features)[order], perm.importances_mean[order],
xerr=perm.importances_std[order])
ax2.set_title("Permutation importance (test set)")
ax2.set_xlabel("Increase in test MAE (F) when shuffled")
plt.tight_layout()
Aquí los dos ordenamientos coinciden en que temp_1 y average dominan, pero esa coincidencia no está garantizada. Para ver cómo las características correlacionadas engañan al ordenamiento por impureza, agregue una que no aporte información nueva en absoluto: temp_1 convertida a Celsius, una copia perfecta salvo por las unidades.
X_train_dup = X_train.copy()
X_test_dup = X_test.copy()
X_train_dup["temp_1_C"] = (X_train_dup["temp_1"] - 32) * 5 / 9
X_test_dup["temp_1_C"] = (X_test_dup["temp_1"] - 32) * 5 / 9
rf_dup = RandomForestRegressor(n_estimators=300, random_state=42).fit(X_train_dup, y_train)
dup_mae = mean_absolute_error(y_test, rf_dup.predict(X_test_dup))
print(f"Test MAE without the duplicate: {rf_mae:.2f} F, with: {dup_mae:.2f} F")
comparison = pd.DataFrame({
"without duplicate": pd.Series(importances, index=features),
"with duplicate": pd.Series(rf_dup.feature_importances_, index=X_train_dup.columns),
}).round(3)
print(comparison)Test MAE without the duplicate: 2.55 F, with: 2.55 F
without duplicate with duplicate
average 0.025 0.025
day 0.010 0.010
doy_cos 0.009 0.009
doy_sin 0.008 0.007
month 0.001 0.001
temp_1 0.929 0.486
temp_1_C NaN 0.444
temp_2 0.018 0.018
Las predicciones apenas se mueven, pero la importancia de temp_1 ahora se reparte con su gemela en Celsius: en cada nodo los árboles eligen la copia que se les ofrezca. Nada sobre la temperatura de ayer se volvió menos informativo — solo cambió la contabilidad. Las propias características del cuaderno ya se correlacionan de esta manera (temp_1 con temp_2 por la persistencia de un día al siguiente, average con el par doy_sin/doy_cos), así que lea cualquier ordenamiento de importancias como la forma en que este modelo reparte el crédito entre las entradas que se le dieron, no como una medición del mundo.
Dependencia parcial¶
Un gráfico de dependencia parcial muestra cómo cambia la predicción del modelo cuando una característica varía, promediando sobre las demás. Lo graficamos para la característica principal.
from sklearn.inspection import PartialDependenceDisplay
disp = PartialDependenceDisplay.from_estimator(rf, X_test, ["temp_1"])
disp.axes_[0, 0].set_ylabel("Predicted max temperature (F)")
plt.gcf().set_size_inches(5, 4)
plt.tight_layout()
La predicción sube de manera casi lineal con la temperatura de ayer, lo que coincide con la persistencia AR(1) incorporada en los datos. Una salvedad aplica a todo lo de esta sección: importancia no es causalidad — reporta lo que el modelo usa para predecir, no lo que determina la temperatura; barajar temp_1 rompe el modelo, no la meteorología.
Comparación: gradient boosting¶
HistGradientBoostingRegressor implementa el gradient boosting (potenciación de gradiente): construye árboles en secuencia, cada uno corrigiendo los residuos del anterior. Lo evaluamos con la misma división y la misma validación cruzada de 5 pliegues.
from sklearn.ensemble import HistGradientBoostingRegressor
hgb = HistGradientBoostingRegressor(random_state=42)
t0 = time.perf_counter()
hgb.fit(X_train, y_train)
hgb_fit_time = time.perf_counter() - t0
hgb_pred = hgb.predict(X_test)
hgb_mae = mean_absolute_error(y_test, hgb_pred)
hgb_rmse = root_mean_squared_error(y_test, hgb_pred)
hgb_r2 = r2_score(y_test, hgb_pred)
hgb_cv_scores = -cross_val_score(
HistGradientBoostingRegressor(random_state=42),
X_train, y_train, cv=cv, scoring="neg_mean_absolute_error",
)
print(f"Gradient boosting (test): MAE = {hgb_mae:.2f} F, RMSE = {hgb_rmse:.2f} F, R2 = {hgb_r2:.3f}")
print(f"Gradient boosting 5-fold CV MAE: {hgb_cv_scores.mean():.2f} +/- {hgb_cv_scores.std():.2f} F")
print(f"Fit times: random forest {rf_fit_time:.2f} s, gradient boosting {hgb_fit_time:.2f} s")Gradient boosting (test): MAE = 2.53 F, RMSE = 3.20 F, R2 = 0.923
Gradient boosting 5-fold CV MAE: 2.58 +/- 0.04 F
Fit times: random forest 1.40 s, gradient boosting 2.42 s
El gradient boosting es la opción por defecto de 2026 para la regresión tabular: sobre tablas de características suele igualar o superar a un bosque aleatorio con un costo de entrenamiento igual o menor, y su ventaja crece con el tamaño del conjunto de datos. En una tabla tan pequeña como esta, los tiempos son parecidos y pueden ir en cualquier dirección. El bosque aleatorio sigue siendo un modelo de referencia fuerte y robusto que casi no necesita afinación, y por eso lo enseñamos primero.
Predicciones contra observaciones¶
Finalmente, grafique las predicciones sobre el conjunto de prueba encima de la serie observada completa.
test_dates = df.loc[X_test.index, "date"]
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(df["date"], df["actual"], "-", lw=0.5, color="tab:blue", label="actual")
ax.plot(test_dates, rf_pred, ".", ms=3, color="tab:red", label="RF prediction (test)")
ax.set_xlabel("Date")
ax.set_ylabel("Maximum temperature (F)")
ax.set_title("Test-set predictions and observed values")
ax.legend()
plt.tight_layout()
print(f"Climatology baseline MAE: {baseline_mae:.2f} F | Random forest MAE: {rf_mae:.2f} F")Climatology baseline MAE: 3.25 F | Random forest MAE: 2.55 F

El bosque supera a la referencia climatológica por un margen claro, así que las características de rezago llevan información real sobre el tiempo de mañana más allá del ciclo estacional. Esa comparación, no el score por sí solo, es la evidencia de que el modelo aprendió algo.