Skip to content

Probability Distributions

Numra’s numra-stats crate provides a rich collection of probability distributions, both continuous and discrete. Each distribution implements a common trait with PDF/PMF, CDF, quantile, mean, variance, and random sampling.

All continuous distributions implement ContinuousDistribution<S>:

MethodSignatureDescription
pdf(x)fn pdf(&self, x: S) -> SProbability density function
cdf(x)fn cdf(&self, x: S) -> SCumulative distribution function P(Xx)P(X \le x)
quantile(p)fn quantile(&self, p: S) -> SInverse CDF (p[0,1]p \in [0, 1])
mean()fn mean(&self) -> SDistribution mean
variance()fn variance(&self) -> SDistribution variance
std_dev()fn std_dev(&self) -> SStandard deviation (variance\sqrt{\text{variance}})
sample(rng)fn sample(&self, rng) -> SGenerate a single random sample
sample_n(rng, n)fn sample_n(&self, rng, n) -> Vec<S>Generate nn random samples

Discrete distributions implement DiscreteDistribution<S>:

MethodSignatureDescription
pmf(k)fn pmf(&self, k: usize) -> SProbability mass function
cdf(k)fn cdf(&self, k: usize) -> SCumulative P(Xk)P(X \le k)
mean()fn mean(&self) -> SDistribution mean
variance()fn variance(&self) -> SDistribution variance
sample(rng)fn sample(&self, rng) -> usizeGenerate a single random sample
sample_n(rng, n)fn sample_n(&self, rng, n) -> Vec<usize>Generate nn random samples

The most fundamental continuous distribution. Parameterized by mean μ\mu and standard deviation σ\sigma:

f(x)=1σ2πexp ⁣((xμ)22σ2)f(x) = \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)
use numra::stats::{Normal, ContinuousDistribution};
let n = Normal::new(0.0_f64, 1.0); // standard normal N(0, 1)
// or equivalently:
let n = Normal::<f64>::standard();
// PDF at the mean is the peak value
let peak = 1.0 / (2.0 * std::f64::consts::PI).sqrt();
assert!((n.pdf(0.0) - peak).abs() < 1e-12);
// CDF: P(X <= 0) = 0.5 for standard normal
assert!((n.cdf(0.0) - 0.5).abs() < 1e-12);
// Quantile: inverse CDF
let x = n.quantile(0.975);
assert!((x - 1.96).abs() < 0.01); // 97.5th percentile ~ 1.96
// Mean and variance
assert!((n.mean()).abs() < 1e-14);
assert!((n.variance() - 1.0).abs() < 1e-14);

Used for hypothesis testing and confidence intervals with small samples. Parameterized by degrees of freedom ν\nu:

f(x)=Γ ⁣(ν+12)νπ  Γ ⁣(ν2)(1+x2ν)ν+12f(x) = \frac{\Gamma\!\bigl(\frac{\nu+1}{2}\bigr)}{\sqrt{\nu\pi}\;\Gamma\!\bigl(\frac{\nu}{2}\bigr)} \left(1 + \frac{x^2}{\nu}\right)^{-\frac{\nu+1}{2}}
use numra::stats::{StudentT, ContinuousDistribution};
let t = StudentT::new(5.0_f64); // 5 degrees of freedom
// Symmetric about zero
assert!((t.pdf(1.0) - t.pdf(-1.0)).abs() < 1e-12);
assert!((t.cdf(0.0) - 0.5).abs() < 1e-10);
// Variance = nu / (nu - 2) for nu > 2
assert!((t.variance() - 5.0 / 3.0).abs() < 1e-14);
// Quantile round-trip
for &p in &[0.05, 0.25, 0.5, 0.75, 0.95] {
let x = t.quantile(p);
assert!((t.cdf(x) - p).abs() < 1e-6);
}

Special case of the Gamma distribution. Used in goodness-of-fit tests and confidence intervals for variance. Parameterized by degrees of freedom kk:

f(x)=xk/21ex/22k/2Γ(k/2),x>0f(x) = \frac{x^{k/2-1} e^{-x/2}}{2^{k/2} \Gamma(k/2)}, \quad x > 0
use numra::stats::{ChiSquared, ContinuousDistribution};
let chi2 = ChiSquared::new(5.0_f64);
assert!((chi2.mean() - 5.0).abs() < 1e-14); // mean = k
assert!((chi2.variance() - 10.0).abs() < 1e-14); // variance = 2k
assert!(chi2.cdf(0.0).abs() < 1e-10); // CDF(0) = 0

The ratio of two chi-squared distributions. Used in ANOVA and regression testing. Parameterized by d1,d2d_1, d_2 degrees of freedom:

use numra::stats::{FDist, ContinuousDistribution};
let f = FDist::new(5.0_f64, 10.0);
// Mean = d2 / (d2 - 2) for d2 > 2
assert!((f.mean() - 10.0 / 8.0).abs() < 1e-12);
assert!(f.cdf(0.0).abs() < 1e-10);

Constant density on the interval [a,b][a, b]:

f(x)=1ba,axbf(x) = \frac{1}{b - a}, \quad a \le x \le b
use numra::stats::{Uniform, ContinuousDistribution};
let u = Uniform::new(0.0_f64, 10.0);
assert!((u.pdf(5.0) - 0.1).abs() < 1e-14);
assert!((u.cdf(5.0) - 0.5).abs() < 1e-14);
assert!((u.mean() - 5.0).abs() < 1e-14);
// Variance = (b-a)^2 / 12
assert!((u.variance() - 100.0 / 12.0).abs() < 1e-12);

Models the time between events in a Poisson process. Parameterized by rate λ\lambda:

f(x)=λeλx,x0f(x) = \lambda e^{-\lambda x}, \quad x \ge 0
use numra::stats::{Exponential, ContinuousDistribution};
let e = Exponential::new(2.0_f64); // rate = 2
assert!((e.pdf(0.0) - 2.0).abs() < 1e-12); // PDF at 0 = lambda
assert!((e.mean() - 0.5).abs() < 1e-14); // mean = 1/lambda
assert!((e.variance() - 0.25).abs() < 1e-14); // variance = 1/lambda^2
// CDF: P(X <= 1) = 1 - exp(-2)
let expected = 1.0 - (-2.0_f64).exp();
assert!((e.cdf(1.0) - expected).abs() < 1e-12);
DistributionConstructorParametersMeanVariance
GammaDistGammaDist::new(alpha, beta)Shape α\alpha, rate β\betaα/β\alpha/\betaα/β2\alpha/\beta^2
BetaDistBetaDist::new(alpha, beta)α,β>0\alpha, \beta > 0αα+β\frac{\alpha}{\alpha+\beta}αβ(α+β)2(α+β+1)\frac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}
LogNormalLogNormal::new(mu, sigma)Log-mean, log-stdeμ+σ2/2e^{\mu+\sigma^2/2}(eσ21)e2μ+σ2(e^{\sigma^2}-1)e^{2\mu+\sigma^2}

Models the number of events in a fixed interval. Parameterized by rate λ\lambda:

P(X=k)=λkeλk!P(X = k) = \frac{\lambda^k e^{-\lambda}}{k!}
use numra::stats::{Poisson, DiscreteDistribution};
let p = Poisson::new(5.0_f64);
// Mean = variance = lambda
assert!((p.mean() - 5.0).abs() < 1e-14);
assert!((p.variance() - 5.0).abs() < 1e-14);
// PMF sums to 1
let sum: f64 = (0..30).map(|k| p.pmf(k)).sum();
assert!((sum - 1.0).abs() < 1e-8);
// CDF is monotone
let mut prev = 0.0;
for k in 0..20 {
let c = p.cdf(k);
assert!(c >= prev);
prev = c;
}

Models the number of successes in nn independent Bernoulli trials:

P(X=k)=(nk)pk(1p)nkP(X = k) = \binom{n}{k} p^k (1-p)^{n-k}
use numra::stats::{Binomial, DiscreteDistribution};
let b = Binomial::new(10, 0.3_f64); // n=10 trials, p=0.3
// Mean = np, Variance = np(1-p)
assert!((b.mean() - 3.0).abs() < 1e-12);
assert!((b.variance() - 2.1).abs() < 1e-12);

All distributions support random sampling through the rand crate’s RngCore trait. Use a seeded RNG for reproducible results.

use numra::stats::{Normal, ContinuousDistribution};
use rand::SeedableRng;
let dist = Normal::new(0.0_f64, 1.0);
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
// Single sample
let x = dist.sample(&mut rng);
// Multiple samples
let samples = dist.sample_n(&mut rng, 10000);
// Sample mean should be close to 0
let sample_mean: f64 = samples.iter().sum::<f64>() / samples.len() as f64;
assert!(sample_mean.abs() < 0.1);
use numra::stats::{
Normal, Uniform, Exponential, Poisson,
ContinuousDistribution, DiscreteDistribution,
};
fn main() {
let mut rng = rand::thread_rng();
// Continuous samples
let normal_samples = Normal::new(100.0, 15.0).sample_n(&mut rng, 1000);
let uniform_samples = Uniform::new(0.0, 1.0).sample_n(&mut rng, 1000);
let exp_samples = Exponential::new(0.5).sample_n(&mut rng, 1000);
// Discrete samples
let poisson_samples = Poisson::new(7.0).sample_n(&mut rng, 1000);
println!("Normal: mean = {:.2}",
normal_samples.iter().sum::<f64>() / 1000.0);
println!("Uniform: mean = {:.2}",
uniform_samples.iter().sum::<f64>() / 1000.0);
println!("Exponential: mean = {:.2}",
exp_samples.iter().sum::<f64>() / 1000.0);
println!("Poisson: mean = {:.2}",
poisson_samples.iter().sum::<usize>() as f64 / 1000.0);
}
DistributionConstructorKey Use
NormalNormal::new(mu, sigma)General modeling, central limit theorem
Normal::standard()N(0, 1)Z-scores, standard reference
StudentTStudentT::new(df)Small-sample inference
ChiSquaredChiSquared::new(df)Goodness-of-fit, variance testing
FDistFDist::new(df1, df2)ANOVA, regression F-tests
UniformUniform::new(a, b)Simulation, random number generation
ExponentialExponential::new(lambda)Waiting times, reliability
GammaDistGammaDist::new(alpha, beta)Bayesian priors, queuing theory
BetaDistBetaDist::new(alpha, beta)Bayesian priors for proportions
LogNormalLogNormal::new(mu, sigma)Financial modeling, multiplicative processes
DistributionConstructorKey Use
PoissonPoisson::new(lambda)Count data, rare events
BinomialBinomial::new(n, p)Success/failure experiments
use numra::stats::{
Normal, StudentT, ContinuousDistribution,
};
fn main() {
let normal = Normal::<f64>::standard();
// As degrees of freedom increase, Student's t approaches the normal
for df in [1, 5, 10, 30, 100] {
let t = StudentT::new(df as f64);
let x = 1.96;
let p_normal = normal.cdf(x);
let p_t = t.cdf(x);
println!("df={:3}: P(T <= 1.96) = {:.6} P(Z <= 1.96) = {:.6} diff = {:.6}",
df, p_t, p_normal, (p_t - p_normal).abs());
}
// At df=100, the difference should be very small
}