ANOVA et tests post-hoc
Comparez les moyennes de trois groupes ou plus avec une ANOVA à un facteur et exécutez Tukey HSD pour déterminer quelles paires diffèrent.
ANOVA et tests post-hoc est une leçon Pandas & NumPy Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Pandas & NumPy Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Pandas & NumPy Academy comprend 4 leçons au total.
Pourquoi pas plusieurs tests t ?
Lorsque vous comparez les moyennes de trois groupes ou plus, effectuer plusieurs tests t augmente le taux d’erreur de type I (taux de faux positifs). Tester les groupes A contre B, A contre C et B contre C avec α=0,05 pour chacun donne une probabilité globale de faux positif d’environ 14 %, et non de 5 %. L’analyse de variance (ANOVA) résout ce problème en testant simultanément tous les groupes au moyen d’un seul test, ce qui maintient le taux de faux positifs exactement à α. Une ANOVA remplace tous les tests t deux à deux pour répondre à la question globale : « Certains groupes sont-ils différents ? »
ANOVA à un facteur : vue d’ensemble
L’ANOVA à un facteur vérifie si les moyennes de trois groupes ou plus sont significativement différentes. Elle décompose la variance totale en variance intergroupes (expliquée par l’appartenance à un groupe) et en variance intragroupe (bruit aléatoire). La statistique F est leur rapport : F = (variance intergroupes) / (variance intragroupe). Une valeur élevée de F signifie que les différences entre les groupes sont importantes par rapport au bruit, ce qui produit une petite valeur p. L’hypothèse nulle est H₀ : toutes les moyennes des groupes sont égales.
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 avec les DataFrames Pandas
En pratique, vos données sont dans un DataFrame au format long, où une colonne contient l’étiquette du groupe et une autre la mesure. Extrayez chaque groupe sous forme de Series et transmettez-les à stats.f_oneway(). Vous pouvez aussi utiliser un DataFrame au format large (une colonne par groupe) et transmettre directement chaque colonne. La fonction accepte un nombre quelconque d’arguments positionnels, ce qui permet facilement de passer à 4, 5 groupes ou davantage.
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))Hypothèses de l’ANOVA
L’ANOVA à un facteur repose sur les hypothèses suivantes : (1) Indépendance — les observations sont indépendantes les unes des autres ; (2) Normalité — les résidus sont approximativement normaux dans chaque groupe (l’ANOVA est robuste pour les grands échantillons grâce au CLT) ; (3) Homoscédasticité — les variances sont égales entre les groupes. Vérifiez l’hypothèse concernant les variances avec le test de Levene (stats.levene(*groups)). Si les variances sont inégales, utilisez plutôt l’ANOVA de Welch, disponible dans la bibliothèque pingouin ou calculable avec 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 non paramétrique
Lorsque les hypothèses de l’ANOVA ne sont pas respectées (distributions non normales, petits échantillons, variances inégales), le test de Kruskal-Wallis constitue l’alternative non paramétrique. Il vérifie si les distributions des groupes diffèrent en convertissant les données en rangs et en comparant les sommes des rangs. Utilisez scipy.stats.kruskal(*groups). L’hypothèse nulle stipule que tous les groupes ont la même distribution (ce qui revient à tester l’égalité des médianes lorsque les distributions ont la même forme). Ce test est toujours valide, mais moins puissant que l’ANOVA lorsque ses hypothèses sont respectées.
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')Tests postérieurs : pourquoi sont-ils nécessaires
Une ANOVA significative vous indique qu’au moins une moyenne de groupe est différente, mais pas quelles paires diffèrent. Les tests postérieurs effectuent toutes les comparaisons deux à deux en corrigeant les comparaisons multiples. Le plus répandu est la différence honnêtement significative de Tukey (Tukey HSD), qui contrôle exactement le taux d’erreur familial à α. Elle est disponible dans statsmodels.stats.multicomp.pairwise_tukeyhsd(). Effectuez les tests postérieurs uniquement après une ANOVA significative — et non pour rechercher au hasard des résultats parmi des résultats non significatifs.
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))Lire les résultats de Tukey HSD
Le tableau Tukey HSD contient une ligne par paire de groupes. Les colonnes à examiner sont : meandiff (différence entre les moyennes de la paire), lower et upper (intervalle de confiance à 95 % pour la différence), et reject (True si la paire diffère significativement au seuil α). Un intervalle de confiance qui n’inclut pas zéro signifie que la paire est significativement différente. Les groupes dont les intervalles de confiance se chevauchent ne sont pas significativement différents les uns des autres.
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)Corrections de Bonferroni et de Benjamini-Hochberg
Comme alternative à Tukey HSD, vous pouvez appliquer une correction de Bonferroni aux tests t deux à deux : effectuez tous les tests t, puis ajustez le seuil à α/k, où k est le nombre de comparaisons. Cette méthode est conservative — elle peut ne pas détecter de vraies différences. La correction du taux de fausses découvertes (FDR) de Benjamini-Hochberg est moins conservative : elle contrôle la proportion attendue de fausses découvertes, ce qui permet d’identifier davantage de vrais positifs au prix d’un nombre légèrement supérieur de faux positifs. Utilisez 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}')Taille de l’effet pour l’ANOVA : eta carré
Pour l’ANOVA, la mesure analogue au d de Cohen est l’eta carré (η²) : la proportion de la variance totale expliquée par l’appartenance à un groupe. η² = SS_between / SS_total. Valeurs indicatives : < 0,01 correspond à un effet faible, 0,06 à un effet moyen et > 0,14 à un effet important. L’eta carré partiel (η²_p) est plus courant dans les publications scientifiques : il divise uniquement par la variance des groupes plus la variance d’erreur, et non par la variance totale. Calculez toujours η² en parallèle de la valeur p de l’ANOVA afin de montrer si l’effet est pertinent en pratique, et pas seulement détectable sur le plan statistique.
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)')Présentation de l’ANOVA à deux facteurs
L’ANOVA à deux facteurs étend l’ANOVA à un facteur à deux facteurs catégoriels étudiés simultanément. Par exemple, elle permet de vérifier si les notes d’examen diffèrent selon la méthode d’enseignement et le niveau d’expérience des étudiants. Elle teste également l’effet d’interaction : l’effet de la méthode d’enseignement dépend-il du niveau d’expérience ? L’ANOVA à deux facteurs s’implémente dans statsmodels avec ols('score ~ C(method) + C(level) + C(method):C(level)', data=df).fit() et anova_lm(model). Elle constitue une base pour des plans expérimentaux plus avancés.
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 à mesures répétées
L’ANOVA à mesures répétées est utilisée lorsque les mêmes sujets sont mesurés plusieurs fois — par exemple pour évaluer le temps de réaction à 0, 30 et 60 minutes après un traitement. Il s’agit de la généralisation à plusieurs groupes du test t apparié. En tenant compte des différences individuelles, elle offre une puissance supérieure à celle de l’ANOVA à un facteur. En Python, utilisez pingouin.rm_anova() ou pg.pairwise_tests() pour l’analyse postérieure. Lorsque la sphéricité n’est pas respectée (ce qui est vérifié avec le test de Mauchly), utilisez des valeurs p corrigées selon 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')Vérification rapide
Vérifiez votre compréhension des concepts d’analyse des données présentés dans cette leçon.
Récapitulatif de la leçon
Dans cette leçon, vous avez appris que scipy.stats.f_oneway() vérifie si des groupes diffèrent par leur moyenne tout en contrôlant le taux global d’erreur de type I, que les tests postérieurs (Tukey HSD) identifient les paires précises qui diffèrent après une ANOVA significative, et que Kruskal-Wallis constitue l’alternative non paramétrique lorsque les hypothèses de normalité ou d’égalité des variances ne sont pas respectées. Nous allons maintenant commencer le projet de synthèse, qui intégrera toutes ces compétences dans un pipeline de données de bout en bout.
Apprends Python avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 30
- Leçons
- 120
Questions Fréquemment Posées
La leçon « ANOVA et tests post-hoc » est-elle gratuite ?
Oui — le texte complet de « ANOVA et tests post-hoc » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Pandas & NumPy Academy, passe à CoddyKit PRO. Le cours Pandas & NumPy Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « ANOVA et tests post-hoc » ?
Comparez les moyennes de trois groupes ou plus avec une ANOVA à un facteur et exécutez Tukey HSD pour déterminer quelles paires diffèrent. Tu pratiques Pandas & NumPy Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Pandas & NumPy Academy ?
Aucune expérience préalable n'est requise. Pandas & NumPy Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « ANOVA et tests post-hoc » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Pandas & NumPy Academy ?
Oui. Chaque leçon Pandas & NumPy Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Statistiques descriptives et tests de normalité
- Tests t pour comparer des moyennes
- Test du khi carré d’indépendance
- ANOVA et tests post-hoc