rand_distr/
fisher_f.rs

1// Copyright 2018 Developers of the Rand project.
2// Copyright 2013 The Rust Project Developers.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! The Fisher F-distribution.
11
12use crate::{ChiSquared, Distribution, Exp1, Open01, StandardNormal};
13use core::fmt;
14use num_traits::Float;
15use rand::Rng;
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19/// The [Fisher F-distribution](https://en.wikipedia.org/wiki/F-distribution) `F(m, n)`.
20///
21/// This distribution is equivalent to the ratio of two normalised
22/// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) /
23/// (χ²(n)/n)`.
24///
25/// # Plot
26///
27/// The plot shows the F-distribution with various values of `m` and `n`.
28///
29/// ![F-distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/fisher_f.svg)
30///
31/// # Example
32///
33/// ```
34/// use rand_distr::{FisherF, Distribution};
35///
36/// let f = FisherF::new(2.0, 32.0).unwrap();
37/// let v = f.sample(&mut rand::rng());
38/// println!("{} is from an F(2, 32) distribution", v)
39/// ```
40#[derive(Clone, Copy, Debug, PartialEq)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42pub struct FisherF<F>
43where
44    F: Float,
45    StandardNormal: Distribution<F>,
46    Exp1: Distribution<F>,
47    Open01: Distribution<F>,
48{
49    numer: ChiSquared<F>,
50    denom: ChiSquared<F>,
51    // denom_dof / numer_dof so that this can just be a straight
52    // multiplication, rather than a division.
53    dof_ratio: F,
54}
55
56/// Error type returned from [`FisherF::new`].
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
59pub enum Error {
60    /// `m <= 0` or `nan`.
61    MTooSmall,
62    /// `n <= 0` or `nan`.
63    NTooSmall,
64}
65
66impl fmt::Display for Error {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.write_str(match self {
69            Error::MTooSmall => "m is not positive in Fisher F distribution",
70            Error::NTooSmall => "n is not positive in Fisher F distribution",
71        })
72    }
73}
74
75#[cfg(feature = "std")]
76impl std::error::Error for Error {}
77
78impl<F> FisherF<F>
79where
80    F: Float,
81    StandardNormal: Distribution<F>,
82    Exp1: Distribution<F>,
83    Open01: Distribution<F>,
84{
85    /// Create a new `FisherF` distribution, with the given parameter.
86    pub fn new(m: F, n: F) -> Result<FisherF<F>, Error> {
87        let zero = F::zero();
88        if !(m > zero) {
89            return Err(Error::MTooSmall);
90        }
91        if !(n > zero) {
92            return Err(Error::NTooSmall);
93        }
94
95        Ok(FisherF {
96            numer: ChiSquared::new(m).unwrap(),
97            denom: ChiSquared::new(n).unwrap(),
98            dof_ratio: n / m,
99        })
100    }
101}
102impl<F> Distribution<F> for FisherF<F>
103where
104    F: Float,
105    StandardNormal: Distribution<F>,
106    Exp1: Distribution<F>,
107    Open01: Distribution<F>,
108{
109    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> F {
110        self.numer.sample(rng) / self.denom.sample(rng) * self.dof_ratio
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use super::*;
117
118    #[test]
119    fn test_f() {
120        let f = FisherF::new(2.0, 32.0).unwrap();
121        let mut rng = crate::test::rng(204);
122        for _ in 0..1000 {
123            f.sample(&mut rng);
124        }
125    }
126
127    #[test]
128    fn fisher_f_distributions_can_be_compared() {
129        assert_eq!(FisherF::new(1.0, 2.0), FisherF::new(1.0, 2.0));
130    }
131}