ANOVA ja post hoc -testit
Vertaa vähintään kolmen ryhmän keskiarvoja yksisuuntaisella ANOVA-testillä ja suorita Tukey HSD -testi selvittääksesi, mitkä parit eroavat toisistaan.
ANOVA ja post hoc -testit on ilmainen Pandas & NumPy Academy-oppitunti CoddyKitissä. Tämä on oppitunti 4/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Pandas & NumPy Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Pandas & NumPy Academy-kurssilla on yhteensä 4 oppituntia.
Miksi ei useita t-testejä?
Kun keskiarvoja verrataan kolmessa tai useammassa ryhmässä, useiden t-testien suorittaminen kasvattaa tyypin I virhetasoa (väärien positiivisten tulosten osuutta). Kun ryhmiä A ja B, A ja C sekä B ja C testataan kullakin arvolla α=0.05, kokonaisriski väärälle positiiviselle tulokselle on noin 14 %, ei 5 %. Varianssianalyysi (ANOVA) ratkaisee tämän testaamalla kaikki ryhmät samanaikaisesti yhdessä testissä, jolloin väärien positiivisten tulosten osuus pysyy täsmälleen arvossa α. Yksi ANOVA-testi korvaa kaikki parittaiset t-testit omnibus-kysymyksessä: ”Eroavatko jotkin ryhmät toisistaan?”
Yksisuuntainen ANOVA: kokonaiskuva
Yksisuuntainen ANOVA testaa, eroavatko kolmen tai useamman ryhmän keskiarvot toisistaan tilastollisesti merkitsevästi. Se jakaa kokonaisvaihtelun ryhmien väliseen vaihteluun (jonka ryhmäjäsenyys selittää) ja ryhmien sisäiseen vaihteluun (satunnaiseen kohinaan). F-statistik on näiden suhde: F = (ryhmien välinen vaihtelu) / (ryhmien sisäinen vaihtelu). Suuri F-arvo tarkoittaa, että ryhmien väliset erot ovat suuria kohinaan verrattuna, mikä tuottaa pienen p-arvon. Nollahypoteesi on H₀: kaikkien ryhmien keskiarvot ovat yhtä suuret.
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 Pandasin DataFrame-kehysten kanssa
Käytännössä data on pitkän muodon DataFrame-kehyksessä, jossa yksi sarake sisältää ryhmätunnisteen ja toinen mittaustuloksen. Poimikaa kukin ryhmä Series-objektiksi ja välittäkää ne funktiolle stats.f_oneway(). Jos käytössänne on vaihtoehtoisesti leveän muodon DataFrame-kehys (yksi sarake ryhmää kohden), välittäkää kukin sarake suoraan. Funktio hyväksyy kuinka monta sijaintiparametria tahansa, joten sen laajentaminen neljään, viiteen tai useampaan ryhmään on helppoa.
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))ANOVAn oletukset
Yksisuuntainen ANOVA olettaa, että: (1) riippumattomuus — havainnot ovat toisistaan riippumattomia; (2) normaalisuus — jäännökset ovat kussakin ryhmässä likimäärin normaalijakautuneita (suurilla otoksilla menetelmä on keskeisen raja-arvolauseen ansiosta robusti); (3) homoskedastisuus — ryhmien varianssit ovat yhtä suuret. Testatkaa varianssioletus Levenen testillä (stats.levene(*groups)). Jos varianssit eivät ole yhtä suuret, käyttäkää sen sijaan Welchin ANOVAa, joka on saatavilla pingouin-kirjastossa tai jonka voi laskea statsmodelsilla.
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: ei-parametrinen ANOVA
Kun ANOVAn oletukset eivät täyty (jakaumat eivät ole normaaleja, otokset ovat pieniä tai varianssit ovat erisuuret), Kruskal–Wallisin testi on ei-parametrinen vaihtoehto. Se testaa, eroavatko ryhmien jakaumat toisistaan muuttamalla datan järjestysluvuiksi ja vertaamalla järjestyslukujen summia. Käyttäkää funktiota scipy.stats.kruskal(*groups). Nollahypoteesi on, että kaikilla ryhmillä on sama jakauma (tämä vastaa yhtä suurten mediaanien testaamista, kun jakaumien muoto on sama). Testi on aina pätevä, mutta oletusten täyttyessä vähemmän tehokas kuin ANOVA.
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')Jälkitestit: miksi niitä tarvitaan
Tilastollisesti merkitsevä ANOVA kertoo, että vähintään yhden ryhmän keskiarvo eroaa muista, mutta ei sitä, mitkä parit eroavat toisistaan. Jälkitesteillä tehdään kaikki parivertailut ja korjataan samalla monivertailun vaikutus. Suosituin menetelmä on Tukeyn rehellisesti merkitsevä ero (Tukey HSD), joka pitää perhekohtaisen virhetason täsmälleen arvossa α. Se on saatavilla funktiolla statsmodels.stats.multicomp.pairwise_tukeyhsd(). Suorittakaa jälkitestit vasta tilastollisesti merkitsevän ANOVAn jälkeen — älkää käyttäkö niitä merkityksettömien tulosten perusteella tehtävään päämäärättömään etsintään.
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 -tulosten tulkitseminen
Tukey HSD -taulukossa on yksi rivi kutakin ryhmäparia kohden. Keskittykää seuraaviin sarakkeisiin: meandiff (parin välinen keskiarvojen ero), lower ja upper (eron 95 %:n luottamusvälin ala- ja yläraja) sekä reject (True, jos pari eroaa tilastollisesti merkitsevästi tasolla α). Jos luottamusväli ei sisällä nollaa, pari eroaa tilastollisesti merkitsevästi. Ryhmät, joiden luottamusvälit menevät päällekkäin, eivät eroa toisistaan tilastollisesti merkitsevästi.
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- ja Benjamini–Hochberg-korjaukset
Vaihtoehtona Tukey HSD:lle parittaisiin t-testeihin voi soveltaa Bonferroni-korjausta: suorittakaa kaikki t-testit ja säätäkää sen jälkeen raja-arvoksi α/k, jossa k on vertailujen määrä. Menetelmä on konservatiivinen, joten se saattaa jättää todellisia eroja havaitsematta. Benjamini–Hochbergin väärien löytöjen osuuden (FDR) korjaus on vähemmän konservatiivinen: se rajoittaa odotettua väärien löytöjen osuutta, joten se sallii enemmän oikeita positiivisia tuloksia hieman useampien väärien positiivisten tulosten kustannuksella. Käyttäkää funktiota 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}')ANOVAn efektikoko: eta-neliö
ANOVAssa Cohenin d:tä vastaava mitta on eta-neliö (η²): ryhmäjäsenyyden selittämän kokonaisvaihtelun osuus. η² = SS_between / SS_total. Arvot: < 0.01 on pieni, 0.06 keskisuuri ja > 0.14 suuri. Osittainen eta-neliö (η²_p) on julkaistussa tutkimuksessa yleisempi: siinä jakajana käytetään vain ryhmävaihtelun ja virhevaihtelun summaa, ei kokonaisvaihtelua. Laskekaa η² aina ANOVAn p-arvon rinnalla, jotta voitte osoittaa, onko vaikutus käytännössä merkityksellinen eikä vain tilastollisesti havaittavissa.
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)')Kaksisuuntaisen ANOVAn yleiskatsaus
Kaksisuuntainen ANOVA laajentaa yksisuuntaisen ANOVAn koskemaan samanaikaisesti kahta kategorista tekijää. Sen avulla voidaan esimerkiksi testata, eroavatko koepisteet samanaikaisesti sekä opetusmenetelmän että opiskelijan kokemustason mukaan. Lisäksi se testaa yhdysvaikutusta: riippuuko opetusmenetelmän vaikutus kokemustasosta? Kaksisuuntainen ANOVA toteutetaan statsmodelsilla käyttämällä funktioita ols('score ~ C(method) + C(level) + C(method):C(level)', data=df).fit() ja anova_lm(model). Tämä muodostaa perustan kehittyneemmälle koeasetelmalle.
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))Toistettujen mittausten ANOVA
Toistettujen mittausten ANOVAlla analysoidaan tilanteita, joissa samoja koehenkilöitä mitataan useita kertoja — esimerkiksi reaktioaikaa ajanhetkillä 0, 30 ja 60 minuuttia hoidon jälkeen. Se on parittaisen t-testin usean ryhmän yleistys. Kun yksilöiden väliset erot otetaan huomioon, menetelmällä on enemmän tilastollista voimaa kuin yksisuuntaisella ANOVAlla. Käyttäkää Pythonissa funktiota pingouin.rm_anova() tai jälkitesteihin funktiota pg.pairwise_tests(). Jos sferisiteettioletus ei täyty (mikä testataan Mauchlyn testillä), käyttäkää Greenhouse–Geisser-korjattuja p-arvoja.
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')Pikatarkistus
Testatkaa, miten hyvin ymmärrätte tämän oppitunnin data-analyysin käsitteet.
Oppitunnin yhteenveto
Tässä oppitunnissa opitte, että scipy.stats.f_oneway() testaa, eroavatko jotkin ryhmät keskiarvoltaan, ja hallitsee samalla tyypin I virhetasoa; jälkitestit (Tukey HSD) tunnistavat, mitkä parit eroavat toisistaan tilastollisesti merkitsevän ANOVAn jälkeen; ja Kruskal–Wallisin testi on ei-parametrinen vaihtoehto, kun normaalisuus- tai yhtä suurten varianssien oletus ei täyty. Seuraavaksi aloitamme lopputyöprojektin, jossa yhdistämme kaikki taidot päästä päähän ulottuvaksi dataputkeksi.
Opi Python tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 30
- Oppitunnit
- 120
Usein kysytyt kysymykset
Onko oppitunti ”ANOVA ja post hoc -testit” ilmainen?
Kyllä – oppitunnin ”ANOVA ja post hoc -testit” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Pandas & NumPy Academy-kurssin, päivitä CoddyKit PROhon. Pandas & NumPy Academy-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”ANOVA ja post hoc -testit”?
Vertaa vähintään kolmen ryhmän keskiarvoja yksisuuntaisella ANOVA-testillä ja suorita Tukey HSD -testi selvittääksesi, mitkä parit eroavat toisistaan. Harjoittelet Pandas & NumPy Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Pandas & NumPy Academy-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Pandas & NumPy Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 4/4.
Kuinka kauan ”ANOVA ja post hoc -testit”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Pandas & NumPy Academy-oppitunnilla?
Kyllä. Jokainen Pandas & NumPy Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- Kuvailevat tunnusluvut ja normaalisuustestaus
- Keskiarvojen vertailu t-testeillä
- Riippumattomuuden khiin neliö -testi
- ANOVA ja post hoc -testit