ANOVA en post-hoc-toetsen
Vergelijk gemiddelden van drie of meer groepen met een eenweg-ANOVA en voer Tukey HSD uit om te bepalen welke paren van elkaar verschillen.
ANOVA en post-hoc-toetsen is een gratis Pandas & NumPy Academy-les op CoddyKit. Dit is les 4 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Pandas & NumPy Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Pandas & NumPy Academy bevat in totaal 4 lessen.
Waarom niet meerdere t-toetsen?
Wanneer je gemiddelden van drie of meer groepen vergelijkt, verhoogt het uitvoeren van meerdere t-toetsen het Type I-foutpercentage (percentage fout-positieven). Als je groep A versus B, A versus C en B versus C elk toetst met α=0,05, is de totale kans op een fout-positief resultaat ongeveer 14%, niet 5%. Analysis of Variance (ANOVA) lost dit op door alle groepen gelijktijdig in één toets te testen, waarbij het percentage fout-positieven precies op α blijft. Eén ANOVA vervangt alle paarsgewijze t-toetsen voor de overkoepelende vraag: 'Verschilt een van de groepen?'
Eenweg-ANOVA: het grote geheel
Met eenweg-ANOVA toets je of de gemiddelden van drie of meer groepen significant van elkaar verschillen. De totale variantie wordt opgesplitst in variantie tussen groepen (verklaard door het groepslidmaatschap) en variantie binnen groepen (willekeurige ruis). De F-statistiek is hun verhouding: F = (variantie tussen groepen) / (variantie binnen groepen). Een grote F betekent dat de groepsverschillen groot zijn in verhouding tot de ruis, wat een kleine p-waarde oplevert. De nulhypothese is H₀: alle groepsgemiddelden zijn gelijk.
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 met Pandas DataFrames
In de praktijk staan je gegevens in een DataFrame in lang formaat, waarin één kolom het groepslabel bevat en een andere kolom de meting. Haal elke groep op als een Series en geef deze door aan stats.f_oneway(). Als alternatief kun je bij een DataFrame in breed formaat (één kolom per groep) elke kolom rechtstreeks doorgeven. De functie accepteert een willekeurig aantal positionele argumenten, waardoor je eenvoudig 4, 5 of meer groepen kunt toevoegen.
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))Aannames van ANOVA
Eenweg-ANOVA gaat uit van: (1) Onafhankelijkheid — waarnemingen zijn onafhankelijk van elkaar; (2) Normaliteit — residuen zijn binnen elke groep ongeveer normaal verdeeld (bij grote steekproeven robuust dankzij de centrale limietstelling); (3) Gelijke varianties — varianties zijn gelijk voor alle groepen. Toets de aanname over varianties met de toets van Levene (stats.levene(*groups)). Als varianties ongelijk zijn, gebruik je in plaats daarvan Welch-ANOVA, die beschikbaar is in de bibliotheek pingouin of via statsmodels kan worden berekend.
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: niet-parametrische ANOVA
Wanneer de aannames van ANOVA worden geschonden (niet-normale verdelingen, kleine steekproeven, ongelijke varianties), is de Kruskal-Wallis-toets het niet-parametrische alternatief. Deze toetst of de verdelingen van groepen verschillen door gegevens om te zetten in rangnummers en de sommen van de rangnummers te vergelijken. Gebruik scipy.stats.kruskal(*groups). De nulhypothese stelt dat alle groepen dezelfde verdeling hebben (gelijk aan het toetsen van gelijke medianen wanneer de verdelingen dezelfde vorm hebben). De toets is altijd geldig, maar minder krachtig dan ANOVA wanneer aan de aannames wordt voldaan.
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')Post-hoc-toetsen: waarom je ze nodig hebt
Een significante ANOVA vertelt je dat het gemiddelde van minstens één groep verschilt, maar niet welke paren verschillen. Met post-hoc-toetsen voer je alle paarsgewijze vergelijkingen uit, waarbij je corrigeert voor meervoudig toetsen. De populairste toets is Tukey's Honest Significant Difference (Tukey HSD), die het family-wise error rate precies op α houdt. Deze is beschikbaar in statsmodels.stats.multicomp.pairwise_tukeyhsd(). Voer post-hoc-toetsen alleen uit na een significante ANOVA — niet als een zoektocht naar toevallige resultaten bij niet-significante uitkomsten.
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))Tukey HSD-uitvoer lezen
De Tukey HSD-tabel bevat één rij per paar groepen. De kolommen waarop je je richt zijn: meandiff (het gemiddelde verschil tussen het paar), lower en upper (het 95%-betrouwbaarheidsinterval voor het verschil), en reject (True als het paar significant verschilt bij α). Een betrouwbaarheidsinterval dat nul niet bevat, betekent dat het paar significant verschilt. Groepen waarvan de betrouwbaarheidsintervallen overlappen, verschillen niet significant van elkaar.
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)Bonferroni- en Benjamini-Hochberg-correcties
Als alternatief voor Tukey HSD kun je een Bonferroni-correctie toepassen op paarsgewijze t-toetsen: voer alle t-toetsen uit en pas daarna de drempel aan naar α/k, waarbij k het aantal vergelijkingen is. Dit is conservatief — echte verschillen kunnen daardoor onopgemerkt blijven. De Benjamini-Hochberg-correctie voor het False Discovery Rate (FDR) is minder conservatief: deze beheerst het verwachte aandeel foutieve ontdekkingen, waardoor meer echte positieven worden toegestaan ten koste van iets meer fout-positieven. Gebruik 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}')Effectgrootte voor ANOVA: eta-kwadraat
Voor ANOVA is de vergelijkbare maatstaf voor Cohen's d het eta-kwadraat (η²): het aandeel van de totale variantie dat door het groepslidmaatschap wordt verklaard. η² = SS_between / SS_total. Waarden: < 0,01 is klein, 0,06 is middelgroot en > 0,14 is groot. Partieel eta-kwadraat (η²_p) komt vaker voor in gepubliceerd onderzoek: dit deelt alleen door de groepsvariantie plus de foutvariantie, niet door de totale variantie. Bereken η² altijd naast de p-waarde van ANOVA om te laten zien of het effect praktisch betekenisvol is, en niet alleen statistisch aantoonbaar.
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)')Overzicht van tweeweg-ANOVA
Tweeweg-ANOVA breidt eenweg-ANOVA uit naar twee categorische factoren tegelijk. Je kunt bijvoorbeeld toetsen of examenscores verschillen op basis van zowel de lesmethode als het ervaringsniveau van studenten. Ook toets je het interactie-effect: hangt het effect van de lesmethode af van het ervaringsniveau? Tweeweg-ANOVA wordt in statsmodels geïmplementeerd via ols('score ~ C(method) + C(level) + C(method):C(level)', data=df).fit() en anova_lm(model). Dit vormt de basis voor geavanceerdere experimentele ontwerpen.
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 met herhaalde metingen
ANOVA met herhaalde metingen gebruik je wanneer dezelfde proefpersonen meerdere keren worden gemeten — bijvoorbeeld om de reactietijd te testen op tijdstippen 0, 30 en 60 minuten na een behandeling. Dit is de generalisatie naar meerdere groepen van de gepaarde t-toets. Doordat individuele verschillen worden meegerekend, heeft deze toets meer power dan eenweg-ANOVA. Gebruik in Python pingouin.rm_anova() of pg.pairwise_tests() voor de post-hocanalyse. Wanneer aan sfericiteit niet wordt voldaan (getest met de toets van Mauchly), gebruik je p-waarden met de correctie van 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')Korte controle
Test je begrip van de concepten voor data-analyse uit deze les.
Samenvatting van de les
In deze les heb je geleerd dat scipy.stats.f_oneway() toetst of gemiddelden van groepen verschillen en daarbij het totale Type I-foutpercentage beheerst, dat post-hoc-toetsen (Tukey HSD) na een significante ANOVA aangeven welke specifieke paren verschillen, en dat Kruskal-Wallis het niet-parametrische alternatief is wanneer niet aan de aannames van normaliteit of gelijke varianties wordt voldaan. Hierna begin je aan het eindproject, waarin je alle vaardigheden samenbrengt in een end-to-end-datapijplijn.
Leer Python met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 30
- Lessen
- 120
Veelgestelde vragen
Is de les “ANOVA en post-hoc-toetsen” gratis?
Ja — de volledige tekst van “ANOVA en post-hoc-toetsen” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Pandas & NumPy Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Pandas & NumPy Academy bevat in totaal 4 lessen.
Wat leer ik in “ANOVA en post-hoc-toetsen”?
Vergelijk gemiddelden van drie of meer groepen met een eenweg-ANOVA en voer Tukey HSD uit om te bepalen welke paren van elkaar verschillen. Je oefent met Pandas & NumPy Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Pandas & NumPy Academy te beginnen?
Ervaring vooraf is niet nodig. Pandas & NumPy Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.
Hoe lang duurt de les “ANOVA en post-hoc-toetsen”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Pandas & NumPy Academy?
Ja. Elke les over Pandas & NumPy Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Beschrijvende statistiek en normaliteitstoetsen
- T-toetsen voor het vergelijken van gemiddelden
- Chi-kwadraattoets voor onafhankelijkheid
- ANOVA en post-hoc-toetsen