r2rs_nmath/distribution/pois/
mod.rs

1mod d;
2mod p;
3mod q;
4mod r;
5
6use strafe_type::{LogProbability64, Positive64, Probability64, Real64};
7
8pub(crate) use self::{d::*, p::*, q::*, r::*};
9use crate::traits::{Distribution, RNG};
10
11/// # The Poisson Distribution
12///
13/// ## Description
14///
15/// Density, distribution function, quantile function and random generation for the Poisson
16/// distribution with parameter lambda.
17///
18/// ## Arguments
19///
20/// * lambda: (non-negative) means.
21///
22/// ## Details
23///
24/// The Poisson distribution has density
25///
26/// $ p(x) = \lambda^x \frac{exp(-\lambda)}{x!} $
27///
28/// for x = 0, 1, 2, … . The mean and variance are $ E(X) = Var(X) = \lambda $.
29///
30/// Note that $ \lambda = 0 $ is really a limit case (setting 0^0 = 1) resulting in a point mass at
31/// 0, see also the example.
32///
33/// If an element of x is not integer, the result of dpois is zero, with a warning. p(x) is
34/// computed using Loader's algorithm, see the reference in dbinom.
35///
36/// The quantile is right continuous: qpois(p, lambda) is the smallest integer x such that
37/// $P(X ≤ x) ≥ p$.
38///
39/// Setting lower.tail = FALSE allows to get much more precise results when the default,
40/// lower.tail = TRUE would return 1, see the example below.
41///
42/// ## Density Plot
43///
44/// ```rust
45/// # use r2rs_base::traits::StatisticalSlice;
46/// # use r2rs_nmath::{distribution::PoissonBuilder, traits::Distribution};
47/// # use strafe_plot::prelude::{IntoDrawingArea, Line, Plot, PlotOptions, SVGBackend, BLACK};
48/// # use strafe_type::FloatConstraint;
49/// let pois = PoissonBuilder::new().build();
50/// let x = <[f64]>::sequence_by(-1.0, 7.0, 0.001);
51/// let y = x
52///     .iter()
53///     .map(|x| pois.density(x).unwrap())
54///     .collect::<Vec<_>>();
55///
56/// let root = SVGBackend::new("density.svg", (1024, 768)).into_drawing_area();
57/// Plot::new()
58///     .with_options(PlotOptions {
59///         x_axis_label: "x".to_string(),
60///         y_axis_label: "density".to_string(),
61///         ..Default::default()
62///     })
63///     .with_plottable(Line {
64///         x,
65///         y,
66///         color: BLACK,
67///         ..Default::default()
68///     })
69///     .plot(&root)
70///     .unwrap();
71/// # use std::fs::rename;
72/// #     drop(root);
73/// #     rename(
74/// #             format!("density.svg"),
75/// #             format!("src/distribution/pois/doctest_out/density.svg"),
76/// #     )
77/// #     .unwrap();
78/// ```
79#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = embed_doc_image::embed_image!("density", "src/distribution/pois/doctest_out/density.svg")))]
80#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = "![Density][density]"))]
81///
82/// ## Source
83///
84/// dpois uses C code contributed by Catherine Loader (see dbinom).
85///
86/// ppois uses pgamma.
87///
88/// qpois uses the Cornish–Fisher Expansion to include a skewness correction to a normal
89/// approximation, followed by a search.
90///
91/// rpois uses
92///
93/// Ahrens, J. H. and Dieter, U. (1982). Computer generation of Poisson deviates from modified
94/// normal distributions. ACM Transactions on Mathematical Software, 8, 163–179.
95///
96/// ## See Also
97/// Distributions for other standard distributions, including dbinom for the binomial and dnbinom
98/// for the negative binomial distribution.
99///
100/// poisson.test.
101///
102/// ## Examples
103///
104/// // Should be 1
105/// ```rust
106/// # use r2rs_nmath::{
107/// #     distribution::PoissonBuilder, func::gamma, traits::Distribution,
108/// # };
109/// # use strafe_type::FloatConstraint;
110/// let x = (0..=7).collect::<Vec<_>>();
111/// let pois = PoissonBuilder::new().with_lambda(1).build();
112/// let r = x
113///     .iter()
114///     .map(|x| -(pois.density(x).unwrap() * gamma(1 + x).unwrap().unwrap()).ln())
115///     .collect::<Vec<_>>();
116/// println!("{r:?}");
117/// # use std::{fs::File, io::Write};
118/// # let mut f = File::create("src/distribution/pois/doctest_out/dens.md").unwrap();
119/// # writeln!(f, "```output").unwrap();
120/// # writeln!(f, "{r:?}").unwrap();
121/// # writeln!(f, "```").unwrap();
122/// ```
123#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/dens.md")))]
124///
125/// ```rust
126/// # use std::collections::HashMap;
127/// #
128/// # use r2rs_nmath::{
129/// #     distribution::PoissonBuilder,
130/// #     rng::MersenneTwister,
131/// #     traits::{Distribution, RNG},
132/// # };
133/// # use strafe_type::FloatConstraint;
134/// let pois = PoissonBuilder::new().with_lambda(4).build();
135/// let mut rng = MersenneTwister::new();
136/// rng.set_seed(1);
137/// let mut r = (0..50)
138///     .map(|_| pois.random_sample(&mut rng).unwrap() as usize)
139///     .fold(HashMap::new(), |mut acc, r| {
140///         *acc.entry(r).or_insert(0) += 1;
141///         acc
142///     })
143///     .into_iter()
144///     .collect::<Vec<_>>();
145/// r.sort_by(|(i1, _), (i2, _)| i1.cmp(i2));
146///
147/// for (key, index) in &r {
148///     println!("{key:2}: {index}");
149/// }
150/// # use std::{fs::File, io::Write};
151/// # let mut f = File::create("src/distribution/pois/doctest_out/table.md").unwrap();
152/// # writeln!(f, "```output").unwrap();
153/// # for (key, index) in &r {
154/// #     writeln!(f, "{key:2}: {index}").unwrap();
155/// # }
156/// # writeln!(f, "```").unwrap();
157/// ```
158#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/table.md")))]
159///
160/// Using lower tail directly fixes the cancellation (values becoming 0)
161/// ```rust
162/// # use r2rs_nmath::{distribution::PoissonBuilder, traits::Distribution};
163/// # use strafe_type::FloatConstraint;
164/// let x = (15..=25).collect::<Vec<_>>();
165/// let pois = PoissonBuilder::new().with_lambda(100).build();
166/// let r1 = x
167///     .iter()
168///     .map(|x| 1.0 - pois.probability(x * 10, true).unwrap())
169///     .collect::<Vec<_>>();
170/// println!("{r1:?}");
171/// let r2 = x
172///     .iter()
173///     .map(|x| pois.probability(x * 10, false).unwrap())
174///     .collect::<Vec<_>>();
175/// println!("{r2:?}");
176/// # use std::{fs::File, io::Write};
177/// # let mut f = File::create("src/distribution/pois/doctest_out/prob.md").unwrap();
178/// # writeln!(f, "```output").unwrap();
179/// # writeln!(f, "{r1:?}").unwrap();
180/// # writeln!(f, "{r2:?}").unwrap();
181/// # writeln!(f, "```").unwrap();
182/// ```
183#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/prob.md")))]
184///
185/// ```rust
186/// # use r2rs_base::traits::StatisticalSlice;
187/// # use r2rs_nmath::{
188/// #     distribution::{BinomialBuilder, PoissonBuilder},
189/// #     traits::Distribution,
190/// # };
191/// # use strafe_plot::prelude::{IntoDrawingArea, Line, Plot, PlotOptions, SVGBackend, BLACK};
192/// # use strafe_type::FloatConstraint;
193/// let x = <[f64]>::sequence_by(-0.01, 5.0, 0.01);
194///
195/// let pois = PoissonBuilder::new().with_lambda(1).build();
196/// let y1 = x
197///     .iter()
198///     .map(|x| pois.probability(x, true).unwrap())
199///     .collect::<Vec<_>>();
200///
201/// let binom = BinomialBuilder::new()
202///     .with_size(100)
203///     .with_success_probability(0.01)
204///     .build();
205/// let y2 = x
206///     .iter()
207///     .map(|x| binom.probability(x, true).unwrap())
208///     .collect::<Vec<_>>();
209///
210/// let root = SVGBackend::new("prob_plots.svg", (1024, 768)).into_drawing_area();
211///
212/// Plot::new()
213///     .with_options(PlotOptions {
214///         x_axis_label: "x".to_string(),
215///         y_axis_label: "F(x)".to_string(),
216///         plot_bottom: 0.5,
217///         title: "Poisson(1) CDF".to_string(),
218///         ..Default::default()
219///     })
220///     .with_plottable(Line {
221///         x: x.clone(),
222///         y: y1,
223///         color: BLACK,
224///         ..Default::default()
225///     })
226///     .plot(&root)
227///     .unwrap();
228///
229/// Plot::new()
230///     .with_options(PlotOptions {
231///         x_axis_label: "x".to_string(),
232///         y_axis_label: "F(x)".to_string(),
233///         plot_top: 0.5,
234///         title: "Binomial(100, 0.01) CDF".to_string(),
235///         ..Default::default()
236///     })
237///     .with_plottable(Line {
238///         x,
239///         y: y2,
240///         color: BLACK,
241///         ..Default::default()
242///     })
243///     .plot(&root)
244///     .unwrap();
245/// # use std::fs::rename;
246/// #     drop(root);
247/// #     rename(
248/// #             format!("prob_plots.svg"),
249/// #             format!("src/distribution/pois/doctest_out/prob_plots.svg"),
250/// #     )
251/// #     .unwrap();
252/// ```
253#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = embed_doc_image::embed_image!("prob_plots", "src/distribution/pois/doctest_out/prob_plots.svg")))]
254#[cfg_attr(
255    feature = "doc_outputs",
256    cfg_attr(all(), doc = "![Prob Plots][prob_plots]")
257)]
258///
259/// ```rust
260/// # use r2rs_nmath::{distribution::PoissonBuilder, traits::Distribution};
261/// # use strafe_type::FloatConstraint;
262/// assert_eq!(
263///     PoissonBuilder::new()
264///         .with_lambda(0)
265///         .build()
266///         .density(0)
267///         .unwrap(),
268///     1.0
269/// );
270/// assert_eq!(
271///     PoissonBuilder::new()
272///         .with_lambda(0)
273///         .build()
274///         .probability(0, true)
275///         .unwrap(),
276///     1.0
277/// );
278/// assert_eq!(
279///     PoissonBuilder::new()
280///         .with_lambda(0)
281///         .build()
282///         .quantile(1, true)
283///         .unwrap(),
284///     0.0
285/// );
286/// ```
287pub struct Poisson {
288    lambda: Positive64,
289}
290
291impl Distribution for Poisson {
292    fn density<R: Into<Real64>>(&self, x: R) -> Real64 {
293        dpois(x, self.lambda, false)
294    }
295
296    fn log_density<R: Into<Real64>>(&self, x: R) -> Real64 {
297        dpois(x, self.lambda, true)
298    }
299
300    fn probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> Probability64 {
301        ppois(q, self.lambda, lower_tail)
302    }
303
304    fn log_probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> LogProbability64 {
305        log_ppois(q, self.lambda, lower_tail)
306    }
307
308    fn quantile<P: Into<Probability64>>(&self, p: P, lower_tail: bool) -> Real64 {
309        qpois(p, self.lambda, lower_tail)
310    }
311
312    fn log_quantile<LP: Into<LogProbability64>>(&self, p: LP, lower_tail: bool) -> Real64 {
313        log_qpois(p, self.lambda, lower_tail)
314    }
315
316    fn random_sample<R: RNG>(&self, rng: &mut R) -> Real64 {
317        rpois(self.lambda, rng)
318    }
319}
320
321pub struct PoissonBuilder {
322    lambda: Option<Positive64>,
323}
324
325impl PoissonBuilder {
326    pub fn new() -> Self {
327        Self { lambda: None }
328    }
329
330    pub fn with_lambda<P: Into<Positive64>>(&mut self, lambda: P) -> &mut Self {
331        self.lambda = Some(lambda.into());
332        self
333    }
334
335    pub fn build(&self) -> Poisson {
336        let lambda = self.lambda.unwrap_or(1.0.into());
337
338        Poisson { lambda }
339    }
340}
341
342#[cfg(test)]
343mod tests;
344
345#[cfg(all(test, feature = "enable_proptest"))]
346mod proptests;
347
348#[cfg(all(test, feature = "enable_covtest"))]
349mod covtests;