Contents
, where , , and
Deep Breakdown
What it tells me (Situation Awareness): The Multinomial distribution describes the vector of counts I get when I repeat independent Categorical trials with the same class probabilities . It generalises the Binomial from two outcomes to (Binomial is Multinomial with ) and the Categorical from one trial to (Categorical is Multinomial with ). The multinomial coefficient counts the orderings that produce the same counts; the product gives the probability of any one ordering. Each single count is marginally Binomial, , with mean and variance , and counts are negatively correlated, , because they must add up to . In DA/DS: plan or product-mix counts, traffic split across channels, survey answer counts, sample-ratio-mismatch checks in A/B/n tests, chi-square goodness-of-fit tests, bag-of-words document models (Multinomial Naive Bayes), and topic models.
Flowchart
Real-world use
Where it is used
Distribution of sign-ups across pricing tiers, sessions across acquisition channels, users across A/B/n variants, star ratings across 1 to 5, words across a vocabulary (text classification), and categorical survey answers.
Problem it solves
Gives the joint probability of a whole vector of class counts, so I can test whether an observed mix matches an expected mix (e.g. is the 33/33/33 traffic split actually happening?) and estimate class shares with their uncertainty.
How it solves the problem
Independence makes any specific sequence with counts have probability . The multinomial coefficient counts how many sequences have those counts. Their product is the PMF; the likelihood ratio between observed shares and hypothesised shares leads to the chi-square goodness-of-fit test.
Example in a business
A SaaS company expects new sign-ups to split Basic/Pro/Enterprise as . For a batch of 5 sign-ups, the probability of exactly 3 Basic, 1 Pro and 1 Enterprise is . On a larger scale, an A/B/C test meant to split traffic equally recorded 3,420 / 3,300 / 3,280 users; a chi-square test against gives and a p-value of about , so there is no evidence of a sample-ratio mismatch.
The formula, piece by piece
- : total number of independent trials (sign-ups, sessions, words).
- : number of categories.
- : observed count in category ; the counts sum to .
- : probability that one trial falls in category .
- : multinomial coefficient, number of distinct orderings giving these counts.
- : probability of any single ordering with these counts.
- , , for .
- MLE: .
The formula in words
"The chance of seeing a particular mix of counts is the number of ways to order the trials into that mix, times the probability of one such ordering." Tiny example: 2 orders, each from category A or B with probability and : , exactly the Binomial answer.
Critical Concepts
- Family tree: is the Binomial; is the Categorical; is the Bernoulli.
- Marginals are Binomial: to reason about one category alone, use .
- Negative covariance: more users in one class means fewer elsewhere; do not treat per-class counts as independent.
- Chi-square test: is approximately under the null, valid when every expected count is at least about 5.
- Poisson connection: independent Poisson counts, conditioned on their total , are Multinomial. This is why Poisson regression and multinomial models often give the same inference on shares.
- Overdispersion: if users cluster (e.g. company accounts signing up together), the real variance exceeds ; the Dirichlet-Multinomial handles this.
Step-by-step calculation (tiny example)
Step 1: Set up sign-ups, for (Basic, Pro, Enterprise), counts .
Step 2: Multinomial coefficient .
Step 3: Probability of one ordering .
Step 4: Multiply .
Step 5: Marginal sanity check Basic count alone: , so ; seeing 3 Basic is close to typical.
Code examples
Beginner example (Python)
from scipy.stats import multinomial
n, p = 5, [0.5, 0.3, 0.2]
dist = multinomial(n, p)
print(f"P(3, 1, 1) = {dist.pmf([3, 1, 1]):.4f}") # 0.1500
print(f"P(1, 1) with n=2, p=(0.6, 0.4) = {multinomial(2, [0.6, 0.4]).pmf([1, 1]):.2f}") # 0.48
print("E[X] =", dist.mean()) # [2.5 1.5 1. ]
print("Cov matrix:\n", dist.cov())Beginner example (R)
p <- c(0.5, 0.3, 0.2)
cat(sprintf("P(3, 1, 1) = %.4f\n", dmultinom(c(3, 1, 1), size = 5, prob = p))) # 0.15
set.seed(42)
print(rmultinom(3, size = 5, prob = p)) # three simulated batches of 5 sign-upsProfessional example (Python)
import numpy as np
from scipy.stats import chisquare, multinomial
def sample_ratio_mismatch(
observed: np.ndarray, expected_share: np.ndarray, alpha: float = 0.01
) -> dict[str, float]:
"""Check whether traffic in an A/B/n test follows the planned Multinomial split.
Args:
observed: Users per variant.
expected_share: Planned allocation probabilities (sum to 1).
alpha: Significance threshold for flagging a mismatch.
Returns:
Dict with chi-square statistic, p-value and a mismatch flag.
"""
n = observed.sum()
expected = n * expected_share
stat, p_value = chisquare(observed, f_exp=expected)
return {"n": float(n), "chi2": float(stat), "p_value": float(p_value),
"mismatch": float(p_value < alpha)}
observed = np.array([3420, 3300, 3280])
planned = np.array([1, 1, 1]) / 3
print(sample_ratio_mismatch(observed, planned)) # chi2 = 3.44, p_value about 0.18, no mismatch
# Monte Carlo check of the same p-value using the Multinomial directly
rng = np.random.default_rng(42)
n = observed.sum()
expected = n * planned
obs_stat = ((observed - expected) ** 2 / expected).sum()
sims = multinomial.rvs(n, planned, size=50_000, random_state=rng)
sim_stats = ((sims - expected) ** 2 / expected).sum(axis=1)
print(f"Simulated p-value: {(sim_stats >= obs_stat).mean():.3f}")Source: domain knowledge