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 }