r2rs_nmath/distribution/logis/
mod.rs

1mod d;
2mod p;
3mod q;
4mod r;
5
6use strafe_type::{LogProbability64, Probability64, Rational64, Real64};
7
8pub(crate) use self::{d::*, p::*, q::*, r::*};
9use crate::traits::{Distribution, RNG};
10
11/// # The Logistic Distribution
12///
13/// ## Description
14///
15/// Density, distribution function, quantile function and random generation for the logistic
16/// distribution with parameters location and scale.
17///
18/// ## Arguments
19///
20/// * location, scale: location and scale parameters.
21///
22/// ## Details
23///
24/// If location or scale are omitted, they assume the default values of 0 and 1 respectively.
25///
26/// The Logistic distribution with location = m and scale = s has distribution function
27///
28/// $ F(x) = \frac{1}{1 + exp(-\frac{x-m}{s})} $
29///
30/// and density
31///
32/// $ f(x) = \frac{1}{s} exp(\frac{x-m}{s}) (1 + exp(\frac{x-m}{s}))^{-2} $.
33///
34/// It is a long-tailed distribution with mean m and variance $ \frac{\pi^2}{3} s^2 $.
35///
36/// ## Value
37///
38/// dlogis gives the density, plogis gives the distribution function, qlogis gives the quantile
39/// function, and rlogis generates random deviates.
40///
41/// The length of the result is determined by n for rlogis, and is the maximum of the lengths of
42/// the numerical arguments for the other functions.
43///
44/// The numerical arguments other than n are recycled to the length of the result. Only the first
45/// elements of the logical arguments are used.
46///
47/// ## Density Plot
48///
49/// ```rust
50/// # use r2rs_base::traits::StatisticalSlice;
51/// # use r2rs_nmath::{distribution::LogisticBuilder, traits::Distribution};
52/// # use strafe_plot::prelude::{IntoDrawingArea, Line, Plot, PlotOptions, SVGBackend, BLACK};
53/// # use strafe_type::FloatConstraint;
54/// let logis = LogisticBuilder::new().build();
55/// let x = <[f64]>::sequence(-6.0, 6.0, 1000);
56/// let y = x
57///     .iter()
58///     .map(|x| logis.density(x).unwrap())
59///     .collect::<Vec<_>>();
60///
61/// let root = SVGBackend::new("density.svg", (1024, 768)).into_drawing_area();
62/// Plot::new()
63///     .with_options(PlotOptions {
64///         x_axis_label: "x".to_string(),
65///         y_axis_label: "density".to_string(),
66///         ..Default::default()
67///     })
68///     .with_plottable(Line {
69///         x,
70///         y,
71///         color: BLACK,
72///         ..Default::default()
73///     })
74///     .plot(&root)
75///     .unwrap();
76/// # use std::fs::rename;
77/// #     drop(root);
78/// #     rename(
79/// #             format!("density.svg"),
80/// #             format!("src/distribution/logis/doctest_out/density.svg"),
81/// #     )
82/// #     .unwrap();
83/// ```
84#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = embed_doc_image::embed_image!("density", "src/distribution/logis/doctest_out/density.svg")))]
85#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = "![Density][density]"))]
86///
87/// ## Note
88///
89/// qlogis(p) is the same as the well known ‘logit’ function, $ logit(p) = log(p/(1-p)) $, and
90/// plogis(x) has consequently been called the ‘inverse logit’.
91///
92/// The distribution function is a rescaled hyperbolic tangent, $ plogis(x) == (1+ tanh(x/2))/2 $,
93/// and it is called a sigmoid function in contexts such as neural networks.
94///
95/// ## Source
96///
97/// \[dpq\]logis are calculated directly from the definitions.
98///
99/// rlogis uses inversion.
100///
101/// # References
102///
103/// Becker, R. A., Chambers, J. M. and Wilks, A. R. (1988) The New S Language. Wadsworth &
104/// Brooks/Cole.
105///
106/// Johnson, N. L., Kotz, S. and Balakrishnan, N. (1995) Continuous Univariate Distributions,
107/// volume 2, chapter 23. Wiley, New York.
108///
109/// ## See Also
110///
111/// Distributions for other standard distributions.
112///
113/// ## Examples
114///
115/// Approximately equal (+/- 3)
116/// ```rust
117/// # use num_traits::FloatConst;
118/// # use r2rs_nmath::{
119/// #     distribution::LogisticBuilder,
120/// #     rng::MersenneTwister,
121/// #     traits::{Distribution, RNG},
122/// # };
123/// # use r2rs_stats::funcs::variance;
124/// # use strafe_type::FloatConstraint;
125/// let logis = LogisticBuilder::new()
126///     .with_location(0)
127///     .with_scale(5)
128///     .build();
129///
130/// let mut rng = MersenneTwister::new();
131/// rng.set_seed(1);
132///
133/// let r = (0..4000)
134///     .map(|_| logis.random_sample(&mut rng).unwrap())
135///     .collect::<Vec<_>>();
136/// println!("{}", variance(&r));
137/// println!("{}", f64::PI().powi(2) / 3.0 * 5.0_f64.powi(2));
138/// # use std::{fs::File, io::Write};
139/// # let mut f = File::create("src/distribution/logis/doctest_out/rand.md").unwrap();
140/// # writeln!(f, "```output").unwrap();
141/// # writeln!(f, "{}", variance(&r)).unwrap();
142/// # writeln!(f, "{}", f64::PI().powi(2) / 3.0 * 5.0_f64.powi(2)).unwrap();
143/// # writeln!(f, "```").unwrap();
144/// ```
145#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/rand.md")))]
146pub struct Logistic {
147    location: Real64,
148    scale: Rational64,
149}
150
151impl Distribution for Logistic {
152    fn density<R: Into<Real64>>(&self, x: R) -> Real64 {
153        dlogis(x, self.location, self.scale, false)
154    }
155
156    fn log_density<R: Into<Real64>>(&self, x: R) -> Real64 {
157        dlogis(x, self.location, self.scale, true)
158    }
159
160    fn probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> Probability64 {
161        plogis(q, self.location, self.scale, lower_tail)
162    }
163
164    fn log_probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> LogProbability64 {
165        log_plogis(q, self.location, self.scale, lower_tail)
166    }
167
168    fn quantile<P: Into<Probability64>>(&self, p: P, lower_tail: bool) -> Real64 {
169        qlogis(p, self.location, self.scale, lower_tail)
170    }
171
172    fn log_quantile<LP: Into<LogProbability64>>(&self, p: LP, lower_tail: bool) -> Real64 {
173        log_qlogis(p, self.location, self.scale, lower_tail)
174    }
175
176    fn random_sample<R: RNG>(&self, rng: &mut R) -> Real64 {
177        rlogis(self.location, self.scale, rng)
178    }
179}
180
181pub struct LogisticBuilder {
182    location: Option<Real64>,
183    scale: Option<Rational64>,
184}
185
186impl LogisticBuilder {
187    pub fn new() -> Self {
188        Self {
189            location: None,
190            scale: None,
191        }
192    }
193
194    pub fn with_location<R: Into<Real64>>(&mut self, location: R) -> &mut Self {
195        self.location = Some(location.into());
196        self
197    }
198
199    pub fn with_scale<R: Into<Rational64>>(&mut self, scale: R) -> &mut Self {
200        self.scale = Some(scale.into());
201        self
202    }
203
204    pub fn build(&self) -> Logistic {
205        let location = self.location.unwrap_or(0.0.into());
206        let scale = self.scale.unwrap_or(1.0.into());
207
208        Logistic { location, scale }
209    }
210}
211
212#[cfg(test)]
213mod tests;
214
215#[cfg(all(test, feature = "enable_proptest"))]
216mod proptests;
217
218#[cfg(all(test, feature = "enable_covtest"))]
219mod covtests;