The OpenD Programming Language

1 /++
2 This module contains algorithms for the $(LINK2 https://en.wikipedia.org/wiki/F-distribution, F Distribution).
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 
6 Authors: John Michael Hall
7 
8 Copyright: 2022-3 Mir Stat Authors.
9 
10 +/
11 
12 module mir.stat.distribution.f;
13 
14 import mir.internal.utility: isFloatingPoint;
15 
16 /++
17 Computes the F probability density function (PDF).
18 
19 Params:
20     x = value to evaluate PDF
21     df1 = degrees of freedom parameter #1
22     df2 = degrees of freedom parameter #2
23 
24 See_also:
25     $(LINK2 https://en.wikipedia.org/wiki/F-distribution, F Distribution)
26 +/
27 @safe pure nothrow @nogc
28 T fPDF(T)(const T x, const T df1, const T df2)
29     if (isFloatingPoint!T)
30     in (x >= 0, "x must be greater than or equal to 0")
31     in (df1 > 0, "df1 must be greater than zero")
32     in (df2 > 0, "df2 must be greater than zero")
33 {
34     import mir.math.common: pow, sqrt;
35     import std.mathspecial: beta;
36 
37     if (df1 == 1 && x == 0)
38         return T.infinity;
39     return sqrt(pow(df1 * x, df1) * pow(df2, df2) / pow(df1 * x + df2, df1 + df2)) / (x * beta(df1 * 0.5, df2 * 0.5));
40 }
41 
42 ///
43 version(mir_stat_test)
44 @safe pure nothrow @nogc
45 unittest {
46     import mir.test: shouldApprox;
47 
48     0.50.fPDF(1, 1).shouldApprox == 0.3001054;
49     0.75.fPDF(1, 2).shouldApprox == 0.2532039;
50     0.25.fPDF(0.5, 4).shouldApprox == 0.4904035;
51     0.10.fPDF(2, 1).shouldApprox == 0.7607258;
52     0.00.fPDF(1, 3).shouldApprox == double.infinity;
53 }
54 
55 /++
56 Computes the F cumulative distribution function (CDF).
57 
58 Params:
59     x = value to evaluate CDF
60     df1 = degrees of freedom parameter #1
61     df2 = degrees of freedom parameter #2
62 
63 See_also:
64     $(LINK2 https://en.wikipedia.org/wiki/F-distribution, F Distribution)
65 +/
66 @safe pure nothrow @nogc
67 T fCDF(T)(const T x, const T df1, const T df2)
68     if (isFloatingPoint!T)
69     in (x >= 0, "x must be greater than or equal to 0")
70     in (df1 > 0, "df1 must be greater than zero")
71     in (df2 > 0, "df2 must be greater than zero")
72 {
73     import std.mathspecial: betaIncomplete;
74 
75     return betaIncomplete(df1 * 0.5, df2 * 0.5, (df1 * x) / (df1 * x + df2));
76 }
77 
78 ///
79 version(mir_stat_test)
80 @safe pure nothrow @nogc
81 unittest {
82     import mir.test: shouldApprox;
83 
84     0.50.fCDF(1, 1).shouldApprox == 0.3918266;
85     0.75.fCDF(1, 2).shouldApprox == 0.522233;
86     0.25.fCDF(0.5, 4).shouldApprox == 0.5183719;
87     0.10.fCDF(2, 1).shouldApprox == 0.08712907;
88 }
89 
90 /++
91 Computes the F complementary cumulative distribution function (CCDF).
92 
93 Params:
94     x = value to evaluate CCDF
95     df1 = degrees of freedom parameter #1
96     df2 = degrees of freedom parameter #2
97 
98 See_also:
99     $(LINK2 https://en.wikipedia.org/wiki/F-distribution, F Distribution)
100 +/
101 @safe pure nothrow @nogc
102 T fCCDF(T)(const T x, const T df1, const T df2)
103     if (isFloatingPoint!T)
104     in (x >= 0, "x must be greater than or equal to 0")
105     in (df1 > 0, "df1 must be greater than zero")
106     in (df2 > 0, "df2 must be greater than zero")
107 {
108     import std.mathspecial: betaIncomplete;
109 
110     return betaIncomplete(df2 * 0.5, df1 * 0.5, df2 / (df1 * x + df2));
111 }
112 
113 ///
114 version(mir_stat_test)
115 @safe pure nothrow @nogc
116 unittest {
117     import mir.test: shouldApprox;
118 
119     0.50.fCCDF(1, 1).shouldApprox == 0.6081734;
120     0.75.fCCDF(1, 2).shouldApprox == 0.477767;
121     0.25.fCCDF(0.5, 4).shouldApprox == 0.4816281;
122     0.10.fCCDF(2, 1).shouldApprox == 0.9128709;
123 }
124 
125 /++
126 Computes the F inverse cumulative distribution function (InvCDF).
127 
128 Params:
129     p = value to evaluate InvCDF
130     df1 = degrees of freedom parameter #1
131     df2 = degrees of freedom parameter #2
132 
133 See_also:
134     $(LINK2 https://en.wikipedia.org/wiki/F-distribution, F Distribution)
135 +/
136 @safe pure nothrow @nogc
137 T fInvCDF(T)(const T p, const T df1, const T df2)
138     if (isFloatingPoint!T)
139     in (p >= 0, "p must be greater than or equal to 0")
140     in (p <= 1, "p must be less than or equal to 1")
141     in (df1 > 0, "df1 must be greater than zero")
142     in (df2 > 0, "df2 must be greater than zero")
143 {
144     import std.mathspecial: betaIncompleteInverse;
145 
146     if (p == 0)
147         return 0;
148     if (p == 1)
149         return T.infinity;
150     const T invBeta = betaIncompleteInverse(df1 * 0.5, df2 * 0.5, p);
151     return invBeta * df2 / ((1 - invBeta) * df1);
152 }
153 
154 ///
155 version(mir_stat_test)
156 @safe pure nothrow @nogc
157 unittest {
158     import mir.test: shouldApprox;
159 
160     0.3918266.fInvCDF(1, 1).shouldApprox == 0.50; 
161     0.522233.fInvCDF(1, 2).shouldApprox == 0.75;
162     0.5183719.fInvCDF(0.5, 4).shouldApprox == 0.25;
163     0.08712907.fInvCDF(2, 1).shouldApprox == 0.10;
164     0.0.fInvCDF(1, 1).shouldApprox == 0;
165     1.0.fInvCDF(1, 1).shouldApprox == double.infinity;
166 }
167 
168 /++
169 Computes the F log probability density function (LPDF).
170 
171 Params:
172     x = value to evaluate LPDF
173     df1 = degrees of freedom parameter #1
174     df2 = degrees of freedom parameter #2
175 
176 See_also:
177     $(LINK2 https://en.wikipedia.org/wiki/F-distribution, F Distribution)
178 +/
179 @safe pure nothrow @nogc
180 T fLPDF(T)(const T x, const T df1, const T df2)
181     if (isFloatingPoint!T)
182     in (x >= 0, "x must be greater than or equal to 0")
183     in (df1 > 0, "df1 must be greater than zero")
184     in (df2 > 0, "df2 must be greater than zero")
185 {
186     import mir.math.common: log;
187     import mir.math.internal.log_beta: logBeta;
188 
189     if (df1 == 1 && x == 0)
190         return T.infinity;
191     return 0.5 * (df1 * log(df1 * x) + df2 * log(df2) - (df1 + df2) * log(df1 * x + df2)) - log(x) - logBeta(df1 * 0.5, df2 * 0.5);
192 }
193 
194 ///
195 version(mir_stat_test)
196 @safe pure nothrow @nogc
197 unittest {
198     import mir.math.common: log;
199     import mir.test: shouldApprox;
200 
201     0.50.fLPDF(1, 1).shouldApprox == log(0.3001054);
202     0.75.fLPDF(1, 2).shouldApprox == log(0.2532039);
203     0.25.fLPDF(0.5, 4).shouldApprox == log(0.4904035);
204     0.10.fLPDF(2, 1).shouldApprox == log(0.7607258);
205     0.00.fLPDF(1, 3).shouldApprox == double.infinity;
206 }