statrs/distribution/
chi_squared.rs

1use crate::distribution::{Continuous, ContinuousCDF, Gamma, GammaError};
2use crate::statistics::*;
3use std::f64;
4
5/// Implements the
6/// [Chi-squared](https://en.wikipedia.org/wiki/Chi-squared_distribution)
7/// distribution which is a special case of the
8/// [Gamma](https://en.wikipedia.org/wiki/Gamma_distribution) distribution
9/// (referenced [Here](./struct.Gamma.html))
10///
11/// # Examples
12///
13/// ```
14/// use statrs::distribution::{ChiSquared, Continuous};
15/// use statrs::statistics::Distribution;
16/// use statrs::prec;
17///
18/// let n = ChiSquared::new(3.0).unwrap();
19/// assert_eq!(n.mean().unwrap(), 3.0);
20/// assert!(prec::almost_eq(n.pdf(4.0), 0.107981933026376103901, 1e-15));
21/// ```
22#[derive(Copy, Clone, PartialEq, Debug)]
23pub struct ChiSquared {
24    freedom: f64,
25    g: Gamma,
26}
27
28impl ChiSquared {
29    /// Constructs a new chi-squared distribution with `freedom`
30    /// degrees of freedom. This is equivalent to a Gamma distribution
31    /// with a shape of `freedom / 2.0` and a rate of `0.5`.
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if `freedom` is `NaN` or less than
36    /// or equal to `0.0`
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use statrs::distribution::ChiSquared;
42    ///
43    /// let mut result = ChiSquared::new(3.0);
44    /// assert!(result.is_ok());
45    ///
46    /// result = ChiSquared::new(0.0);
47    /// assert!(result.is_err());
48    /// ```
49    pub fn new(freedom: f64) -> Result<ChiSquared, GammaError> {
50        Gamma::new(freedom / 2.0, 0.5).map(|g| ChiSquared { freedom, g })
51    }
52
53    /// Returns the degrees of freedom of the chi-squared
54    /// distribution
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use statrs::distribution::ChiSquared;
60    ///
61    /// let n = ChiSquared::new(3.0).unwrap();
62    /// assert_eq!(n.freedom(), 3.0);
63    /// ```
64    pub fn freedom(&self) -> f64 {
65        self.freedom
66    }
67
68    /// Returns the shape of the underlying Gamma distribution
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use statrs::distribution::ChiSquared;
74    ///
75    /// let n = ChiSquared::new(3.0).unwrap();
76    /// assert_eq!(n.shape(), 3.0 / 2.0);
77    /// ```
78    pub fn shape(&self) -> f64 {
79        self.g.shape()
80    }
81
82    /// Returns the rate of the underlying Gamma distribution
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use statrs::distribution::ChiSquared;
88    ///
89    /// let n = ChiSquared::new(3.0).unwrap();
90    /// assert_eq!(n.rate(), 0.5);
91    /// ```
92    pub fn rate(&self) -> f64 {
93        self.g.rate()
94    }
95}
96
97impl std::fmt::Display for ChiSquared {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        write!(f, "χ^2_{}", self.freedom)
100    }
101}
102
103#[cfg(feature = "rand")]
104#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
105impl ::rand::distributions::Distribution<f64> for ChiSquared {
106    fn sample<R: ::rand::Rng + ?Sized>(&self, r: &mut R) -> f64 {
107        ::rand::distributions::Distribution::sample(&self.g, r)
108    }
109}
110
111impl ContinuousCDF<f64, f64> for ChiSquared {
112    /// Calculates the cumulative distribution function for the
113    /// chi-squared distribution at `x`
114    ///
115    /// # Formula
116    ///
117    /// ```text
118    /// (1 / Γ(k / 2)) * γ(k / 2, x / 2)
119    /// ```
120    ///
121    /// where `k` is the degrees of freedom, `Γ` is the gamma function,
122    /// and `γ` is the lower incomplete gamma function
123    fn cdf(&self, x: f64) -> f64 {
124        self.g.cdf(x)
125    }
126
127    /// Calculates the cumulative distribution function for the
128    /// chi-squared distribution at `x`
129    ///
130    /// # Formula
131    ///
132    /// ```text
133    /// (1 / Γ(k / 2)) * γ(k / 2, x / 2)
134    /// ```
135    ///
136    /// where `k` is the degrees of freedom, `Γ` is the gamma function,
137    /// and `γ` is the upper incomplete gamma function
138    fn sf(&self, x: f64) -> f64 {
139        self.g.sf(x)
140    }
141
142    /// Calculates the inverse cumulative distribution function for the
143    /// chi-squared distribution at `x`
144    ///
145    /// # Formula
146    ///
147    /// ```text
148    /// γ^{-1}(k / 2, x * Γ(k / 2) / 2)
149    /// ```
150    ///
151    /// where `k` is the degrees of freedom, `Γ` is the gamma function,
152    /// and `γ` is the lower incomplete gamma function
153    fn inverse_cdf(&self, p: f64) -> f64 {
154        self.g.inverse_cdf(p)
155    }
156}
157
158impl Min<f64> for ChiSquared {
159    /// Returns the minimum value in the domain of the
160    /// chi-squared distribution representable by a double precision
161    /// float
162    ///
163    /// # Formula
164    ///
165    /// ```text
166    /// 0
167    /// ```
168    fn min(&self) -> f64 {
169        0.0
170    }
171}
172
173impl Max<f64> for ChiSquared {
174    /// Returns the maximum value in the domain of the
175    /// chi-squared distribution representable by a double precision
176    /// float
177    ///
178    /// # Formula
179    ///
180    /// ```text
181    /// f64::INFINITY
182    /// ```
183    fn max(&self) -> f64 {
184        f64::INFINITY
185    }
186}
187
188impl Distribution<f64> for ChiSquared {
189    /// Returns the mean of the chi-squared distribution
190    ///
191    /// # Formula
192    ///
193    /// ```text
194    /// k
195    /// ```
196    ///
197    /// where `k` is the degrees of freedom
198    fn mean(&self) -> Option<f64> {
199        self.g.mean()
200    }
201
202    /// Returns the variance of the chi-squared distribution
203    ///
204    /// # Formula
205    ///
206    /// ```text
207    /// 2k
208    /// ```
209    ///
210    /// where `k` is the degrees of freedom
211    fn variance(&self) -> Option<f64> {
212        self.g.variance()
213    }
214
215    /// Returns the entropy of the chi-squared distribution
216    ///
217    /// # Formula
218    ///
219    /// ```text
220    /// (k / 2) + ln(2 * Γ(k / 2)) + (1 - (k / 2)) * ψ(k / 2)
221    /// ```
222    ///
223    /// where `k` is the degrees of freedom, `Γ` is the gamma function,
224    /// and `ψ` is the digamma function
225    fn entropy(&self) -> Option<f64> {
226        self.g.entropy()
227    }
228
229    /// Returns the skewness of the chi-squared distribution
230    ///
231    /// # Formula
232    ///
233    /// ```text
234    /// sqrt(8 / k)
235    /// ```
236    ///
237    /// where `k` is the degrees of freedom
238    fn skewness(&self) -> Option<f64> {
239        self.g.skewness()
240    }
241}
242
243impl Median<f64> for ChiSquared {
244    /// Returns the median  of the chi-squared distribution
245    ///
246    /// # Formula
247    ///
248    /// ```text
249    /// k * (1 - (2 / 9k))^3
250    /// ```
251    fn median(&self) -> f64 {
252        if self.freedom < 1.0 {
253            // if k is small, calculate using expansion of formula
254            self.freedom - 2.0 / 3.0 + 12.0 / (81.0 * self.freedom)
255                - 8.0 / (729.0 * self.freedom * self.freedom)
256        } else {
257            // if k is large enough, median heads toward k - 2/3
258            self.freedom - 2.0 / 3.0
259        }
260    }
261}
262
263impl Mode<Option<f64>> for ChiSquared {
264    /// Returns the mode of the chi-squared distribution
265    ///
266    /// # Formula
267    ///
268    /// ```text
269    /// k - 2
270    /// ```
271    ///
272    /// where `k` is the degrees of freedom
273    fn mode(&self) -> Option<f64> {
274        self.g.mode()
275    }
276}
277
278impl Continuous<f64, f64> for ChiSquared {
279    /// Calculates the probability density function for the chi-squared
280    /// distribution at `x`
281    ///
282    /// # Formula
283    ///
284    /// ```text
285    /// 1 / (2^(k / 2) * Γ(k / 2)) * x^((k / 2) - 1) * e^(-x / 2)
286    /// ```
287    ///
288    /// where `k` is the degrees of freedom and `Γ` is the gamma function
289    fn pdf(&self, x: f64) -> f64 {
290        self.g.pdf(x)
291    }
292
293    /// Calculates the log probability density function for the chi-squared
294    /// distribution at `x`
295    ///
296    /// # Formula
297    ///
298    /// ```text
299    /// ln(1 / (2^(k / 2) * Γ(k / 2)) * x^((k / 2) - 1) * e^(-x / 2))
300    /// ```
301    fn ln_pdf(&self, x: f64) -> f64 {
302        self.g.ln_pdf(x)
303    }
304}
305
306#[rustfmt::skip]
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::distribution::internal::*;
311    use crate::testing_boiler;
312
313    testing_boiler!(freedom: f64; ChiSquared; GammaError);
314
315    #[test]
316    fn test_median() {
317        let median = |x: ChiSquared| x.median();
318        test_absolute(0.5, 0.0857338820301783264746, 1e-16, median);
319        test_exact(1.0, 1.0 - 2.0 / 3.0, median);
320        test_exact(2.0, 2.0 - 2.0 / 3.0, median);
321        test_exact(2.5, 2.5 - 2.0 / 3.0, median);
322        test_exact(3.0, 3.0 - 2.0 / 3.0, median);
323    }
324
325    #[test]
326    fn test_continuous() {
327        // TODO: figure out why this test fails:
328        //test::check_continuous_distribution(&create_ok(1.0), 0.0, 10.0);
329        test::check_continuous_distribution(&create_ok(2.0), 0.0, 10.0);
330        test::check_continuous_distribution(&create_ok(5.0), 0.0, 50.0);
331    }
332}