PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Calculation / Statistical / Distributions / ChiSquared.php

ChiSquared.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php

272 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
4
5 use TablePress\PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
6 use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception;
7 use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Functions;
8 use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
9
10 class ChiSquared
11 {
12 use ArrayEnabled;
13
14 private const EPS = 2.22e-16;
15
16 /**
17 * CHIDIST.
18 *
19 * Returns the one-tailed probability of the chi-squared distribution.
20 *
21 * @param mixed $value Float value for which we want the probability
22 * Or can be an array of values
23 * @param mixed $degrees Integer degrees of freedom
24 * Or can be an array of values
25 *
26 * @return array<mixed>|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
27 * with the same dimensions
28 */
29 public static function distributionRightTail($value, $degrees)
30 {
31 if (is_array($value) || is_array($degrees)) {
32 return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees);
33 }
34
35 try {
36 $value = DistributionValidations::validateFloat($value);
37 $degrees = DistributionValidations::validateInt($degrees);
38 } catch (Exception $e) {
39 return $e->getMessage();
40 }
41
42 if ($degrees < 1) {
43 return ExcelError::NAN();
44 }
45 if ($value < 0) {
46 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
47 return 1;
48 }
49
50 return ExcelError::NAN();
51 }
52
53 return Gamma::regularizedGammaQ($degrees / 2, $value / 2);
54 }
55
56 /**
57 * CHIDIST.
58 *
59 * Returns the one-tailed probability of the chi-squared distribution.
60 *
61 * @param mixed $value Float value for which we want the probability
62 * Or can be an array of values
63 * @param mixed $degrees Integer degrees of freedom
64 * Or can be an array of values
65 * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
66 * Or can be an array of values
67 *
68 * @return array<mixed>|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
69 * with the same dimensions
70 */
71 public static function distributionLeftTail($value, $degrees, $cumulative)
72 {
73 if (is_array($value) || is_array($degrees) || is_array($cumulative)) {
74 return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $cumulative);
75 }
76
77 try {
78 $value = DistributionValidations::validateFloat($value);
79 $degrees = DistributionValidations::validateInt($degrees);
80 $cumulative = DistributionValidations::validateBool($cumulative);
81 } catch (Exception $e) {
82 return $e->getMessage();
83 }
84
85 if ($degrees < 1) {
86 return ExcelError::NAN();
87 }
88 if ($value < 0) {
89 if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
90 return 1;
91 }
92
93 return ExcelError::NAN();
94 }
95
96 if ($cumulative === true) {
97 $temp = self::distributionRightTail($value, $degrees);
98
99 return 1 - (is_numeric($temp) ? $temp : 0);
100 }
101
102 if ($value == 0.0) {
103 if ($degrees === 2) {
104 return 0.5;
105 }
106
107 return ($degrees === 1) ? INF : 0.0;
108 }
109
110 // Log domain, so large degrees of freedom cannot overflow Gamma(d/2).
111 return exp((($degrees / 2) - 1) * log($value) - $value / 2 - ($degrees / 2) * M_LN2 - Gamma::logGamma($degrees / 2));
112 }
113
114 /**
115 * CHIINV.
116 *
117 * Returns the inverse of the right-tailed probability of the chi-squared distribution.
118 *
119 * @param mixed $probability Float probability at which you want to evaluate the distribution
120 * Or can be an array of values
121 * @param mixed $degrees Integer degrees of freedom
122 * Or can be an array of values
123 *
124 * @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
125 * with the same dimensions
126 */
127 public static function inverseRightTail($probability, $degrees)
128 {
129 if (is_array($probability) || is_array($degrees)) {
130 return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
131 }
132
133 try {
134 $probability = DistributionValidations::validateProbability($probability);
135 $degrees = DistributionValidations::validateInt($degrees);
136 } catch (Exception $e) {
137 return $e->getMessage();
138 }
139
140 if ($degrees < 1) {
141 return ExcelError::NAN();
142 }
143
144 $callback = fn (float $value): float => Gamma::regularizedGammaQ($degrees / 2, $value / 2);
145
146 $newtonRaphson = new NewtonRaphson($callback);
147
148 return $newtonRaphson->execute($probability);
149 }
150
151 /**
152 * CHIINV.
153 *
154 * Returns the inverse of the left-tailed probability of the chi-squared distribution.
155 *
156 * @param mixed $probability Float probability at which you want to evaluate the distribution
157 * Or can be an array of values
158 * @param mixed $degrees Integer degrees of freedom
159 * Or can be an array of values
160 *
161 * @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
162 * with the same dimensions
163 */
164 public static function inverseLeftTail($probability, $degrees)
165 {
166 if (is_array($probability) || is_array($degrees)) {
167 return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
168 }
169
170 try {
171 $probability = DistributionValidations::validateProbability($probability);
172 $degrees = DistributionValidations::validateInt($degrees);
173 } catch (Exception $e) {
174 return $e->getMessage();
175 }
176
177 if ($degrees < 1) {
178 return ExcelError::NAN();
179 }
180
181 return self::inverseLeftTailCalculation($probability, $degrees);
182 }
183
184 /**
185 * CHITEST.
186 *
187 * Uses the chi-square test to calculate the probability that the differences between two supplied data sets
188 * (of observed and expected frequencies), are likely to be simply due to sampling error,
189 * or if they are likely to be real.
190 *
191 * @param float[] $actual an array of observed frequencies
192 * @param float[] $expected an array of expected frequencies
193 * @return float|string
194 */
195 public static function test($actual, $expected)
196 {
197 $rows = count($actual);
198 /** @var float[] */
199 $actual = Functions::flattenArray($actual);
200 /** @var float[] */
201 $expected = Functions::flattenArray($expected);
202 $columns = intdiv(count($actual), $rows);
203
204 $countActuals = count($actual);
205 $countExpected = count($expected);
206 if ($countActuals !== $countExpected || $countActuals === 1) {
207 return ExcelError::NAN();
208 }
209
210 $result = 0.0;
211 for ($i = 0; $i < $countActuals; ++$i) {
212 if ($expected[$i] == 0.0) {
213 return ExcelError::DIV0();
214 } elseif ($expected[$i] < 0.0) {
215 return ExcelError::NAN();
216 }
217 $result += (($actual[$i] - $expected[$i]) ** 2) / $expected[$i];
218 }
219
220 $degrees = self::degrees($rows, $columns);
221
222 /** @var float|string */
223 $result = Functions::scalar(self::distributionRightTail($result, $degrees));
224
225 return $result;
226 }
227
228 protected static function degrees(int $rows, int $columns): int
229 {
230 if ($rows === 1) {
231 return $columns - 1;
232 } elseif ($columns === 1) {
233 return $rows - 1;
234 }
235
236 return ($columns - 1) * ($rows - 1);
237 }
238
239 private static function inverseLeftTailCalculation(float $probability, int $degrees): float
240 {
241 // bracket the root
242 $min = 0;
243 $sd = sqrt(2.0 * $degrees);
244 $max = 2 * $sd;
245 $s = -1;
246
247 while ($s * self::pchisq($max, $degrees) > $probability * $s) {
248 $min = $max;
249 $max += 2 * $sd;
250 }
251
252 // Find root using bisection
253 $chi2 = 0.5 * ($min + $max);
254
255 while (($max - $min) > self::EPS * $chi2) {
256 if ($s * self::pchisq($chi2, $degrees) > $probability * $s) {
257 $min = $chi2;
258 } else {
259 $max = $chi2;
260 }
261 $chi2 = 0.5 * ($min + $max);
262 }
263
264 return $chi2;
265 }
266
267 private static function pchisq(float $chi2, int $degrees): float
268 {
269 return Gamma::regularizedGammaP($degrees / 2, 0.5 * $chi2);
270 }
271 }
272