Where tonight sits
Session 1 wrote the posterior by hand: a + y and b + n - y. Session 3 turned the posterior into samples and read summaries off them as quantiles. Session 4 explained why a sampler is needed at all and what R-hat and effective sample size are checking. Tonight puts all of that behind one call to pm.sample() and asks you to trust it only as far as the diagnostics allow. Session 6 uses the same tool on twelve outlets at once. The running example is still Halloway Bakeries, and tonight's outlet is Westfield, the flagship, because 900 vouchers is the case where the prior matters least and the tooling matters most.
★ What you walk out with tonight. One complete PyMC 6 script for the Westfield model, short enough to retype: imports, a model block, prior predictive, sample, diagnostics, summary, plot. The map from every name a 2023 notebook uses to the name the tool uses now: pymc3 to pymc, InferenceData to DataTree, plot_posterior to plot_dist, hdi_prob to prob. And the one default that silently changes every number: the summary interval is 89 percent equal-tailed, cut at the 5.5 and 94.5 percent quantiles, where it used to be a 94 percent HDI.
LiveThe tool as it is on 18 September 20264 min▶
PyMC is at 6.3.2, released on 8 September 2026. The 6.0.0 release shipped on 13 May 2026 and it was a breaking one: PyMC 6 is built on PyTensor 3.0, the default compute backend switched to numba, and if nutpie is installed it is the default NUTS sampler, tuning for 400 steps where PyMC's own NUTS tunes for 1000. It requires Python 3.12 or later. The install that gets you the default sampler is pip install pymc[nutpie].
ArviZ is at 1.3.0 and is no longer one library. It is three, arviz-base for data and converters, arviz-stats for diagnostics, arviz-plots for plotting, all reached through the same import arviz as az. The result container changed with it: arviz.InferenceData no longer exists and what pm.sample() returns is an xarray.DataTree. Existing netCDF and zarr files still load, because az.from_netcdf now wraps open_datatree, and a group is reached as dt["posterior"].dataset.
Halloway's analyst inherited a voucher notebook that began import pymc3 as pm. On the current tool that line fails at import, which is the kind version of the problem. The unkind version is the notebook that imports cleanly, runs, and reports intervals cut under a default that changed underneath it while the prose still describes the old one. Tonight's session exists so that the second thing does not happen to you.
Self-studyThe words that will keep coming back4 min▶
- PyTensor 3.0. The tensor library PyMC 6 compiles your model with. Its default backend is numba; pytensor.config.linker = "cvm" reverts to the old one.
- nutpie. The NUTS sampler PyMC 6 hands the work to when it is installed. 400 tuning steps by default against 1000 for PyMC's own NUTS.
- xarray.DataTree. The container a sample call returns. Groups such as the posterior are reached as dt["posterior"].dataset. It replaced InferenceData.
- arviz-base, arviz-stats, arviz-plots. The three libraries that make up ArviZ 1.3.0. You do not import them separately for ordinary work; import arviz as az reaches all three.
- PlotCollection. What every ArviZ 1.x plot returns. Not axes.
- prob. The renamed hdi_prob. The interval level argument on summaries and plots.
- stats.ci_prob and stats.ci_kind. The two rcParams that set the default interval: 0.89 and "eti". They were 0.94 and HDI.
- Equal-tailed interval. The 5.5 and 94.5 percent quantiles of the posterior draws. Every interval in these sessions is one, and every page says so.
Eight boxes between your model and your picture
7 min liveYou write the model block and you read the summary table. Everything in between is other people's software, and in 2026 almost every box in that chain has a new name or a new default. The figure is the chain as it is now. Learn it once and the renames in Part 2 stop being trivia and become a map of where the work moved.
LiveWhat PyMC 6.0 broke, and which defaults now decide your run4 min▶
Three of the changes in the 6.0.0 release notes touch every model. The compute backend is numba, through PyTensor 3.0; you can revert to the previous one with pytensor.config.linker = "cvm", and you will want to know that line exists the first time a compile surprises you. The sampler is nutpie whenever it is installed, which is why the install line is pip install pymc[nutpie] rather than plain pymc. And nutpie tunes for 400 steps by default where PyMC's own NUTS tunes for 1000, so a run on the new default is doing less warm-up than the same run a year ago, which is precisely why session 4's diagnostics are read every time rather than trusted once.
Two smaller changes bite specific lines. sample_prior_predictive lost its deprecated samples argument, so a notebook that asks for a particular number of prior draws by that name stops at that line. sample_posterior_predictive gained sample_vars and freeze_vars, which session 8 uses. Several functions were also removed from the root namespace. None of this is exotic; it is the ordinary churn of a tool at a major version, and the fix is to write the model for the tool you have rather than the one the notebook remembers.
LiveThe result is a DataTree, and you read it by group3 min▶
What pm.sample() hands back is an xarray.DataTree. It holds groups, and the one you want most nights is the posterior, reached as dt["posterior"].dataset: an xarray dataset with a chain dimension and a draw dimension, which is exactly the shape session 4 said a set of chains has. The diagnostics are written for that shape. az.rhat(dt) and az.ess(dt, sample_dims=["chain", "draw"]) take the tree directly, and after import arviz_stats the same effective sample size is one accessor away as dt.azstats.ess().
The old container, arviz.InferenceData, is gone from the tool, but the files it wrote are not stranded. az.from_netcdf now wraps open_datatree, so a result saved last year opens as a DataTree this year. The thing that does not survive the move is code that reaches into the old container by its old attributes. The honest rule for a migration is to load the file, look at the tree, and rewrite the reads.
Self-studyThe interval default changed, and it matters for every number4 min▶
The rename everyone notices is hdi_prob to prob. The change that matters is in the defaults behind it. Two rcParams now govern every summary interval: stats.ci_prob is 0.89, where it was 0.94, and stats.ci_kind is "eti", an equal-tailed interval, where it was HDI. The migration guide's reason for the level: "Using 0.89 produces intervals with lower variability, leading to more stable summaries." So a summary table produced today, with no arguments, reports an 89 percent interval cut at the 5.5 and 94.5 percent quantiles of the draws. The same call in 2023 reported a 94 percent highest-density interval.
That is not a cosmetic difference. A third party, the CoursIA repo in its issue #15592, documented two notebooks whose intervals silently became equal-tailed on upgrade while the prose beside them still said HDI. The code ran, the numbers changed, the words did not. Every interval in these sessions is therefore stated as what it is, an 89 percent equal-tailed interval, and the sentence you write for anyone else should carry both the level and the kind. If you need a different interval, pass prob and say so in the text; if you take the default, say that too. What you may not do is leave the reader to assume.
Every old name, its new name, and the one that changes your numbers
6 min liveMost of the renames are one-for-one and a search will fix them. Two are not. The plot that used to be one call is now two, and the interval that used to be 94 percent HDI is now 89 percent equal-tailed. The left column is what a notebook copied in 2023 says. The right column is the tool.
LiveThe walk: model, prior predictive, sample, diagnose, summarise, plot3 min▶
Six moves, always in this order. Model: inside with pm.Model(), a pm.Beta for the rate with the flat Beta(1, 1) of session 2, two pseudo-observations, and a pm.Binomial with n=900 and observed=127. Prior predictive: pm.sample_prior_predictive() before touching the data, to see what counts out of 900 the prior alone considers plausible; under a flat prior that is every count from 0 to 900 equally, which is what "flat" means and why session 2 counts it as worth only two pseudo-observations. Sample: pm.sample(), which goes to nutpie if it is installed. Diagnose: az.rhat(dt) and az.ess(dt, sample_dims=["chain", "draw"]), read the way session 4 taught, before anything is reported. Summarise: az.summary(dt), one row for the rate. Plot: az.plot_dist, the picture of the posterior.
The number you are checking against is 0.142, the flat-prior posterior mean at Westfield that session 1 got with a + 127 and b + 773. If the summary row says something else, the model is wrong, not the arithmetic. That is the whole reason to run the tool on a case whose answer you already hold: the first model is a test of the tooling, and only after it passes does the tool get to tell you anything you did not already know.
LiveReading the summary row without importing another vocabulary3 min▶
The row az.summary(dt) prints for the rate has three kinds of column, and all of them are statements about the posterior draws. Location and spread: the posterior mean, which at Westfield under the flat prior is 0.142, and the spread of the draws around it. The interval: two columns giving the ends of the default interval, which in ArviZ 1.3.0 is 89 percent equal-tailed, the 5.5 and 94.5 percent quantiles of the draws. Read them as session 3 taught: an 89 percent probability, given this prior and these 900 vouchers, that the rate sits between them. The diagnostics: effective sample size and R-hat, the session 4 checks, telling you how much to trust the columns to their left.
What the row does not contain is anything from the other tradition. There is no confidence interval on it and no p-value, and the interval columns must not be read as either; learn-statistics-with-phoebe owns those words and the procedures behind them. The credible interval is the sentence everybody thinks a confidence interval is, and the summary row is where that sentence's two numbers come from. Say the level and say the kind: "0.142, with an 89 percent equal-tailed interval." Both halves are defaults, and defaults change.
Self-studyThe notebook copied in 20233 min▶
Three words tell you a notebook predates the tool: pymc3, InferenceData, hdi_prob. Any one of them is enough. The first is a dead import and will not run. The second names a container that no longer exists. The third names an argument that was renamed, and its presence means every interval in the notebook was cut under the old default, a 94 percent HDI, so none of its reported intervals matches what the current tool prints for the same model. McElreath's own 2024 course page still lists "Python with PyMC3"; do not copy that either.
The fix is not to patch the three lines. It is to rerun the model on the current tool, read the new summary, and rewrite every sentence that quoted an interval, because the level and the kind both changed. A notebook whose code was patched and whose prose was not is exactly the case the CoursIA issue documented: correct-looking output under a description that is no longer true of it. If you are handed such a notebook, the two-line reply is "this predates PyMC 6 and ArviZ 1; the intervals need recutting and restating before anyone quotes them."
Westfield, end to end, in one script
24 min build-alongLaptop out. Python 3.12 or later, then pip install pymc[nutpie], which brings ArviZ with it. The script below is the whole session in a form you can retype. Every number in it is in a comment, because the point is that the tool reproduces them, not that you type them in.
Check the versions before you write a line. PyMC should report 6.3.2 or later and ArviZ 1.3.0 or later. If PyMC reports 5-something, the defaults in this session do not apply and the summary interval will not be what the script's comments say.
Write the model block. A Beta(1, 1) prior on the rate, two pseudo-observations, and a Binomial with n = 900 and 127 observed. Nothing else. The first model is a test of the plumbing and the plumbing should have nowhere to hide.
Sample the prior predictive first. With no data in play, what does the model think 900 vouchers could produce? Under the flat prior, any count at all. That is what you asked for, and seeing it is the check that the prior you wrote is the prior you meant.
Sample, then read R-hat and effective sample size before anything else. nutpie has tuned for 400 steps, not 1000. Session 4's checks are the reason you are allowed to trust the next two lines.
Read the summary row. The posterior mean should be 0.142 to three decimals. The interval columns are the 5.5 and 94.5 percent quantiles: 89 percent, equal-tailed, the default. Say both when you quote them.
Plot the posterior with plot_dist. It returns a PlotCollection, not axes, so code that reaches for an axes object afterwards needs changing. Look at the picture; it should be a narrow hill around 0.142, which is what 900 vouchers buy.
If the summary does not say 0.142, stop. The flat-prior posterior mean at Westfield is known from session 1, so a different number is a wrong model, a wrong observed count, or a tool that is not the one this session describes. Run print(pm.__version__) before you debug anything else; a notebook that imports cleanly on PyMC 5 will produce a different interval and call it by the wrong name.
Three questions
A colleague sends a voucher notebook whose first line is import pymc3 as pm and whose summaries pass hdi_prob=0.94. What do those two facts tell you?
pymc3 is a dead import, InferenceData no longer exists, and hdi_prob was renamed prob. The rcParams stats.ci_prob and stats.ci_kind moved the default from a 94 percent HDI to an 89 percent equal-tailed interval, so a patched notebook that keeps its old prose is exactly the silent failure the CoursIA issue documented.
You install with pip install pymc[nutpie] and call pm.sample() with no arguments. Which sampler runs, and how long does it tune?
PyMC 6 made nutpie the default when it is present, with 400 tuning steps by default against 1000 for PyMC's NUTS. Less warm-up by default is one more reason R-hat and effective sample size are read on every run.
az.summary(dt) prints two interval columns for Westfield's rate. With no arguments passed, what are they?
The default level is 0.89 and the default kind is equal-tailed, both set by rcParams that changed in ArviZ 1.x. The columns are quantiles of the draws and describe the posterior; they are not a confidence interval, and the summary row does not contain one.
Before session 6
40 minRun tonight's script unchanged and confirm 0.142. Then replace 127 and 900 with a count and a total from your own work, a rate you have quoted from a single group, and run it again. Write the one sentence you would send about it, with the level and the kind of interval stated: "an 89 percent equal-tailed interval." Bring the sentence, and bring the twelve-group version of your data if you have one, because session 6 fits all twelve at once and lets the eleven inform the twelfth.