r2rs_nmath/distribution/f/mod.rs
1mod d;
2mod non_central;
3mod p;
4mod q;
5mod r;
6
7use strafe_type::{LogProbability64, Positive64, Probability64, Rational64, Real64};
8
9pub(crate) use self::{d::*, non_central::*, p::*, q::*, r::*};
10use crate::traits::{Distribution, RNG};
11
12/// # The F Distribution
13///
14/// ## Description:
15///
16/// Density, distribution function, quantile function and random
17/// generation for the F distribution with ‘df1’ and ‘df2’ degrees of
18/// freedom (and optional non-centrality parameter ‘ncp’).
19///
20/// ## Arguments:
21///
22/// * df1, df2: degrees of freedom. ‘Inf’ is allowed.
23/// * ncp: non-centrality parameter. If omitted the central F is
24/// assumed.
25///
26/// ## Details:
27///
28/// The F distribution with ‘df1 =’ n1 and ‘df2 =’ n2 degrees of
29/// freedom has density
30///
31/// $f(x) = \frac{\Gamma(\frac{n1 + n2}{2})}{\Gamma(\frac{n1}{2}) \Gamma(\frac{n2}{2})}
32/// (\frac{n1}{n2})^{\frac{n1}{2}} x^{\frac{n1}{2} - 1}
33/// (1 + \frac{n1}{n2} x)^{-\frac{n1 + n2}{2}}$
34///
35/// for $x > 0$.
36///
37/// It is the distribution of the ratio of the mean squares of n1 and
38/// n2 independent standard normals, and hence of the ratio of two
39/// independent chi-squared variates each divided by its degrees of
40/// freedom. Since the ratio of a normal and the root mean-square of
41/// m independent normals has a Student's t_m distribution, the square
42/// of a t_m variate has a F distribution on 1 and m degrees of
43/// freedom.
44///
45/// The non-central F distribution is again the ratio of mean squares
46/// of independent normals of unit variance, but those in the
47/// numerator are allowed to have non-zero means and ‘ncp’ is the sum
48/// of squares of the means. See Chisquare for further details on
49/// non-central distributions.
50///
51/// ## Value:
52///
53/// ‘df’ gives the density, ‘pf’ gives the distribution function ‘qf’
54/// gives the quantile function, and ‘rf’ generates random deviates.
55///
56/// Invalid arguments will result in return value ‘NaN’, with a
57/// warning.
58///
59/// The length of the result is determined by ‘n’ for ‘rf’, and is the
60/// maximum of the lengths of the numerical arguments for the other
61/// functions.
62///
63/// The numerical arguments other than ‘n’ are recycled to the length
64/// of the result. Only the first elements of the logical arguments
65/// are used.
66///
67/// ## Density Plot
68///
69/// ```rust
70/// # use r2rs_base::traits::StatisticalSlice;
71/// # use r2rs_nmath::{distribution::FBuilder, traits::Distribution};
72/// # use strafe_plot::prelude::{IntoDrawingArea, Line, Plot, PlotOptions, SVGBackend, BLACK};
73/// # use strafe_type::FloatConstraint;
74/// let f = FBuilder::new().build();
75/// let x = <[f64]>::sequence(-0.5, 4.0, 1000);
76/// let y = x
77/// .iter()
78/// .map(|x| f.density(x).unwrap())
79/// .collect::<Vec<_>>();
80///
81/// let root = SVGBackend::new("density.svg", (1024, 768)).into_drawing_area();
82/// Plot::new()
83/// .with_options(PlotOptions {
84/// x_axis_label: "x".to_string(),
85/// y_axis_label: "density".to_string(),
86/// ..Default::default()
87/// })
88/// .with_plottable(Line {
89/// x,
90/// y,
91/// color: BLACK,
92/// ..Default::default()
93/// })
94/// .plot(&root)
95/// .unwrap();
96/// # use std::fs::rename;
97/// # drop(root);
98/// # rename(
99/// # format!("density.svg"),
100/// # format!("src/distribution/f/doctest_out/density.svg"),
101/// # )
102/// # .unwrap();
103/// ```
104#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = embed_doc_image::embed_image!("density", "src/distribution/f/doctest_out/density.svg")))]
105#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = "![Density][density]"))]
106///
107/// ## Note:
108///
109/// Supplying ‘ncp = 0’ uses the algorithm for the non-central
110/// distribution, which is not the same algorithm used if ‘ncp’ is
111/// omitted. This is to give consistent behaviour in extreme cases
112/// with values of ‘ncp’ very near zero.
113///
114/// The code for non-zero ‘ncp’ is principally intended to be used for
115/// moderate values of ‘ncp’: it will not be highly accurate,
116/// especially in the tails, for large values.
117///
118/// ## Source:
119///
120/// For the central case of ‘df’, computed _via_ a binomial
121/// probability, code contributed by Catherine Loader (see ‘dbinom’);
122/// for the non-central case computed _via_ ‘dbeta’, code contributed
123/// by Peter Ruckdeschel.
124///
125/// For ‘pf’, _via_ ‘pbeta’ (or for large ‘df2’, _via_ ‘pchisq’).
126///
127/// For ‘qf’, _via_ ‘qchisq’ for large ‘df2’, else _via_ ‘qbeta’.
128///
129/// ## References:
130///
131/// Becker, R. A., Chambers, J. M. and Wilks, A. R. (1988) _The New S
132/// Language_. Wadsworth & Brooks/Cole.
133///
134/// Johnson, N. L., Kotz, S. and Balakrishnan, N. (1995) _Continuous
135/// Univariate Distributions_, volume 2, chapters 27 and 30. Wiley,
136/// New York.
137///
138/// ## See Also:
139///
140/// Distributions for other standard distributions, including ‘dchisq’
141/// for chi-squared and ‘dt’ for Student's t distributions.
142///
143/// ## Examples:
144///
145/// Lower Tails
146/// ```rust
147/// # use r2rs_base::traits::StatisticalSlice;
148/// # use r2rs_nmath::{
149/// # distribution::{FBuilder, TBuilder},
150/// # traits::Distribution,
151/// # };
152/// # use strafe_type::FloatConstraint;
153/// let x = <[f64]>::sequence(0.001, 5.0, 100);
154/// let nu = 4.0;
155///
156/// let t = TBuilder::new().with_df(nu).unwrap().build();
157/// let f = FBuilder::new().with_df1(1).with_df2(nu).build();
158/// let r1 = x
159/// .iter()
160/// .map(|x| 2.0 * t.probability(x, true).unwrap() - 1.0)
161/// .collect::<Vec<_>>();
162/// let r2 = x
163/// .iter()
164/// .map(|x| f.probability(x.powi(2), true).unwrap())
165/// .collect::<Vec<_>>();
166/// println!("{r1:?}");
167/// println!("{r2:?}");
168/// # use std::{fs::File, io::Write};
169/// # let mut f = File::create("src/distribution/f/doctest_out/lower_tail.md").unwrap();
170/// # writeln!(f, "```output").unwrap();
171/// # writeln!(f, "{r1:?}").unwrap();
172/// # writeln!(f, "{r2:?}").unwrap();
173/// # writeln!(f, "```").unwrap();
174/// ```
175#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/lower_tail.md")))]
176///
177/// Upper Tails
178/// ```rust
179/// # use r2rs_base::traits::StatisticalSlice;
180/// # use r2rs_nmath::{
181/// # distribution::{FBuilder, TBuilder},
182/// # traits::Distribution,
183/// # };
184/// # use strafe_type::FloatConstraint;
185/// let x = <[f64]>::sequence(0.001, 5.0, 100);
186/// let nu = 4.0;
187///
188/// let t = TBuilder::new().with_df(nu).unwrap().build();
189/// let f = FBuilder::new().with_df1(1).with_df2(nu).build();
190/// let r1 = x
191/// .iter()
192/// .map(|x| 2.0 * t.probability(x, false).unwrap())
193/// .collect::<Vec<_>>();
194/// let r2 = x
195/// .iter()
196/// .map(|x| f.probability(x.powi(2), false).unwrap())
197/// .collect::<Vec<_>>();
198/// println!("{r1:?}");
199/// println!("{r2:?}");
200/// # use std::{fs::File, io::Write};
201/// # let mut f = File::create("src/distribution/f/doctest_out/upper_tail.md").unwrap();
202/// # writeln!(f, "```output").unwrap();
203/// # writeln!(f, "{r1:?}").unwrap();
204/// # writeln!(f, "{r2:?}").unwrap();
205/// # writeln!(f, "```").unwrap();
206/// ```
207#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/upper_tail.md")))]
208///
209/// The density of the square of a $t_m$ is $2*\frac{\{t-density}(x, m)}{2*x}$
210///
211/// Check this is the same as the density of $F_{1,m}$
212/// ```rust
213/// # use r2rs_base::traits::StatisticalSlice;
214/// # use r2rs_nmath::{
215/// # distribution::{FBuilder, TBuilder},
216/// # traits::Distribution,
217/// # };
218/// # use strafe_type::FloatConstraint;
219/// let x = <[f64]>::sequence(0.001, 5.0, 100);
220/// let nu = 5.0;
221///
222/// let t = TBuilder::new().with_df(nu).unwrap().build();
223/// let f = FBuilder::new().with_df1(1).with_df2(nu).build();
224/// let r1 = x
225/// .iter()
226/// .map(|x| t.density(x).unwrap() / x)
227/// .collect::<Vec<_>>();
228/// let r2 = x
229/// .iter()
230/// .map(|x| f.density(x.powi(2)).unwrap())
231/// .collect::<Vec<_>>();
232/// println!("{r1:?}");
233/// println!("{r2:?}");
234/// # use std::{fs::File, io::Write};
235/// # let mut f = File::create("src/distribution/f/doctest_out/dens.md").unwrap();
236/// # writeln!(f, "```output").unwrap();
237/// # writeln!(f, "{r1:?}").unwrap();
238/// # writeln!(f, "{r2:?}").unwrap();
239/// # writeln!(f, "```").unwrap();
240/// ```
241#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/dens.md")))]
242///
243/// Identity: qf(2*p - 1, 1, df) == qt(p, df)^2 for p >= 1/2
244/// ```rust
245/// use r2rs_base::traits::{QuantileType, StatisticalSlice};
246/// use r2rs_nmath::{
247/// distribution::{FBuilder, TBuilder},
248/// traits::Distribution,
249/// };
250/// use strafe_type::FloatConstraint;
251///
252/// let p = <[f64]>::sequence(0.5, 0.99, 50);
253/// let df = 10.0;
254///
255/// let rel_err = |x: &[f64], y: &[f64]| {
256/// let mean = x
257/// .iter()
258/// .chain(y.iter())
259/// .map(|f| f.abs())
260/// .collect::<Vec<_>>()
261/// .mean();
262/// x.iter()
263/// .zip(y.iter())
264/// .map(|(x, y)| if x == y { 0.0 } else { (x - y).abs() / mean })
265/// .collect::<Vec<_>>()
266/// };
267///
268/// let t = TBuilder::new().with_df(df).unwrap().build();
269/// let f = FBuilder::new().with_df1(1).with_df2(df).build();
270/// let r1 = p
271/// .iter()
272/// .map(|p| t.quantile(p, true).unwrap().powi(2))
273/// .collect::<Vec<_>>();
274/// let r2 = p
275/// .iter()
276/// .map(|p| f.quantile(2.0 * p - 1.0, true).unwrap())
277/// .collect::<Vec<_>>();
278///
279/// let error = rel_err(&r1, &r2).quantile(&[0.9], QuantileType::S);
280///
281/// println!("{error:?}");
282/// # use std::{fs::File, io::Write};
283/// # let mut f = File::create("src/distribution/f/doctest_out/quan.md").unwrap();
284/// # writeln!(f, "```output").unwrap();
285/// # writeln!(f, "{error:?}").unwrap();
286/// # writeln!(f, "```").unwrap();
287/// ```
288#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/quan.md")))]
289pub struct F {
290 df1: Rational64,
291 df2: Rational64,
292 ncp: Option<Positive64>,
293}
294
295impl Distribution for F {
296 fn density<R: Into<Real64>>(&self, x: R) -> Real64 {
297 if let Some(ncp) = self.ncp {
298 dnf(x, self.df1, self.df2, ncp, false)
299 } else {
300 df(x, self.df1, self.df2, false)
301 }
302 }
303
304 fn log_density<R: Into<Real64>>(&self, x: R) -> Real64 {
305 if let Some(ncp) = self.ncp {
306 dnf(x, self.df1, self.df2, ncp, true)
307 } else {
308 df(x, self.df1, self.df2, true)
309 }
310 }
311
312 fn probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> Probability64 {
313 if let Some(ncp) = self.ncp {
314 pnf(q, self.df1, self.df2, ncp, lower_tail)
315 } else {
316 pf(q, self.df1, self.df2, lower_tail)
317 }
318 }
319
320 fn log_probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> LogProbability64 {
321 if let Some(ncp) = self.ncp {
322 log_pnf(q, self.df1, self.df2, ncp, lower_tail)
323 } else {
324 log_pf(q, self.df1, self.df2, lower_tail)
325 }
326 }
327
328 fn quantile<P: Into<Probability64>>(&self, p: P, lower_tail: bool) -> Real64 {
329 if let Some(ncp) = self.ncp {
330 qnf(p, self.df1, self.df2, ncp, lower_tail)
331 } else {
332 qf(p, self.df1, self.df2, lower_tail)
333 }
334 }
335
336 fn log_quantile<LP: Into<LogProbability64>>(&self, p: LP, lower_tail: bool) -> Real64 {
337 if let Some(ncp) = self.ncp {
338 log_qnf(p, self.df1, self.df2, ncp, lower_tail)
339 } else {
340 log_qf(p, self.df1, self.df2, lower_tail)
341 }
342 }
343
344 fn random_sample<R: RNG>(&self, rng: &mut R) -> Real64 {
345 if let Some(ncp) = self.ncp {
346 rnf(self.df1, self.df2, ncp, rng)
347 } else {
348 rf(self.df1, self.df2, rng)
349 }
350 }
351}
352
353pub struct FBuilder {
354 df1: Option<Rational64>,
355 df2: Option<Rational64>,
356 ncp: Option<Positive64>,
357}
358
359impl FBuilder {
360 pub fn new() -> Self {
361 Self {
362 df1: None,
363 df2: None,
364 ncp: None,
365 }
366 }
367
368 pub fn with_df1<R: Into<Rational64>>(&mut self, df1: R) -> &mut Self {
369 self.df1 = Some(df1.into());
370 self
371 }
372
373 pub fn with_df2<R: Into<Rational64>>(&mut self, df2: R) -> &mut Self {
374 self.df2 = Some(df2.into());
375 self
376 }
377
378 pub fn with_ncp<P: Into<Positive64>>(&mut self, ncp: P) -> &mut Self {
379 self.ncp = Some(ncp.into());
380 self
381 }
382
383 pub fn build(&self) -> F {
384 let df1 = self.df1.unwrap_or(1.0.into());
385 let df2 = self.df2.unwrap_or(1.0.into());
386
387 F {
388 df1,
389 df2,
390 ncp: self.ncp,
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests;
397
398#[cfg(all(test, feature = "enable_proptest"))]
399mod proptests;
400
401#[cfg(all(test, feature = "enable_covtest"))]
402mod covtests;