Data

Binomial Distribution and Binomial Test in Python with SciPy

Data · Published 2026-10-27

The binomial distribution counts successes in n independent yes/no trials that each succeed with probability p. In Python, scipy.stats.binom(n, p) answers probability questions ("how likely are 10 or more successes?"), and scipy.stats.binomtest(k, n, p) runs the exact binomial test ("is 62 heads out of 100 consistent with a fair coin?") and returns a p-value and a confidence interval.

The examples use SciPy 1.18. binomtest replaced the older binom_test function, which was deprecated and has since been removed; if you find stats.binom_test(...) in old code, switch to stats.binomtest(...).pvalue.

When the binomial model applies

Check four conditions before using it:

  1. A fixed number of trials, n, decided in advance.
  2. Each trial has two outcomes, success or failure.
  3. The probability of success p is the same for every trial.
  4. Trials are independent.

Conversion counts, pass/fail quality checks and correct answers on a multiple-choice quiz usually fit. Sampling without replacement from a small population does not (that is hypergeometric), and counts with no fixed n are better described by the Poisson distribution.

The mean is np and the variance np(1 − p). The probability of exactly k successes is C(n, k) pk (1 − p)n − k.

Binomial probabilities with scipy.stats.binom

Take 20 trials with a success rate of 30%.

from scipy import stats

B = stats.binom(n=20, p=0.3)
print(B.pmf([0, 6, 10]).round(4))
print(round(B.cdf(4), 4))     # P(X <= 4)
print(round(B.sf(9), 4))      # P(X >= 10)
print(B.mean(), B.var(), round(B.std(), 4))
[0.0008 0.1916 0.0308]
0.2375
0.048
6.0 4.2 2.0494

A quick simulation confirms the numbers:

import numpy as np

rng = np.random.default_rng(1)
sims = rng.binomial(n=20, p=0.3, size=100_000)
print(round(sims.mean(), 3), round(sims.var(), 3), round((sims >= 10).mean(), 4))
6.0 4.194 0.0472

The exact binomial test with binomtest

The binomial test asks whether an observed number of successes is compatible with a hypothesised probability p0. It is exact: the p-value is computed from the binomial distribution itself, with no normal approximation, so it is valid for small samples.

Suppose a coin lands heads 62 times in 100 flips. Is it fair?

res = stats.binomtest(k=62, n=100, p=0.5, alternative="two-sided")
print(round(res.pvalue, 4))
print(res.statistic)
0.021
0.62

The returned BinomTestResult holds the p-value, the observed proportion (statistic, here 0.62) and the inputs. A two-sided p-value of 0.021 means that, if the coin were fair, a result at least as unlikely as 62 heads would occur about 2.1% of the time. At the conventional 5% level, you would reject fairness.

One-sided alternatives

If, before collecting data, you only cared whether the coin favours heads, use alternative="greater":

res = stats.binomtest(k=62, n=100, p=0.5, alternative="greater")
print(round(res.pvalue, 4))
0.0105

This is P(X ≥ 62) under p = 0.5. The one-sided p-value is half the two-sided one here because the null distribution is symmetric; for p ≠ 0.5 the relationship is not exact. Choose the direction from the question, never after looking at the data.

alternative="less" works the same way in the other direction. A supplier promises a defect rate of 5%, and an inspection finds 3 defects in 200 items. Is the true rate below 5%?

res = stats.binomtest(k=3, n=200, p=0.05, alternative="less")
print(round(res.pvalue, 4))
0.009

With 10 defects expected and only 3 observed, the data support a lower rate.

Confidence intervals for the proportion

A p-value answers a yes/no question; a confidence interval tells you which values of p are plausible. proportion_ci is a method of the test result:

res = stats.binomtest(k=62, n=100, p=0.5)
ci = res.proportion_ci(confidence_level=0.95)
print(round(ci.low, 4), round(ci.high, 4))

ci = res.proportion_ci(confidence_level=0.95, method="wilson")
print(round(ci.low, 4), round(ci.high, 4))
0.5175 0.7152
0.5221 0.709

The default "exact" method is the Clopper–Pearson interval: it guarantees at least 95% coverage and is therefore a little wide. The Wilson score interval ("wilson", or "wilsoncc" with a continuity correction) is narrower and usually has coverage closer to the nominal level. Both exclude 0.5, consistent with the test.

Avoid the textbook "Wald" interval p̂ ± 1.96·√(p̂(1 − p̂)/n) for small samples or proportions near 0 or 1: it can extend below 0 or above 1 and undercovers badly.

Testing many groups

When you have success counts for several segments, for example conversions per landing page, aggregate them with pandas groupby, then call binomtest on each row. If you run many tests, adjust for multiple comparisons (SciPy provides scipy.stats.false_discovery_control for the Benjamini–Hochberg procedure). To compare two groups with each other rather than against a fixed p0, use Fisher's exact test (scipy.stats.fisher_exact) or a chi-square test on the 2×2 table instead.

Common mistakes

Summary

Use stats.binom(n, p) for probabilities, with pmf, cdf and sf, minding the ">" versus "≥" boundary. Use stats.binomtest(k, n, p) for an exact test, read .pvalue, pick alternative from your question, and report .proportion_ci() (Wilson or exact) alongside it.

Sources

Read next

More in Data