Ga naar hoofdinhoud

Fisher’s exact-toets: beslissingen nemen met kleine steekproeven

Leer categorische relaties te analyseren wanneer je steekproeven klein zijn.
· 9 min lezen

Verken met AI

Organisaties nemen elke dag beslissingen op basis van hoe klanten zich gedragen, hoe producten presteren en hoe operaties fluctueren. Om ervoor te zorgen dat deze beslissingen zijn gebaseerd op echte onderliggende relaties en patronen in data, gebruiken analisten vaak statistische toetsen. 

Hoewel gangbare statistische toetsen goed werken voor grote datasets, hebben bedrijven niet altijd het voorrecht om met grote datasets te werken. Sterker nog, veel beslissingen moeten worden genomen met kleine steekproeven, nicheklantsegmenten of zeldzame gebeurtenissen. In dit soort situaties kunnen traditionele toetsen, zoals chi-kwadraat, tekortschieten: ze leveren misleidende resultaten op of missen de precisie om met vertrouwen op de inzichten te handelen. 

Daar komt Fisher’s exact-toets van pas. Lees verder en ik laat je zien hoe.

Wat is Fisher's exact-toets?

Fisher’s exact-toets wordt gebruikt om de associatie tussen categorische variabelen te beoordelen wanneer de data beperkt is. De toets wordt het vaakst toegepast op 2×2-contingentietabellen en berekent de exacte kans om data te observeren die minstens zo extreem zijn als wat is verzameld, uitgaande van de nulhypothese van onafhankelijkheid.

Een analist kan Fisher’s exact-toets bijvoorbeeld gebruiken bij het evalueren van de effectiviteit van een gerichte marketingcampagne. Als de analist bijvoorbeeld de effectiviteit van een e-mail- versus socialmediacampagne test, en dit is voor een nicheklantgroep met kleine steekproeven, dan kan hij of zij een contingentietabel maken en Fisher’s exact-toets uitvoeren. Of, in een kwaliteitscontrolemilieu, kan een analist de toets gebruiken om uitdagingen rond incidentrapportage en het bijhouden van defecten aan te pakken. Wanneer bepaalde faaltypen maar een paar keer voorkomen, kan Fisher’s exact-toets beoordelen of factoren zoals het type machine daadwerkelijk samenhangen met het optreden van defecten.

Statistische theorie en wiskundige basis

Nu we een intuïtief gevoel hebben ontwikkeld voor waarom de toets belangrijk is, kunnen we die formaliseren. In de kern is Fisher’s exact-toets een statistische methode om te bepalen of er een niet-willekeurige associatie bestaat tussen twee categorische variabelen. De toets wordt het meest toegepast op 2×2-contingentietabellen om twee groepen over twee uitkomsten te vergelijken, bijvoorbeeld “behandeling versus controle” tegenover “verbeterd versus niet verbeterd”.

Interessant genoeg gaat de oorsprong terug naar een van de bekendste verhalen in de statistiek, het “lady tasting tea”-experiment. Sir Ronald A. Fisher ontwierp een experiment om de claim van dr. Muriel Bristol te evalueren dat zij puur op smaak kon onderscheiden of eerst melk of thee in een kopje was geschonken. Als onderdeel van het experiment rangschikte Fisher acht kopjes en vroeg haar te identificeren welke “eerst melk” en welke “eerst thee” waren. Vanwege de kleine steekproefgrootte ontwikkelde Fisher een exacte methode om te bepalen of het patroon van juiste antwoorden toevallig had kunnen optreden, wat de basis legde voor wat we nu kennen als Fisher’s exact-toets.

In situaties waarin data beperkt, onevenwichtig of moeilijk te verkrijgen is, zoals in klinisch onderzoek, helpt Fisher’s toets te bepalen of behandelingen en uitkomsten op een betekenisvolle manier samenhangen. Ecologie, epidemiologie, genetica en kwaliteitscontrole zijn enkele vakgebieden met schaarse data die op deze toets steunen.

Fisher’s exact-toets beoordeelt onafhankelijkheid door exacte hypergeometrische kansen te berekenen. Voor een 2×2-tabel:

 

Groep B1

Groep B2

Totaal

Groep A1

a

b

a+b

Groep A2

c

d

c+d

Totaal

a+c

b+d

n

De kans om precies die configuratie te observeren onder de nulhypothese (geen associatie) wordt berekend met de hypergeometrische verdeling:

Toetsen kunnen eenzijdig zijn, zoals de vraag “Komt A1 vaker voor in B1 dan in B2?”, of tweezijdig: “Is er überhaupt een associatie?”. In tegenstelling tot benaderingen voor grote steekproeven (bijv. chi-kwadraat) waarborgt Fisher’s methode nauwkeurigheid, zelfs wanneer de verwachte frequenties laag zijn.

Aannames en randvoorwaarden

Om Fisher’s exact-toets toe te passen, moeten de volgende voorwaarden gelden.

  • Data moeten categorisch zijn
  • Weergegeven in een 2×2-contingentietabel
  • Observaties zijn onafhankelijk
  • Marges (rij- en kolomsommen) zijn vast onder de nulhypothese

Fisher’s toets heeft de voorkeur wanneer een of meer verwachte aantallen in het raster erg laag zijn (minder dan 5), of de resultaten schaars of sterk uit balans zijn.

Fisher's exact-toets in Python

Stel dat we toetsen of een medicijn herstel verbetert:

 

Hersteld

Niet hersteld

Totaal

Behandeling

8

2

10

Controle

4

11

15

Totaal

12

13

25

Dit zijn de hypotheses:

  • H₀: Behandeling en herstel zijn onafhankelijk
  • H₁: Behandeling verbetert herstel (eenzijdig)

En zo zou ik dit in Python aanpakken:

from scipy.stats import fisher_exact

# Construct contingency table
table = [[8, 2],
         [4, 11]]

oddsratio, p_value = fisher_exact(table, alternative='two-sided')
print("Odds Ratio:", oddsratio)
print("Two-sided p-value:", round(p_value, 2))

# One-sided test (treatment better)
_, p_value_one = fisher_exact(table, alternative='greater')
print("One-sided p-value:", round(p_value_one, 2))
Odds Ratio: 11.0
Two-sided p-value: 0.02
One-sided p-value: 0.01

Een kleine p-waarde van 0,02 suggereert een echte associatie tussen behandeling en herstel.

Laten we nu een voorbeeld uit de praktijk nemen met openbare data van NHANES 2017–2018 om de associatie te onderzoeken tussen “huidige rookstatus” en “ooit te horen gekregen dat je astma hebt” bij volwassenen.

Gebruikte bestanden (allemaal van de officiële NHANES-site):

  • DEMO_J.XPT — Demografie (leeftijd, geslacht, wegingen, enz.)

  • SMQ_J.XPT — Roken - sigarettengebruik (rookgeschiedenis & huidig gebruik)

  • MCQ_J.XPT — Medische aandoeningen (inclusief "ooit is gezegd dat je astma hebt")

Hieronder staan de stappen die we gaan volgen:

  1. Downloaden en laden van de datasets

  2. Binaire variabelen maken:

    1. current_smoker (ja/nee)

    2. asthma_ever (ja/nee)

  3. Een 2×2-contingentietabel opbouwen

  4. Fisher’s exact-toets uitvoeren (tweezijdig en eenzijdig)

  5. Een 95%-betrouwbaarheidsinterval voor de oddsratio berekenen

  6. De resultaten interpreteren

Let op: Een correcte NHANES-analyse vereist steekproefwegingen en een complex design.

Hier negeren we wegingen voor didactische doeleinden. Laten we de vereiste libraries importeren.

# %% Imports
import pandas as pd
import numpy as np
from scipy.stats import fisher_exact
from statsmodels.stats.contingency_tables import Table2x2

Laten we de XPT-bestanden handmatig downloaden van de CDC en in een lokale map data/ plaatsen:

  • Ga naar:

    • Demografie: 2017–2018 Demografie Data (DEMO_J)

    • Medische aandoeningen: MCQ_J

    • Roken - sigarettengebruik: SMQ_J

  • Download:

    • DEMO_J Data [XPT]

    • MCQ_J Data [XPT]

    • SMQ_J Data [XPT]

  • Sla ze op als:

    • data/DEMO_J.XPT

    • data/MCQ_J.XPT

    • data/SMQ_J.XPT

# Set paths or URLs here.
# If you've downloaded locally, point to the local paths:
DEMO_PATH = "data/DEMO_J.XPT"
MCQ_PATH  = "data/MCQ_J.XPT"
SMQ_PATH  = "data/SMQ_J.XPT"

# If you prefer to read directly from URLs, uncomment these instead:
# DEMO_PATH = "https://wwwn.cdc.gov/Nchs/NHanes/2017-2018/DEMO_J.XPT"
# MCQ_PATH  = "https://wwwn.cdc.gov/Nchs/NHanes/2017-2018/MCQ_J.XPT"
# SMQ_PATH  = "https://wwwn.cdc.gov/Nchs/NHanes/2017-2018/SMQ_J.XPT"

# Read SAS transport (.XPT) files
demo = pd.read_sas(DEMO_PATH, format="xport")
mcq  = pd.read_sas(MCQ_PATH,  format="xport")
smq  = pd.read_sas(SMQ_PATH,  format="xport")
demo.head()

mcq.head()

smq.head()

Laten we enkele sleutelfeatures in deze DataFrames bekijken. Hier is SEQN het respondent-ID en te gebruiken als joinsleutel.

# %% Inspect key variables

# SEQN is the respondent ID present in all three files.
# Check columns and pick what we need:
print("DEMO columns (subset):", demo.columns[:10])
print("MCQ columns (subset):", mcq.columns[:10])
print("SMQ columns (subset):", smq.columns[:10])
DEMO columns (subset): Index(['SEQN', 'SDDSRVYR', 'RIDSTATR', 'RIAGENDR', 'RIDAGEYR', 'RIDAGEMN',
       'RIDRETH1', 'RIDRETH3', 'RIDEXMON', 'RIDEXAGM'],
      dtype='object')
MCQ columns (subset): Index(['SEQN', 'MCQ010', 'MCQ025', 'MCQ035', 'MCQ040', 'MCQ050', 'AGQ030',
       'MCQ053', 'MCQ080', 'MCQ092'],
      dtype='object')
SMQ columns (subset): Index(['SEQN', 'SMQ020', 'SMD030', 'SMQ040', 'SMQ050Q', 'SMQ050U', 'SMD057',
       'SMQ078', 'SMD641', 'SMD650'],
      dtype='object')

Laten we filteren op de vereiste kolommen en ook filteren op leeftijd (ouder dan 20). 

# %% Keep only necessary variables and filter adults

demo_sub = demo[["SEQN", "RIDAGEYR", "RIAGENDR"]].copy()
mcq_sub  = mcq[["SEQN", "MCQ010"]].copy()
smq_sub  = smq[["SEQN", "SMQ040"]].copy()

# Merge on SEQN (inner join: keep only participants present in all three files)
df = (
    demo_sub
    .merge(mcq_sub, on="SEQN", how="inner")
    .merge(smq_sub, on="SEQN", how="inner")
)
# Restrict to adults aged 20+
df = df[df["RIDAGEYR"] >= 20].copy()
df.head()

MCQ010 vertegenwoordigt respondenten met astma, waarbij de waarde 1 staat voor Ja en 2 voor Nee. Op vergelijkbare wijze geeft SMQ040 aan of de respondent rookt of niet. 

# %% Recode variables into binary indicators

# 1. Asthma: MCQ010
#    1 = Yes, 2 = No, 7 = Refused, 9 = Don't know
#    We'll create asthma_ever: 1 = yes, 0 = no, NaN otherwise

df["asthma_ever"] = np.nan
df.loc[df["MCQ010"] == 1, "asthma_ever"] = 1
df.loc[df["MCQ010"] == 2, "asthma_ever"] = 0

# 2. Current smoker: SMQ040
#    1 = Every day, 2 = Some days, 3 = Not at all, 7/9 = missing
#    We'll create current_smoker: 1 = yes (1 or 2), 0 = no (3), NaN otherwise

df["current_smoker"] = np.nan
df.loc[df["SMQ040"].isin([1, 2]), "current_smoker"] = 1
df.loc[df["SMQ040"] == 3, "current_smoker"] = 0

# Drop rows with missing recodes
df_clean = df.dropna(subset=["asthma_ever", "current_smoker"]).copy()
df_clean[["RIDAGEYR", "MCQ010", "asthma_ever", "SMQ040", "current_smoker"]].head()

We maken nieuwe kolommen, asthma_ever en current_smoker

Laten we de contingentietabel maken zodat we Fisher’s exact-toets kunnen gebruiken.

# %% Build a 2×2 contingency table

# Ensure ints
df_clean["asthma_ever"] = df_clean["asthma_ever"].astype(int)
df_clean["current_smoker"] = df_clean["current_smoker"].astype(int)

contingency = pd.crosstab(
    df_clean["current_smoker"],
    df_clean["asthma_ever"],
    rownames=["Current smoker (1=yes,0=no)"],
    colnames=["Ever told asthma (1=yes,0=no)"]
)
contingency

Zet de contingentietabel om naar een NumPy-array.

# %% Fisher's Exact Test

# Convert to 2x2 numpy array with explicit ordering:
# rows: [non-smoker, smoker] ; cols: [no asthma, asthma]
table = np.array([
    [contingency.loc[0, 0], contingency.loc[0, 1]],
    [contingency.loc[1, 0], contingency.loc[1, 1]]
])

table
array([[1102,  226],
       [ 832,  172]])

Voer tot slot tweezijdige en eenzijdige Fisher’s exact-toetsen uit.

# Run Fisher's exact test (two-sided and one-sided)
oddsratio, p_two_sided = fisher_exact(table, alternative="two-sided")
_, p_smoker_higher_asthma = fisher_exact(table, alternative="greater")
_, p_smoker_lower_asthma  = fisher_exact(table, alternative="less")

print("2x2 table (rows: non-smoker, smoker; cols: no asthma, asthma)")
print(table)
print()
print(f"Odds ratio (Fisher): {oddsratio:.3f}")
print(f"Two-sided p-value: {p_two_sided:.5f}")
print(f"One-sided p-value (H1: smokers have higher asthma odds): {p_smoker_higher_asthma:.5f}")
print(f"One-sided p-value (H1: smokers have lower asthma odds): {p_smoker_lower_asthma:.5f}")
2x2 table (rows: non-smoker, smoker; cols: no asthma, asthma)
[[1102  226]
 [ 832  172]]

Odds ratio (Fisher): 1.008
Two-sided p-value: 0.95571
One-sided p-value (H1: smokers have higher asthma odds): 0.49274
One-sided p-value (H1: smokers have lower asthma odds): 0.55144

Hoge p-waarden laten zien dat er in deze steekproef geen bewijs is voor een statistisch significante associatie tussen momenteel roken en ooit de diagnose astma hebben gekregen.

De oddsratio ~1 betekent dat huidige rokers vrijwel dezelfde kans hebben om een astmadiagnose te rapporteren als niet-rokers. Elk waargenomen verschil valt ruim binnen wat door toeval kan optreden.

Vergelijking met alternatieve statistische methoden

Verschillende statistische toetsen worden gebruikt om relaties in contingentietabellen te analyseren, afhankelijk van steekproefgrootte, datastructuur en aannames:

Fisher’s exact-toets

Het beste voor kleine steekproeven en wanneer sommige verwachte celvullingen laag zijn. Levert exacte p-waarden en is daardoor zeer betrouwbaar, maar kan computationeel traag worden naarmate tabellen groter worden.

Chi-kwadraattoets

De standaardkeuze voor grote steekproeven. Snel, gangbaar en eenvoudig toe te passen, maar wordt onnauwkeurig wanneer verwachte aantallen onder 5 vallen door de afhankelijkheid van benaderingen.

Barnard’s exact-toets

Een krachtiger alternatief voor Fisher’s toets bij 2×2-tabellen. Veronderstelt geen vaste marges, wat de resultaten gevoeliger kan maken, maar de methode is minder breed geïmplementeerd in software.

McNemar-toets

Ontworpen voor gepaarde of gematchte categorische data (bijv. vóór- versus nabehandeling bij dezelfde proefpersonen). Houdt rekening met afhankelijkheid tussen observaties, maar is niet te gebruiken wanneer datapunten onafhankelijk zijn.

Methode

Wanneer te gebruiken

Sterke punten

Beperkingen

Fisher’s exact-toets

Kleine steekproeven, lage verwachte aantallen

Exacte p-waarden

Conservatief, traag bij grote tabellen

Chi-kwadraattoets

Grote steekproeven

Snel, wijdverbreid

Vertekend bij verwachte aantallen < 5

Barnard’s exact-toets

Alternatief voor Fisher, meer power

Niet beperkt door vaste marges

Minder gangbaar beschikbaar

McNemar-toets

Gepaarde categorische data

Gaat afhankelijkheden aan

Niet voor onafhankelijke steekproeven

Computationele overwegingen

Prestaties kunnen een aandachtspunt zijn bij Fisher’s exact-toets, omdat de berekeningen slecht opschalen buiten eenvoudige 2×2-contingentietabellen. Hoewel de Freeman–Halton-uitbreiding toelaat dat de toets wordt toegepast op 2×3- en 3×3-tabellen, nemen de rekenkosten snel toe naarmate de tabel groter wordt. Daarom zijn efficiënte algoritmen en optimalisaties extra belangrijk in vakgebieden zoals genetica en klinisch onderzoek, waar exact toetsen herhaaldelijk moeten worden uitgevoerd op veel kleine maar complexe datasets.

Interpretatie & statistische rapportage

Bij het interpreteren en rapporteren van resultaten van Fisher’s exact-toets is het belangrijk om de exacte p-waarde te vermelden, de oddsratio te geven met een 95%-betrouwbaarheidsinterval, en duidelijk te maken of een eenzijdige of tweezijdige toets is uitgevoerd. Ondersteun de resultaten ook met praktische context, zodat lezers de betekenis in de echte wereld begrijpen. Bijvoorbeeld: “Fisher’s exact-toets liet een significante associatie zien tussen behandeling en herstel (OR=11,0, p=0,02, tweezijdig).”

Uitbreidingen en geavanceerde toepassingen

Uitbreidingen en geavanceerde toepassingen van Fisher’s exact-toets helpen complexere datasituaties aan te pakken en de nauwkeurigheid te verbeteren bij kleine of schaarse datasets. De Freeman–Halton-uitbreiding maakt toepassing mogelijk op grotere contingentietabellen dan het standaard 2×2-formaat, zoals 2×3- of 3×3-tabellen. De mid-P-exact-toets biedt een minder conservatief alternatief voor Fisher’s toets, wat de statistische power verbetert met behoud van betrouwbaarheid. Bayesiaanse exacte toetsen verwerken voorkennis in de analyse, wat vooral nuttig is wanneer data beperkt is of wanneer deskundig oordeel de inferentie kan sturen. Daarnaast leveren bias-correctiemethoden voor oddsratio’s nauwkeurigere schattingen van effectgrootte in studies met kleine steekproeven of nulcellen, wat zorgt voor een betere interpretatie van de sterkte en richting van associaties.

Beperkingen en methodologische overwegingen

Fisher’s exact-toets kan conservatief zijn, wat de statistische power kan verlagen en het moeilijker kan maken om echte effecten te detecteren, vooral in kleine studies. Wanneer data zeer schaars is, kan interpretatie lastig zijn omdat betrouwbaarheidsintervallen breder worden en schattingen instabiel. Bovendien vergroot het herhaald toepassen van de toets over veel variabelen het risico op valse ontdekkingen, dus zorgvuldige correctie voor multiple testing is belangrijk.

Contingentietabellen visualiseren

Goede visualisaties om resultaten van contingentietabellen te presenteren zijn onder meer gestapelde staafdiagrammen, staafdiagrammen naast elkaar en mozaïekplots. Deze visuals helpen de relatie tussen categorische variabelen helder te communiceren en maken patronen of groepsverschillen makkelijker te interpreteren.

Hier is een Python-voorbeeld:

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "Outcome": ["Recovered", "Not Recovered"] * 2,
    "Count": [8, 2, 4, 11],
    "Group": ["Treatment"] * 2 + ["Control"] * 2
})

sns.barplot(data=df, x="Outcome", y="Count", hue="Group")
plt.title("Treatment Outcomes")
plt.show()

Conclusie

Fisher’s exact-toets blijft de gouden standaard voor het analyseren van kleine categorische datasets. Geboren uit een theekopje in een tuin, is het uitgegroeid tot een belangrijk en veelgebruikt instrument in de geneeskunde, genetica, onderzoek met proefpersonen en zakelijke experimenten.

Schrijf je in voor onze Statistics Fundamentals in Python-skill track om verder te leren; naast hypothesetoetsing oefen je daar ook met het bouwen van statistische modellen met lineaire en logistische regressie.

Vidhi Chugh

Auteur

Vidhi Chugh

Ik ben een AI-strateeg en -ethicus die op het snijvlak van data science, product en engineering werkt aan schaalbare machinelearningsystemen. Genoteerd als een van de "Top 200 Business and Technology Innovators" ter wereld, heb ik als missie machine learning te democratiseren en het jargon te doorbreken, zodat iedereen deel kan uitmaken van deze transformatie.

Veelgestelde vragen

Hoe verschilt Fisher’s exact-toets van de chi-kwadraattoets?

Fisher’s exact-toets berekent een exacte kans en is ideaal voor kleine steekproeven of lage verwachte celvullingen, terwijl de chi-kwadraattoets een benadering voor grote steekproeven gebruikt die onnauwkeurig kan worden wanneer verwachte aantallen onder 5 vallen.

Wat zijn de praktische toepassingen van Fisher’s exact-toets in medisch onderzoek?

De toets wordt veel gebruikt om de effectiviteit van behandelingen te evalueren, uitkomsten van klinische onderzoeken te analyseren, ziekteassociaties te onderzoeken en genetische risicofactoren te bestuderen — vooral wanneer de patiëntsteekproeven klein zijn.

Kan Fisher’s exact-toets worden gebruikt voor contingentietabellen groter dan 2×2?

Ja, Fisher’s exact-toets kan worden uitgebreid naar grotere tabellen met methoden zoals de Freeman–Halton-uitbreiding, al worden de berekeningen zwaarder naarmate de tabel groter wordt.

Wat zijn de aannames die nodig zijn voor het gebruik van Fisher’s exact-toets?

De data moeten categorisch zijn, georganiseerd in een contingentietabel, met onafhankelijke observaties en vaste rij- en kolomsommen onder de nulhypothese.

Hoe hangt de hypergeometrische verdeling samen met Fisher’s exact-toets?

Fisher’s exact-toets baseert zijn kansberekeningen op de hypergeometrische verdeling, die de waarschijnlijkheid modelleert om een specifieke rangschikking van aantallen te observeren gegeven de vaste marges.

Onderwerpen

Gerelateerd