ANOVA y pruebas post hoc
Compare las medias de tres o más grupos con un ANOVA de un factor y ejecute Tukey HSD para identificar qué pares difieren.
ANOVA y pruebas post hoc es una lección gratuita de Pandas & NumPy Academy en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Pandas & NumPy Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Pandas & NumPy Academy incluye 4 lecciones en total.
¿Por qué no realizar varias pruebas t?
Al comparar las medias de tres grupos o más, realizar varias pruebas t aumenta la tasa de error de tipo I (tasa de falsos positivos). Comparar los grupos A frente a B, A frente a C y B frente a C con α=0.05 en cada caso da una probabilidad global de falso positivo de aproximadamente el 14 %, no del 5 %. El análisis de varianza (ANOVA) resuelve este problema al evaluar todos los grupos simultáneamente en una sola prueba, manteniendo la tasa de falsos positivos exactamente en α. Un ANOVA sustituye todas las pruebas t por pares para responder a la pregunta global: «¿Difieren algunos grupos?»
ANOVA de un factor: visión general
El ANOVA de un factor evalúa si las medias de tres grupos o más difieren significativamente. Descompone la varianza total en varianza entre grupos (explicada por la pertenencia al grupo) y varianza dentro de los grupos (ruido aleatorio). El estadístico F es su cociente: F = (varianza entre grupos) / (varianza dentro de los grupos). Un valor grande de F indica que las diferencias entre grupos son grandes en relación con el ruido, lo que produce un valor p pequeño. La hipótesis nula es H₀: todas las medias de los grupos son iguales.
import numpy as np
from scipy import stats
np.random.seed(0)
# Three product variants with different conversion rates
group_a = np.random.normal(10.0, 2.0, 50) # baseline
group_b = np.random.normal(11.5, 2.0, 50) # slightly better
group_c = np.random.normal(13.0, 2.0, 50) # clearly better
f_stat, p = stats.f_oneway(group_a, group_b, group_c)
print(f'F-statistic: {f_stat:.3f}')
print(f'p-value: {p:.6f}')
print('At least one group differs?', 'Yes' if p < 0.05 else 'No')ANOVA con DataFrames de Pandas
En la práctica, sus datos se encuentran en un DataFrame en formato largo, donde una columna contiene la etiqueta del grupo y otra contiene la medición. Extraiga cada grupo como una Series y páselos a stats.f_oneway(). Como alternativa, si dispone de un DataFrame en formato ancho (una columna por grupo), pase cada columna directamente. La función acepta cualquier número de argumentos posicionales, por lo que es fácil ampliarla a 4, 5 o más grupos.
import pandas as pd
import numpy as np
from scipy import stats
np.random.seed(1)
df = pd.DataFrame({
'group': ['A']*40 + ['B']*40 + ['C']*40 + ['D']*40,
'score': np.concatenate([
np.random.normal(70, 10, 40),
np.random.normal(75, 10, 40),
np.random.normal(80, 10, 40),
np.random.normal(72, 10, 40)
])
})
groups = [group['score'].values
for _, group in df.groupby('group')]
f, p = stats.f_oneway(*groups)
print(f'F={f:.3f}, p={p:.4f}')
print(df.groupby('group')['score'].mean().round(2))Supuestos del ANOVA
El ANOVA de un factor supone: (1) independencia — las observaciones son independientes entre sí; (2) normalidad — los residuos son aproximadamente normales dentro de cada grupo (es robusto con muestras grandes gracias al TCL); (3) homocedasticidad — las varianzas son iguales entre los grupos. Compruebe el supuesto de igualdad de varianzas con la prueba de Levene (stats.levene(*groups)). Si las varianzas son desiguales, utilice el ANOVA de Welch, disponible en la biblioteca pingouin o calculable mediante statsmodels.
import numpy as np
from scipy import stats
np.random.seed(0)
g1 = np.random.normal(10, 2, 40)
g2 = np.random.normal(12, 2, 40)
g3 = np.random.normal(11, 8, 40) # Much higher variance
# Test equal variances
stat_l, p_l = stats.levene(g1, g2, g3)
print(f'Levene\'s test: stat={stat_l:.3f}, p={p_l:.4f}')
if p_l < 0.05:
print('Warning: Unequal variances! Standard ANOVA may be unreliable.')
print('Consider Welch\'s ANOVA or Kruskal-Wallis.')Kruskal-Wallis: ANOVA no paramétrico
Cuando no se cumplen los supuestos del ANOVA (distribuciones no normales, muestras pequeñas o varianzas desiguales), la prueba de Kruskal-Wallis es la alternativa no paramétrica. Evalúa si las distribuciones de los grupos difieren transformando los datos en rangos y comparando las sumas de rangos. Utilice scipy.stats.kruskal(*groups). La hipótesis nula establece que todos los grupos tienen la misma distribución (equivalente a evaluar la igualdad de las medianas cuando las distribuciones tienen la misma forma). Es siempre válida, pero tiene menos potencia que el ANOVA cuando se cumplen sus supuestos.
import numpy as np
from scipy import stats
np.random.seed(0)
# Non-normal data: customer satisfaction scores (1-10 scale)
g1 = np.random.choice(range(1, 11), 50, p=[0.05]*10)
g2 = np.random.choice(range(1, 11), 50, p=[0.02, 0.03, 0.05, 0.08, 0.12, 0.15, 0.20, 0.15, 0.12, 0.08])
g3 = np.random.choice(range(1, 11), 50, p=[0.15, 0.15, 0.15, 0.15, 0.10, 0.10, 0.08, 0.05, 0.04, 0.03])
stat, p = stats.kruskal(g1, g2, g3)
print(f'Kruskal-Wallis: H={stat:.3f}, p={p:.4f}')
print('Groups differ?', 'Yes' if p < 0.05 else 'No')Pruebas post hoc: por qué son necesarias
Un ANOVA significativo indica que al menos la media de un grupo difiere, pero no indica qué pares difieren. Las pruebas post hoc realizan todas las comparaciones por pares y corrigen las comparaciones múltiples. La más popular es la diferencia honestamente significativa de Tukey (Tukey HSD), que controla exactamente la tasa de error familiar en α. Está disponible en statsmodels.stats.multicomp.pairwise_tukeyhsd(). Ejecute pruebas post hoc solo después de obtener un ANOVA significativo, no como una búsqueda indiscriminada en resultados no significativos.
import numpy as np
import pandas as pd
from scipy import stats
from statsmodels.stats.multicomp import pairwise_tukeyhsd
np.random.seed(0)
data = np.concatenate([
np.random.normal(70, 10, 40),
np.random.normal(80, 10, 40),
np.random.normal(75, 10, 40)
])
groups = ['A']*40 + ['B']*40 + ['C']*40
# First confirm ANOVA is significant
f, p = stats.f_oneway(*[data[i*40:(i+1)*40] for i in range(3)])
print(f'ANOVA p={p:.4f}')
if p < 0.05:
print('Post-hoc Tukey HSD:')
print(pairwise_tukeyhsd(data, groups, alpha=0.05))Interpretación de los resultados de Tukey HSD
La tabla de Tukey HSD contiene una fila por cada par de grupos. Las columnas en las que debe centrarse son: meandiff (diferencia de medias entre el par), lower y upper (intervalo de confianza del 95 % para la diferencia), y reject (True si el par difiere significativamente con α). Un intervalo de confianza que no incluye cero indica que el par presenta una diferencia significativa. Los grupos cuyos intervalos de confianza se solapan no difieren significativamente entre sí.
import numpy as np
from statsmodels.stats.multicomp import pairwise_tukeyhsd
np.random.seed(42)
data = np.concatenate([
np.random.normal(10, 2, 60), # Group A
np.random.normal(13, 2, 60), # Group B (clearly higher)
np.random.normal(10.5, 2, 60) # Group C (barely different from A)
])
groups = ['A']*60 + ['B']*60 + ['C']*60
result = pairwise_tukeyhsd(data, groups, alpha=0.05)
print(result)
# A-B: reject=True (B is higher)
# A-C: reject=False (barely different)
# B-C: reject=True (B is higher)Correcciones de Bonferroni y Benjamini-Hochberg
Como alternativa a Tukey HSD, puede aplicar una corrección de Bonferroni a las pruebas t por pares: realice todas las pruebas t y, después, ajuste el umbral a α/k, donde k es el número de comparaciones. Es un método conservador, por lo que puede pasar por alto diferencias reales. La corrección de la tasa de falsos descubrimientos de Benjamini-Hochberg (FDR) es menos conservadora: controla la proporción esperada de falsos descubrimientos, lo que permite detectar más positivos verdaderos a cambio de admitir ligeramente más falsos positivos. Utilice statsmodels.stats.multitest.multipletests(p_values, method='fdr_bh').
import numpy as np
from scipy import stats
from statsmodels.stats.multitest import multipletests
np.random.seed(0)
groups = [np.random.normal(10 + i*1.5, 2, 40) for i in range(4)]
names = ['A', 'B', 'C', 'D']
# All pairwise t-tests
p_values = []
pairs = []
for i in range(len(groups)):
for j in range(i+1, len(groups)):
_, p = stats.ttest_ind(groups[i], groups[j])
p_values.append(p)
pairs.append(f'{names[i]}-{names[j]}')
# Benjamini-Hochberg correction
reject, adj_p, _, _ = multipletests(p_values, method='fdr_bh')
for pair, p, ap, r in zip(pairs, p_values, adj_p, reject):
print(f'{pair}: raw_p={p:.4f}, adj_p={ap:.4f}, significant={r}')Tamaño del efecto en ANOVA: eta cuadrado
En el ANOVA, la medida análoga a la d de Cohen es el eta cuadrado (η²): la proporción de la varianza total explicada por la pertenencia al grupo. η² = SS_between / SS_total. Valores: < 0.01 es pequeño, 0.06 es mediano y > 0.14 es grande. El eta cuadrado parcial (η²_p) es más habitual en las investigaciones publicadas: divide únicamente entre la varianza del grupo más la varianza del error, no entre la varianza total. Calcule siempre η² junto con el valor p del ANOVA para mostrar si el efecto tiene relevancia práctica, no solo si es detectable estadísticamente.
import numpy as np
from scipy import stats
np.random.seed(0)
groups = [
np.random.normal(10, 2, 50),
np.random.normal(12, 2, 50),
np.random.normal(14, 2, 50)
]
all_data = np.concatenate(groups)
grand_mean = all_data.mean()
ss_between = sum(len(g) * (g.mean() - grand_mean)**2 for g in groups)
ss_total = sum((x - grand_mean)**2 for g in groups for x in g)
eta_sq = ss_between / ss_total
f, p = stats.f_oneway(*groups)
print(f'F={f:.3f}, p={p:.4f}')
print(f'Eta-squared: {eta_sq:.4f} ({eta_sq:.1%} of variance explained)')Descripción general del ANOVA de dos factores
El ANOVA de dos factores amplía el ANOVA de un factor a dos factores categóricos simultáneamente. Por ejemplo, permite evaluar si las calificaciones de un examen difieren según el método de enseñanza y el nivel de experiencia del estudiante al mismo tiempo. También evalúa el efecto de interacción: ¿depende el efecto del método de enseñanza del nivel de experiencia? El ANOVA de dos factores se implementa en statsmodels mediante ols('score ~ C(method) + C(level) + C(method):C(level)', data=df).fit() y anova_lm(model). Es una base para diseños experimentales más avanzados.
import pandas as pd
import numpy as np
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
np.random.seed(0)
df = pd.DataFrame({
'method': ['trad']*60 + ['active']*60,
'level': ['beginner']*30 + ['advanced']*30 + ['beginner']*30 + ['advanced']*30,
'score': (np.random.normal(70, 8, 30).tolist() +
np.random.normal(80, 8, 30).tolist() +
np.random.normal(75, 8, 30).tolist() +
np.random.normal(88, 8, 30).tolist())
})
model = ols('score ~ C(method) + C(level) + C(method):C(level)', data=df).fit()
print(anova_lm(model, typ=2)[['F', 'PR(>F)']].round(4))ANOVA de medidas repetidas
El ANOVA de medidas repetidas se utiliza cuando se mide a los mismos sujetos varias veces; por ejemplo, para evaluar el tiempo de reacción en los minutos 0, 30 y 60 después de un tratamiento. Es la generalización a varios grupos de la prueba t para muestras relacionadas. Al tener en cuenta las diferencias individuales, ofrece mayor potencia que el ANOVA de un factor. En Python, utilice pingouin.rm_anova() o pg.pairwise_tests() para el análisis post hoc. Cuando se viola la esfericidad (evaluada con la prueba de Mauchly), utilice valores p corregidos mediante Greenhouse-Geisser.
import pandas as pd
import numpy as np
# Example structure for repeated measures (pingouin library)
np.random.seed(0)
subjects = list(range(20)) * 3 # 20 subjects, 3 time points
timepoints = ['T0']*20 + ['T1']*20 + ['T2']*20
scores = np.concatenate([
np.random.normal(50, 10, 20), # baseline
np.random.normal(55, 10, 20), # improved
np.random.normal(60, 10, 20) # further improved
])
df_rm = pd.DataFrame({'subject': subjects, 'time': timepoints, 'score': scores})
print(df_rm.groupby('time')['score'].mean().round(2))
# Use pingouin.rm_anova(data=df_rm, dv='score', within='time', subject='subject')Comprobación rápida
Compruebe su comprensión de los conceptos de análisis de datos de esta lección.
Resumen de la lección
En esta lección ha aprendido que scipy.stats.f_oneway() evalúa si algún grupo difiere en su media controlando la tasa global de error de tipo I; las pruebas post hoc (Tukey HSD) identifican qué pares concretos difieren después de un ANOVA significativo; y Kruskal-Wallis es la alternativa no paramétrica cuando no se cumplen los supuestos de normalidad o de igualdad de varianzas. A continuación comenzaremos el proyecto final, en el que integraremos todas las habilidades en un flujo de datos completo de principio a fin.
Aprende Python con un tutor de IA — gratis
Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.
- Cursos
- 30
- Lecciones
- 120
Preguntas frecuentes
¿La lección «ANOVA y pruebas post hoc» es gratis?
Sí — el texto completo de «ANOVA y pruebas post hoc» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Pandas & NumPy Academy, actualiza a CoddyKit PRO. El curso de Pandas & NumPy Academy incluye 4 lecciones en total.
¿Qué aprenderé en «ANOVA y pruebas post hoc»?
Compare las medias de tres o más grupos con un ANOVA de un factor y ejecute Tukey HSD para identificar qué pares difieren. Practicas Pandas & NumPy Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Pandas & NumPy Academy?
No se requiere experiencia previa. Pandas & NumPy Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.
¿Cuánto tiempo toma la lección «ANOVA y pruebas post hoc»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Pandas & NumPy Academy?
Sí. Cada lección de Pandas & NumPy Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Estadística descriptiva y pruebas de normalidad
- Pruebas t para comparar medias
- Prueba de chi cuadrado de independencia
- ANOVA y pruebas post hoc