Apply Bayesian methods to make better decisions with uncertainty. Covers prior selection, posterior inference, Bayesian A/B testing, credible intervals, hierarchical models, and the patterns that quantify what you don't know as precisely as what you do.
Bayesian statistics enables data scientists to incorporate prior knowledge, quantify uncertainty, and update beliefs incrementally as new data arrives. It matters when decisions must be made under uncertainty, when data is sparse or noisy, and when models need to adapt over time — from A/B test interpretation to real-time anomaly detection and model deployment. Unlike classical frequentist statistics, Bayesian methods treat parameters as random variables and yield full posterior distributions, not just point estimates and p-values.
To estimate a parameter — say, the conversion rate of a new signup flow — start with a prior belief about the value, then update it with observed data. The prior is specified as a probability distribution over the parameter space.
For example, suppose you believe the conversion rate is around 30% with moderate confidence. Use a beta distribution, the conjugate prior for binomial likelihoods:
import numpy as np
from scipy.stats import beta
prior = beta(a=6, b=14)
# Observed data: 120 conversions out of 400 users
data = np.random.binomial(n=400, p=0.3, size=10000) # Simulated data
conversions = 120
trials = 400
# Posterior: Beta(α+conversions, β+trials-conversions)
posterior = beta(a=6 + conversions, b=14 + trials - conversions)
The posterior distribution now represents your updated belief. Extract summaries:
posterior_mean = posterior.mean()
posterior_std = posterior.std()
credible_interval = posterior.interval(0.95)
The 95% credible interval gives a range where the true conversion rate lies with 95% probability. Unlike frequentist confidence intervals, this interval directly answers: “What is the probability that the conversion rate is between X and Y?”
In streaming systems, new data arrives continuously. Bayesian inference supports sequential updating: each new observation updates the posterior, which becomes the prior for the next round.
Use a streaming pipeline that maintains the current posterior parameters. For instance, in a data processing job, store the posterior as a record in a database:
CREATE TABLE parameter_posteriors (
id UUID PRIMARY KEY,
model_name TEXT NOT NULL,
parameter_name TEXT NOT NULL,
alpha DOUBLE PRECISION NOT NULL,
beta DOUBLE PRECISION NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
When new data arrives, update the posterior:
def update_posterior(prior_alpha, prior_beta, new_conversions, new_trials):
new_alpha = prior_alpha + new_conversions
new_beta = prior_beta + new_trials - new_conversions
return new_alpha, new_beta
# Example: process 100 new users
alpha, beta = 6, 14
conversions, trials = 32, 100
alpha, beta = update_posterior(alpha, beta, conversions, trials)
# Save to database
psql -c """
INSERT INTO parameter_posteriors (id, model_name, parameter_name, alpha, beta, updated_at)
VALUES ('a1b2c3', 'signup_conversion', 'rate', $1, $2, NOW())
ON CONFLICT (model_name, parameter_name)
DO UPDATE SET alpha = EXCLUDED.alpha, beta = EXCLUDED.beta, updated_at = NOW();
"""
The system now supports real-time belief updating. A dashboard can show the evolving posterior, and alerts trigger when credible intervals narrow or shift.
When choosing between models — e.g., linear vs. logistic regression for a binary outcome — compute the marginal likelihood (evidence) of each model.
The marginal likelihood integrates the likelihood over the prior:
\[ p(D \mid M) = \int p(D \mid \theta, M) p(\theta \mid M) \, d\theta \]
For discrete models, use the Bayes factor:
import numpy as np
from scipy.integrate import quad
# Model M1: logistic regression with prior on coefficients
# Model M2: linear regression with prior on coefficients
def marginal_likelihood(data, prior, likelihood):
# Compute: ∫ p(data|θ) p(θ) dθ
# Use numerical integration (e.g., quadrature)
def integrand(theta):
return likelihood(data, theta) * prior.pdf(theta)
integral, error = quad(integrand, -np.inf, np.inf)
return integral
# Example: compare two models for conversion prediction
marginal_M1 = marginal_likelihood(data, prior_logistic, likelihood_logistic)
marginal_M2 = marginal_likelihood(data, prior_linear, likelihood_linear)
bayes_factor = marginal_M1 / marginal_M2
A Bayes factor > 3 indicates substantial evidence for M1 over M2. Use this to decide which model to deploy.
When data is grouped — users by region, products by category — use hierarchical (or multilevel) models to share information across groups.
For example, estimate conversion rates per region, with global and regional priors:
The model assumes:
\[ \theta_i \sim \text{Beta}(\alpha_i, \beta_i), \quad \alpha_i \sim \text{Gamma}(a, b), \quad \beta_i \sim \text{Gamma}(c, d) \]
In code:
import pymc3 as pm
with pm.Model() as hierarchical_model:
# Global hyperpriors
mu_alpha = pm.Gamma('mu_alpha', alpha=1, beta=1)
sigma_alpha = pm.HalfNormal('sigma_alpha', sigma=1)
mu_beta = pm.Gamma('mu_beta', alpha=1, beta=1)
sigma_beta = pm.HalfNormal('sigma_beta', sigma=1)
# Regional parameters
alpha_i = pm.Gamma('alpha_i', mu=mu_alpha, sigma=sigma_alpha, shape=num_regions)
beta_i = pm.Gamma('beta_i', mu=mu_beta, sigma=sigma_beta, shape=num_regions)
# Observed data per region
conversions = pm.Beta('conversions', alpha=alpha_i, beta=beta_i, observed=regional_conversions)
# Sample from posterior
trace = pm.sample(2000, tune=1000)
The posterior trace now contains full distributions for each region’s conversion rate, with partial pooling: regions with little data borrow strength from the global prior.
Bayesian models predict not just point estimates but full distributions over future outcomes.
Given a posterior distribution over parameters, generate predictions:
def predictive_distribution(trace, new_data):
predictions = []
for sample in trace:
# For each posterior sample
alpha = sample['alpha_i']
beta = sample['beta_i']
# Simulate new data
new_conversions = np.random.beta(alpha, beta, size=new_data.shape[0])
predictions.append(new_conversions)
return np.stack(predictions) # Shape: (num_samples, num_new_data)
# Example: predict conversion for 100 new users
new_data = np.ones(100)
predictions = predictive_distribution(trace, new_data)
# Compute credible intervals for each prediction
posterior_predictive = np.mean(predictions, axis=0)
prediction_interval = np.percentile(predictions, [2.5, 97.5], axis=0)
Use these predictions for decision-making: optimize for expected utility, or compute the probability that a new campaign exceeds a threshold.
Each step is composable: the posterior from one task becomes the prior for the next. This end-to-end workflow is the foundation of Bayesian thinking in data science.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy