learn-bayesian-with-phoebe / Practitioner session 6 of 10
Learn Bayesian with Phoebe · Practitioner session 6 of 10

Hierarchical models

Session 5 fitted one outlet. Halloway has twelve, running the same voucher in the same month, with footfall from 18 at Northgate to 900 at Westfield, and every one of them has a true redemption rate written into the generator. That makes tonight a rare thing: a pooling method can be scored, not argued. Treat each outlet alone and the twelve estimates miss the truth by 0.054 root-mean-square. Give every outlet the chain rate and the miss is 0.031. Let each outlet be shrunk toward a centre the data choose, by an amount the data choose, and it is 0.025. The amount has a name, kappa, and on the Halloway data it is 116: each outlet's own month carries weight n / (n + 116). Nobody set that. The build-along fits it in twenty lines, then hands the same structure to PyMC 6 so that kappa stops being a number and becomes a posterior.

🔴 Hardest Data scientists and analysts Anyone estimating a rate across many small units 45 min
0-3 · Welcome 3-16 · Kappa is read off the data 16-40 · Build-along: the fit, the ladder, the PyMC block 40-45 · Q&A
Part 0

Where tonight sits

Session 2 put four priors on Northgate and found that an informed prior worth 100 pseudo-observations was confidently wrong there. Session 5 fitted Westfield in PyMC 6 with a flat prior and got 0.142. Tonight the prior is neither asserted nor flat. Its centre and its pseudo-count are learned from the other eleven outlets, and the same tool that fitted one outlet fits twelve at once with a chain level above them. The source is McElreath's Statistical Rethinking, lectures 12 and 13, "Multilevel Models" and "Multilevel Adventures". Session 7 moves from estimating rates to deciding between two of them. The example is still Halloway Bakeries, and the truth is still known, so every rung tonight has a score.

Live = presented in the 45 minutes Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with tonight. The empirical-Bayes fit as twenty lines of Python: a grid over the chain centre mu and the pooling strength kappa, the marginal Beta-Binomial likelihood from scipy.special.betaln, and the maximiser at mu = 0.135, kappa = 116. The ladder it produces, 0.054, 0.031, 0.025, and the same ladder in two other worlds where kappa goes to the grid ceiling and down to 3.6. The per-outlet ledger, six helped and six hurt, with Northgate still 0.068 short of its truth. And a PyMC 6 block with the same structure that returns a posterior over mu and kappa themselves, which is the one thing the twenty lines cannot do.

LiveThree estimators, one score4 min▶

Three ways to estimate twelve rates. No pooling: each outlet's observed rate, y over n, twelve unrelated problems. Complete pooling: one rate for all, the grand redemption rate of 0.141, twelve copies of one answer. Partial pooling: each outlet shrunk toward a centre learned from all twelve, by an amount that depends on its own volume and on how different the outlets really are. The score is the same for all three: root-mean-square error of the twelve estimates against the twelve true rates, which can be computed because the true rates are in the generator.

On the Halloway data the ladder reads 0.054, 0.031, 0.025. The order is what McElreath's lectures 12 and 13 predict and the numbers are what the arithmetic gives. Complete pooling beats no pooling because most small outlets' observed rates are further from their own truths than the chain centre is; partial pooling beats both because it trusts each outlet's month in proportion to how much month there was. Tonight is about how that proportion is found, what it is worth, and what it costs.

Real world

The regional pack ranked Halloway's outlets by observed rate and flagged the bottom two. Northgate, one redemption in eighteen, was bottom at 0.056 with a true rate of 0.192, the best in the chain. The partially pooled estimate was 0.124, not the truth but enough to take it off the list. The analyst's argument was not "Northgate is good". It was "an eighteen-voucher month carries weight 0.13, and the other 0.87 is what eleven outlets like it do."

Self-studyThe words that will keep coming back4 min▶
  • mu. The chain-level centre every outlet is shrunk toward. Learned from the twelve: 0.135.
  • kappa. The pooling strength, expressed as a pseudo-count in session 2's currency. Learned from the twelve: 116. A Beta(mu kappa, (1 - mu) kappa) prior on each outlet's rate is worth kappa pseudo-observations.
  • Weight. An outlet's own month carries n / (n + kappa) of its estimate; the centre carries the rest. 0.13 at Northgate, 0.89 at Westfield.
  • Marginal Beta-Binomial likelihood. The probability of an outlet's observed count given mu and kappa, with the outlet's own rate integrated out. Summed in logs across the twelve, it is the function the grid search maximises.
  • Empirical Bayes. Fixing mu and kappa at the values that maximise that likelihood, then treating them as known. Fast, exact, and honest about being the simpler method.
  • Hierarchical model. The fully Bayesian version: a prior on mu and kappa too, and a posterior over them alongside the twelve rates. What the PyMC block adds.
  • Shrinkage. What partial pooling does to an estimate: pulls it from the observed rate toward mu. Thin outlets a long way, thick ones barely.
  • RMSE. Root-mean-square error of twelve estimates against twelve true rates. Possible here and almost nowhere else.
Part 1 · covers the fit, the weights and the per-outlet ledger

Kappa is read off the data

7 min live

Leader session 3 drew the twelve outlets as positions on a rate axis. Tonight the axis is error: each outlet's estimate minus its true rate, so the truth is the centre line and the question for every arrow is whether it moved toward zero or away. Hollow is the observed rate, filled is the shrunk estimate. Six arrows point in, six point out, and the lengths tell you who paid.

Twelve outlets, error against the truth: hollow is observed, filled is shrunk. Truth is the centre line. observed shrunk, error fell (six) shrunk, error rose (six) unchanged truth = 0 Northgate · n = 18 0.136 to 0.068 Riverside · n = 24 0.043 to 0.017 Old Quay · n = 31 0.058 to 0.023 Hillcrest · n = 40 0.088 to 0.021 Market Row · n = 55 0.000 to 0.007 Station · n = 70 0.020 to 0.024 Abbey Green · n = 95 0.024 to 0.010 Parkside · n = 140 0.002 to 0.008 Docklands · n = 210 0.007 to 0.009 Kingsway · n = 320 0.046 to 0.027 The Arcade · n = 540 unchanged, 3 decimals Westfield · n = 900 unchanged, 3 decimals -0.15 -0.10 -0.05 0 = truth +0.05 +0.10 estimate minus true rate Six errors fell by tenths on thin outlets; six rose by thousandths on thick ones. Northgate is still 0.068 short.
🔍 Click to zoom - the long arrows all point in and all belong to thin outlets. The red arrows are short, and two of the thick outlets did not move at all.
LiveThe fit: which mu and kappa make the twelve months most plausible4 min▶

Put a Beta(mu kappa, (1 - mu) kappa) prior on every outlet's rate. That is session 2's currency exactly: a prior centred on mu and worth kappa pseudo-observations. Now ask, for a candidate pair (mu, kappa), how probable the twelve observed counts are with each outlet's own rate integrated out. That probability has a closed form, the Beta-Binomial, and its logarithm is two calls to betaln per outlet. Sum it across the twelve, do that for every pair on a grid, and keep the pair with the highest total. On the Halloway data the winner is mu = 0.135, kappa = 116.

Everything else follows without a further decision. Each outlet's shrunk estimate is the posterior mean under that prior, which is the observed rate weighted by n / (n + 116) plus the centre weighted by the rest. At Northgate 18 / 134 is 0.13; at Westfield 900 / 1016 is 0.89. Nobody chose 0.13, 0.89 or 116. The grid search chose them by asking which chain-level description made this month's twelve counts most likely, and a different chain, or a different month, would have produced a different kappa. This is what "the method reads the answer off the data" means in practice, and the build-along is where you watch it do so.

LiveThe ledger: six helped, six hurt, both halves4 min▶

The RMSE fell from 0.054 to 0.025, and averages hide who paid. Outlet by outlet, six errors fell and six rose. The falls are large and land on the thin outlets: Northgate 0.136 to 0.068, Hillcrest 0.088 to 0.021, Old Quay 0.058 to 0.023, Kingsway 0.046 to 0.027, Riverside 0.043 to 0.017, Abbey Green 0.024 to 0.010. The rises are small and land on the well-measured ones: Market Row 0.000 to 0.007, Parkside 0.002 to 0.008, Station 0.020 to 0.024, Docklands 0.007 to 0.009, and The Arcade and Westfield did not change to three decimals. Shrinkage buys tenths on the outlets that need it and costs thousandths on the ones that do not.

Say both halves, always. The asymmetry is not luck; it is the weights. The thick outlets barely moved because the fit trusted their own months, at weights near 0.89, so the centre had little room to pull. The thin ones moved a lot because the fit did not trust theirs, at weights near 0.13, and lent them the chain's experience instead. Market Row is the sharpest case: its observed rate happened to sit on its truth to three decimals, and shrinkage moved it off by 0.007. That is a real cost, and it is the price of a method that cannot know in advance which thin-looking month happened to be right. Northgate, the biggest winner, is still under-estimated at 0.124 against 0.192. The method halved the error. It did not remove it.

Self-studyKappa in the same currency as the prior3 min▶

Session 2's informed prior was Beta(12, 88): centre 0.120, worth 100 pseudo-observations, chosen from a feeling about the chain's history. Tonight's learned prior has centre 0.135 and is worth 116, similar strength, slightly higher centre. At Northgate the asserted prior gave 0.110 and the learned one gives 0.124. The improvement is real but modest, and it is not the point. The point is provenance: 100 was asserted and 116 was measured, and a measured pseudo-count comes with a reason anyone can check, namely that it is the value under which the twelve observed months are most plausible together.

The learned prior still paid session 2's price at Northgate, because Northgate genuinely differs from the chain and 116 pseudo-observations of chain experience pulled it toward the crowd anyway. There is no version of pooling that is free for the unit that truly stands apart. What the data-chosen kappa buys is that the fee is set by how alike the units actually are, and that in a world where they were not alike, it would fall. Part 2 shows it doing exactly that. Whether a prior ought to be learned this way at all is the argument of leader session 5, and it is not settled.

Part 2 · covers the three worlds and the fully Bayesian version

One method, three worlds, and what PyMC adds

6 min live

A method that pulls every estimate toward the middle should worry you until you have seen it in a world where pulling is wrong. So the same three estimators were run on two more worlds built from the same twelve outlets, one where the true rates are identical and one where they are wildly spread. The grid below reads the ladder across all three, and the last column is the reason the method survives: kappa is different in each world, and the data chose it.

Root-mean-square error against the true rates, and the kappa the fit learned in each world. World No pooling Complete pooling Partial pooling Learned kappa Real spread, the Halloway data twelve outlets as generated 0.054 0.031 0.025 best 116 weights n / (n + 116) Outlets truly identical one true rate for all twelve 0.034 0.0065 0.0051 best grid ceiling pool everything Outlets wildly different true rates spread far apart 0.033 0.145 catastrophic 0.030 best 3.6 barely pool Partial pooling wins or ties in all three worlds. Complete pooling is catastrophic when the outlets differ: 0.145.
🔍 Click to zoom - read the last column first. The same code learned 116, then the ceiling, then 3.6, and the middle columns follow from that.
LiveThree worlds: the method changes its mind because kappa does3 min▶

In the world where the twelve outlets are truly identical, complete pooling is right by construction and scores 0.0065. The fit sees that the twelve counts are all consistent with one rate, pushes kappa to the ceiling of its grid, pools almost everything, and scores 0.0051. In the world where the outlets are wildly different, complete pooling is wrong by construction and collapses to 0.145, worse than doing nothing by a factor of more than four. The fit learns kappa = 3.6, barely pools, and scores 0.030, a hair better than the 0.033 of no pooling. On the real data it learned 116 and scored 0.025.

Read the kappa column as the method's opinion about how alike the units are, formed from their counts and nothing else. At 3.6, Northgate's own month would carry 18 / (18 + 3.6), about 0.83, instead of 0.13: almost all of the trust goes back to the outlet, because the chain has stopped being informative about it. That is the whole case for the approach. Not that partial pooling is the best estimator in every world, since complete pooling is nearly as good in the identical one and no pooling is close in the wild one, but that it is never the wrong estimator, because it measures before it pools.

LiveWhat the full hierarchical model adds over empirical Bayes3 min▶

The twenty-line fit finds the single best (mu, kappa) and then treats it as known. Every shrunk estimate and every weight is conditional on kappa being exactly 116, and the fit has no way to say how sure it is of that. A hierarchical model removes the assumption. Put a prior on mu and a prior on kappa, put the same Beta(mu kappa, (1 - mu) kappa) prior on each outlet's rate, and let PyMC sample all fourteen quantities together. What comes back is a posterior over mu and kappa themselves, alongside the twelve rates, so the pooling strength carries its own uncertainty and each outlet's interval is wider by however unsure the model is about how much to pool.

That is the one thing the grid cannot give you, and it is why the PyMC block in the build-along has the same structure with two more lines. The bench in session 10 uses the empirical-Bayes fit because it is exact, fast and honest about being the simpler method; the hierarchical model is what you reach for when the question "how sure are we about kappa" is going to be asked, which in a room deciding an outlet's future it will be. Read the diagnostics with particular care here: a model with a level above the units is exactly where session 4's checks earn their keep.

Self-studyWhere the same structure shows up2 min▶

Outlets in a chain, players in a league, regions under one campaign, cohorts of one product: any time many small units are measured the same way and could reasonably resemble each other, the structure tonight applies unchanged, and the question is always the same one, how much should one unit's own record be trusted. learn-ai-sports-with-phoebe b3 names Bayesian hierarchical models that pool player effects with shrinkage as what works for expected goals, and this is the session that page points at: a player with few shots is Northgate, a player with hundreds is Westfield, and the league's kappa is learned from all of them.

Two things the structure does not do. It does not explain why the units differ; why Northgate's true rate is 0.192 and Old Quay's is 0.090 is a causal question, and learn-causal-inference-with-phoebe owns it. And it does not tell you whether the units should be compared at all. That is a judgement about the family, made before any fitting, and the fit will pool whatever you hand it.

Part 3 · build-along

The fit, the ladder, then the same structure in PyMC 6

24 min build-along

Laptop out. Two scripts. The first is the empirical-Bayes fit with numpy and scipy only, and it should print mu 0.135, kappa 116 and the ladder 0.054, 0.031, 0.025 before you type anything else. The second is the PyMC 6 hierarchical block with the same structure, whose summary should put its central estimates for mu and kappa near what the grid found and give you an interval on each, which the grid never could.

Type the twelve outlets in. Counts, totals and true rates, from the map. The true rates are only used for scoring; the fit never sees them, and in your own work you will not have them.

Write the marginal log-likelihood. For a candidate (mu, kappa), a = mu kappa and b = (1 - mu) kappa, and each outlet contributes betaln(y + a, n - y + b) minus betaln(a, b). Sum across the twelve.

Search the grid. mu in steps of 1/200, kappa on 80 log-spaced points from 2 to 5000, the same grid the bench uses. The maximiser is mu = 0.135, kappa = 116. In the identical world it lands at 5000, the ceiling; in the wild world at 3.6.

Shrink and score. Weight w = n / (n + kappa); estimate = w times observed plus (1 - w) times mu. Print Northgate's weight, 0.13, and Westfield's, 0.89. Then the three RMSEs: 0.054, 0.031, 0.025.

Print the ledger. Error before and after for every outlet. Count the falls and the rises: six and six. Find Market Row, 0.000 to 0.007, and be able to say why.

Hand the structure to PyMC 6. A prior on mu, a prior on kappa, twelve rates drawn from Beta(mu kappa, (1 - mu) kappa), twelve Binomials. Sample, read R-hat and ESS, then read the rows for mu and kappa: an 89 percent equal-tailed interval on each, which is the thing the grid could not give you.

★ Run it now (Python) - the empirical-Bayes fit and the ladder# Halloway Bakeries, twelve outlets, one voucher, one month. True rates known by construction. import numpy as np from scipy.special import betaln outlets = ["Northgate", "Riverside", "Old Quay", "Hillcrest", "Market Row", "Station", "Abbey Green", "Parkside", "Docklands", "Kingsway", "The Arcade", "Westfield"] n = np.array([18, 24, 31, 40, 55, 70, 95, 140, 210, 320, 540, 900]) y = np.array([1, 4, 1, 9, 8, 9, 7, 22, 19, 67, 71, 127]) truth = np.array([0.192, 0.124, 0.090, 0.137, 0.145, 0.109, 0.098, 0.155, 0.098, 0.163, 0.128, 0.150]) def log_marginal(mu, kappa): # Beta-Binomial marginal likelihood of the twelve months, each outlet's rate integrated out a, b = mu * kappa, (1 - mu) * kappa return np.sum(betaln(y + a, n - y + b) - betaln(a, b)) mus = np.arange(1, 200) / 200 # 0.005 to 0.995 kappas = np.exp(np.linspace(np.log(2), np.log(5000), 80)) # 2 to 5000; 5000 is the grid ceiling grid = np.array([[log_marginal(m, k) for k in kappas] for m in mus]) i, j = np.unravel_index(grid.argmax(), grid.shape) mu_hat, kappa_hat = mus[i], kappas[j] print(f"mu {mu_hat:.3f} kappa {kappa_hat:.0f}") # mu 0.135 kappa 116 w = n / (n + kappa_hat) # 0.13 at Northgate, 0.89 at Westfield observed = y / n # no pooling complete = np.full(12, y.sum() / n.sum()) # complete pooling: the grand rate, 0.141 partial = w * observed + (1 - w) * mu_hat # partial pooling: Northgate 0.056 to 0.124 rmse = lambda est: np.sqrt(np.mean((est - truth) ** 2)) print(f"{rmse(observed):.3f} {rmse(complete):.3f} {rmse(partial):.3f}") # 0.054 0.031 0.025 # the ledger: six errors fall, six rise before, after = np.abs(observed - truth), np.abs(partial - truth) for name, b_, a_ in zip(outlets, before, after): print(f"{name:12s} {b_:.3f} to {a_:.3f}") # Northgate 0.136 to 0.068; Market Row 0.000 to 0.007
★ Then the same structure in PyMC 6 - a posterior over mu and kappa themselves# PyMC 6.3.2, ArviZ 1.3.0. n and y are the arrays above. import pymc as pm import arviz as az with pm.Model() as halloway: mu = pm.Beta("mu", 1.0, 1.0) # chain centre; the grid fixed it at 0.135 kappa = pm.HalfNormal("kappa", 200.0) # pooling strength; the grid fixed it at 116 rate = pm.Beta("rate", mu * kappa, (1 - mu) * kappa, shape=12) pm.Binomial("redeemed", n=n, p=rate, observed=y) dt = pm.sample() # nutpie when installed, 400 tuning steps print(az.rhat(dt)) # session 4's checks first print(az.ess(dt, sample_dims=["chain", "draw"])) # rows for mu, kappa and the twelve rates; intervals 89 percent equal-tailed by default print(az.summary(dt)) pc = az.plot_forest(dt) # plot_forest stays in ArviZ 1.x

The grid and the sampler should agree on the centre and disagree on the confidence. If the PyMC summary puts mu and kappa somewhere the grid did not, one of the two is wrong and the grid is the easier one to check. What the sampler adds is the width: an interval on kappa is the sentence "the data are this sure about how much to pool", and no single number, 116 or otherwise, can say that.

Check yourself

Three questions

The fit learns kappa = 116 on the Halloway data. What is kappa, and where did 116 come from?

Kappa is the pseudo-count of the learned Beta(mu kappa, (1 - mu) kappa) prior, in session 2's currency. It is the maximiser of the marginal Beta-Binomial likelihood across the twelve outlets, and it would be different on different data: the grid ceiling when the outlets are identical, 3.6 when they are wildly different.

In the world where the outlets are wildly different, the ladder reads 0.033, 0.145, 0.030. What happened?

The method's value is that kappa is read off the data. When the outlets differ wildly the fit learns a tiny kappa and gives almost all the weight back to each outlet's own month, so it cannot be dragged into the 0.145 that complete pooling suffers. Level with no pooling is the right answer in that world, and partial pooling found it without being told.

The empirical-Bayes grid and the PyMC hierarchical block have the same structure. What does the PyMC version add?

Empirical Bayes fixes mu and kappa at the grid maximum and treats them as known. The hierarchical model puts priors on them and samples all fourteen quantities together, returning an interval on kappa that no single number can express. The bench uses the grid because it is exact and fast; the hierarchical model is for the room that will ask how sure you are about 116.

Homework

Before session 7

45 min

Take the many-unit rate you brought from session 5, stores, regions, cohorts, segments, and run the first script on it with the truth line removed, because you will not have one. Report three things: the kappa the fit learned and each unit's weight n / (n + kappa); how far each unit's estimate moved; and whether the units that moved most are the ones with the least data. If they are, the method is doing what it should. If a large unit moved a long way, look at it before you trust anything. Then bring one comparison your area is about to make between two versions of something, because session 7 moves from estimating rates to deciding between two of them, and the probability that B beats A turns out not to be enough on its own.

Sources covered

What tonight maps to

✓ Statistical Rethinking, lectures 12 and 13 "Multilevel Models" and "Multilevel Adventures", from the 2024 course: partial pooling, shrinkage, and the pooling strength as a quantity to be estimated. Run here against a known truth in three worlds.
✓ The pooling ladder, scored against the truth No pooling 0.054, complete pooling 0.031, partial pooling 0.025 on the real data; 0.034, 0.0065, 0.0051 with identical outlets; 0.033, 0.145, 0.030 with wildly different ones. Partial pooling wins or ties everywhere.
✓ Kappa as learned pooling strength The empirical-Bayes fit by grid over (mu, kappa) maximising the marginal Beta-Binomial likelihood: mu = 0.135, kappa = 116 on the real data, the grid ceiling in the identical world, 3.6 in the wild one. Weights n / (n + 116): 0.13 at Northgate, 0.89 at Westfield.
✓ The honest half: six helped, six hurt Falls by tenths on thin outlets (Northgate 0.136 to 0.068, Hillcrest 0.088 to 0.021, Old Quay 0.058 to 0.023, Kingsway 0.046 to 0.027, Riverside 0.043 to 0.017, Abbey Green 0.024 to 0.010); rises by thousandths on thick ones (Market Row 0.000 to 0.007, Parkside 0.002 to 0.008, Station 0.020 to 0.024, Docklands 0.007 to 0.009; The Arcade and Westfield unchanged). Northgate still 0.124 against 0.192.
◐ The full hierarchical model in PyMC 6 Built here as a minimal block and named for what it adds: a posterior over mu and kappa. Session 10's bench uses the empirical-Bayes fit because it is exact and fast; session 8 checks whether a model like this could have produced the data.
◐ Pooling player effects for expected goals learn-ai-sports-with-phoebe b3 names Bayesian hierarchical models that pool player effects with shrinkage as what works for xG. Named there, taught here; this is the session that page points at.
· Why the outlets differ Not covered by design. Nothing tonight is causal; why Northgate's true rate is 0.192 and Old Quay's is 0.090 is learn-causal-inference-with-phoebe's territory. Tonight estimates the rates; it does not explain them.
· Frequentist estimation of the same twelve rates Not covered by design. learn-statistics-with-phoebe owns estimation and its intervals from the frequentist side; tonight's estimates are posterior means and the ladder is scored on them alone.

Session 6 cheat sheet

The ladder: 0.054, 0.031, 0.025No pooling, complete pooling, partial pooling. RMSE against the true rates. Lower is better.
mu = 0.135, kappa = 116The grid maximiser of the marginal Beta-Binomial likelihood across the twelve. Learned, not asserted.
Weight n / (n + kappa)0.13 at Northgate, 0.89 at Westfield. The prior is worth kappa pseudo-observations, session 2's currency.
Three worlds, three kappas116 on the real data; the grid ceiling when outlets are identical; 3.6 when they are wildly different. Complete pooling: 0.145 there.
Six helped, six hurtTenths on the thin outlets, thousandths on the thick ones. Market Row 0.000 to 0.007. Say both halves.
Northgate: 0.124 vs 0.192Error 0.136 to 0.068. Halved, not removed. Pooling cannot rescue the unit that truly stands apart.
betaln(y + a, n - y + b) - betaln(a, b)One outlet's marginal log-likelihood with a = mu kappa, b = (1 - mu) kappa. Sum twelve, search the grid.
What PyMC addsA posterior over mu and kappa themselves. An interval on kappa is "this sure about how much to pool"; no single number says that.