Skip to content

Commit fa7863a

Browse files
authored
Merge pull request #8 from SyntaxSpirits/docs/conjugate-examples
docs: add conjugate model examples
2 parents 4443644 + 24b56c2 commit fa7863a

2 files changed

Lines changed: 121 additions & 0 deletions

File tree

examples/conjugate_models.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//! Conjugate Bayesian model examples.
2+
//!
3+
//! These examples use closed-form posterior updates, then build the matching
4+
//! bayes-rs distributions for posterior summaries and simple plug-in predictive
5+
//! checks. The plug-in checks are intentionally lightweight examples; they are
6+
//! not substitutes for the full posterior-predictive distributions that these
7+
//! conjugate pairs also provide in closed form. They are deterministic and quick
8+
//! enough to use as executable docs.
9+
10+
use bayes_rs::distributions::{Beta, Binomial, DiscreteDistribution, Distribution, Gamma, Poisson};
11+
12+
fn beta_binomial_posterior(
13+
prior_alpha: f64,
14+
prior_beta: f64,
15+
successes: u64,
16+
trials: u64,
17+
) -> bayes_rs::Result<Beta> {
18+
let failures = trials
19+
.checked_sub(successes)
20+
.ok_or_else(|| bayes_rs::BayesError::invalid_parameter("successes cannot exceed trials"))?;
21+
22+
Beta::new(prior_alpha + successes as f64, prior_beta + failures as f64)
23+
}
24+
25+
fn gamma_poisson_posterior(
26+
prior_shape: f64,
27+
prior_rate: f64,
28+
counts: &[u64],
29+
) -> bayes_rs::Result<Gamma> {
30+
let observed_events: u64 = counts.iter().sum();
31+
// bayes-rs parameterizes Gamma as shape/rate, so exposure increments rate.
32+
Gamma::new(
33+
prior_shape + observed_events as f64,
34+
prior_rate + counts.len() as f64,
35+
)
36+
}
37+
38+
fn main() -> bayes_rs::Result<()> {
39+
let conversion_posterior = beta_binomial_posterior(2.0, 2.0, 42, 120)?;
40+
let posterior_mean = conversion_posterior.mean();
41+
let predictive_successes = Binomial::new(25, posterior_mean)?;
42+
43+
println!("Beta-binomial conversion-rate update");
44+
println!(" Posterior mean success probability: {posterior_mean:.3}");
45+
println!(
46+
" Plug-in predictive probability of at least 8 successes in 25 trials: {:.3}",
47+
(8..=25)
48+
.map(|successes| predictive_successes.pmf(successes))
49+
.sum::<f64>()
50+
);
51+
52+
let daily_defects = [3, 4, 2, 5, 4, 1, 3];
53+
let defect_rate_posterior = gamma_poisson_posterior(1.5, 1.0, &daily_defects)?;
54+
let posterior_rate = defect_rate_posterior.mean();
55+
let next_day_defects = Poisson::new(posterior_rate)?;
56+
57+
println!("Gamma-Poisson count-rate update");
58+
println!(" Posterior mean event rate: {posterior_rate:.3}");
59+
println!(
60+
" Plug-in predictive probability of at most 2 events tomorrow: {:.3}",
61+
(0..=2)
62+
.map(|count| next_day_defects.pmf(count))
63+
.sum::<f64>()
64+
);
65+
println!(
66+
" Posterior density at rate 3.0: {:.3}",
67+
defect_rate_posterior.pdf(3.0)
68+
);
69+
70+
Ok(())
71+
}

tests/conjugate_models_test.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use approx::assert_relative_eq;
2+
use bayes_rs::distributions::{Beta, Binomial, DiscreteDistribution, Gamma, Poisson};
3+
4+
#[test]
5+
fn beta_binomial_update_matches_closed_form_posterior() {
6+
let prior_alpha = 2.0;
7+
let prior_beta = 2.0;
8+
let successes = 42;
9+
let trials = 120;
10+
let failures = trials - successes;
11+
12+
let posterior = Beta::new(prior_alpha + successes as f64, prior_beta + failures as f64)
13+
.expect("posterior parameters should be valid");
14+
15+
assert_relative_eq!(posterior.alpha(), 44.0);
16+
assert_relative_eq!(posterior.beta(), 80.0);
17+
assert_relative_eq!(posterior.mean(), 44.0 / 124.0);
18+
19+
let plug_in_predictive =
20+
Binomial::new(25, posterior.mean()).expect("posterior mean is a probability");
21+
let probability_at_least_eight: f64 = (8..=25).map(|k| plug_in_predictive.pmf(k)).sum();
22+
23+
assert!(probability_at_least_eight > 0.71);
24+
assert!(probability_at_least_eight < 0.72);
25+
}
26+
27+
#[test]
28+
fn gamma_poisson_update_matches_closed_form_posterior() {
29+
let prior_shape = 1.5;
30+
let prior_rate = 1.0;
31+
let counts = [3_u64, 4, 2, 5, 4, 1, 3];
32+
let observed_events: u64 = counts.iter().sum();
33+
34+
let posterior = Gamma::new(
35+
prior_shape + observed_events as f64,
36+
prior_rate + counts.len() as f64,
37+
)
38+
.expect("posterior parameters should be valid");
39+
40+
assert_relative_eq!(posterior.shape(), 23.5);
41+
assert_relative_eq!(posterior.rate(), 8.0);
42+
assert_relative_eq!(posterior.mean(), 23.5 / 8.0);
43+
44+
let plug_in_predictive =
45+
Poisson::new(posterior.mean()).expect("posterior mean is a valid rate");
46+
let probability_at_most_two: f64 = (0..=2).map(|k| plug_in_predictive.pmf(k)).sum();
47+
48+
assert!(probability_at_most_two > 0.42);
49+
assert!(probability_at_most_two < 0.44);
50+
}

0 commit comments

Comments
 (0)