PyMC: Bayesian Modeling with MCMC and Variational Inference in Python
Bayesian Modeling and Probabilistic Programming in Python
At a glance
- What is it?
- PyMC is a Python library for Bayesian statistical modeling built on PyTensor, offering the No U-Turn Sampler and ADVI. It suits analysts who need full posterior distributions rather than point estimates, and it costs you a sampling budget.
- Who is it for?
- Adopt PyMC when you need a posterior distribution, not a coefficient table, and your model is small enough to sample. Skip it when you only need point predictions or when your dataset is large enough that minibatch ADVI becomes the only affordable option.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What PyMC solves, and who ends up using it
Frequentist regression returns a point estimate and a confidence interval derived from assumptions about the data-generating process. PyMC returns draws from the posterior distribution of every parameter, which means uncertainty propagates into predictions instead of being bolted on afterward. The README frames the package as "a Python package for Bayesian statistical modeling focusing on advanced Markov chain Monte Carlo (MCMC) and variational inference (VI) algorithms." The intended audience is researchers and analysts who can write down a generative story for their data and want the fitting machinery handled for them.
The syntax is the selling point. A normal prior is written as pm.Normal("betas", dims="features"), and the README notes the mapping directly: "x ~ N(0,1) translates to x = Normal('x',0,1)". Anyone who has read a statistics paper can transcribe it into code without learning a new probabilistic language. That matters because the alternative is often Stan, whose modeling language is separate from the host language and requires its own toolchain.
Where PyMC earns its keep is in models that are awkward elsewhere: hierarchical structures with partial pooling, measurement error, missing value imputation (which the README lists as "transparent support"), and counterfactual questions answered with pm.do and pm.observe.
The PyTensor backend and what the sampler actually does
PyMC does not compute gradients itself. It builds a symbolic graph through PyTensor, which the README describes as providing "computation optimization and dynamic C or JAX compilation" along with NumPy broadcasting, advanced indexing, and linear algebra operators. Every model you write becomes such a graph, and the sampler differentiates through it. This is the architectural decision that shapes everything else: you get speed from compiled C or JAX kernels, and you inherit PyTensor's constraints when you write custom distributions.
The default inference path is the No U-Turn Sampler, a variant of Hamiltonian Monte Carlo that the README cites alongside the paper by Hoffman and Gelman. NUTS needs gradients, so it only works on continuous parameters. Discrete latent variables force you into alternatives such as pm.Metropolis or a marginalization rewrite. This is a real constraint, not a footnote, and it is the first thing that bites people porting a model with a latent categorical variable.
The second path is variational inference. ADVI approximates the posterior with a simpler distribution and optimizes the fit, which the README presents as "fast approximate posterior estimation as well as mini-batch ADVI for large data sets." The trade is explicit: you get an answer in seconds or minutes instead of hours, and you get an approximation whose quality you cannot check with r_hat because there are no chains in the MCMC sense.
Data flows through pm.Data containers that can be swapped after fitting. The README's example calls pm.set_data({"x": new_x_data}, coords=new_coords) inside the inference model context, then pm.sample_posterior_predictive with extend_inferencedata=True to attach predictions to the existing idata object. That pattern, fit once and predict under new covariates or new interventions, is the workflow the documentation pushes hardest.
Installing PyMC and running the README's regression example
The README does not spell out an install command; it points to the PyMC overview, the API quickstart guide, and the examples gallery. The repository carries a requirements.txt pinning the runtime dependencies, including pytensor>=3.2.2,<3.4, arviz>=1.1.0,<2.0, and numpy>=1.25.0, plus conda-envs/ and binder/ directories for environment definitions. The conventional install is through pip or conda, and the pinned PyTensor range tells you the backend version is tightly coupled to the PyMC release.
pip install pymcAfter that, the smallest real use is the generative model from the README. Note that pm.Data registers the design matrix as a replaceable container, and dims ties each parameter to a named coordinate.
import pymc as pm
coords = {
"trial": range(100),
"features": ["sunlight hours", "water amount", "soil nitrogen"],
}
with pm.Model(coords=coords) as generative_model:
x = pm.Data("x", x_data, dims=["trial", "features"])
betas = pm.Normal("betas", dims="features")
sigma = pm.HalfNormal("sigma")
mu = x @ betas
plant_growth = pm.Normal("plant growth", mu, sigma, dims="trial")To fit, the README wraps the model in pm.observe and calls pm.sample, then prints a summary table with pm.stats.summary(idata, var_names=["betas", "sigma"]). In the README's own run, the recovered means land at 4.972, 19.963, and 1.994 against fixed values of 5, 20, and 2, with r_hat of 1 and ess_bulk in the low thousands. Those columns are what you read first: r_hat near 1 and large effective sample sizes mean the chains agree.
with pm.observe(generative_model, {"plant growth": synthetic_y}) as inference_model:
idata = pm.sample(random_seed=seed)
summary = pm.stats.summary(idata, var_names=["betas", "sigma"])
print(summary)Counterfactuals use pm.do, which fixes parameters to values and produces a new model. The README multiplies the first beta by [0, 1, 1] to simulate a world where sunlight has no effect, then samples posterior predictive draws from that modified graph.
Where PyMC gets in your way
Sampling cost is the obvious one. NUTS evaluates the gradient of the log posterior at every leapfrog step, and a model with thousands of parameters over a large dataset can take hours per fit. The README's own feature list concedes the point by offering mini-batch ADVI for large data sets, which is an admission that MCMC does not scale to them.
Discrete parameters are the second wall. NUTS requires differentiability, so a model with a latent categorical variable either marginalizes it analytically or falls back to a gradient-free sampler that mixes poorly in high dimensions. The README does not document a general workaround; it lists the algorithms and leaves the modeling decision to you.
Diagnostics are the third. PyMC hands you r_hat, ess_bulk, ess_tail, and mcse_mean, and it will happily return a summary table for a model that never converged. Nothing in the package stops you from reporting posterior means from a chain that is still drifting. Divergences appear as warnings rather than errors in many workflows, and interpreting them requires understanding the geometry of the posterior, not just reading a number.
Finally, the PyTensor dependency range is narrow: pytensor>=3.2.2,<3.4. If another package in your environment pins a different PyTensor, you resolve it by changing PyMC's version, not by loosening the bound.
PyMC against Stan and against scikit-learn style estimation
Stan is the closest analogue and the most instructive comparison. Stan defines models in its own language, compiles them to C++, and exposes interfaces for Python, R, and the command line. PyMC keeps the model in Python, so you can loop over specifications, pull priors from a database, or wrap a model inside a larger pipeline without a code-generation step. The cost is that PyMC's performance depends on PyTensor's graph optimization and JAX or C compilation, while Stan's compiler is tuned for its own language. If your team already writes Stan and values the separation between modeling code and host-language code, PyMC is not obviously better.
Against scikit-learn, the difference is philosophical. scikit-learn estimators fit parameters and expose predict. PyMC fits distributions and exposes sample_posterior_predictive. If your deliverable is a ranking or a label, scikit-learn is faster and simpler. If your deliverable is a statement like "the effect is 5.0 with a 94% highest density interval of 4.87 to 5.07," PyMC is the tool that produces it.
Within the PyMC family, the ecosystem splits by domain. The related searches point at PyMC-Marketing for marketing mix modeling and PyMC-extras for additional functionality, but those are separate packages with their own release cycles. Installing PyMC does not install them.
Maintenance, releases, and the licence question
The repository is not archived, and the last push was on 2026-09-28. Releases have been frequent: v6.3.0 on 2026-08-12, v6.3.1 on 2026-08-16, and v6.3.2 on 2026-09-08. That cadence means minor versions arrive within weeks of each other, so pinning a version in a production environment is worth the effort.
The licence metadata is the part to check yourself. The repository's LICENSE file exists, and setup.py carries the header "Licensed under the Apache License, Version 2.0" with the classifier "License :: OSI Approved :: Apache Software License". The GitHub API reports the licence as NOASSERTION, which means the automated detector could not classify it. Those two signals disagree, and the discrepancy is worth resolving against the LICENSE file before you ship. Apache 2.0 includes an explicit patent grant and requires attribution; if your legal team has questions about the NOASSERTION flag, that is a conversation with them, not something this article can settle.
Upgrade cost is dominated by PyTensor. Because PyMC pins pytensor>=3.2.2,<3.4, a PyTensor major release forces a coordinated PyMC upgrade. Custom distributions written against PyTensor internals are the code most likely to break.
Editorial conclusion
Adopt PyMC when you need a posterior distribution, not a coefficient table, and your model is small enough to sample. Skip it when you only need point predictions or when your dataset is large enough that minibatch ADVI becomes the only affordable option. Before committing, run the README's linear regression example end to end and inspect the r_hat and ess_bulk columns in pm.stats.summary; if those diagnostics do not converge on your own data, the sampler is telling you the model is not identified yet.
Frequently asked questions
What does PyMC stand for and what is it used for?
PyMC is the current name of the project formerly called PyMC3, and the README describes it as a Python package for Bayesian statistical modeling. It is used to specify generative models and fit them with MCMC or variational inference algorithms.
What is Bayesian probabilistic modeling and how is it used in PyMC?
In PyMC you write a model by assigning distributions to parameters and to the observed data, as in betas = pm.Normal("betas", dims="features"). Fitting returns draws from the posterior, which pm.stats.summary reports as means, standard deviations, and highest density intervals.
How can I use a Bayesian model in Python with PyMC?
Define the model inside a with pm.Model(coords=coords) block, wrap it with pm.observe to attach observed data, and call pm.sample to draw from the posterior. The README's linear regression example walks through exactly that sequence.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pymc-devs-pymc)