Tablas cruzadas y chi-cuadrado
¿Por qué chi-cuadrado?
Cuando los datos son categóricos (sexo, región, sí/no, recuperado/no-recuperado), las herramientas anteriores no aplican. No hay medias que comparar. La pregunta cambia a “¿están las dos variables asociadas?”. El test estándar es el chi-cuadrado de independencia.
Tablas cruzadas: table() y prop.table()
El primer paso es siempre la tabla cruzada. R lo hace con table():
# Dataset interno: supervivencia en el Titanic
tabla <- with(as.data.frame(Titanic), table(Class, Survived))
tabla
#> Survived
#> Class No Yes
#> 1st 122 203
#> 2nd 167 118
#> 3rd 528 178
#> Crew 673 212Para ver porcentajes en lugar de conteos, prop.table():
prop.table(tabla, margin = 1) # porcentajes por FILA (clase)
#> Survived
#> Class No Yes
#> 1st 0.3753846 0.6246154
#> 2nd 0.5859649 0.4140351
#> 3rd 0.7478754 0.2521246
#> Crew 0.7604520 0.2395480margin = 1 normaliza por filas (lectura: “de la clase 1ª, el 62 % sobrevivió”). margin = 2 normaliza por columnas. Sin margin, normaliza por el total.
Para reportar con miles de filas y márgenes ya formateados, janitor::tabyl() es más legible:
library(janitor)
library(dplyr)
as.data.frame(Titanic) |>
uncount(Freq) |>
tabyl(Class, Survived) |>
adorn_totals(c("row", "col")) |>
adorn_percentages("row") |>
adorn_pct_formatting(digits = 1) |>
adorn_ns()Una tabla pulida directamente para informe.
chisq.test(): el test canónico
chisq.test(tabla)
#>
#> Pearson's Chi-squared test
#>
#> data: tabla
#> X-squared = 190.4, df = 3, p-value < 2.2e-16Cómo funciona conceptualmente: el test compara las frecuencias observadas con las esperadas si las dos variables fueran independientes. Si la discrepancia es grande, rechaza la independencia.
test <- chisq.test(tabla)
test$expected # las frecuencias esperadas bajo independencia
test$residuals # residuos de Pearson (cuánto se aleja cada celda)Los residuos de Pearson ((observado - esperado) / sqrt(esperado)) son muy útiles para identificar qué celdas contribuyen más al rechazo. Valores > |2| señalan asociaciones notables.
La regla del 80 % / 5 y por qué importa
chisq.test() asume que la mayoría de las frecuencias esperadas son ≥ 5. La regla habitual:
- ≥ 80 % de las celdas con frecuencia esperada ≥ 5.
- Ninguna celda con frecuencia esperada < 1.
Cuando esto se viola, el test de chi-cuadrado da p-values poco fiables. R te avisa con un warning:
Warning message:
In chisq.test(tabla) : Chi-squared approximation may be incorrect
Léelo. No es decoración. Si lo ves, mira test$expected y verifica.
Fisher exact: cuando las frecuencias son bajas
Para tablas 2×2 con celdas pequeñas, fisher.test() es la alternativa exacta:
# Tabla 2x2 con frecuencias bajas
tabla_pequena <- matrix(c(8, 2, 1, 5), nrow = 2,
dimnames = list(c("A", "B"), c("Sí", "No")))
tabla_pequena
fisher.test(tabla_pequena)Ventajas:
- No depende de aproximación asintótica: calcula el p-value exacto.
- Funciona con cualquier tamaño muestral, incluso con frecuencias muy bajas.
- Devuelve odds ratio con IC en tablas 2×2, lo cual es directamente reportable.
Desventajas: computacionalmente caro en tablas grandes (> 2x2), aunque R simula cuando hace falta.
Regla práctica: si la tabla es 2×2 y hay alguna celda esperada < 5, usa Fisher. Para tablas más grandes con problemas de frecuencia, considera agrupar categorías o usar simulación: chisq.test(tabla, simulate.p.value = TRUE).
McNemar: para datos pareados binarios
Si tus datos categóricos son pareados (mismos individuos antes/después, casos vs controles emparejados), el chi-cuadrado normal no es válido. Necesitas McNemar:
# Mismo paciente medido antes y después del tratamiento (sí/no recuperado)
tabla_mcnemar <- matrix(c(30, 15, 5, 50), nrow = 2,
dimnames = list(Antes = c("No", "Sí"),
Despues = c("No", "Sí")))
tabla_mcnemar
mcnemar.test(tabla_mcnemar)McNemar mira solo las celdas off-diagonal, los individuos que cambiaron de estado. Si los que mejoraron son aproximadamente iguales a los que empeoraron, no hay efecto. Si hay desbalance claro, lo detecta.
Visualización: mosaicos
Para tablas grandes, una visualización de mosaico ayuda a ver patrones:
mosaicplot(tabla, color = TRUE, main = "Supervivencia por clase (Titanic)")O con ggplot2 + el paquete ggmosaic:
# install.packages("ggmosaic")
library(ggmosaic)
library(ggplot2)
as.data.frame(Titanic) |>
ggplot() +
geom_mosaic(aes(weight = Freq, x = product(Class), fill = Survived)) +
labs(title = "Supervivencia por clase")Los anchos de las columnas reflejan tamaños de grupo. Las áreas de color reflejan proporciones.
Trampas habituales
- Ignorar el warning de aproximación incorrecta. R te avisa cuando las frecuencias esperadas son demasiado bajas. No filtres los warnings, léelos. Usa Fisher si la advertencia aparece en tabla 2×2.
- Aplicar chi-cuadrado a datos pareados. El test ignora la estructura pareada y reporta un p-value menos potente. Si el mismo individuo aparece dos veces (medidas repetidas), usa McNemar.
- Reportar solo el p-value. Una tabla cruzada con porcentajes por fila (
prop.table(margin = 1)) y el chi-cuadrado juntos es lo que comunica el hallazgo. El p-value sin la tabla es opaco. chisq.test()con un vector en lugar de tabla.chisq.test(c(10, 20, 30))ejecuta un test de bondad de ajuste (¿se ajustan estos conteos a la distribución uniforme?). Distinto del test de independencia. Si tu objetivo es el segundo, asegúrate de pasar una matriz otable().
En la siguiente entrega
Has aprendido a testar asociación entre variables categóricas. La siguiente entrega cierra el bloque 2 con la correlación: cómo medir la asociación entre dos variables numéricas, con Pearson, Spearman y Kendall. Es lo siguiente.