Más sobre ciencia de datos en: cienciadedatos.net


Introducción

La mayoría de los modelos de machine learning supervisado aplicados a problemas de regresión generan una estimación puntual de la variable objetivo $y$ dado un conjunto de predictores $\mathbf{X}$. Esta estimación corresponde típicamente a la media condicional $\mathbb{E}[y \mid \mathbf{X}]$. Si bien es perfectamente adecuada para muchas tareas, las predicciones puntuales tienen una limitación fundamental: no dicen nada sobre la incertidumbre que rodea al valor predicho.

Imaginemos una empresa que gestiona la distribución de electricidad. La red debe dimensionarse para manejar el pico de demanda, no la demanda media. Saber que el consumo esperado a las 10 de la mañana es de 15 MWh es útil, pero saber que hay un 5% de probabilidad de que supere los 22,5 MWh es mucho más valioso para la planificación de capacidad. Un modelo de regresión estándar no puede responder a la segunda pregunta.

Distribución de consumo eléctrico

Más formalmente, los modelos de estimación puntual no proporcionan:

  • Intervalos de predicción: un rango $[L, U]$ que cubre el valor verdadero con una probabilidad dada, p. ej., $P(L \leq y \leq U \mid \mathbf{X}) = 0.90$.
  • Predicciones de probabilidad: $P(y > c \mid \mathbf{X})$ para un umbral arbitrario $c$.
  • Distribución condicional completa $p(y \mid \mathbf{X})$, que contiene toda la información sobre la variable respuesta.

La regresión distribucional aborda estas limitaciones modelando la distribución condicional completa de $y$ en lugar de solo su media. La salida del modelo ya no es un único número, sino los parámetros de una distribución de probabilidad paramétrica; por ejemplo, la media $\mu$ y la desviación típica $\sigma$ de una distribución Normal, ambas como funciones de $\mathbf{X}$.

Gradient Boosting Machines para Posición, Escala y Forma (GBM-LSS) extiende el marco de gradient boosting a la regresión distribucional. En lugar de ajustar un único modelo de boosting a la media condicional, GBM-LSS ajusta un modelo de gradient boosting por parámetro distribucional, guiando a cada uno hacia la estimación de máxima verosimilitud de la distribución completa. El resultado es un modelo flexible y no paramétrico para toda la distribución condicional $p(y \mid \mathbf{X})$.

Este enfoque hereda todas las fortalezas del gradient boosting moderno —manejo de relaciones no lineales, robustez frente a valores atípicos (con distribuciones adecuadas), compatibilidad con datos tabulares heterogéneos, selección de variables incorporada mediante regularización— al tiempo que extiende sus predicciones de un único número a una distribución de probabilidad completa.

Este artículo cubre los fundamentos conceptuales, una comparación con enfoques probabilísticos alternativos, la API práctica de Python y dos ejemplos trabajados: uno con datos simulados de verdad conocida, y otro con un conjunto de datos pediátrico de crecimiento real.

Gradient boosting distribucional: GBM-LSS

El gradient boosting estándar para regresión entrena un único conjunto de árboles que mapea las características $\mathbf{X}$ a una predicción de la media condicional $\mathbb{E}[y \mid \mathbf{X}]$. Se trata fundamentalmente de una predicción puntual: un número por observación.

GBM-LSS extiende esta idea de modo que la salida del modelo no sea un número sino una distribución de probabilidad completa. Para ello, se apoya en el concepto de regresión distribucional: en lugar de modelar únicamente el valor esperado de $y$, modela todos los parámetros de una distribución paramétrica elegida como funciones de los predictores. Para una distribución Normal, eso significa modelar tanto la media $\mu(\mathbf{X})$ como la desviación típica $\sigma(\mathbf{X})$. Para una distribución Gamma, significa modelar los parámetros de forma y tasa. Cada uno de esos parámetros obtiene su propio conjunto de gradient boosting, entrenados conjuntamente para maximizar la verosimilitud de los datos observados.

El nombre Posición, Escala y Forma proviene de la convención de nomenclatura genérica utilizada en el marco GAMLSS (Rigby & Stasinopoulos, 2005), que este enfoque extiende directamente al gradient boosting con árboles. Posición se refiere a dónde está centrada la distribución ($\mu$), escala a cuán dispersa está ($\sigma$), y forma a la asimetría y el comportamiento en las colas ($\nu$, $\tau$). No todas las distribuciones requieren los cuatro parámetros —una distribución Normal solo necesita posición y escala—, pero el marco admite hasta cuatro parámetros modelados de forma independiente. La consecuencia práctica es que el modelo puede capturar heterocedasticidad (varianza que cambia con los predictores), asimetría variable y peso variable en las colas, ninguno de los cuales puede representar el GBM estándar.

Las dos implementaciones en Python que se cubren en este artículo son XGBoostLSS y LightGBMLSS, ambas escritas y mantenidas por Alexander März. Envuelven XGBoost y LightGBM respectivamente, extendiéndolos con regresión distribucional mientras mantienen la misma API de entrenamiento familiar.

Cómo funciona GBM-LSS

Parámetros de Posición, Escala y Forma

Cualquier distribución paramétrica continua puede caracterizarse por sus parámetros. El marco GBM-LSS utiliza la notación genérica $(\mu, \sigma, \nu, \tau)$ para hasta cuatro parámetros, que se corresponden aproximadamente con:

Parámetro Rol Ejemplo
$\mu$ (Posición) Dónde está centrada la distribución Media de la Normal, mediana de la Laplace
$\sigma$ (Escala) Cuán dispersa está la distribución Desv. típica de la Normal, escala de la Gamma
$\nu$ (Forma 1) Asimetría / sesgo Grados de libertad de la t de Student
$\tau$ (Forma 2) Peso en las colas / curtosis Segundo parámetro de forma en distribuciones de cuatro parámetros

No todas las distribuciones requieren los cuatro parámetros. Una distribución Normal solo necesita $(\mu, \sigma)$; una t de Student necesita $(\mu, \sigma, \nu)$; las distribuciones de cuatro parámetros rara vez son necesarias en la práctica. En la regresión estándar, solo $\mu$ se modela como función de $\mathbf{X}$, y el resto de parámetros se estiman como constantes globales. GBM-LSS modela todos los parámetros distribucionales como funciones de $\mathbf{X}$:

$$\hat{\theta}_k(\mathbf{X}) = h_k^{-1}(F_k(\mathbf{X}))$$

donde $h_k$ es una función de enlace que mapea la salida sin restricciones del modelo de boosting al rango válido del parámetro $k$, y $F_k$ es un conjunto de gradient boosting.

El objetivo de entrenamiento: log-verosimilitud negativa

En el gradient boosting estándar para regresión, el modelo minimiza el error cuadrático esperado o la pérdida MAE. En GBM-LSS, la pérdida es la log-verosimilitud negativa de la distribución asumida:

$$\mathcal{L}(\mathbf{F}) = -\sum_{i=1}^{n} \log p\!\left(y_i \mid \boldsymbol{\theta}(\mathbf{X}_i)\right)$$

donde $\mathbf{F} = (F_1, \ldots, F_K)$ es el vector de modelos GBM, uno por parámetro distribucional, y $K$ es el número de parámetros en la distribución elegida. Minimizar esta pérdida es equivalente a la estimación de máxima verosimilitud de todos los parámetros distribucionales simultáneamente.

El gradient boosting se aplica a este objetivo: en cada iteración $t$, se añade un nuevo árbol al modelo para cada parámetro $k$ ajustando el gradiente negativo (y la Hessiana) de $\mathcal{L}$ con respecto a la predicción actual:

$$r_{i,k}^{(t)} = -\left[\frac{\partial \log p(y_i \mid \boldsymbol{\theta})}{\partial F_k(\mathbf{X}_i)}\right]_{F_k = F_k^{(t-1)}}$$

Para calcular estas derivadas de forma eficiente, marcos como lightgbmlss aprovechan las capacidades de diferenciación automática de PyTorch. Esto significa que los gradientes y las Hessianas se estiman simultáneamente para todos los parámetros de forma automática, permitiendo a los usuarios implementar fácilmente distribuciones personalizadas sin necesidad de derivar analíticamente derivadas complejas.

El resultado son $K$ conjuntos de gradient boosting entrenados conjuntamente, cada uno responsable de predecir un parámetro distribucional como función no lineal de los predictores.

Una función de pérdida alternativa soportada por la librería es el Continuous Ranked Probability Score (CRPS) (loss_fn="crps"). El CRPS es una regla de puntuación propia que generaliza el MAE a predicciones probabilísticas y tiene las mismas unidades que $y$. A diferencia de la NLL, el CRPS no requiere especificar una familia distribucional: evalúa la calidad de la predicción comparando la CDF predicha $F$ con el resultado observado $y$:

$$\text{CRPS}(F, y) = \int_{-\infty}^{\infty} \left[F(t) - \mathbf{1}(t \geq y)\right]^2 dt$$

En la práctica, loss_fn="nll" (el valor por defecto) es la opción más habitual; loss_fn="crps" puede ser útil cuando la robustez frente a la mala especificación distribucional es prioritaria.

Algoritmo de entrenamiento

El algoritmo de entrenamiento de GBM-LSS es el siguiente:

  1. Inicializar cada modelo de parámetro $F_k^{(0)}$ con la estimación MLE de $\theta_k$ en el conjunto de entrenamiento (una constante por parámetro que maximiza la verosimilitud incondicional).

  2. Para cada iteración de boosting $t = 1, \ldots, T$:

    • Calcular el gradiente negativo (pseudo-residuos) para cada parámetro $k$.
    • Ajustar un nuevo árbol de regresión $h_k^{(t)}$ a los pseudo-residuos del parámetro $k$.
    • Actualizar $F_k^{(t)} = F_k^{(t-1)} + \eta \cdot h_k^{(t)}$, donde $\eta$ es la tasa de aprendizaje.
    • Aplicar la función de enlace para mapear la predicción bruta al espacio válido del parámetro.
  3. Devolver el conjunto final $\mathbf{F} = (F_1, \ldots, F_K)$.

Tras el entrenamiento, la predicción para una nueva observación $\mathbf{X}^*$ es la distribución personalizada:

$$\hat{\boldsymbol{\theta}}(\mathbf{X}^*) = \left(h_1^{-1}(F_1(\mathbf{X}^*)),\ \ldots,\ h_K^{-1}(F_K(\mathbf{X}^*))\right)$$

Cada observación recibe su propia distribución $p(y \mid \hat{\boldsymbol{\theta}}(\mathbf{X}^*))$ en lugar de una estimación puntual.

Funciones de enlace

Las funciones de enlace garantizan que la salida bruta sin restricciones del GBM se mapee a un valor en el dominio válido de cada parámetro distribucional:

Dominio del parámetro Función de enlace Enlace inverso Uso típico
$\mathbb{R}$ (sin restricciones) Identidad: $g(\theta) = \theta$ $\theta = F$ Media de la Normal o Laplace
$(0, \infty)$ (estrictamente positivo) Log: $g(\theta) = \log(\theta)$ $\theta = e^F$ Parámetros de escala, forma Gamma
$(0, 1)$ (probabilidad) Logit: $g(\theta) = \log\frac{\theta}{1-\theta}$ $\theta = \sigma(F)$ Probabilidad de inflado de ceros

Por ejemplo, al ajustar una distribución Normal, la media $\mu$ usa el enlace identidad (cualquier número real es válido), y la desviación típica $\sigma$ usa el enlace logarítmico (debe ser estrictamente positiva). La librería gestiona la aplicación de las funciones de enlace automáticamente; el usuario solo necesita elegir la familia distribucional.

Distribuciones disponibles

Una de las decisiones de modelado más relevantes es la elección de la familia distribucional. GBM-LSS admite una amplia gama de distribuciones paramétricas. A continuación se presenta una guía práctica organizada por el tipo de variable respuesta.

Distribuciones continuas simétricas

Distribución Parámetros Caso de uso
Normal $\mu \in \mathbb{R}$, $\sigma > 0$ Datos continuos, errores aproximadamente simétricos; elección base
t de Student $\mu \in \mathbb{R}$, $\sigma > 0$, $\nu > 0$ Datos con colas pesadas, robusta frente a valores atípicos; generaliza la Normal
Laplace $\mu \in \mathbb{R}$, $\sigma > 0$ Colas más pesadas que la Normal (decaimiento exponencial), pico agudo en la moda; análoga a la pérdida L1
Logística $\mu \in \mathbb{R}$, $\sigma > 0$ Colas ligeramente más pesadas que la Normal

La distribución t de Student es especialmente valiosa en la práctica. Cuando el parámetro de grados de libertad $\nu$ es grande, se aproxima a una distribución Normal; a medida que $\nu$ disminuye, las colas se vuelven más pesadas. Esto proporciona robustez frente a valores atípicos sin necesidad de que el analista especifique umbrales de atípicos con antelación.

Distribuciones continuas asimétricas (dominio positivo)

Distribución Parámetros Caso de uso
LogNormal $\mu \in \mathbb{R}$, $\sigma > 0$ Datos positivos con sesgo derecho; ingresos, precios
Gamma $\mu > 0$, $\sigma > 0$ Datos positivos; reclamaciones de seguros, tiempos de espera
Beta $\mu \in (0,1)$, $\sigma > 0$ Proporciones y tasas acotadas en $(0, 1)$
Weibull $\mu > 0$, $\sigma > 0$ Tiempos de fallo y supervivencia
Gumbel $\mu \in \mathbb{R}$, $\sigma > 0$ Valores extremos, niveles de retorno


Datos discretos y de recuento

Distribución Parámetros Caso de uso
Poisson $\mu > 0$ Datos de recuento; asume media = varianza
Binomial Negativa $\mu > 0$, $\sigma > 0$ Datos de recuento sobredispersos (varianza > media)
Poisson Inflada en Cero (ZIP) $\mu > 0$, $\pi \in (0,1)$ Recuentos con exceso de ceros
Binomial Negativa Inflada en Cero (ZINB) $\mu > 0$, $\sigma > 0$, $\pi$ Recuentos sobredispersos con exceso de ceros


Modelado distribucional avanzado (Multivariante, Mezclas y Flujos)

Más allá de las distribuciones univariantes estándar, el marco GBM-LSS es lo suficientemente genérico para manejar escenarios de modelado muy complejos:

  • Objetivos multivariantes: predicción de respuestas de dimensión $D$ simultáneamente (p. ej., Gaussiana o t de Student multivariante) modelando no solo las medias y varianzas, sino también las matrices de covarianza (usando, por ejemplo, descomposición de Cholesky o aproximaciones de bajo rango) como función de los predictores.
  • Distribuciones de mezcla: aproximación de datos multimodales combinando múltiples distribuciones componente (p. ej., una Mezcla Gaussiana donde los pesos, medias y varianzas de cada componente dependen de las covariables).
  • Normalizing Flows: para distribuciones extremadamente complejas y no paramétricas, el marco admite Normalizing Flows (como Neural Spline Flows) que aplican transformaciones biyectivas para convertir una distribución base simple en una distribución objetivo realista.

Cómo elegir la distribución adecuada

Conocimiento del dominio: comience con las restricciones que debe satisfacer la variable respuesta:

  • ¿Es $y$ estrictamente positiva? → Gamma, LogNormal, Weibull
  • ¿Está $y$ acotada en $[0, 1]$? → Beta
  • ¿Es $y$ un número entero (recuento)? → Poisson, Binomial Negativa
  • ¿Tiene $y$ masa puntual en cero pero es de otro modo continua? → distribuciones Ajustadas en Cero (ZAGamma, ZALN, ZABeta)

Análisis exploratorio de datos: visualice la distribución marginal de $y$:

  • Simétrica, con forma de campana → Normal, t de Student
  • Sesgada a la derecha, estrictamente positiva → LogNormal, Gamma
  • Colas pesadas en ambos lados → t de Student
  • Pico agudo → Laplace

Selección automática (lightgbmlss): la librería lightgbmlss proporciona una función auxiliar dist_select (a través de DistributionClass) que ajusta cada distribución candidata a la variable objetivo por máxima verosimilitud y las devuelve ordenadas por log-verosimilitud negativa (NLL). La distribución con la NLL más baja es la que mejor explica los datos observados. La función también representa la densidad de la variable objetivo junto con las densidades ajustadas de todos los candidatos.

Diagnósticos de residuos: tras ajustar un modelo candidato, inspeccione el histograma PIT (Transformada Integral de Probabilidad), la distribución de $F(y_i \mid \hat{\boldsymbol{\theta}}_i)$ en el conjunto de test. Un modelo bien calibrado produce un histograma plano; las desviaciones indican mala especificación:

  • Forma de U → colas demasiado ligeras (el modelo es demasiado confiado)
  • Forma de montículo → colas demasiado pesadas (el modelo es demasiado conservador)
  • Sesgado → la asimetría de la distribución está mal especificada

GBM-LSS frente a otros enfoques probabilísticos

Existen varios enfoques para la regresión distribucional o probabilística disponibles en Python. Comprender sus ventajas e inconvenientes de antemano ayuda a motivar la elección de GBM-LSS.

GBM-LSS vs Regresión por Cuantiles

Aspecto Regresión por Cuantiles GBM-LSS
Salida Cuantiles específicos Distribución paramétrica completa
Supuesto distribucional Ninguno (no paramétrico) Paramétrico (se debe elegir la distribución)
$P(y > c \mid \mathbf{X})$ para $c$ arbitrario Requiere interpolación Exacto, desde la CDF
Cruce de cuantiles Posible Imposible (coherencia paramétrica)
Número de modelos entrenados Uno por cuantil Uno por parámetro distribucional
Captura heterocedasticidad
Captura asimetría variable Parcialmente

Elija Regresión por Cuantiles cuando ninguna distribución paramétrica sea una suposición razonable (p. ej., respuestas multimodales o con forma de mezcla), o cuando solo se necesite un conjunto fijo de niveles de cuantil.

Elija GBM-LSS cuando una familia paramétrica sea plausible y desee una descripción distribucional completamente coherente que permita calcular cualquier cuantil, cualquier probabilidad de cola y cualquier momento sin entrenar modelos separados.

GBM-LSS vs NGBoost

Aspecto NGBoost GBM-LSS
Optimización Gradiente natural (escalado por información de Fisher) Gradiente estándar (pérdida NLL)
Backend GBDT Personalizado (librería ngboost) XGBoost / LightGBM (optimizado)
Velocidad Más lento Más rápido (especialmente en conjuntos de datos grandes)
Familias distribucionales Limitadas (~5) Extensas (30+)
Importancia de variables Importancia estándar por árbol SHAP por parámetro distribucional
Desarrollo activo Más lento (a partir de 2026) Activo

En la práctica, GBM-LSS supera a NGBoost en conjuntos de datos tabulares grandes en velocidad y precisión distribucional, gracias a los backends altamente optimizados de XGBoost y LightGBM. La actualización por gradiente natural de NGBoost puede ser ventajosa cuando la superficie de verosimilitud está mal condicionada, pero esto rara vez resulta decisivo en la práctica.

Resumen de recomendaciones

Situación Enfoque recomendado
Línea base rápida, solo unos pocos niveles de cuantil Regresión por cuantiles (GBM con pérdida pinball)
Conjunto de datos pequeño, inferencia estadística con coeficientes GAMLSS (R) o NGBoost
Conjunto de datos tabular grande, familia paramétrica conocida GBM-LSS (XGBoostLSS / LightGBMLSS)
Forma distribucional desconocida o compleja Regresión por cuantiles o normalizing flows
Se requiere inferencia bayesiana completa PyMC o Stan

Ejemplo 1: Datos simulados heteroscedásticos

El primer ejemplo utiliza datos simulados donde la varianza de la variable respuesta cambia en función del predictor. Este es un caso canónico donde la regresión estándar falla y el modelado distribucional muestra su valor.

Descripción del problema: Una empresa de servicios públicos monitorea el consumo horario de electricidad en miles de hogares. El consumo medio es aproximadamente constante a lo largo del día (~15 MWh), pero la variabilidad no lo es: el consumo está muy concentrado durante la noche y muy disperso durante las horas pico de la mañana y la tarde. La empresa desea:

  1. Predecir la distribución completa del consumo para cada hora.
  2. Calcular intervalos de predicción con cobertura del 90%.
  3. Estimar la probabilidad de que el consumo supere un umbral en cada hora.

Librerías y datos

# Librerías
# ==============================================================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import lightgbm as lgb
from lightgbmlss.model import LightGBMLSS
from lightgbmlss.distributions.Gaussian import Gaussian
import lightgbmlss.distributions.Gaussian as Gaussian_mod
import lightgbmlss.distributions.StudentT as StudentT_mod
import lightgbmlss.distributions.Gamma as Gamma_mod
import lightgbmlss.distributions.LogNormal as LogNormal_mod
from sklearn.model_selection import train_test_split
from scipy import stats

import warnings
warnings.filterwarnings('once')

plt.style.use('fivethirtyeight')
plt.rcParams['lines.linewidth'] = 1.5
plt.rcParams['font.size'] = 8

✏️ Nota

Los usuarios pueden cambiar del modelo basado en LightGBM al modelo basado en XGBoost simplemente sustituyendo lightgbmlss por xgboostlss.

# Datos simulados
# ==============================================================================
# Simulación ligeramente modificada del ejemplo publicado en
# XGBoostLSS - An extension of XGBoost to probabilistic forecasting Alexander März
np.random.seed(1234)
n = 3000
X = np.linspace(0, 24, n)
y = np.random.normal(
    loc=15,
    scale=(
        1
        + 1.5 * ((4.8 < X) & (X < 7.2))
        + 4.0 * ((7.2 < X) & (X < 12.0))
        + 1.5 * ((12.0 < X) & (X < 14.4))
        + 2.0 * (X > 16.8)
    )
)

Los datos se generan a partir de $\mathcal{N}(15, \sigma^2(t))$ donde $\sigma(t)$ varía con la hora del día. El GBM estándar captura bien la media constante, pero no puede representar la varianza cambiante.

# Calcular los cuantiles 0.1 y 0.9 para cada valor simulado de X
# ==============================================================================
cuantil_10 = stats.norm.ppf(
                q = 0.1,
                loc   = 15,
                scale = (
                    1 + 1.5 * ((4.8 < X) & (X < 7.2)) + 4 * ((7.2 < X) & (X < 12))
                    + 1.5 * ((12 < X) & (X < 14.4)) + 2 * (X > 16.8)
                )
             )

cuantil_90 = stats.norm.ppf(
                q = 0.9,
                loc   = 15,
                scale = (
                    1 + 1.5 * ((4.8 < X) & (X < 7.2)) + 4 * ((7.2 < X) & (X < 12))
                    + 1.5 * ((12 < X) & (X < 14.4)) + 2 * (X > 16.8)
                )
             )
# Gráfico
# ==============================================================================
fig, ax = plt.subplots(figsize=(7, 3.5))
ax.scatter(X, y, alpha = 0.3, c = "black", s = 8)
ax.hlines(y=15, xmin=0, xmax=24, colors='tomato', lw=2, label='Media real (15 MWh)')
ax.plot(X, cuantil_10, c = "black")
ax.plot(X, cuantil_90, c = "black")
ax.fill_between(
    X, cuantil_10, cuantil_90, alpha=0.3, color='red',
    label='Intervalo de cuantiles teórico 0.1-0.9'
)
ax.set_xlabel('Hora del día')
ax.set_ylabel('Consumo (MWh)')
ax.set_title('Consumo eléctrico por hora')
ax.set_xticks(range(0, 25, 2))
ax.legend()
plt.tight_layout()

Entrenamiento del modelo

# División entrenamiento-test
# ==============================================================================
X_train, X_test, y_train, y_test = train_test_split(
    X.reshape(-1, 1), y, test_size=0.2, random_state=42, shuffle=True
)

# Con XGBoostLSS los datos deben transformarse a xgb.DMatrix
# dX_train = xgb.DMatrix(X_train, label=y_train)
# dX_test  = xgb.DMatrix(X_test,  label=y_test)

# Con LightGBMLSS los datos deben transformarse a lgb.Dataset
dX_train = lgb.Dataset(X_train, label=y_train)
dX_test  = lgb.Dataset(X_test,  label=y_test)

print(f"Conjunto de entrenamiento: {X_train.shape[0]} muestras")
print(f"Conjunto de test: {X_test.shape[0]} muestras")
Conjunto de entrenamiento: 2400 muestras
Conjunto de test: 600 muestras

Al ajustar el modelo GBM-LSS es necesario especificar una familia distribucional. Cada clase de distribución acepta tres argumentos clave:

  • stabilization : "None" | "MAD" | "L2" — método de estabilización del gradiente/Hessiana
  • response_fn : "exp" | "softplus" — enlace inverso para parámetros positivos (p. ej., escala)
  • loss_fn : "nll" | "crps" — función de pérdida de entrenamiento (log-verosimilitud negativa o CRPS)

En este caso se elige la distribución Normal, que tiene dos parámetros: $\mu$ (media) y $\sigma$ (desviación típica). La media se modela con el enlace identidad y la desviación típica con el enlace logarítmico para garantizar la positividad. El objetivo de entrenamiento es la log-verosimilitud negativa de la distribución Normal.

# Especificación del modelo
# ==============================================================================
modelo = LightGBMLSS(
    Gaussian(stabilization="None", response_fn="exp", loss_fn="nll")
)

Para encontrar los hiperparámetros óptimos se realiza una búsqueda bayesiana mediante la integración con Optuna proporcionada por la librería. La búsqueda optimiza la log-verosimilitud negativa de validación sobre un espacio de hiperparámetros predefinido, usando parada temprana para evitar el sobreajuste. Luego se reajusta el mejor modelo en el conjunto de entrenamiento completo con los hiperparámetros óptimos.

# Optimización de hiperparámetros
# ==============================================================================
# !pip install optuna-integration[lightgbm]\n

espacio_hiperparametros = {
    "learning_rate":            ["float", {"low": 1e-5,   "high": 1,     "log": True}],
    "max_depth":                ["int",   {"low": 1,      "high": 10,    "log": False}],
    "min_gain_to_split":        ["float", {"low": 1e-8,   "high": 40,    "log": False}],
    "min_sum_hessian_in_leaf":  ["float", {"low": 1e-8,   "high": 500,   "log": True}],
    "subsample":                ["float", {"low": 0.2,    "high": 1.0,   "log": False}],
    "feature_fraction":         ["float", {"low": 0.2,    "high": 1.0,   "log": False}],
    "boosting":                 ["categorical", ["gbdt"]],
}

mejores_hiperparametros = modelo.hyper_opt(
    hp_dict = espacio_hiperparametros,
    train_set = dX_train,
    num_boost_round=100,       # Número de iteraciones de boosting.
    nfold=5,                   # Número de folds para validación cruzada.
    early_stopping_rounds=20,  # Número de rondas de parada temprana.
    max_minutes=10,            # Presupuesto de tiempo en minutos.
    n_trials=30,               # Número de ensayos de búsqueda.
    silence=True,              # Controla la verbosidad de los ensayos.
    seed=123,                  # Semilla para generar los folds de cv.
    hp_seed=6715               # Semilla para el generador de números aleatorios en la búsqueda bayesiana.
)
  0%|          | 0/30 [00:00<?, ?it/s]
Hyper-Parameter Optimization successfully finished.
  Number of finished trials:  30
  Best trial:
    Value: 2.265606331242457
    Params: 
    learning_rate: 0.9273640481563128
    max_depth: 1
    min_gain_to_split: 0.035986405436204016
    min_sum_hessian_in_leaf: 0.025909830444300425
    subsample: 0.7788823527111574
    feature_fraction: 0.6019028154022705
    boosting: gbdt
    opt_rounds: 31

El modelo se entrena con los mejores hiperparámetros encontrados en la búsqueda.

# Entrenar el modelo con los hiperparámetros óptimos
# ==============================================================================
n_rondas = mejores_hiperparametros.pop("opt_rounds")

modelo.train(
    params = mejores_hiperparametros,
    train_set = dX_train,
    num_boost_round = n_rondas
)

Predicciones

lightgbmlss ofrece tres opciones distintas para realizar predicciones:

  • samples: extrae $n$ muestras de la distribución predicha.

  • quantiles: calcula cuantiles de la distribución predicha.

  • parameters: devuelve los parámetros distribucionales predichos.

Cuando se usa el tipo de predicción samples, la salida es un DataFrame con una fila por observación del conjunto de test y n_samples columnas, cada una conteniendo una extracción de la distribución predicha para esa observación. Estas representan posibles resultados futuros consistentes con la incertidumbre del modelo.

# Muestreo desde la distribución predicha
# ==============================================================================
predicciones_muestras = modelo.predict(data=X_test, pred_type="samples", n_samples=50)
predicciones_muestras
y_sample0 y_sample1 y_sample2 y_sample3 y_sample4 y_sample5 y_sample6 y_sample7 y_sample8 y_sample9 ... y_sample40 y_sample41 y_sample42 y_sample43 y_sample44 y_sample45 y_sample46 y_sample47 y_sample48 y_sample49
0 15.697314 16.262325 16.202932 15.307989 17.619083 13.853810 15.308901 12.309548 15.882831 19.780371 ... 17.041090 16.348074 14.840222 11.370354 12.450369 13.575173 13.577733 16.836323 13.708682 18.007421
1 14.166789 18.025551 23.503204 19.067127 11.026548 14.392353 7.978942 4.036101 25.193146 14.973810 ... 11.270795 14.132236 15.480999 10.183868 19.415047 27.263340 12.544895 14.446023 19.760115 10.920843
2 14.586010 13.869585 15.001597 16.088562 14.989753 15.055332 13.881459 14.251566 15.061149 15.794733 ... 15.848513 15.297975 16.091333 14.388843 15.170838 14.735168 14.798789 13.742496 13.630329 13.089798
3 14.592566 14.624412 14.269474 15.491898 15.569511 16.288342 15.164846 14.566668 13.327274 14.233431 ... 13.844853 15.285441 14.233530 14.675815 15.908656 15.858871 15.202689 15.007872 14.247766 14.850210
4 16.392326 14.859611 19.943062 13.869667 17.015881 15.606420 22.863440 16.727417 20.740311 12.586379 ... 16.827066 13.654144 14.565105 19.044834 13.281296 10.824958 10.942783 11.074723 12.510992 13.662723
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
595 14.199569 12.481850 15.573352 15.122883 15.642666 13.908475 16.400906 15.557431 17.194227 17.689520 ... 15.000471 17.024361 14.361902 15.227952 14.057050 16.765730 15.868464 15.708106 14.718128 13.703343
596 16.862871 15.523841 14.538917 13.587122 15.685340 16.660721 15.864644 14.723064 15.661668 17.678423 ... 14.425453 14.341781 15.765164 14.548565 13.399756 17.723585 16.486721 17.126137 14.486805 17.241503
597 16.452101 14.660017 14.022560 14.965786 15.117435 14.897168 15.493895 15.492521 15.704017 15.135339 ... 14.843552 14.960234 15.990202 14.974794 15.501653 16.243484 15.444390 14.754246 14.522926 15.973195
598 14.150993 16.409111 15.669437 15.129354 20.987328 15.376616 15.166396 15.373619 15.436773 14.702501 ... 14.456417 17.717783 14.643255 11.609344 17.478497 14.445816 14.792369 15.499956 14.302234 15.070537
599 18.794319 22.029449 16.802168 12.680256 8.722343 13.182603 15.911584 6.012594 20.524622 18.866379 ... 15.987385 6.264203 9.161081 5.458413 18.129385 17.038593 17.099699 26.817642 18.101303 17.728611

600 rows × 50 columns

# Predicción de cuantiles
# ==============================================================================
predicciones_cuantiles = modelo.predict(data=X_test, pred_type="quantiles", quantiles=[0.1, 0.9])
predicciones_cuantiles
quant_0.1 quant_0.9
0 12.138686 17.828659
1 8.832257 21.029195
2 13.532447 16.402666
3 13.928053 16.376365
4 11.775336 19.083310
... ... ...
595 13.756510 16.352524
596 13.673839 16.496371
597 13.872277 16.089009
598 12.043160 18.024937
599 9.239491 21.524747

600 rows × 2 columns

# Predicción de los parámetros distribucionales
# ==============================================================================
predicciones_parametros = modelo.predict(data=X_test, pred_type="parameters")
predicciones_parametros
loc scale
0 14.931164 2.270949
1 15.008944 4.737135
2 14.931164 1.137141
3 15.172076 0.985543
4 15.406059 2.829181
... ... ...
595 15.053152 0.985543
596 15.050743 1.137141
597 15.008944 0.855896
598 14.931164 2.270949
599 15.332772 4.737135

600 rows × 2 columns

# Gráfico del intervalo de cuantiles 10%-90%
# ==============================================================================
idx_orden = np.argsort(X_test.ravel())
X_test_ordenado = X_test.ravel()[idx_orden]
predicciones_cuantiles_ordn = predicciones_cuantiles.iloc[idx_orden]

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(7, 3.5))
ax.scatter(X, y, alpha = 0.3, c = "black", s = 8)
ax.plot(X, cuantil_10, c = "black")
ax.plot(X, cuantil_90, c = "black")
ax.fill_between(X, cuantil_10, cuantil_90, alpha=0.3, color='red',
                label='Intervalo de cuantiles teórico 0.1-0.9')
ax.plot(X_test_ordenado, predicciones_cuantiles_ordn.iloc[:, 0], c = "blue", label='Intervalo de cuantiles predicho')
ax.plot(X_test_ordenado, predicciones_cuantiles_ordn.iloc[:, 1], c = "blue")

ax.set_xticks(range(0, 25))
ax.set_title('Consumo eléctrico a lo largo del día')
ax.set_xlabel('Hora del día')
ax.set_ylabel('Consumo eléctrico (MWh)')
plt.legend();

Cobertura

Una de las métricas utilizadas para evaluar los intervalos es la cobertura. Su valor corresponde al porcentaje de observaciones que caen dentro del intervalo estimado. Idealmente, la cobertura empírica debería ser igual al nivel de cobertura nominal del intervalo. Un modelo bien calibrado debería lograr una cobertura empírica cercana al nivel nominal.

En este ejemplo se han calculado los cuantiles 0.1 y 0.9, por lo que el intervalo tiene un nivel de cobertura nominal del 80%. Si el intervalo predicho es correcto, aproximadamente el 80% de las observaciones deberían caer dentro de él.

# Cobertura del intervalo predicho
# ==============================================================================
limite_inferior = predicciones_cuantiles.iloc[:, 0].to_numpy()
limite_superior = predicciones_cuantiles.iloc[:, 1].to_numpy()

cobertura = ((y_test >= limite_inferior) & (y_test <= limite_superior)).mean()
ancho_medio = (limite_superior - limite_inferior).mean()

print(f'Cobertura empírica del IP 80% : {cobertura:.3f}')
print(f'Ancho medio del intervalo     : {ancho_medio:.3f} (MWh)')
Cobertura empírica del IP 80% : 0.747
Ancho medio del intervalo     : 6.470 (MWh)

Predicción probabilística

Por ejemplo, para determinar la probabilidad de que a las 8 de la mañana el consumo supere los 22,5 MWh, primero se predicen los parámetros de la distribución para $hora=8$ y luego, usando esos parámetros, se calcula la probabilidad $consumo \geq 22.5$ con la función de distribución acumulada.

# Predecir los parámetros de la distribución cuando hora=8
# ==============================================================================
x_nueva = np.array([[8.0]])
params_hora_8 = modelo.predict(data=x_nueva, pred_type="parameters")
print("Parámetros distribucionales predichos cuando hora=8:")
print(params_hora_8)
Parámetros distribucionales predichos cuando hora=8:
         loc     scale
0  15.008944  4.737135
# Probabilidad de que el consumo supere 22.5 cuando hora=8
# ==============================================================================
# Los nombres de columna dependen de la distribución elegida (p. ej., 'loc'/'scale' para Gaussiana)
mu_8 = params_hora_8['loc'].to_numpy()[0]
sigma_8 = params_hora_8['scale'].to_numpy()[0]

prob_exceder = 1 - stats.norm.cdf(22.5, loc=mu_8, scale=sigma_8)
print(f'P(consumo >= 22.5 MWh | hora=8) = {prob_exceder:.4f}')
P(consumo >= 22.5 MWh | hora=8) = 0.0569

Según la distribución predicha por el modelo, existe un 5,69% de probabilidad de que el consumo a las 8 de la mañana sea igual o superior a 22,5 MWh.

Si este mismo cálculo se realiza para todo el rango horario, se puede determinar la probabilidad de que el consumo supere un valor dado a lo largo del día.

# Predicción de probabilidad en el rango horario completo de consumo superior a 22.5 MWh
# ==============================================================================
horas = np.linspace(0, 24, 500)
d_horas = horas.reshape(-1, 1)
params_horas = modelo.predict(data=d_horas, pred_type="parameters")

prob_horas = 1 - stats.norm.cdf(
    22.5,
    loc=params_horas['loc'].to_numpy(),
    scale=params_horas['scale'].to_numpy()
)

fig, ax = plt.subplots(figsize=(7, 3.5))
ax.plot(horas, prob_horas)
ax.set_xlabel('Hora del día')
ax.set_ylabel('P(consumo ≥ 22.5 MWh)')
ax.set_title('Probabilidad de superar 22.5 MWh a lo largo del día')
ax.set_xticks(range(0, 25, 2))
plt.tight_layout()

Ejemplo 2

El conjunto de datos dbbmi (Cuarto Estudio de Crecimiento Neerlandés, Fredriks et al. (2000a, 2000b)), disponible en el paquete R gamlss.data, contiene información sobre la edad y el índice de masa corporal (imc) de 7.294 jóvenes neerlandeses de entre 0 y 20 años. El objetivo es entrenar un modelo capaz de predecir cuantiles del índice de masa corporal en función de la edad, ya que este es uno de los estándares utilizados para detectar casos anómalos que puedan requerir atención médica.

Librerías y datos

# Librerías
# ==============================================================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import lightgbm as lgb
from lightgbmlss.model import LightGBMLSS
from lightgbmlss.distributions.Gaussian import Gaussian
from lightgbmlss.distributions.Gamma import Gamma
from lightgbmlss.distributions.LogNormal import LogNormal
from lightgbmlss.distributions.StudentT import StudentT
from lightgbmlss.distributions.distribution_utils import DistributionClass
import lightgbmlss.distributions.Gaussian as Gaussian_mod
import lightgbmlss.distributions.StudentT as StudentT_mod
import lightgbmlss.distributions.Gamma as Gamma_mod
import lightgbmlss.distributions.LogNormal as LogNormal_mod
import warnings

warnings.filterwarnings('once')
plt.style.use('fivethirtyeight')
plt.rcParams['lines.linewidth'] = 1.5
plt.rcParams['font.size'] = 8
# Cargar datos
# ==============================================================================
url = (
    'https://raw.githubusercontent.com/JoaquinAmatRodrigo/'
    'Estadistica-machine-learning-python/master/data/dbbmi.csv'
)
datos = pd.read_csv(url)
datos.head(3)
age bmi
0 0.03 13.235289
1 0.04 12.438775
2 0.04 14.541775
# Gráfico de la distribución de valores de imc
# ==============================================================================
fig, ax = plt.subplots(figsize=(6, 3))
ax.hist(datos.bmi, bins=30, density=True, color='#3182bd', alpha=0.8)
ax.set_title('Distribución de los valores del índice de masa corporal')
ax.set_xlabel('imc')
ax.set_ylabel('densidad');
# Gráfico de la distribución del índice de masa corporal en función de la edad
# ==============================================================================
fig, axs = plt.subplots(ncols=2, nrows=1, figsize=(8, 4), sharey=True)
datos[datos.age <= 2.5].plot(
    x    = 'age',
    y    = 'bmi',
    c    = 'black',
    kind = "scatter",
    alpha = 0.1,
    ax   = axs[0]
)
axs[0].set_ylim([10, 30])
axs[0].set_title('Edad <= 2.5 años')

datos[datos.age > 2.5].plot(
    x    = 'age',
    y    = 'bmi',
    c    = 'black',
    kind = "scatter",
    alpha = 0.1,
    ax   = axs[1]
)
axs[1].set_title('Edad > 2.5 años')

fig.tight_layout()
plt.subplots_adjust(top = 0.85)
fig.suptitle('Distribución del índice de masa corporal en función de la edad', fontsize = 12);

Esta distribución presenta tres características importantes que deben tenerse en cuenta al modelarla:

  • La relación entre la edad y el índice de masa corporal no es ni lineal ni constante. Existe una relación positiva notable hasta aproximadamente los 0,25 años de edad, luego se estabiliza hasta los 10 años y vuelve a aumentar marcadamente de los 10 a los 20 años.

  • La varianza es heterogénea (heterocedasticidad), siendo menor en edades más jóvenes y mayor en edades más avanzadas.

  • La distribución de la variable respuesta no es normal; presenta asimetría y una cola positiva pronunciada.

Dadas estas características, se necesita un modelo que:

  • Sea capaz de aprender relaciones no lineales.

  • Pueda modelar explícitamente la varianza en función de los predictores, ya que no es constante.

  • Sea capaz de aprender distribuciones asimétricas con una cola positiva pronunciada.

Selección de distribución

lightgbmlss proporciona un procedimiento automatizado de selección de distribución mediante DistributionClass.dist_select(). La función ajusta cada distribución candidata a la variable objetivo por máxima verosimilitud y las ordena por log-verosimilitud negativa (NLL). Una NLL más baja indica un mejor ajuste a la distribución marginal (incondicional) de la respuesta (ignorando las características $\mathbf{X}$).

# Selección de distribución
# ==============================================================================
clase_dist = DistributionClass()
distribuciones_candidatas = [Gaussian_mod, StudentT_mod, Gamma_mod, LogNormal_mod]

nll_distribuciones = clase_dist.dist_select(
    target=datos['bmi'].to_numpy(),
    candidate_distributions=distribuciones_candidatas,
    max_iter=50,
    plot=True,
    figure_size=(6, 3)
)
nll_distribuciones
nll distribution
rank
1 17688.355825 LogNormal
2 17807.554688 Gamma
3 18133.892757 Gaussian
4 18175.127931 StudentT

En base al ranking de NLL, se selecciona la distribución con el valor más bajo y se procede al entrenamiento del modelo. Para este conjunto de datos, LogNormal ocupa el primer lugar, lo que tiene sentido dado el sesgo positivo y la cola pesada observados en la distribución del índice de masa corporal.

Entrenamiento del modelo y ajuste de hiperparámetros

# División entrenamiento-test
# ==============================================================================
X_train, X_test, y_train, y_test = train_test_split(
    datos['age'].to_numpy().reshape(-1, 1),
    datos['bmi'].to_numpy(),
    test_size=0.2,
    random_state=42
)

# Entrenamiento del modelo con la distribución seleccionada y optimización de hiperparámetros
# ==============================================================================
# Con XGBoostLSS los datos deben transformarse a xgb.DMatrix
# d_datos = xgb.DMatrix(X_train, label=y_train)

# Con LightGBMLSS los datos deben transformarse a lgb.Dataset
d_datos = lgb.Dataset(X_train, label=y_train)

modelo = LightGBMLSS(
    LogNormal(stabilization='None', response_fn='exp', loss_fn='nll')
)

espacio_hiperparametros = {
    "learning_rate":            ["float", {"low": 1e-5,   "high": 1,     "log": True}],
    "max_depth":                ["int",   {"low": 1,      "high": 10,    "log": False}],
    "min_gain_to_split":        ["float", {"low": 1e-8,   "high": 40,    "log": False}],
    "min_sum_hessian_in_leaf":  ["float", {"low": 1e-8,   "high": 500,   "log": True}],
    "subsample":                ["float", {"low": 0.2,    "high": 1.0,   "log": False}],
    "feature_fraction":         ["float", {"low": 0.2,    "high": 1.0,   "log": False}],
    "boosting":                 ["categorical", ["gbdt"]],
}

mejores_hiperparametros = modelo.hyper_opt(
    hp_dict=espacio_hiperparametros,
    train_set=d_datos,
    num_boost_round=100,
    nfold=5,
    early_stopping_rounds=10,
    max_minutes=10,
    n_trials=30,
    silence=True,
    seed=123,
    hp_seed=123
)
  0%|          | 0/30 [00:00<?, ?it/s]
Hyper-Parameter Optimization successfully finished.
  Number of finished trials:  30
  Best trial:
    Value: 2.0346465443268174
    Params: 
    learning_rate: 0.7526113148050829
    max_depth: 1
    min_gain_to_split: 0.12693190605161014
    min_sum_hessian_in_leaf: 1.17102461864558e-08
    subsample: 0.9928628563764392
    feature_fraction: 0.20415744257468788
    boosting: gbdt
    opt_rounds: 100
# Entrenar el modelo con los hiperparámetros óptimos
# ==============================================================================
n_rondas = mejores_hiperparametros.pop('opt_rounds')
modelo.train(mejores_hiperparametros, d_datos, num_boost_round=n_rondas)

Predicciones e intervalos de predicción

Una vez entrenado el modelo, el objetivo es predecir cuantiles del IMC en función de la edad: no un único valor esperado, sino la forma de la distribución condicional completa $p(\text{imc} \mid \text{edad})$ en cada punto.

Concretamente, se predicen tres curvas de cuantiles sobre una rejilla fina de valores de edad:

  • q0.05 y q0.95: los límites inferior y superior de un intervalo de predicción del 90%. Para cualquier edad dada, se espera que el 90% de los niños tengan un IMC dentro de este rango.
  • q0.50: la mediana predicha, una medida robusta de tendencia central para la distribución asimétrica del IMC.

Se utiliza una rejilla regular de 500 valores de edad uniformemente espaciados (en lugar de las edades observadas) para que las curvas resultantes sean suaves, haciendo visualmente clara la heterocedasticidad y la no linealidad en todo el rango de edades.

# Predicción de la mediana (cuantil 50%) y el intervalo de predicción del 90%
# ==============================================================================
rejilla_edad = np.linspace(datos['age'].min(), datos['age'].max(), 500)
cuantiles_objetivo = [0.05, 0.5, 0.95]
cuantiles_rejilla = modelo.predict(
    data=rejilla_edad.reshape(-1, 1), pred_type='quantiles', quantiles=cuantiles_objetivo
)
cuantiles_rejilla.head(3)
quant_0.05 quant_0.5 quant_0.95
0 12.119442 13.910258 15.860954
1 12.080288 13.831336 15.894178
2 13.200316 14.995238 16.919324
# Gráfico
# ==============================================================================
arr_cuantiles = cuantiles_rejilla.to_numpy()  # forma (500, 3): q0.05, q0.50, q0.95
mascara_jovenes = rejilla_edad <= 2.5
mascara_mayores = rejilla_edad > 2.5

fig, axs = plt.subplots(ncols=2, nrows=1, figsize=(9, 4.5), sharey=True)

for ax, m_rejilla, m_datos, titulo in zip(
    axs,
    [mascara_jovenes, mascara_mayores],
    [datos['age'] <= 2.5, datos['age'] > 2.5],
    ['Edad ≤ 2.5 años', 'Edad > 2.5 años']
):
    ax.scatter(datos.loc[m_datos, 'age'], datos.loc[m_datos, 'bmi'], c='black', alpha=0.1, s=6)
    ax.fill_between(
        rejilla_edad[m_rejilla], arr_cuantiles[m_rejilla, 0], arr_cuantiles[m_rejilla, 2],
        alpha=0.3, color='tomato', label='Intervalo de predicción 90%'
    )
    ax.plot(rejilla_edad[m_rejilla], arr_cuantiles[m_rejilla, 1], color='tomato', lw=2, label='Mediana (q0.50)')
    ax.plot(rejilla_edad[m_rejilla], arr_cuantiles[m_rejilla, 0], color='tomato', lw=1, ls='--', label='q0.05 / q0.95')
    ax.plot(rejilla_edad[m_rejilla], arr_cuantiles[m_rejilla, 2], color='tomato', lw=1, ls='--')
    ax.set_title(titulo)
    ax.set_xlabel('Edad (años)')
    ax.set_ylabel('IMC')

axs[0].legend(fontsize=7)
fig.suptitle('Cuantiles de IMC predichos por edad (LogNormal GBM-LSS)', fontsize=11)
plt.tight_layout()

Por último, se calcula la cobertura empírica del intervalo de predicción del 90% en el conjunto de test retenido, donde un modelo bien calibrado debería lograr una cobertura cercana a 0,90.

# Predicción de cuantiles
# ==============================================================================
predicciones_cuantiles = modelo.predict(
    data=X_test,
    pred_type="quantiles",
    quantiles=[0.05, 0.95]
)
predicciones_cuantiles
quant_0.05 quant_0.95
0 13.938657 21.610874
1 17.045627 25.072260
2 14.604642 18.645010
3 13.751138 19.467578
4 14.599775 21.713120
... ... ...
1454 14.551365 18.590945
1455 16.931873 25.026875
1456 14.720979 19.045074
1457 16.014579 23.116499
1458 14.618010 18.566463

1459 rows × 2 columns

# Cobertura del intervalo predicho
# ==============================================================================
limite_inferior = predicciones_cuantiles.iloc[:, 0].to_numpy()
limite_superior = predicciones_cuantiles.iloc[:, 1].to_numpy()

cobertura = ((y_test >= limite_inferior) & (y_test <= limite_superior)).mean()
ancho_medio = (limite_superior - limite_inferior).mean()

print(f'Cobertura empírica del IP 90% : {cobertura:.3f}')
print(f'Ancho medio del intervalo     : {ancho_medio:.3f}')
Cobertura empírica del IP 90% : 0.906
Ancho medio del intervalo     : 6.159

Instrucciones para citar

Cómo citar este documento

Si utilizas este documento o cualquier parte del mismo, por favor reconoce la fuente. ¡Gracias!

Gradient Boosting Probabilístico con Python: GBM-LSS, por Joaquín Amat Rodrigo, disponible bajo licencia Atribución-NoComercial-CompartirIgual 4.0 Internacional (CC BY-NC-SA 4.0 DEED) en https://cienciadedatos.net/documentos/py26b-gradient-boosting-probabilistico-python-gbmlss.html


¿Te ha gustado el artículo? Tu apoyo es importante

Tu contribución me ayudará a seguir generando contenido educativo gratuito. ¡Muchas gracias! 😊

Conviértete en patrocinador de GitHub Conviértete en patrocinador de GitHub

Licencia Creative Commons

Este trabajo de Joaquín Amat Rodrigo, Javier Escobar Ortiz está bajo una licencia Atribución-NoComercial-CompartirIgual 4.0 Internacional.

Permitido:

  • Compartir: copiar y redistribuir el material en cualquier medio o formato.

  • Adaptar: remezclar, transformar y construir a partir del material.

Bajo los siguientes términos:

  • Atribución: debes dar el crédito apropiado, proporcionar un enlace a la licencia e indicar si se realizaron cambios. Puedes hacerlo de cualquier manera razonable, pero no de una forma que sugiera que el licenciante te respalda a ti o a tu uso.

  • NoComercial: no puedes utilizar el material para fines comerciales.

  • CompartirIgual: si remezclas, transformas o construyes a partir del material, debes distribuir tus contribuciones bajo la misma licencia que el original.