Distributions#

A collection of commonly used distributions and their properties.

Discrete Distributions #

Discrete distributions describe the likelihood of outcomes for discrete random variables, which can only take on a finite or countably infinite number of distinct values.

Bernoulli Distribution #

Example: Coin toss, heads (success) or tails (failure).

Binary, boolean output: 1 (success) with probability p, or 0 (failure) with probability q=1-p.

  • Mean: \(p\)

  • Variance: \(pq\)

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import scipy.stats as stats
import warnings

warnings.filterwarnings('ignore')

plt.rcParams['figure.figsize'] = (8, 4)
rng = np.random.default_rng(seed=42)
num_simulations = 10000

p = 0.7 # probability of getting a "success"
simulations = rng.binomial(n=1, p=p, size=num_simulations)
successes = np.count_nonzero(simulations == 1)
failures = np.count_nonzero(simulations == 0)
print(f'Successes: {successes}, Failures: {failures}')

sns.barplot(x=['Successes', 'Failures'], y=[successes, failures], palette=['#1f77b4', '#ff7f0e'])
plt.xlabel('Outcome')
plt.ylabel('Count')
plt.title('Bernoulli Distribution Simulation')
plt.show()
Successes: 7058, Failures: 2942
../_images/811ee95c7fc3db1a3181230d547aa4a9921db8a1a4a3d4f3b85995717868731b.png

Binomial Distribution #

Number of successful Bernoulli trials in a sequence.

Example: Calculating the probability of getting a specific number of heads in a fixed number of coin tosses.

Online Calculator

Parameters:

  • n: # of successes in a sequence of independent experiments, each asking a yes–no question

  • x: # of times for a specific outcome within n trials

  • p: probability of success or failure

  • q: probability of failure (1-p)

Properties:

  • Formula: \({n \choose x} p^x q^{n-x}\)

  • Mean: \(np\)

  • Variance: \(npq\)

fig, axs = plt.subplots(1, 2, figsize=(12, 4))

n = 100 # number of trials
p = 0.5 # probability of getting a "success" (heads)
simulations = rng.binomial(n=n, p=p, size=num_simulations)
plot_settings = {'bins': len(np.unique(simulations)), 'color': '#1f77b4', 'stat': 'density'}
sns.histplot(simulations, ax=axs[0], **plot_settings)
axs[0].set_title(f'Trials (n): {n}\nProbability of Success (p): {p}')

n = 100 # number of trials
p = 0.95 # probability of getting a "success" (heads)
simulations = rng.binomial(n=n, p=p, size=num_simulations)
plot_settings = {'bins': len(np.unique(simulations)), 'color': '#1f77b4', 'stat': 'density'}
sns.histplot(simulations, ax=axs[1], **plot_settings)
axs[1].set_title(f'Trials (n): {n}\nProbability of Success (p): {p}')
plt.tight_layout()
plt.show()
../_images/7e67fb24d6ce973af7299d6f55523264c4db61770d6400b27716f57f484eb85e.png

Negative Binomial Distribution #

Modeling the number of failed attempts before achieving a specified number of successes in a process.

Example: Determining the number of days until a certain number of people have been infected in a disease outbreak.

FFFSSFS = NegBin(3,p)=7

\( f(y) = \binom{k + r - 1}{k} q^{y - r} p^r \), where \(y = r, r + 1, \ldots\) Properties:

  • Mean: \( \frac{r}{p} \)

  • Variance: \( \frac{qr}{p^2} \)

r = 5 # number of successes
p = 0.5 # probability of getting a "success" (heads)
simulations = rng.negative_binomial(n=r, p=p, size=num_simulations)

sns.histplot(simulations, color='#1f77b4', discrete=True)
plt.xlabel('Number of Trials Until {} Successes'.format(r))
plt.ylabel('Count')
plt.title('Negative Binomial Distribution')
plt.show()
../_images/72a440619e37c1bb2fc3dd4d787af9068b6d55e5efd6ed081c048ee14aa0fb26.png

Geometric Distribution #

Number of Bernoulli trials until a success. This is a special case of the negative binomial distribution looking for the number of trials until the first success.

Example: Finding the probability that a customer will have to call a certain number of times before their issue is resolved.

Properties:

  • Mean: \( \frac{1}{p} \)

  • Variance: \(\frac{q}{p^2} \)

p = 0.5 # probability of getting a "success" (heads)
simulations = rng.geometric(p=p, size=num_simulations)

sns.histplot(simulations, color='#1f77b4', discrete=True)
plt.xlabel('Number of Trials Until First Success')
plt.ylabel('Count')
plt.title('Geometric Distribution')
plt.show()
../_images/6a8010b45ef26e80b06d72f28ef0982c871e3e184ee22c87f7ee210032587ff0.png

Poisson Distribution #

Example: Estimating the number of incoming calls to a call center in a fixed time period.

\( f(x) = \frac{\lambda^x e^{-\lambda}}{x!} \)

\( F(x) = e^{-\lambda} \sum_{i=0}^{x} \frac{\lambda^i}{i!} \)

Properties:

  • Mean: \( \lambda \)

  • Variance: \( \lambda \)

Eg. Pois(12), What is the prob of exactly 8? (use pmf) 128*e-12/8! =6.55%

lam = 5 # lambda - average rate of success (events per interval)
simulations = rng.poisson(lam=lam, size=num_simulations)

sns.histplot(simulations, color='#1f77b4', discrete=True)
plt.xlabel('Number of Events')
plt.ylabel('Count')
plt.title('Poisson Distribution')
plt.show()
../_images/255acc3cc490f98aac7c88dfb3d46f5795a1d8da59baa75f4f89e9658b1b587f.png

Continuous Distributions #

Normal Distribution #

Example: Modeling the distribution of scores on standardized tests such as the SAT.

All you need to know about Gaussian distribution is a great dive into the topic.

mu = 0 # mean
sigma = 1 # standard deviation
simulations = rng.normal(loc=mu, scale=sigma, size=num_simulations)

sns.histplot(simulations, color='#1f77b4')
plt.ylabel('Count')
plt.title('Normal Distribution')
plt.show()
../_images/54439fc29e83c76b1a4d5f7f7473dd389586bdbf1bc6d9b0973721c5a3532fe3.png

Log-Normal Distribution #

The Log-Normal distribution effectively models data that grows exponentially or has multiplicative rather than additive effects. If a random variable \(X\) is log-normally distributed, then \(Y = ln(X)\) has a normal distribution.

Example: Average call time at a call center.

Fun fact: You can get a log-normal distribution by exponentiating the normal distribution.

mu = 1
sigma = 0.5
normal_samples = rng.normal(loc=mu, scale=sigma, size=num_simulations)
log_normal_samples_direct = rng.lognormal(mean=mu, sigma=sigma, size=num_simulations)

sns.histplot(np.exp(normal_samples), color='#1f77b4', kde=True, label='Exp of Normal Samples', alpha=0.6)
sns.histplot(log_normal_samples_direct, color='#ff7f0e', kde=True, label='Lognormal Samples', alpha=0.6)
plt.xlabel('Value')
plt.ylabel('Count')
plt.title('Comparison of Log-normal Distributions')
plt.legend()
plt.show()
../_images/9e247968eda3a8d942cb88984a0a4e039d384bb5ba380c15ce0551b522703d76.png

Cauchy distribution#

Characterized by its heavy tails and undefined moments. It violates the law of large numbers since the sample mean of iid Cauchy variables does not converge to a finite value as the sample size increases.

The standard Cauchy distribution is a special case of the Student’s t-distribution with one degree of freedom.

Due to the undefined mean and variance, traditional statistical measures like the sample mean and variance are not reliable estimators for the Cauchy distribution. Alternative measures, such as the sample median and interquartile range, are more appropriate.​

The sum of two independent Cauchy-distributed variables is also Cauchy-distributed.

It is used in modeling phenomena where large deviations are more common than predicted by Gaussian models.

standard_cauchy = rng.standard_cauchy(size=num_simulations)
print(f"Limiting the graph to be between (-25, 25), but the actual sample includes values between ({np.min(standard_cauchy):0.2f}, {np.max(standard_cauchy):0.2f})")
standard_cauchy = standard_cauchy[(standard_cauchy > -25) & (standard_cauchy < 25)]

sns.histplot(standard_cauchy, color='#1f77b4')
plt.xlabel('Value')
plt.ylabel('Count')
plt.title('Standard Cauchy Distribution with Mode = 0')
plt.legend()
plt.show()
Limiting the graph to be between (-25, 25), but the actual draws include values between (-2329.17, 2207.21)
../_images/5507d4bf4d1e2ed0a1a1ba5c3f874eed8b20d694a5c4b4f5b457ff498bdc58ee.png

Chi-squared distribution #

Used primarily in hypothesis testing, and to a lesser extent for confidence intervals for population variance when the underlying distribution is normal. Not as often applied in the direct modeling of natural phenomena.

Goodness of Fit Test: Checking how well observed data fit a theoretical distribution.

Independence Test: Testing for independence between two categorical variables in a contingency table.

\( \chi^2 = \sum_{i=1}^{k} Z_i^2 \)

Where \( Z_1^2, ..., Z_k^2 \) are standard normal random variables and \(k\) is “degrees of freedom”

Z_1 = rng.standard_normal(num_simulations)**2
Z_2 = rng.standard_normal(num_simulations)**2
Z_3 = rng.standard_normal(num_simulations)**2
Z_4 = rng.standard_normal(num_simulations)**2

plot_settings = {'bins': 50, 'color': '#1f77b4', 'stat': 'density'}
fig, axs = plt.subplots(2, 2, figsize=(12, 6))
sns.histplot(Z_1, ax=axs[0, 0], **plot_settings)
axs[0, 0].set_title('Chi-squared (1 degree of freedom)')

sns.histplot(Z_1 + Z_2, ax=axs[0, 1], **plot_settings)
axs[0, 1].set_title('Chi-squared (2 degree of freedom)')

sns.histplot(Z_1 + Z_2 + Z_3, ax=axs[1, 0], **plot_settings)
axs[1, 0].set_title('Chi-squared (3 degrees of freedom)')

sns.histplot(Z_1 + Z_2 + Z_3 + Z_4, ax=axs[1, 1], **plot_settings)
axs[1, 1].set_title('Chi-squared (4 degrees of freedom)')

plt.tight_layout()
plt.show()
../_images/8cdde3609d724a8fee17ccfbf828c7d2cee9df2ffd37ba4f1e9753019afa5de4.png

Uniform Distribution #

Example: Rolling a dice.

low = 0 # lower boundary of the output interval
high = 1 # upper boundary of the output interval
simulations = rng.uniform(low=low, high=high, size=num_simulations)

sns.histplot(simulations, color='#1f77b4')
plt.xlabel('Value')
plt.ylabel('Count')
plt.title('Uniform Distribution')
plt.show()
../_images/9ecd9c33f1dd851b657f950e718c505988c4b5bf525beb2fa4217a3ceb883ca6.png

Gamma distribution#

Positive and skewed distributions. Great for modeling the time between events like wait times, failure times, or rainfall.

Commonly used in Bayesian analysis.

scale = 1 # scale parameter (1 / lambda)
simulations = rng.gamma(shape=1, scale=scale, size=num_simulations)

sns.histplot(simulations, color='#1f77b4', kde=True)
plt.xlabel('Value')
plt.ylabel('Count')
plt.title('Gamma / Exponential Distribution')
plt.show()
../_images/3d2f5b986084108d5ba3d686b54eaae2102a1cc52519f23aea8526c2a6b103bd.png

Exponential Distribution #

Modeling the time between events in a Poisson process, such as the time between arrivals of customers at a bank.

Example: Estimating the time until failure for electronic components that have a constant failure rate over time.

The exponential distribution is a special case of the gamma Distribution with a shape parameter 1.

scale = 1 # scale parameter (1 / lambda)
simulations = rng.exponential(scale=scale, size=num_simulations)

sns.histplot(simulations, color='#1f77b4', kde=True)
plt.xlabel('Value')
plt.ylabel('Count')
plt.title('Exponential Distribution')
plt.show()
../_images/e2973a9402282107dddc88733e25da96f805f4a22ad3fddab92a19c79cbf73b8.png

Tweedie Distribution#

A special case of an exponential distribution with a mass of data at zero.

The code comes from thequackdaddy’s tweedie package. I just took the relevant parts to generate random variables so I did not have to install the whole package.

Hide code cell source
def est_kappa(mu, p):
    kappa = np.where(
        p == 2,
        np.log(mu),
        mu ** (2 - p) / (2 - p)
    )
    return kappa

def est_gamma(phi, p, mu):
    mu = np.array(mu, dtype=float)
    return phi * (p - 1) * mu ** (p - 1)

def rvs(p, mu, phi, size=1000, random_state=42):
    p = np.array(p, ndmin=1)
    if not (p > 1).all() & (p < 2).all():
        raise ValueError('p only valid for 1 < p < 2')
    rate = est_kappa(mu, p) / phi
    scale = est_gamma(phi, p, mu)
    shape = -(2 - p) / (1 - p)
    N = stats.poisson(rate).rvs(size=size, random_state=random_state)
    mask = N > 0
    if not np.isscalar(scale) and len(scale) == len(mask):
        scale = scale[mask]
    if not np.isscalar(shape) and len(shape) == len(mask):
        shape = shape[mask]

    rvs = stats.gamma(
            a=N[mask] * shape,
        scale=scale).rvs(size=np.sum(mask), random_state=random_state)
    rvs2 = np.zeros(N.shape, dtype=rvs.dtype)
    rvs2[mask] = rvs
    return rvs2
p = 1.15 # Power parameter
mu = 5.0  # Mean
phi = 5.0  # Dispersion parameter

simulations = rvs(p, mu, phi, size=num_simulations)

sns.histplot(simulations, color='#1f77b4', kde=True)
plt.xlabel('Value')
plt.ylabel('Count')
plt.title('Tweedie Distribution')
plt.show()
../_images/47943a279c2d28747595c76f274d957202fc20a1a94de97f1c55896ce31d02de.png

Mixture Distribution #

dist1 = rng.normal(loc=0, scale=1, size=num_simulations)
dist2 = rng.normal(loc=2.5, scale=1, size=num_simulations)
dist3 = rng.normal(loc=5, scale=1, size=num_simulations)

fig, axs = plt.subplots(1, 2, figsize=(12, 4))
sns.histplot(dist1, color='#1f77b4', kde=False, ax=axs[0], label='dist1', bins=30)
sns.histplot(dist2, color='#ff7f0e', kde=False, ax=axs[0], label='dist2', bins=30)
sns.histplot(dist3, color='#2ca02c', kde=False, ax=axs[0], label='dist3', bins=30)
axs[0].set_ylabel('Count')
axs[0].set_title('Individual Normal Distributions')
axs[0].legend()

sns.histplot(np.hstack([dist1, dist2, dist3]), color='#1f77b4', kde=False, ax=axs[1], bins=30)
axs[1].set_ylabel('Count')
axs[1].set_title('Mixture Distribution')

plt.tight_layout()
plt.show()
../_images/02f24988a01b51b309dc3f9960115f4e4ea9a7553206f59233e489c5095d88a4.png

Beta Distribution #

The beta distribution is the conjugate prior probability distribution for the bernoulli, binomial, and negative binomial distributions which makes it uniquely useful in Bayesian statistics.

Here is an example adapted from John Cook’s “A Bayesian view of Amazon Resellers”.

We want to choose the best seller given the following ratings:

  • 100% positive out of 10 reviews

  • 98% positive out of 100 reviews

  • 97% positive out of 1000 reviews

To find the posterior estimate, we add 1 positive and 1 negative review to each.

# Three beta distributions
params = [
    (10, 0), 
    (98, 2), 
    (970, 30)
]
params = [(p[0]+1, p[1]+1) for p in params] # Add 1 every value
x = np.linspace(0, 1, 1000)

fig, axs = plt.subplots(1, 3, figsize=(12, 4))
for i, (a, b) in enumerate(params):
    mean, var, skew, kurt = stats.beta.stats(a, b, moments='mvsk')
    print(f"Beta({a}, {b}) -> Mean: {mean:.03f}, Variance: {var:.03f}, Skewness: {skew:.03f}, Kurtosis: {kurt:.03f}")
    
    y = stats.beta.pdf(x, a, b)
    axs[i].plot(x, y, label=f'α={a}, β={b}')
    axs[i].fill_between(x, y, alpha=0.2)
    axs[i].set_title(f'Beta Distribution (α={a}, β={b})')
    axs[i].set_xlabel('x')
    axs[i].set_ylabel('Density')
    axs[i].legend()

plt.tight_layout()
plt.show()
Beta(11, 1) -> Mean: 0.917, Variance: 0.006, Skewness: -1.553, Kurtosis: 2.977
Beta(99, 3) -> Mean: 0.971, Variance: 0.000, Skewness: -1.087, Kurtosis: 1.699
Beta(971, 31) -> Mean: 0.969, Variance: 0.000, Skewness: -0.342, Kurtosis: 2.734
../_images/9cce82c3e9a013c43292ec2a6f546ef25a7f53b9b910b162885db4ce05ca8b0e.png

Then to compare them, we find the probability that a sample from one Beta distribution is greater than a sample from the other Beta distribution.

\(P(X > Y)\) for \(X \sim \text{Beta}(\alpha_1, \beta_1)\) and \(Y \sim \text{Beta}(\alpha_2, \beta_2)\) is:

\( P(X > Y) = \int_0^1 \text{BetaCDF}(y; \alpha_1, \beta_1) \cdot \text{BetaPDF}(y; \alpha_2, \beta_2) \, dy \)

from scipy.integrate import quad

alpha1, beta1 = 99, 3
alpha2, beta2 = 971, 31

def integrand(y):
    return stats.beta.cdf(y, alpha1, beta1) * stats.beta.pdf(y, alpha2, beta2)

result, _ = quad(integrand, 0, 1) # Compute the integral
print(f"The probability that a sample from Beta({alpha1}, {beta1})\n is greater than a sample from Beta({alpha2}, {beta2}) is {result:.3f}")
The probability that a sample from Beta(99, 3)
 is greater than a sample from Beta(971, 31) is 0.403

We can also find an approximate estimate my sampling from the distributions.

size = 100000
b1 = stats.beta.rvs(alpha1, beta1, size=size)
b2 = stats.beta.rvs(alpha2, beta2, size=size)
result = len(b1[b1<b2]) / size
print(f"The probability that a sample from Beta({alpha1}, {beta1})\n is greater than a sample from Beta({alpha2}, {beta2}) is {result:.3f}")
The probability that a sample from Beta(99, 3) is greater than a sample from Beta(971, 31) is 0.404

Alternatively, a sorting algorithm was proposed in Ch. 4 of Bayesian Methods for Hackers.

Central Limit Theorem#

The distribution of sample means approximates a normal distribution as the sample size gets larger, regardless of the population’s distribution.

fig, axs = plt.subplots(1, 3, figsize=(14, 4))
for i, sample_size in enumerate([50, 500, 5000]):
    sample_means = [rng.lognormal(mean=1, sigma=0.7, size=10).mean() for _ in range(sample_size)]
    
    axs[i].hist(sample_means, bins=30, density=True, alpha=0.6, color='tab:blue', label=f'Mean: {np.mean(sample_means):0.02f}')
    
    # Fit and plot a normal distribution to compare
    mu, sigma = np.mean(sample_means), np.std(sample_means)
    x = np.linspace(min(sample_means), max(sample_means), 100)
    axs[i].plot(x, stats.norm.pdf(x, mu, sigma), color='tab:orange', label=f'Normal fit: μ={mu:.2f}, σ={sigma:.2f}')
    
    # Titles and labels
    axs[i].set_title(f'Sample Size = {sample_size}', fontsize=12)
    axs[i].set_xlabel('Sample Mean')
    axs[i].set_ylabel('Density')
    axs[i].legend()

plt.tight_layout()
plt.show()
../_images/1c9a478259314ca3f92f322f906ea76827e9d52b59f4a87cb7dee15a82b77765.png

Statistical Tests for Determining Distribution #

Quantile-Quantile Plots (QQ plots) #

Graphically compares two distributions. Recommended overview: StatQuest - Quantile-Quantile Plots (QQ plots), Clearly Explained!!!

data = rng.lognormal(mean=0.5, sigma=0.7, size=num_simulations)

plt.figure(figsize=(12, 4))

# Exponential Q-Q plot
plt.subplot(1, 2, 1)  # 1 row, 2 columns, 1st plot
stats.probplot(data, dist=stats.expon, plot=plt)
plt.title('Q-Q Plot Comparing Log-Normal Data Versus a Exponential Distribution')

# Log-normal Q-Q plot
plt.subplot(1, 2, 2)  # 1 row, 2 columns, 2nd plot
stats.probplot(data, dist=stats.lognorm(s=0.7), plot=plt)
plt.title('Q-Q Plot Comparing Log-Normal Data Versus a Log-normal Distribution')

plt.tight_layout() 
plt.show()
../_images/48d0b566853a681de097837283e45ece604dba87c2fb47aad594f86595d02b33.png