Binomial

A Binomial distribution models the number of successes in \(n\) independent trials, where each trial is a Bernoulli event with success probability \(p\).

Intuition:

  • One Bernoulli trial answers a yes/no question (true/false).

  • A Binomial aggregates \(n\) such trials and returns a count in \(\{0, 1, ..., n\}\).

What question does the Binomial answer?

The Binomial distribution answers the following core question:

If each individual in a population independently has a certain feature with probability \(p\) (e.g. “this email gets clicked”, “this part is defective”, “this person tests positive”), then for a random sample of \(n\) individuals, what is the probability that the feature is present in exactly \(k\) of them?

We write this as:

\(X \sim Binomial(n, p)\) and ask for \(P(X = k)\).

Closed form (probability mass function)

For \(k = 0, 1, ..., n\):

\[P(X = k) = {n \choose k} p^k (1-p)^{n-k}\]

where

\[{n \choose k} = \frac{n!}{k!(n-k)!}\]

is the number of ways to choose which :math:`k of the \(n\) trials are successes.

Intuition (why this formula is true)

Think of \(n\) independent Bernoulli trials (success probability p).

  1. Pick one specific sequence of outcomes that has exactly \(k\) successes and \(n-k\) failures, for example:

    S S F S F ...

    Because the trials are independent:

    • the probability of the \(k\) successes is \(p^k\)

    • the probability of the \(n-k\) failures is \((1-p)^{n-k}\)

    so the probability of this particular sequence is \(p^k (1-p)^{n-k}\).

  2. Now count how many different sequences have exactly \(k\) successes. This is purely combinatorial: you choose which \(k\) positions (out of \(n\)) are successes, so there are \(C(n,k) = {n \choose k}\) such sequences.

  3. These sequences are disjoint events, and each has the same probability \(p^k (1-p)^{n-k}\), so you multiply:

\[P(X = k) = {n \choose k} p^k (1-p)^(n-k).\]

This is why the Binomial is one of the most important distributions: it is the canonical model for counts of “how many successes out of n independent tries?”

Constructor

Binomial({p: ..., n: ...})

  • p: success probability in [0, 1]

  • n: number of trials (integer, n >= 1)

  • support: integers 0..n

Relationship to Bernoulli

A Binomial random variable can be understood as the sum of n Bernoulli trials.

In code, these two ideas align:

  • One Bernoulli outcome: sample(Bernoulli({p: p})) → boolean

  • Many trials + counting successes → integer count

Use Binomial when you only care about the total number of successes, not the individual trial outcomes.

Typical use cases

  • Counting successes in n independent Bernoulli trials (e.g. “how many clicks out of 10 emails?”)

  • Likelihood for count data via observe(Binomial(...), k) when you observe k successes out of n

Executable example: basics (samples and score)

 1var d = Binomial({n: 10, p: 0.3});
 2
 3var out = {
 4  oneSample: sample(d),
 5  someSamples: repeat(8, function() { return sample(d); }),
 6
 7  // score(k) is log P(X = k)
 8  logp_k4: d.score(4),
 9  logp_k0: d.score(0),
10  logp_k10: d.score(10)
11};
12
13out;
{
  oneSample: 4,
  someSamples: [
    3, 3, 2, 3,
    3, 2, 3, 2
  ],
  logp_k4: -1.6088333502186698,
  logp_k0: -3.5667494393873245,
  logp_k10: -12.03972804325936
}

Scoring

d.score(k) is the log probability of observing exactly k successes out of n.

How does score(k) relate to the Binomial formula?

Recall the Binomial probability mass function:

\[P(X = k) = {n \choose k} p^k (1-p)^{n-k}\]

WebPPL returns probabilities in log space. For d = Binomial({n: n, p: p}):

  • d.score(k) equals \(\log P(X = k)\) (natural log)

  • therefore Math.exp(d.score(k)) equals the ordinary probability \(P(X = k)\)

Equivalently:

\[d.score(k) = \log {n \choose k} + k \log p + (n-k) \log (1-p)\]

This is convenient because very small probabilities remain representable as log values, and you can always convert back with exp when you want human-readable probabilities.

Executable example: converting score(k) back to probability

 1// Convert Binomial log-probabilities (score) back to ordinary probabilities.
 2// Avoid imperative loops: WebPPL's CPS transform can choke on some JS statements.
 3
 4var n = 10;
 5var p = 0.3;
 6var d = Binomial({n: n, p: p});
 7
 8// Simple recursive range [a..b]
 9var range = function(a, b) {
10  return (a > b) ? [] : [a].concat(range(a + 1, b));
11};
12
13var pmf = function(k) {
14  return Math.exp(d.score(k));
15};
16
17// A few selected k values:
18var ks = [0, 1, 2, 3, 4, 5, 10];
19
20var rows = map(function(k) {
21  var logp = d.score(k);
22  return {k: k, logp: logp, p: Math.exp(logp)};
23}, ks);
24
25// Check that probabilities across *all* k sum to ~1.
26var allProbs = map(pmf, range(0, n));
27var total = sum(allProbs);
28
29var out = {
30  n: n,
31  p: p,
32  selected: rows,
33  sum_over_all_k: total
34};
35
36out;
{
  n: 10,
  p: 0.3,
  selected: [
    { k: 0, logp: -3.5667494393873245, p: 0.02824752489999998 },
    { k: 1, logp: -2.111462206780482, p: 0.12106082099999996 },
    { k: 2, logp: -1.4546826703914122, p: 0.23347444049999977 },
    { k: 3, logp: -1.321151277766889, p: 0.2668279319999999 },
    { k: 4, logp: -1.6088333502186698, p: 0.20012094899999994 },
    { k: 5, logp: -2.2738096538119192, p: 0.10291934519999993 },
    { k: 10, logp: -12.03972804325936, p: 0.000005904899999999995 }
  ],
  sum_over_all_k: 0.9999999999999993
}

Beyond \(P(X = k)\): CDF and tail probabilities

In practice you often want cumulative questions such as:

  • \(P(X ≤ k)\) (“at most k successes”)

  • \(P(X ≥ k)\) (“at least k successes”)

For a Binomial, these can be computed by summing the point probabilities:

\[P(X \le k) = \sum\limits_{i=0}^{k} P(X=i)\]
\[P(X \ge k) = \sum\limits_{i=k}^{n} P(X=i)\]

In WebPPL you can obtain \(P(X=i)\) as Math.exp(d.score(i)) and then sum over the desired range.

Executable example: CDF and tail probabilities

 1// Binomial CDF and tail probabilities by summing exp(score(i)).
 2// Avoid imperative loops.
 3
 4var n = 10;
 5var p = 0.3;
 6var k = 4;
 7
 8var d = Binomial({n: n, p: p});
 9
10var range = function(a, b) {
11  return (a > b) ? [] : [a].concat(range(a + 1, b));
12};
13
14var pmf = function(i) {
15  return Math.exp(d.score(i));
16}; var pEq = pmf(k);
17var pLe = sum(map(pmf, range(0, k)));
18var pGe = sum(map(pmf, range(k, n)));
19
20var pLt = (k > 0) ? sum(map(pmf, range(0, k - 1))) : 0;
21var pGt = (k < n) ? sum(map(pmf, range(k + 1, n))) : 0;
22
23var total = sum(map(pmf, range(0, n)));
24
25var out = {
26  n: n,
27  p: p,
28  k: k,
29
30  p_eq_k: pEq,
31  p_le_k: pLe,
32  p_lt_k: pLt,
33
34  p_ge_k: pGe,
35  p_gt_k: pGt,
36
37  sum_all: total,
38  check_complements: {
39    p_le_k_plus_p_gt_k: pLe + pGt,
40    p_lt_k_plus_p_ge_k: pLt + pGe
41  }
42};
43
44out;
{
  n: 10,
  p: 0.3,
  k: 4,
  p_eq_k: 0.20012094899999994,
  p_le_k: 0.8497316673999994,
  p_lt_k: 0.6496107183999995,
  p_ge_k: 0.3503892815999998,
  p_gt_k: 0.1502683325999999,
  sum_all: 0.9999999999999993,
  check_complements: {
    p_le_k_plus_p_gt_k: 0.9999999999999993,
    p_lt_k_plus_p_ge_k: 0.9999999999999993
  }
}

A real-life example: estimating click-through rate (CTR) on a small grid

Story: you send \(n = 10\) emails and observe \(k = 4\) clicks. Assume clicks are independent with unknown click probability \(p\) (a Bernoulli success rate). We place a discrete prior on a small candidate set for \(p\) and compute the posterior exactly using enumeration.

 1// Click-through rate example:
 2// n emails sent, k clicks observed (successes).
 3var n = 10;
 4var k = 4;
 5
 6// Discrete prior grid for p so we can enumerate exactly.
 7var grid = [0.1, 0.3, 0.5];
 8var prior = Categorical({vs: grid}); // uniform over the grid
 9
10var model = function() {
11  var p = sample(prior);
12  observe(Binomial({n: n, p: p}), k);
13  return p;
14};
15
16var posterior = Infer({method: "enumerate", model: model});
17
18// Convert log scores to ordinary probabilities
19var supp = posterior.support();
20var probs = map(function(v) { return Math.exp(posterior.score(v)); }, supp);
21
22var out = {
23  n: n,
24  k: k,
25  p_grid: grid,
26  posterior_support: supp,
27  posterior_probs: probs,
28  sum: sum(probs)
29};
30
31out;
{
  n: 10,
  k: 4,
  p_grid: [ 0.1, 0.3, 0.5 ],
  posterior_support: [ 0.5, 0.3, 0.1 ],
  posterior_probs: [ 0.4925508035024606, 0.48064479928137027, 0.02680439721616908 ],
  sum: 1
}