Carlos Jiménez-Gallardo
Estadístico | MSc Informática Educativa | Universidad de La
Frontera
carlos.jimenez@ufrontera.cl
Data Expert | www.innovate.cl
cjimenez@innovate.cl
Cuando se dispone de una variable aleatoria continua (tiempos, pesos, rendimientos, etc.), un paso habitual es identificar qué modelo de probabilidad describe mejor su comportamiento. Esto permite calcular probabilidades, cuantiles y simular el proceso.
El procedimiento sigue cuatro etapas:
| Etapa | Pregunta | Herramienta |
|---|---|---|
| 1 | ¿Cómo se ven los datos? | Estadística descriptiva, gráfico de Cullen y Frey |
| 2 | ¿Qué modelos son candidatos? | fitdist() + gráficos comparativos |
| 3 | ¿Cuál ajusta mejor? | Criterios de información (AIC, BIC) |
| 4 | ¿El ajuste es aceptable? | Pruebas de bondad de ajuste (K-S, normalidad) |
Distribuciones disponibles en fitdist() (se usa el
nombre corto de R):
weibull,
gamma, lnorm, norm,
logis, cauchy, exp,
beta, unif, chisq,
f, tbinom, geom,
hyper, nbinom, poisEl documento desarrolla el proceso completo con dos ejemplos simulados (uno con datos normales y otro con datos Weibull), para luego aplicarlo a datos propios.
library(Analitica) # descripYG(): estadística descriptiva
library(tidyverse) # tratamiento y visualizacion de datos
library(readxl) # lectura de archivos Excel
library(psych) # estadística descriptiva
library(EnvStats) # distribuciones adicionales
library(fitdistrplus) # ajuste de distribuciones
library(nortest) # pruebas de normalidadLos datos se simulan desde una \(N(\mu = 220,\ \sigma = 25)\), por lo que sabemos que el modelo correcto es la Normal. El objetivo es verificar que el proceso lo detecta.
## n Mean Median SD Kurtosis Skewness CV Min Max P25
## 1 200 219.9185 221.7 28.23187 3.065778 -0.03745778 0.1283743 143.6 300 201.7
## P75 IQR Fence_Low Fence_High
## 1 238.175 36.475 146.9875 292.8875
El gráfico de Cullen y Frey ubica los datos según su asimetría y curtosis. Para datos normales, la observación debe quedar cerca del punto marcado como normal (asimetría 0, curtosis 3).
## summary statistics
## ------
## min: 143.6 max: 300
## median: 221.7
## mean: 219.9185
## estimated sd: 28.23187
## estimated skewness: -0.03774144
## estimated kurtosis: 3.098063
fitdist() estima los parámetros de cada distribución por
máxima verosimilitud.
Weibull, Gamma y Log-normal solo admiten valores estrictamente positivos (\(y > 0\)).
f1 <- fitdist(ej1$y, "weibull")
f2 <- fitdist(ej1$y, "gamma")
f3 <- fitdist(ej1$y, "norm")
f4 <- fitdist(ej1$y, "lnorm")
leyenda <- c("Weibull", "Gamma", "Normal", "Lognormal")
# Parámetros estimados
lapply(list(Weibull = f1, Gamma = f2, Normal = f3, Lognormal = f4),
function(f) round(f$estimate, 4))## $Weibull
## shape scale
## 8.4172 232.2612
##
## $Gamma
## shape rate
## 59.3693 0.2700
##
## $Normal
## mean sd
## 219.9185 28.1612
##
## $Lognormal
## meanlog sdlog
## 5.3848 0.1312
La Normal estimada (\(\hat\mu\) = 219.92, \(\hat\sigma\) = 28.16) se acerca a los valores reales usados en la simulación (\(\mu = 220\), \(\sigma = 25\)).
| Gráfico | Qué compara | Ajuste bueno si… |
|---|---|---|
denscomp |
Histograma vs. densidad teórica | la curva sigue la forma del histograma |
qqcomp |
Cuantiles empíricos vs. teóricos | los puntos caen sobre la diagonal (sensible a las colas) |
cdfcomp |
Distribución acumulada | la curva sigue la escalera empírica |
ppcomp |
Probabilidades empíricas vs. teóricas | los puntos caen sobre la diagonal (sensible al centro) |
par(mfrow = c(2, 2))
denscomp(list(f1, f2, f3, f4), legendtext = leyenda)
qqcomp(list(f1, f2, f3, f4), legendtext = leyenda)
cdfcomp(list(f1, f2, f3, f4), legendtext = leyenda)
ppcomp(list(f1, f2, f3, f4), legendtext = leyenda)Se elige el modelo con el menor AIC (criterio de Akaike) o BIC. Ambos premian el buen ajuste y penalizan la cantidad de parámetros.
## Goodness-of-fit statistics
## Weibull Gamma Normal Lognormal
## Kolmogorov-Smirnov statistic 0.06716626 0.05807421 0.04170254 0.06666453
## Cramer-von Mises statistic 0.16957378 0.11258201 0.05468902 0.16087707
## Anderson-Darling statistic 1.19715845 0.60107048 0.31677079 0.86555390
##
## Goodness-of-fit criteria
## Weibull Gamma Normal Lognormal
## Akaike's Information Criterion 1918.106 1909.826 1906.754 1913.145
## Bayesian Information Criterion 1924.702 1916.422 1913.350 1919.741
Regla práctica: si dos modelos difieren en menos de 2 unidades de AIC (\(\Delta AIC < 2\)), su ajuste es prácticamente equivalente. En ese caso se prefiere el más simple o el que tenga sentido según el contexto de la variable.
\[ H_0: \text{los datos siguen la distribución propuesta} \qquad H_1: \text{no la siguen} \] Nota: Consideración importante, el test de Kolmogorov_smirnov presenta desventajas que son convenientes tener en cuenta a la hora de la definición. En este caso pierde potencia frente a tamaños de muestra grandes (n>100)
Se rechaza \(H_0\) si el valor-p \(< 0{,}05\).
ks1 <- ks.test(ej1$y, "pweibull", shape = f1$estimate["shape"], scale = f1$estimate["scale"])
ks2 <- ks.test(ej1$y, "pgamma", shape = f2$estimate["shape"], rate = f2$estimate["rate"])
ks3 <- ks.test(ej1$y, "pnorm", mean = f3$estimate["mean"], sd = f3$estimate["sd"])
ks4 <- ks.test(ej1$y, "plnorm", meanlog = f4$estimate["meanlog"], sdlog = f4$estimate["sdlog"])
ks_ej1 <- data.frame(modelo = leyenda,
D = sapply(list(ks1, ks2, ks3, ks4), function(k) unname(k$statistic)),
valor_p = sapply(list(ks1, ks2, ks3, ks4), function(k) k$p.value))
ks_ej1$decision <- ifelse(ks_ej1$valor_p < 0.05, "Se rechaza H0", "No se rechaza H0")
knitr::kable(ks_ej1, digits = 4)| modelo | D | valor_p | decision |
|---|---|---|---|
| Weibull | 0.0672 | 0.3277 | No se rechaza H0 |
| Gamma | 0.0581 | 0.5099 | No se rechaza H0 |
| Normal | 0.0417 | 0.8775 | No se rechaza H0 |
| Lognormal | 0.0667 | 0.3364 | No se rechaza H0 |
️ Como los parámetros se estimaron con los mismos datos, el valor-p de K-S es conservador (tiende a no rechazar). Para la normal, la versión corregida es la prueba de Lilliefors.
Todas contrastan \(H_0\): los datos provienen de una distribución normal.
| Prueba | Función | Observación |
|---|---|---|
| Shapiro–Wilk | shapiro.test() |
La más potente hasta 25 datos; máximo 5.000 datos |
| Anderson–Darling | ad.test() |
Da más peso a las colas |
| Cramér–von Mises | cvm.test() |
Basada en la distribución acumulada |
| Lilliefors | lillie.test() |
K-S corregido por estimar \(\mu\) y \(\sigma\) n > 70 |
| Chi-cuadrado de Pearson | pearson.test() |
Agrupa en clases; menos potente |
| Shapiro–Francia | sf.test() |
Variante de Shapiro–Wilk; máximo 5.000 datos |
##
## Shapiro-Wilk normality test
##
## data: ej1$y
## W = 0.99546, p-value = 0.8147
##
## Anderson-Darling normality test
##
## data: ej1$y
## A = 0.31988, p-value = 0.5305
##
## Cramer-von Mises normality test
##
## data: ej1$y
## W = 0.055678, p-value = 0.4296
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: ej1$y
## D = 0.042012, p-value = 0.5274
##
## Pearson chi-square normality test
##
## data: ej1$y
## P = 10.12, p-value = 0.7534
##
## Shapiro-Francia normality test
##
## data: ej1$y
## W = 0.99573, p-value = 0.7738
Lección: con datos simétricos, la Gamma y la Log-normal quedan relativamente cerca de la Normal en el AIC y también “pasan” la prueba K-S, porque cuando su forma es de campana se parecen mucho a ella. Por eso la decisión no se toma con una sola prueba: se combina el AIC, los gráficos Q-Q/P-P y las pruebas de normalidad. Aquí la evidencia apunta a la Normal, que es el modelo con el que se generaron los datos y el más simple de interpretar.
Los datos se simulan desde una \(\text{Weibull}(forma = 1{,}5;\ escala = 10)\): una distribución asimétrica a la derecha, típica de tiempos de falla y reparación.
set.seed(2026)
ej2 <- data.frame(id = 1:200,
y = round(rweibull(200, shape = 1.5, scale = 10), 2))
head(ej2)## id y
## 1 1 5.05
## 2 2 7.00
## 3 3 15.69
## 4 4 11.62
## 5 5 7.02
## 6 6 23.85
Para no repetir el código del ejemplo 1, el proceso se agrupa en
funciones que reciben el vector de datos y. Se pueden
reutilizar con cualquier conjunto de datos.
# Ajusta los cuatro modelos candidatos
ajustar_modelos <- function(y) {
m <- mean(y); v <- var(y)
list(Weibull = fitdist(y, "weibull"),
Gamma = fitdist(y, "gamma", start = list(shape = m^2 / v, rate = m / v)),
Normal = fitdist(y, "norm"),
Lognormal = fitdist(y, "lnorm"))
}
# Cuatro gráficos comparativos en un panel
graficar_ajustes <- function(modelos) {
par(mfrow = c(2, 2))
denscomp(modelos, legendtext = names(modelos))
qqcomp(modelos, legendtext = names(modelos))
cdfcomp(modelos, legendtext = names(modelos))
ppcomp(modelos, legendtext = names(modelos))
par(mfrow = c(1, 1))
}
# Tabla AIC/BIC ordenada de menor a mayor AIC
tabla_aic <- function(modelos) {
g <- gofstat(modelos, fitnames = names(modelos))
r <- data.frame(modelo = names(modelos), AIC = round(g$aic, 2), BIC = round(g$bic, 2))
r <- r[order(r$AIC), ]
r$delta_AIC <- r$AIC - min(r$AIC)
r
}
# Prueba K-S para cada modelo, con sus parámetros estimados
tabla_ks <- function(y, modelos) {
r <- t(sapply(modelos, function(f) {
k <- do.call(ks.test, c(list(y, paste0("p", f$distname)), as.list(f$estimate)))
c(D = unname(k$statistic), valor_p = k$p.value)
}))
r <- data.frame(modelo = names(modelos), r)
r$decision <- ifelse(r$valor_p < 0.05, "Se rechaza H0", "No se rechaza H0")
r
}
# Seis pruebas de normalidad en una tabla
tabla_normalidad <- function(y) {
p <- list("Shapiro-Wilk" = shapiro.test(y),
"Anderson-Darling" = ad.test(y),
"Cramer-von Mises" = cvm.test(y),
"Lilliefors" = lillie.test(y),
"Pearson" = pearson.test(y),
"Shapiro-Francia" = sf.test(y))
r <- data.frame(prueba = names(p),
estadistico = sapply(p, function(t) unname(t$statistic)),
valor_p = sapply(p, function(t) t$p.value))
r$decision <- ifelse(r$valor_p < 0.05, "No normal", "Normal")
r
}## n Mean Median SD Kurtosis Skewness CV Min Max P25
## 1 200 10.0845 8.93 6.637357 3.999857 0.9893702 0.6581742 0.4 35.64 5.065
## P75 IQR Fence_Low Fence_High
## 1 14.145 9.08 -8.555 27.765
A diferencia del ejemplo 1, el histograma muestra una cola larga a la derecha y el diagrama de caja presenta valores alejados en el extremo superior.
## summary statistics
## ------
## min: 0.4 max: 35.64
## median: 8.93
## mean: 10.0845
## estimated sd: 6.637357
## estimated skewness: 0.9968623
## estimated kurtosis: 4.055922
En el gráfico de Cullen y Frey la observación se aleja del punto normal y queda en la zona de las distribuciones asimétricas (Gamma, Log-normal, Weibull).
## $Weibull
## shape scale
## 1.5615 11.2278
##
## $Gamma
## shape rate
## 2.0697 0.2052
##
## $Normal
## mean sd
## 10.0845 6.6207
##
## $Lognormal
## meanlog sdlog
## 2.0504 0.8038
La Weibull estimada (forma = 1.56, escala = 11.23) se acerca a los valores reales de la simulación (forma = 1,5; escala = 10).
En el gráfico Q-Q, la Normal se separa claramente de la diagonal en ambas colas.
| modelo | AIC | BIC | delta_AIC |
|---|---|---|---|
| Weibull | 1275.81 | 1282.41 | 0.00 |
| Gamma | 1277.99 | 1284.59 | 2.18 |
| Lognormal | 1304.35 | 1310.95 | 28.54 |
| Normal | 1327.66 | 1334.26 | 51.85 |
Modelo con menor AIC: Weibull
| modelo | D | valor_p | decision |
|---|---|---|---|
| Weibull | 0.0235 | 0.9999 | No se rechaza H0 |
| Gamma | 0.0346 | 0.9706 | No se rechaza H0 |
| Normal | 0.0915 | 0.0704 | No se rechaza H0 |
| Lognormal | 0.0826 | 0.1304 | No se rechaza H0 |
| prueba | estadistico | valor_p | decision |
|---|---|---|---|
| Shapiro-Wilk | 0.9357 | 0.0000 | No normal |
| Anderson-Darling | 2.9128 | 0.0000 | No normal |
| Cramer-von Mises | 0.4587 | 0.0000 | No normal |
| Lilliefors | 0.0915 | 0.0003 | No normal |
| Pearson | 33.7500 | 0.0022 | No normal |
| Shapiro-Francia | 0.9362 | 0.0000 | No normal |
En este ejemplo los resultados se contradicen: K-S no rechaza que los datos sean normales, mientras que las pruebas de normalidad la rechazan en 6 de 6 casos. Sabemos que los datos provienen de una Weibull, de modo que K-S se equivoca (error tipo II).
comparacion <- data.frame(
prueba = c("Kolmogorov-Smirnov", "Lilliefors"),
D = c(ks_ej2["Normal", "D"],
unname(lillie.test(ej2$y)$statistic)),
valor_p = c(ks_ej2["Normal", "valor_p"],
lillie.test(ej2$y)$p.value))
comparacion$decision <- ifelse(comparacion$valor_p < 0.05, "No normal", "Normal")
knitr::kable(comparacion, digits = 4, row.names = FALSE)| prueba | D | valor_p | decision |
|---|---|---|---|
| Kolmogorov-Smirnov | 0.0915 | 0.0704 | Normal |
| Lilliefors | 0.0915 | 0.0003 | No normal |
Ambas pruebas usan prácticamente el mismo estadístico \(D\) (la máxima distancia entre la distribución acumulada empírica y la teórica). La diferencia está en cómo se obtiene el valor-p:
La siguiente simulación lo verifica: se generan 1.000 muestras verdaderamente normales, a cada una se le estiman \(\mu\) y \(\sigma\) y se calcula \(D\). La proporción de esos \(D\) que supera al observado es el valor-p correcto (bootstrap paramétrico):
set.seed(123)
n <- length(ej2$y)
D_obs <- ks_ej2["Normal", "D"]
D_sim <- replicate(1000, {
z <- rnorm(n, mean = mean(ej2$y), sd = sd(ej2$y))
unname(ks.test(z, "pnorm", mean(z), sd(z))$statistic)
})
c(D_observado = D_obs,
valor_p_KS_tabla = ks_ej2["Normal", "valor_p"],
valor_p_bootstrap = mean(D_sim >= D_obs))## D_observado valor_p_KS_tabla valor_p_bootstrap
## 0.09147628 0.07036494 0.00000000
El \(D\) observado queda en el extremo de la distribución simulada (un valor-p bootstrap de 0 significa que ninguna de las 1.000 muestras normales alcanzó ese \(D\), es decir, \(p < 0{,}001\)): con el valor-p correcto, la normalidad sí se rechaza, en concordancia con Lilliefors, Shapiro–Wilk y Anderson–Darling.
Recomendación: para evaluar
normalidad, no usar ks.test() con
parámetros estimados; usar lillie.test(),
shapiro.test() o ad.test(). Para los demás
modelos (Weibull, Gamma, Log-normal), el valor-p de K-S debe leerse con
cautela y apoyarse siempre en el AIC y en los gráficos Q-Q y P-P.
Lección: un K-S favorable no basta (ver la acotación anterior); para la Normal manda Lilliefors / Shapiro–Wilk. Cuando los datos son asimétricos, las pruebas de normalidad lo detectan con claridad, y el AIC permite elegir entre los modelos asimétricos que compiten (Weibull, Gamma, Log-normal).
Con el modelo ajustado se pueden responder preguntas de probabilidad. Por ejemplo, con la Weibull estimada:
- ¿Probabilidad de que una reparación tome más de 15 horas?
- ¿Bajo qué tiempo se completa el 90 % de las reparaciones?
forma <- modelos2$Weibull$estimate["shape"]
escala <- modelos2$Weibull$estimate["scale"]
# a) P(Y > 15)
pweibull(15, shape = forma, scale = escala, lower.tail = FALSE)## [1] 0.2076462
## [1] 19.15413
# Intervalos de confianza bootstrap de los parámetros
summary(bootdist(modelos2$Weibull, niter = 500))## Parametric bootstrap medians and 95% percentile CI
## Median 2.5% 97.5%
## shape 1.570362 1.426149 1.744788
## scale 11.218400 10.145621 12.295983
Esta sección repite el proceso con un archivo propio. Los chunks se
ejecutan solo si el archivo existe en la carpeta del proyecto (opción
eval); basta con cambiar el nombre en
archivo.
## [1] FALSE
Se recomienda que el data frame tenga un nombre
corto (d0, d1), aunque el archivo
original tenga un nombre largo. Si los nombres de las variables no son
“cómodos”, se pueden cambiar; el índice [2] corresponde a
la columna que se quiere renombrar.
d0 <- as.data.frame(read_excel(archivo))
# Si los datos ya están cargados en el entorno:
# d0 <- AG_P_tiempos_de_reparacion
names(d0)[2] <- "y"
head(d0)Con más de 5.000 datos conviene trabajar con una
submuestra: las pruebas de bondad de ajuste se vuelven extremadamente
sensibles (rechazan por diferencias mínimas) y
shapiro.test() no acepta más de 5.000 observaciones.
set.seed(123)
if (nrow(d0) > 5000) {
# p = porcentaje de elementos que se conservan (0,20 = 20 %)
indice <- createDataPartition(d0$y, p = 0.20)$Resample1
d1 <- d0[indice, , drop = FALSE]
} else {
d1 <- d0
}
# Se eliminan datos faltantes de la variable de interés
d1 <- d1[!is.na(d1$y), , drop = FALSE]
c(n_original = nrow(d0), n_analisis = nrow(d1))modelos <- ajustar_modelos(d1$y)
lapply(modelos, function(f) round(f$estimate, 4))
graficar_ajustes(modelos)knitr::kable(tabla_aic(modelos), row.names = FALSE)
knitr::kable(tabla_ks(d1$y, modelos), digits = 4, row.names = FALSE)
knitr::kable(tabla_normalidad(d1$y), digits = 4, row.names = FALSE)De acuerdo con lo anterior, se observa un comportamiento … (completar la interpretación)
| Señal | Datos normales (ej. 1) | Datos Weibull (ej. 2) |
|---|---|---|
| Histograma | Simétrico, forma de campana | Cola larga a la derecha |
| Cullen y Frey | Cerca del punto normal | Zona Gamma / Weibull / Log-normal |
| Q-Q de la Normal | Puntos sobre la diagonal | Se curva en las colas |
| Menor AIC | ||
| Pruebas de normalidad | No rechazan \(H_0\) | Rechazan \(H_0\) |