Fisher’s Exact Test in R: Association in Small Contingency Tables

The exact alternative to chi-square when cell counts are small — test association in 2×2 and larger tables, with the odds ratio

Biostatistics

Learn to run Fisher’s exact test in R — the exact alternative to the chi-square test of independence when expected cell counts are small (< 5). Test association in a 2×2 table (with the odds ratio and its confidence interval) or a larger R×C table, with base fisher.test() and rstatix fisher_test().

Published

June 23, 2026

Modified

July 7, 2026

TipKey takeaways
  • Fisher’s exact test tests whether two categorical variables are associated — like the chi-square test — but it is exact, so it is correct when cell counts are small (expected counts < 5) and chi-square is not.
  • Run it with base fisher.test() or rstatix::fisher_test() on the contingency table.
  • For a 2×2 table it also returns the odds ratio and its confidence interval — a built-in effect size.
  • For large R×C tables it can be slow; add simulate.p.value = TRUE for a Monte-Carlo p-value.
  • Use it whenever a chi-square’s expected-count assumption fails; otherwise either gives the same answer.

Introduction

Fisher’s exact test analyses a contingency table to test whether two categorical variables are associated — the same question as the chi-square test of independence. The difference: chi-square relies on a large-sample approximation that breaks down when cell counts are small, whereas Fisher’s test computes the exact probability of the observed table (and more extreme ones) under independence. So it is the test to reach for with small counts.

This lesson runs it on a small 2×2 table (where it also gives an odds ratio) and a larger R×C table, with base R and rstatix.

Note

Fisher’s exact test hypotheses. H₀: the row and column variables are independent. Hₐ: they are associated. For a 2×2 table this is equivalent to testing whether the odds ratio = 1.

A 2×2 table: small-sample association + odds ratio

A small trial: of 10 patients on a drug, 9 were cured; of 10 on placebo, 3 were cured. With counts this small, chi-square is unreliable — use Fisher:

tx <- matrix(
  c(9, 1, 3, 7), nrow = 2, byrow = TRUE,
  dimnames = list(treatment = c("Drug", "Placebo"), outcome = c("Cured", "NotCured"))
)
tx
         outcome
treatment Cured NotCured
  Drug        9        1
  Placebo     3        7
fisher.test(tx)

    Fisher's Exact Test for Count Data

data:  tx
p-value = 0.01977
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
    1.387127 1039.290968
sample estimates:
odds ratio 
  17.27587 

The association is significant, p = 0.02. For a 2×2 table Fisher’s test also reports the odds ratio — here ≈ 17, meaning the odds of being cured are about 17× higher on the drug — with a wide 95% CI (reflecting the small sample). The CI excludes 1, consistent with the significant p-value.

Tip

One-sided tests. For a directional hypothesis (e.g. the drug is better, not just different), pass alternative = "greater" (or "less").

A larger R×C table

Fisher’s test is not limited to 2×2. Here is the classic Agresti job-satisfaction table (income × satisfaction, 4×4) with several small cells:

job <- matrix(
  c(1, 2, 1, 0, 3, 3, 6, 1, 10, 10, 14, 9, 6, 7, 12, 11), nrow = 4, byrow = TRUE,
  dimnames = list(
    income = c("< 15k", "15-25k", "25-40k", "> 40k"),
    satisfaction = c("VeryD", "LittleD", "ModerateS", "VeryS")
  )
)
job
        satisfaction
income   VeryD LittleD ModerateS VeryS
  < 15k      1       2         1     0
  15-25k     3       3         6     1
  25-40k    10      10        14     9
  > 40k      6       7        12    11
fisher.test(job)

    Fisher's Exact Test for Count Data

data:  job
p-value = 0.7827
alternative hypothesis: two.sided

The p-value is 0.78not significant, so no association could be established between income and job satisfaction. (For a large R×C table the exact computation can be slow or memory-heavy; add simulate.p.value = TRUE to get a fast Monte-Carlo p-value instead.)

With rstatix

rstatix::fisher_test() is the pipe-friendly wrapper (it returns a tidy row; pass a table/as.table object):

library(rstatix)

tx <- as.table(matrix(
  c(9, 1, 3, 7), nrow = 2, byrow = TRUE,
  dimnames = list(treatment = c("Drug", "Placebo"), outcome = c("Cured", "NotCured"))
))

fisher_test(tx)
# A tibble: 1 × 3
      n      p p.signif
* <dbl>  <dbl> <chr>   
1    20 0.0198 *       

Plot it

library(rstatix)
library(ggpubr)

tx <- as.table(matrix(
  c(9, 1, 3, 7), nrow = 2, byrow = TRUE,
  dimnames = list(treatment = c("Drug", "Placebo"), outcome = c("Cured", "NotCured"))
))
df <- as.data.frame(tx)
stat.test <- fisher_test(tx)

ggbarplot(df, x = "treatment", y = "Freq", fill = "outcome", palette = "jco",
          ylab = "Count", xlab = "Group") +
  labs(subtitle = get_test_label(stat.test, detailed = TRUE))

A bar chart of cure counts by treatment group with Fisher's exact test p-value in the subtitle; the drug group has far more cures than the placebo group.

Report

Fisher’s exact test showed a significant association between treatment and cure (9/10 cured on the drug vs 3/10 on placebo), p = 0.02, odds ratio ≈ 17 (95% CI 1.4 to 1039). The wide interval reflects the small sample.

For a 2×2 table with fixed margins, the probability of any particular table follows the hypergeometric distribution: \(P = \dfrac{\binom{a+b}{a}\binom{c+d}{c}}{\binom{n}{a+c}}\). Fisher’s test sums this probability over the observed table and all tables at least as extreme, giving an exact p-value with no large-sample approximation — which is why it stays valid when chi-square’s expected-count assumption fails.

Try it live

Run Fisher’s test on your own 2×2 counts and see the odds ratio. Edit the four numbers and re-run; the sandbox boots on first Run.

🟢 With an AI agent

Ask Prova “my contingency table has small counts — run Fisher’s exact test instead of chi-square and give me the odds ratio” — it answers with code you can run on your own table. The runtime is the judge. Ask Prova →

Common issues

Your R×C Fisher test is slow or errors on memory. Exact computation on a large table with big counts is expensive — add simulate.p.value = TRUE (and optionally B = 10000) for a Monte-Carlo p-value.

You wanted the odds ratio but have a larger table. Fisher’s odds ratio is defined only for 2×2 tables. For bigger tables, report the test p-value and use Cramér’s V for the strength.

You used chi-square with small counts anyway. If any expected count is below 5, the chi-square p-value is unreliable — Fisher’s exact test is the correct choice.

Frequently asked questions

Use Fisher’s exact test when your contingency table has small cell counts — specifically when any expected count is below 5, where the chi-square approximation is unreliable. Fisher’s test computes an exact p-value, so it is valid for small samples. For large counts, the two tests agree.

Yes — fisher.test() handles R×C tables. But the exact computation can be slow or memory-intensive for large tables with big counts; add simulate.p.value = TRUE for a Monte-Carlo p-value. The built-in odds ratio, however, is only reported for 2×2 tables.

For a 2×2 table, fisher.test() returns the conditional maximum-likelihood odds ratio and its confidence interval — an effect size for the association. An odds ratio of 1 means no association; the CI excluding 1 corresponds to a significant test. For more on odds ratios see the dedicated odds ratio lesson.

Test your understanding

  1. Run it. In the live cell, run Fisher’s exact test on the 2×2 table. Fill the blank. Is the association significant, and what is the odds ratio?
  2. Conceptual. A reviewer asks why you used Fisher’s exact test instead of chi-square on your 2×2 table where one expected count was 3. What’s your answer?

Fill the blank with fisher.test. Look at the p-value and the odds-ratio estimate. For question 2, recall the chi-square assumption that fails with small expected counts.

tab <- matrix(c(9, 1, 3, 7), nrow = 2, byrow = TRUE)
fisher.test(tab)   #> p = 0.02, odds ratio ≈ 17 — significant association.

For question 2: chi-square relies on a large-sample approximation that requires every expected count to be at least ~5. With an expected count of 3, that assumption fails and the chi-square p-value would be unreliable, so Fisher’s exact test — which computes the exact probability — is the correct choice.

TipWhich test when?

Conclusion

You can now run Fisher’s exact test in R: fisher.test() (or rstatix::fisher_test()) on the contingency table gives an exact p-value for association — correct when cell counts are too small for chi-square — plus the odds ratio for a 2×2 table. Reach for it whenever the chi-square expected-count assumption fails, and use simulate.p.value = TRUE for large R×C tables.

Reuse

Citation

BibTeX citation:
@online{2026,
  author = {},
  title = {Fisher’s {Exact} {Test} in {R:} {Association} in {Small}
    {Contingency} {Tables}},
  date = {2026-06-23},
  url = {https://www.datanovia.com/learn/biostatistics/categorical/fisher-exact-test-in-r},
  langid = {en}
}
For attribution, please cite this work as:
“Fisher’s Exact Test in R: Association in Small Contingency Tables.” 2026. June 23. https://www.datanovia.com/learn/biostatistics/categorical/fisher-exact-test-in-r.