Introducción

Muchos métodos estadísticos clásicos (t-test, ANOVA, regresión lineal, correlación de Pearson, entre otros) asumen que los datos —o los residuos del modelo— siguen una distribución normal. Evaluar este supuesto es un paso previo obligatorio antes de aplicar dichas técnicas, o bien para decidir si conviene transformar variables o recurrir a alternativas no paramétricas.

En términos prácticos, la detección de normalidad sirve para:

  • Decidir entre un test paramétrico (asume normalidad) y uno no paramétrico (libre de distribución).
  • Detectar asimetría, colas pesadas o valores atípicos que distorsionan medias y varianzas.
  • Justificar transformaciones de variables (log, raíz cuadrada, Box-Cox) antes de modelar.
  • Validar los residuos de un modelo de regresión o ANOVA.
  • Evitar conclusiones erróneas por aplicar un método cuyo supuesto no se cumple.

En este documento usaremos datos simulados a propósito: un conjunto claramente normal, uno claramente no normal, y al final un caso difícil donde distintos métodos no se ponen de acuerdo. La idea es que el lector vea, con ejemplos concretos, cómo se comporta cada método frente a cada tipo de dato.

No existe una única prueba “definitiva”: lo recomendable es combinar métodos gráficos (informales) con pruebas formales, y no depender de un solo criterio.

Requisitos para evaluar normalidad

Datos adecuados

  • Variable cuantitativa (continua o cuasi-continua).
  • Tamaño de muestra suficiente (las pruebas formales son muy sensibles con n grande y poco potentes con n pequeño).

Conocimiento del contexto

  • Saber si la variable es, por naturaleza, simétrica o si se espera asimetría (p. ej., ingresos, tiempos de espera).
  • Revisar si existen subgrupos que conviene evaluar por separado.

Selección de métodos

  • Gráficos exploratorios (informales): histograma, densidad, boxplot, gráfico Q-Q.
  • Estadísticos descriptivos (semi-formales): asimetría (skewness) y curtosis.
  • Pruebas formales (con hipótesis y p-valor): Shapiro-Wilk, Kolmogorov-Smirnov (Lilliefors), Anderson-Darling, Cramér-von Mises, Shapiro-Francia, Jarque-Bera (versión clásica y con correcciones de Glinskiy), Pearson chi-cuadrado.

Buenas prácticas

No confiar en un solo método

  • Cada método informal y cada prueba formal tiene distinta sensibilidad a asimetría, curtosis o colas pesadas.
  • Combinar siempre gráfico + prueba formal, y comparar varias pruebas entre sí.

Cuidado con el tamaño muestral

  • Con n muy grande, casi cualquier prueba rechaza H0 aunque la desviación de la normal sea trivial.
  • Con n muy pequeño, las pruebas tienen poca potencia y casi nunca rechazan H0.
  • El gráfico Q-Q y el histograma ayudan a juzgar si la desviación es relevante en la práctica, más allá del p-valor.

Revisar outliers antes de concluir

  • Un solo valor extremo puede hacer fallar una prueba de normalidad.
  • Evaluar si el outlier es un error de registro o un valor genuino.

Evaluar normalidad donde realmente importa

  • En regresión/ANOVA se evalúan los residuos, no necesariamente la variable original.
  • En comparación de grupos, evaluar normalidad dentro de cada grupo.

Entender que los métodos pueden discrepar

  • Es normal (sin ironía) que un test rechace y otro no, especialmente si la desviación afecta más a las colas que al centro, o viceversa.
  • Por eso se arma, al final de este documento, un caso difícil donde eso ocurre explícitamente.

Documentar el proceso

  • Registrar qué pruebas se usaron, su resultado y la decisión tomada (paramétrico vs. no paramétrico).

1. Librerías

Usaremos tidyverse para manipulación/visualización, broom para ordenar los resultados de los tests (broom::tidy), moments para asimetría/curtosis y para el Jarque-Bera clásico (moments::jarque.test), nortest para pruebas de normalidad adicionales (Lilliefors, Anderson-Darling, Cramér-von Mises, Shapiro-Francia, Pearson), Analitica para el test de Jarque-Bera con correcciones de Glinskiy (JBGTest) y ggpubr para gráficos Q-Q con estética ggplot2.

library(tidyverse)
library(broom)
library(moments)
library(nortest)
library(Analitica)
library(ggpubr)

2. Casos de ejemplo: datos con y sin distribución normal

Para ilustrar cada método usaremos dos vectores simulados, que se repiten a lo largo de todo el documento:

  • x_normal: 150 observaciones de una normal teórica (rnorm), media 50 y desviación estándar 10. Representa el caso “fácil”: los datos realmente provienen de una normal.
  • x_no_normal: 150 observaciones de una exponencial (rexp), claramente asimétrica a la derecha y con cola pesada. Representa el caso “fácil” opuesto: los datos claramente no son normales.
n <- 150

set.seed(111)
x_normal <- rnorm(n, mean = 50, sd = 10)

set.seed(222)
x_no_normal <- rexp(n, rate = 0.08)

datos_ejemplo <- tibble::tibble(
  valor = c(x_normal, x_no_normal),
  caso = rep(c("x_normal (normal teórica)", "x_no_normal (exponencial)"), each = n)
)

datos_ejemplo %>%
  dplyr::group_by(caso) %>%
  dplyr::summarise(
    n = dplyr::n(),
    media = mean(valor),
    de = sd(valor),
    minimo = min(valor),
    maximo = max(valor),
    .groups = "drop"
  )
## # A tibble: 2 × 6
##   caso                          n media    de  minimo maximo
##   <chr>                     <int> <dbl> <dbl>   <dbl>  <dbl>
## 1 x_no_normal (exponencial)   150  11.7  13.1  0.0495   96.1
## 2 x_normal (normal teórica)   150  50.3  10.9 16.8      77.2

3. Exploración inicial de los dos casos

ggplot2::ggplot(datos_ejemplo, ggplot2::aes(x = valor, fill = caso)) +
  ggplot2::geom_histogram(bins = 25, color = "white", alpha = 0.8, show.legend = FALSE) +
  ggplot2::facet_wrap(~ caso, scales = "free") +
  ggplot2::labs(title = "Distribución de los dos casos de ejemplo", x = NULL, y = "Frecuencia")

Lo esperable: x_normal debería verse como una campana simétrica; x_no_normal debería verse concentrada hacia la izquierda con una cola larga hacia la derecha.

Métodos informales (gráficos)

4. Método informal 1: Histograma + curva de densidad normal teórica

Comparar el histograma real contra la curva de una normal teórica con la misma media y desviación estándar que los datos es el primer chequeo visual. Si el histograma “calza” con la curva roja, es una señal (no una prueba) de normalidad.

ggplot2::ggplot(datos_ejemplo, ggplot2::aes(x = valor)) +
  ggplot2::geom_histogram(ggplot2::aes(y = ggplot2::after_stat(density)),
                           bins = 25, fill = "#4C72B0", alpha = 0.6, color = "white") +
  ggplot2::stat_function(
    fun = function(x, m, s) dnorm(x, m, s),
    args = list(m = mean(datos_ejemplo$valor), s = sd(datos_ejemplo$valor)),
    color = "red", linewidth = 0.8, n = 200
  ) +
  ggplot2::facet_wrap(~ caso, scales = "free") +
  ggplot2::labs(title = "Histograma vs. densidad normal teórica", x = NULL, y = "Densidad")

Lectura esperada: en x_normal el histograma debería seguir de cerca la curva roja; en x_no_normal el histograma debería quedar notoriamente desplazado respecto a la curva (asimetría a la derecha).

5. Método informal 2: Gráfico Q-Q (cuantil-cuantil)

El gráfico Q-Q compara los cuantiles observados contra los cuantiles teóricos de una normal. Si los puntos siguen la línea de referencia (y se mantienen dentro de la banda de confianza), hay evidencia visual de normalidad; si se “curvan” o se escapan de la banda —sobre todo en los extremos— hay evidencia de no-normalidad.

ggpubr::ggqqplot(x_normal, title = "Q-Q plot: x_normal")

ggpubr::ggqqplot(x_no_normal, title = "Q-Q plot: x_no_normal")

Lectura esperada: en x_normal los puntos deberían alinearse bien con la diagonal; en x_no_normal se debería observar una curvatura característica de asimetría (los puntos se alejan de la línea, especialmente en la cola derecha).

6. Método informal 3: Boxplot

El boxplot no prueba normalidad, pero permite ver rápidamente asimetría (caja desbalanceada respecto a la mediana) y valores atípicos.

ggplot2::ggplot(datos_ejemplo, ggplot2::aes(x = caso, y = valor, fill = caso)) +
  ggplot2::geom_boxplot(alpha = 0.7, show.legend = FALSE) +
  ggplot2::labs(title = "Boxplot por caso", x = NULL, y = "Valor")

Lectura esperada: la caja de x_normal debería verse razonablemente simétrica respecto a su mediana; la de x_no_normal debería mostrar una caja desbalanceada y varios puntos atípicos hacia la derecha.

7. Método semi-formal: asimetría y curtosis

Una normal teórica tiene asimetría (skewness) = 0 y curtosis (kurtosis) = 3 (exceso de curtosis = 0). No es una prueba de hipótesis, pero es un indicador numérico útil y rápido.

tabla_skew_kurt <- datos_ejemplo %>%
  dplyr::group_by(caso) %>%
  dplyr::summarise(
    asimetria = moments::skewness(valor),
    curtosis = moments::kurtosis(valor),
    curtosis_exceso = moments::kurtosis(valor) - 3,
    .groups = "drop"
  )

tabla_skew_kurt
## # A tibble: 2 × 4
##   caso                      asimetria curtosis curtosis_exceso
##   <chr>                         <dbl>    <dbl>           <dbl>
## 1 x_no_normal (exponencial)     2.69     14.6           11.6  
## 2 x_normal (normal teórica)    -0.181     3.29           0.287

Regla práctica: |asimetría| > 1 sugiere asimetría marcada; |curtosis - 3| > 1 sugiere colas más pesadas/livianas que la normal. Se espera que x_normal tenga ambos valores cercanos a 0, y que x_no_normal tenga asimetría claramente positiva.

Pruebas formales (con hipótesis y p-valor)

En todas las pruebas que siguen, la hipótesis nula es H0: los datos provienen de una distribución normal. Un p-valor menor a alpha (típicamente 0.05) lleva a rechazar H0 (evidencia de no-normalidad); un p-valor mayor significa que no hay evidencia suficiente para rechazar H0 (compatible con normalidad, pero no la “prueba”).

alpha <- 0.05

8. Prueba formal 1: Shapiro-Wilk

Es la prueba más usada y una de las más potentes para muestras pequeñas y medianas (n < 5000).

tabla_shapiro <- dplyr::bind_rows(
  broom::tidy(shapiro.test(x_normal)) %>% dplyr::mutate(caso = "x_normal"),
  broom::tidy(shapiro.test(x_no_normal)) %>% dplyr::mutate(caso = "x_no_normal")
) %>%
  dplyr::transmute(caso, estadistico_W = statistic, p_value = p.value,
                    decision = dplyr::if_else(p_value > alpha, "No se rechaza H0", "Se rechaza H0"))

tabla_shapiro
## # A tibble: 2 × 4
##   caso        estadistico_W  p_value decision        
##   <chr>               <dbl>    <dbl> <chr>           
## 1 x_normal            0.992 5.15e- 1 No se rechaza H0
## 2 x_no_normal         0.757 1.74e-14 Se rechaza H0

9. Prueba formal 2: Kolmogorov-Smirnov con corrección de Lilliefors

El test de Kolmogorov-Smirnov clásico no es adecuado cuando media y varianza se estiman de los propios datos; para eso se usa la corrección de Lilliefors (nortest::lillie.test). Esta prueba tiende a ser más sensible a desviaciones cerca del centro de la distribución que en las colas.

tabla_lillie <- dplyr::bind_rows(
  broom::tidy(nortest::lillie.test(x_normal)) %>% dplyr::mutate(caso = "x_normal"),
  broom::tidy(nortest::lillie.test(x_no_normal)) %>% dplyr::mutate(caso = "x_no_normal")
) %>%
  dplyr::transmute(caso, estadistico_D = statistic, p_value = p.value,
                    decision = dplyr::if_else(p_value > alpha, "No se rechaza H0", "Se rechaza H0"))

tabla_lillie
## # A tibble: 2 × 4
##   caso        estadistico_D  p_value decision        
##   <chr>               <dbl>    <dbl> <chr>           
## 1 x_normal           0.0601 2.04e- 1 No se rechaza H0
## 2 x_no_normal        0.185  9.04e-14 Se rechaza H0

10. Prueba formal 3: Anderson-Darling

Da más peso a las colas de la distribución que Kolmogorov-Smirnov, por lo que es especialmente sensible a colas pesadas y asimetrías marcadas.

tabla_ad <- dplyr::bind_rows(
  broom::tidy(nortest::ad.test(x_normal)) %>% dplyr::mutate(caso = "x_normal"),
  broom::tidy(nortest::ad.test(x_no_normal)) %>% dplyr::mutate(caso = "x_no_normal")
) %>%
  dplyr::transmute(caso, estadistico_A = statistic, p_value = p.value,
                    decision = dplyr::if_else(p_value > alpha, "No se rechaza H0", "Se rechaza H0"))

tabla_ad
## # A tibble: 2 × 4
##   caso        estadistico_A  p_value decision        
##   <chr>               <dbl>    <dbl> <chr>           
## 1 x_normal            0.401 3.56e- 1 No se rechaza H0
## 2 x_no_normal         8.19  4.88e-20 Se rechaza H0

11. Prueba formal 4: Cramér-von Mises

Al igual que Kolmogorov-Smirnov y Anderson-Darling, es una prueba basada en la función de distribución empírica (EDF): compara la distribución acumulada observada con la teórica. A diferencia de KS (que mira la máxima distancia puntual) y de AD (que pondera fuertemente las colas), Cramér-von Mises usa una distancia cuadrática integrada a lo largo de toda la distribución, por lo que suele comportarse como un punto intermedio entre ambas (nortest::cvm.test).

tabla_cvm <- dplyr::bind_rows(
  broom::tidy(nortest::cvm.test(x_normal)) %>% dplyr::mutate(caso = "x_normal"),
  broom::tidy(nortest::cvm.test(x_no_normal)) %>% dplyr::mutate(caso = "x_no_normal")
) %>%
  dplyr::transmute(caso, estadistico_W2 = statistic, p_value = p.value,
                    decision = dplyr::if_else(p_value > alpha, "No se rechaza H0", "Se rechaza H0"))

tabla_cvm
## # A tibble: 2 × 4
##   caso        estadistico_W2  p_value decision        
##   <chr>                <dbl>    <dbl> <chr>           
## 1 x_normal            0.0772 2.24e- 1 No se rechaza H0
## 2 x_no_normal         1.43   7.37e-10 Se rechaza H0

12. Prueba formal 5: Shapiro-Francia

Variante simplificada de Shapiro-Wilk, útil como confirmación adicional (nortest::sf.test).

tabla_sf <- dplyr::bind_rows(
  broom::tidy(nortest::sf.test(x_normal)) %>% dplyr::mutate(caso = "x_normal"),
  broom::tidy(nortest::sf.test(x_no_normal)) %>% dplyr::mutate(caso = "x_no_normal")
) %>%
  dplyr::transmute(caso, estadistico_W = statistic, p_value = p.value,
                    decision = dplyr::if_else(p_value > alpha, "No se rechaza H0", "Se rechaza H0"))

tabla_sf
## # A tibble: 2 × 4
##   caso        estadistico_W  p_value decision        
##   <chr>               <dbl>    <dbl> <chr>           
## 1 x_normal            0.991 4.01e- 1 No se rechaza H0
## 2 x_no_normal         0.750 1.05e-12 Se rechaza H0

13. Prueba formal 6: Jarque-Bera — versión clásica vs. corrección de Glinskiy (Analitica::JBGTest)

Ambas versiones se basan en la misma idea: combinar la asimetría y la curtosis muestral en un solo estadístico.

Jarque-Bera clásico (Jarque & Bera, 1980; moments::jarque.test):

\[JB = \frac{n}{6} S^2 + \frac{n}{24} (K-3)^2\]

donde \(S\) es la asimetría muestral y \(K\) la curtosis muestral. Bajo H0, \(JB\) se distribuye asintóticamente chi-cuadrado con 2 grados de libertad. El problema: esa aproximación chi-cuadrado(2) es solo asintótica, y en muestras pequeñas o moderadas el tamaño real del test (la tasa de falsos rechazos) puede alejarse bastante del nivel nominal alpha que uno cree estar usando.

Jarque-Bera con correcciones de Glinskiy (Glinskiy et al., 2024; Analitica::JBGTest): propone modificaciones al estadístico (y a su distribución de referencia) para los casos en que la media y/o la varianza poblacional se conocen a priori, y también para el caso más común en que ambas son desconocidas (mu = NULL, sigma2 = NULL, que es lo que usamos aquí). La idea central es mejorar la aproximación en muestras pequeñas/moderadas, de modo que el p-valor reportado sea más confiable que el del JB clásico cuando n no es grande.

## Versión clásica
jb_clasico_normal <- moments::jarque.test(x_normal)
jb_clasico_no_normal <- moments::jarque.test(x_no_normal)

## Versión con correcciones de Glinskiy
jbg_normal <- Analitica::JBGTest(x_normal)
jbg_no_normal <- Analitica::JBGTest(x_no_normal)

tabla_jb_comparacion <- tibble::tribble(
  ~version, ~caso, ~estadistico_JB, ~p_value,
  "JB clásico (moments)", "x_normal", jb_clasico_normal$statistic, jb_clasico_normal$p.value,
  "JB clásico (moments)", "x_no_normal", jb_clasico_no_normal$statistic, jb_clasico_no_normal$p.value,
  "JB con corrección de Glinskiy (Analitica)", "x_normal", jbg_normal$statistic, jbg_normal$p_value,
  "JB con corrección de Glinskiy (Analitica)", "x_no_normal", jbg_no_normal$statistic, jbg_no_normal$p_value
) %>%
  dplyr::mutate(decision = dplyr::if_else(p_value > alpha, "No se rechaza H0", "Se rechaza H0"))

tabla_jb_comparacion
## # A tibble: 4 × 5
##   version                                  caso  estadistico_JB p_value decision
##   <chr>                                    <chr>          <dbl>   <dbl> <chr>   
## 1 JB clásico (moments)                     x_no…           1.33   0.514 No se r…
## 2 JB clásico (moments)                     x_no…        1019.     0     Se rech…
## 3 JB con corrección de Glinskiy (Analitic… x_no…           1.33   0.514 No se r…
## 4 JB con corrección de Glinskiy (Analitic… x_no…        1019.     0     Se rech…

Qué comparar en la tabla: para cada caso (x_normal, x_no_normal), el estadístico y el p-valor pueden diferir entre ambas versiones, sobre todo si n es pequeño/moderado. En muestras grandes, ambas versiones deberían tender a dar la misma conclusión; la diferencia se nota más cuando n es chico, que es justamente el escenario que motivó la corrección de Glinskiy. Prueba cambiar n en la sección 2 (por ejemplo a 20 o 30) y vuelve a correr esta sección para ver la diferencia de forma más marcada.

Para el resto del documento (tabla resumen, caso difícil y función del apéndice) seguimos usando la versión de Glinskiy (tabla_jbg), por ser la más reciente y más confiable en muestras no muy grandes.

tabla_jbg <- tibble::tibble(
  caso = c("x_normal", "x_no_normal"),
  estadistico_JB = c(jbg_normal$statistic, jbg_no_normal$statistic),
  df = c(jbg_normal$df, jbg_no_normal$df),
  p_value = c(jbg_normal$p_value, jbg_no_normal$p_value),
  decision = c(jbg_normal$decision, jbg_no_normal$decision)
)

tabla_jbg
## # A tibble: 2 × 5
##   caso        estadistico_JB    df p_value decision  
##   <chr>                <dbl> <dbl>   <dbl> <chr>     
## 1 x_normal              1.33     2   0.514 Normal    
## 2 x_no_normal        1019.       2   0     Not Normal

14. Prueba formal 7: Pearson chi-cuadrado de bondad de ajuste

Compara frecuencias observadas vs. esperadas por intervalos (bins). Es, en general, menos potente que Shapiro-Wilk, pero sigue la lógica clásica de bondad de ajuste (nortest::pearson.test).

tabla_pearson <- dplyr::bind_rows(
  broom::tidy(nortest::pearson.test(x_normal)) %>% dplyr::mutate(caso = "x_normal"),
  broom::tidy(nortest::pearson.test(x_no_normal)) %>% dplyr::mutate(caso = "x_no_normal")
) %>%
  dplyr::transmute(caso, estadistico_chi2 = statistic, p_value = p.value,
                    decision = dplyr::if_else(p_value > alpha, "No se rechaza H0", "Se rechaza H0"))

tabla_pearson
## # A tibble: 2 × 4
##   caso        estadistico_chi2  p_value decision     
##   <chr>                  <dbl>    <dbl> <chr>        
## 1 x_normal                  22 3.75e- 2 Se rechaza H0
## 2 x_no_normal               98 1.37e-15 Se rechaza H0

15. Tabla resumen: los dos casos “fáciles”

resumen_facil <- dplyr::bind_rows(
  tabla_shapiro %>% dplyr::mutate(metodo = "Shapiro-Wilk"),
  tabla_lillie %>% dplyr::mutate(metodo = "Kolmogorov-Smirnov (Lilliefors)"),
  tabla_ad %>% dplyr::mutate(metodo = "Anderson-Darling"),
  tabla_cvm %>% dplyr::rename(estadistico = estadistico_W2) %>% dplyr::mutate(metodo = "Cramér-von Mises"),
  tabla_sf %>% dplyr::mutate(metodo = "Shapiro-Francia"),
  tabla_jbg %>% dplyr::mutate(metodo = "Jarque-Bera (Glinskiy)"),
  tabla_pearson %>% dplyr::mutate(metodo = "Pearson chi-cuadrado")
) %>%
  dplyr::select(metodo, caso, p_value, decision) %>%
  tidyr::pivot_wider(names_from = caso, values_from = c(p_value, decision))

resumen_facil
## # A tibble: 7 × 5
##   metodo                  p_value_x_normal p_value_x_no_normal decision_x_normal
##   <chr>                              <dbl>               <dbl> <chr>            
## 1 Shapiro-Wilk                      0.515             1.74e-14 No se rechaza H0 
## 2 Kolmogorov-Smirnov (Li…           0.204             9.04e-14 No se rechaza H0 
## 3 Anderson-Darling                  0.356             4.88e-20 No se rechaza H0 
## 4 Cramér-von Mises                  0.224             7.37e-10 No se rechaza H0 
## 5 Shapiro-Francia                   0.401             1.05e-12 No se rechaza H0 
## 6 Jarque-Bera (Glinskiy)            0.514             0        Normal           
## 7 Pearson chi-cuadrado              0.0375            1.37e-15 Se rechaza H0    
## # ℹ 1 more variable: decision_x_no_normal <chr>

Lo esperable: en los casos “fáciles” las 7 pruebas deberían coincidir: todas sin evidencia para rechazar normalidad en x_normal, y todas rechazando normalidad en x_no_normal. Esto cambia en el siguiente ejemplo.

16. Caso difícil: cuando los métodos no se ponen de acuerdo

Hasta aquí, ambos casos fueron “fáciles” porque todos los métodos apuntan en la misma dirección. En la práctica, sin embargo, hay distribuciones que parecen razonablemente normales a simple vista (simétricas, en forma de campana) pero que tienen colas más pesadas que una normal. Un ejemplo clásico es la distribución t de Student con pocos grados de libertad: es simétrica como la normal, pero con colas pesadas.

set.seed(333)
x_dificil <- rt(200, df = 3) * 10 + 50 ## simétrica, pero con colas pesadas

16.1 Métodos informales sobre el caso difícil

ggplot2::ggplot(data.frame(valor = x_dificil), ggplot2::aes(x = valor)) +
  ggplot2::geom_histogram(ggplot2::aes(y = ggplot2::after_stat(density)),
                           bins = 25, fill = "#55A868", alpha = 0.6, color = "white") +
  ggplot2::stat_function(
    fun = function(x, m, s) dnorm(x, m, s),
    args = list(m = mean(x_dificil), s = sd(x_dificil)),
    color = "red", linewidth = 0.8, n = 200
  ) +
  ggplot2::labs(title = "Caso difícil: histograma vs. densidad normal teórica", x = NULL, y = "Densidad")

ggpubr::ggqqplot(x_dificil, title = "Q-Q plot: caso difícil")

ggplot2::ggplot(data.frame(valor = x_dificil), ggplot2::aes(x = "", y = valor)) +
  ggplot2::geom_boxplot(fill = "#55A868", alpha = 0.7) +
  ggplot2::labs(title = "Boxplot: caso difícil", x = NULL, y = "Valor")

tibble::tibble(
  asimetria = moments::skewness(x_dificil),
  curtosis = moments::kurtosis(x_dificil),
  curtosis_exceso = moments::kurtosis(x_dificil) - 3
)
## # A tibble: 1 × 3
##   asimetria curtosis curtosis_exceso
##       <dbl>    <dbl>           <dbl>
## 1    -0.292     6.31            3.31

Lectura esperada: el histograma debería verse razonablemente simétrico y “acampanado” (a diferencia de x_no_normal), y la asimetría debería quedar cercana a 0. Sin embargo, en el Q-Q plot es esperable ver puntos que se escapan de la banda de confianza en ambos extremos (colas más largas que la normal), y la curtosis debería ser notoriamente mayor a 3. Es decir: los métodos informales ya dan una pista de que algo no calza del todo, aunque menos evidente que en x_no_normal.

16.2 Pruebas formales sobre el caso difícil

jbg_dificil <- Analitica::JBGTest(x_dificil)
jb_clasico_dificil <- moments::jarque.test(x_dificil)

tabla_dificil <- tibble::tribble(
  ~metodo, ~estadistico, ~p_value,
  "Shapiro-Wilk", shapiro.test(x_dificil)$statistic, shapiro.test(x_dificil)$p.value,
  "Kolmogorov-Smirnov (Lilliefors)", nortest::lillie.test(x_dificil)$statistic, nortest::lillie.test(x_dificil)$p.value,
  "Anderson-Darling", nortest::ad.test(x_dificil)$statistic, nortest::ad.test(x_dificil)$p.value,
  "Cramer-von Mises", nortest::cvm.test(x_dificil)$statistic, nortest::cvm.test(x_dificil)$p.value,
  "Shapiro-Francia", nortest::sf.test(x_dificil)$statistic, nortest::sf.test(x_dificil)$p.value,
  "Jarque-Bera clásico (moments)", jb_clasico_dificil$statistic, jb_clasico_dificil$p.value,
  "Jarque-Bera (Glinskiy)", jbg_dificil$statistic, jbg_dificil$p_value,
  "Pearson chi-cuadrado", nortest::pearson.test(x_dificil)$statistic, nortest::pearson.test(x_dificil)$p.value
) %>%
  dplyr::mutate(decision = dplyr::if_else(p_value > alpha, "No se rechaza H0 (parece normal)", "Se rechaza H0 (no normal)"))

tabla_dificil
## # A tibble: 8 × 4
##   metodo                          estadistico    p_value decision               
##   <chr>                                 <dbl>      <dbl> <chr>                  
## 1 Shapiro-Wilk                         0.955  0.00000553 Se rechaza H0 (no norm…
## 2 Kolmogorov-Smirnov (Lilliefors)      0.0932 0.000229   Se rechaza H0 (no norm…
## 3 Anderson-Darling                     2.26   0.00000937 Se rechaza H0 (no norm…
## 4 Cramer-von Mises                     0.443  0.00000955 Se rechaza H0 (no norm…
## 5 Shapiro-Francia                      0.948  0.00000484 Se rechaza H0 (no norm…
## 6 Jarque-Bera clásico (moments)       94.4    0          Se rechaza H0 (no norm…
## 7 Jarque-Bera (Glinskiy)              94.4    0          Se rechaza H0 (no norm…
## 8 Pearson chi-cuadrado                33.8    0.00224    Se rechaza H0 (no norm…

17. Interpretación del caso difícil

Este es el punto central del documento: con datos de colas pesadas como x_dificil, no todas las pruebas coinciden.

  • Anderson-Darling, Jarque-Bera clásico y Jarque-Bera (Glinskiy) son particularmente sensibles a colas pesadas y a un exceso de curtosis, por lo que es frecuente que rechacen H0 (detecten no-normalidad) incluso cuando la asimetría es prácticamente nula. Entre las dos versiones de Jarque-Bera, la de Glinskiy es la que debería dar el p-valor más confiable si n no es grande, ya que fue diseñada justamente para corregir el comportamiento del JB clásico en esas condiciones.
  • Kolmogorov-Smirnov (Lilliefors) suele ser más sensible a desviaciones en el centro de la distribución que en las colas, por lo que en muestras moderadas puede no rechazar H0, dando una falsa sensación de normalidad.
  • Cramér-von Mises, al integrar la distancia a lo largo de toda la distribución (no solo el máximo como KS, ni solo las colas como AD), suele quedar en un punto intermedio: a veces coincide con KS, a veces con AD, dependiendo de qué tan concentrada esté la desviación.
  • Shapiro-Wilk y Shapiro-Francia suelen ubicarse en un punto intermedio: su resultado depende bastante del tamaño muestral y de qué tan pocos grados de libertad tenga la t utilizada.
  • Pearson chi-cuadrado, al depender de la elección de intervalos (bins), puede perder potencia para detectar colas pesadas si los bins son anchos.

Importante: los resultados exactos (qué prueba rechaza y cuál no) dependen de la semilla, el tamaño de muestra y los grados de libertad de la t utilizada. Si al ejecutar este Rmd obtienes una combinación distinta, es exactamente el punto: pequeños cambios en los datos pueden mover el veredicto de pruebas límite. Por eso nunca se debe decidir normalidad en base a un solo test, y por eso los métodos gráficos (histograma, Q-Q) son un complemento indispensable para ver en qué parte de la distribución está el problema (centro vs. colas).

18. Recomendaciones prácticas

  1. Empieza siempre por lo gráfico (histograma + Q-Q): te dice dónde está la posible desviación (centro, colas, asimetría).
  2. Complementa con asimetría/curtosis: un diagnóstico numérico rápido, sin necesidad de un test formal.
  3. Aplica al menos 2 o 3 pruebas formales con sensibilidades distintas (p. ej., Shapiro-Wilk + Anderson-Darling o Cramér-von Mises + Jarque-Bera/Glinskiy), no solo una.
  4. Si vas a usar Jarque-Bera y tu muestra es pequeña o moderada (n < ~100), prefiere la versión de Glinskiy (Analitica::JBGTest) sobre la clásica (moments::jarque.test), ya que su p-valor es más confiable en ese rango de tamaños muestrales.
  5. Si las pruebas discrepan, prioriza lo que muestran los gráficos y el tamaño del efecto (asimetría/curtosis) por sobre el p-valor aislado.
  6. Considera el tamaño muestral: con n muy grande, revisa si la desviación detectada es estadísticamente significativa pero prácticamente irrelevante.
  7. Documenta qué pruebas usaste, cuáles coincidieron, cuáles no, y qué decisión final tomaste (paramétrico, no paramétrico, o transformación).