diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d252d5..d7b123ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Added -- Implemented the CHITEST() Statistical function. +- Implemented the CHITEST() and CHISQ.DIST() Statistical function. - Support for ActiveSheet and SelectedCells in the ODS Reader and Writer. [PR #1908](https://github.com/PHPOffice/PhpSpreadsheet/pull/1908) ### Changed diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 62524c37..c4a88bc3 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -488,22 +488,22 @@ class Calculation ], 'CHIDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distribution'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], 'argumentCount' => '2', ], 'CHISQ.DIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'], 'argumentCount' => '3', ], 'CHISQ.DIST.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distribution'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], 'argumentCount' => '2', ], 'CHIINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverse'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], 'argumentCount' => '2', ], 'CHISQ.INV' => [ @@ -513,7 +513,7 @@ class Calculation ], 'CHISQ.INV.RT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverse'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], 'argumentCount' => '2', ], 'CHITEST' => [ diff --git a/src/PhpSpreadsheet/Calculation/Statistical.php b/src/PhpSpreadsheet/Calculation/Statistical.php index 5f557431..01caba14 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical.php +++ b/src/PhpSpreadsheet/Calculation/Statistical.php @@ -297,8 +297,8 @@ class Statistical * * @Deprecated 1.18.0 * - * @see Statistical\Distributions\ChiSquared::distribution() - * Use the distribution() method in the Statistical\Distributions\ChiSquared class instead + * @see Statistical\Distributions\ChiSquared::distributionRightTail() + * Use the distributionRightTail() method in the Statistical\Distributions\ChiSquared class instead * * @param float $value Value for the function * @param float $degrees degrees of freedom @@ -307,7 +307,7 @@ class Statistical */ public static function CHIDIST($value, $degrees) { - return Statistical\Distributions\ChiSquared::distribution($value, $degrees); + return Statistical\Distributions\ChiSquared::distributionRightTail($value, $degrees); } /** @@ -317,8 +317,8 @@ class Statistical * * @Deprecated 1.18.0 * - * @see Statistical\Distributions\ChiSquared::inverse() - * Use the inverse() method in the Statistical\Distributions\ChiSquared class instead + * @see Statistical\Distributions\ChiSquared::inverseRightTail() + * Use the inverseRightTail() method in the Statistical\Distributions\ChiSquared class instead * * @param float $probability Probability for the function * @param float $degrees degrees of freedom @@ -327,7 +327,7 @@ class Statistical */ public static function CHIINV($probability, $degrees) { - return Statistical\Distributions\ChiSquared::inverse($probability, $degrees); + return Statistical\Distributions\ChiSquared::inverseRightTail($probability, $degrees); } /** @@ -2159,7 +2159,7 @@ class Statistical /** * TINV. * - * Returns the one-tailed probability of the chi-squared distribution. + * Returns the one-tailed probability of the Student-T distribution. * * @Deprecated 1.18.0 * diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php index dfd090de..409e5883 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php @@ -21,7 +21,7 @@ class ChiSquared * * @return float|string */ - public static function distribution($value, $degrees) + public static function distributionRightTail($value, $degrees) { $value = Functions::flattenSingleValue($value); $degrees = Functions::flattenSingleValue($degrees); @@ -48,16 +48,60 @@ class ChiSquared } /** - * CHIINV. + * CHIDIST. * * Returns the one-tailed probability of the chi-squared distribution. * + * @param mixed (float) $value Value for the function + * @param mixed (int) $degrees degrees of freedom + * @param mixed $cumulative + * + * @return float|string + */ + public static function distributionLeftTail($value, $degrees, $cumulative) + { + $value = Functions::flattenSingleValue($value); + $degrees = Functions::flattenSingleValue($degrees); + $cumulative = Functions::flattenSingleValue($cumulative); + + try { + $value = self::validateFloat($value); + $degrees = self::validateInt($degrees); + $cumulative = self::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return Functions::NAN(); + } + if ($value < 0) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return 1; + } + + return Functions::NAN(); + } + + if ($cumulative === true) { + return 1 - self::distributionRightTail($value, $degrees); + } + + return (($value ** (($degrees / 2) - 1) * exp(-$value / 2))) / + ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2)); + } + + /** + * CHIINV. + * + * Returns the inverse of the right-tailed probability of the chi-squared distribution. + * * @param mixed (float) $probability Probability for the function * @param mixed (int) $degrees degrees of freedom * * @return float|string */ - public static function inverse($probability, $degrees) + public static function inverseRightTail($probability, $degrees) { $probability = Functions::flattenSingleValue($probability); $degrees = Functions::flattenSingleValue($degrees); @@ -108,7 +152,7 @@ class ChiSquared $degrees = self::degrees($rows, $columns); - $result = self::distribution($result, $degrees); + $result = self::distributionRightTail($result, $degrees); return $result; } diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php index 3f76787d..89170f7c 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php @@ -30,14 +30,15 @@ abstract class GammaBase $xLo = 0; $xHi = $alpha * $beta * 5; - $x = $xNew = 1; $dx = 1024; + $x = $xNew = 1; $i = 0; while ((abs($dx) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { // Apply Newton-Raphson step $result = self::calculateDistribution($x, $alpha, $beta, true); $error = $result - $probability; + if ($error == 0.0) { $dx = 0; } elseif ($error < 0.0) { diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php index 298cdfaf..d4025f6f 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php @@ -20,8 +20,8 @@ class NewtonRaphson $xLo = 100; $xHi = 0; - $x = $xNew = 1; $dx = 1; + $x = $xNew = 1; $i = 0; while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistLeftTailTest.php similarity index 58% rename from tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvTest.php rename to tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistLeftTailTest.php index 72680914..3c7a8d4e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistLeftTailTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Statistical; use PHPUnit\Framework\TestCase; -class ChiInvTest extends TestCase +class ChiDistLeftTailTest extends TestCase { protected function setUp(): void { @@ -14,18 +14,18 @@ class ChiInvTest extends TestCase } /** - * @dataProvider providerCHIINV + * @dataProvider providerCHIDIST * * @param mixed $expectedResult */ - public function testCHIINV($expectedResult, ...$args): void + public function testCHIDIST($expectedResult, ...$args): void { - $result = Statistical::CHIINV(...$args); + $result = Statistical\Distributions\ChiSquared::distributionLeftTail(...$args); self::assertEqualsWithDelta($expectedResult, $result, 1E-12); } - public function providerCHIINV() + public function providerCHIDIST() { - return require 'tests/data/Calculation/Statistical/CHIINV.php'; + return require 'tests/data/Calculation/Statistical/CHIDISTLeftTail.php'; } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistRightTailTest.php similarity index 92% rename from tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistTest.php rename to tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistRightTailTest.php index 9dc7326c..26bf5ab7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiDistRightTailTest.php @@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Statistical; use PHPUnit\Framework\TestCase; -class ChiDistTest extends TestCase +class ChiDistRightTailTest extends TestCase { protected function setUp(): void { @@ -26,6 +26,6 @@ class ChiDistTest extends TestCase public function providerCHIDIST() { - return require 'tests/data/Calculation/Statistical/CHIDIST.php'; + return require 'tests/data/Calculation/Statistical/CHIDISTRightTail.php'; } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvRightTailTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvRightTailTest.php new file mode 100644 index 00000000..75949f39 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/ChiInvRightTailTest.php @@ -0,0 +1,37 @@ + [ + '#NUM!', + -8, 3, true, + ], + 'Degrees < 1' => [ + '#NUM!', + 8, 0, true, + ], +]; diff --git a/tests/data/Calculation/Statistical/CHIDIST.php b/tests/data/Calculation/Statistical/CHIDISTRightTail.php similarity index 100% rename from tests/data/Calculation/Statistical/CHIDIST.php rename to tests/data/Calculation/Statistical/CHIDISTRightTail.php diff --git a/tests/data/Calculation/Statistical/CHIINV.php b/tests/data/Calculation/Statistical/CHIINVRightTail.php similarity index 82% rename from tests/data/Calculation/Statistical/CHIINV.php rename to tests/data/Calculation/Statistical/CHIINVRightTail.php index f931a780..58b317e5 100644 --- a/tests/data/Calculation/Statistical/CHIINV.php +++ b/tests/data/Calculation/Statistical/CHIINVRightTail.php @@ -10,13 +10,21 @@ return [ 0.75, 10, ], [ - 18.30697345702, - 0.050001, 10, + 0.007716715545, + 0.93, 1, + ], + [ + 1.021651247532, + 0.6, 2, ], [ 0.45493642312, 0.5, 1, ], + [ + 4.351460191096, + 0.5, 5, + ], [ 0.101531044268, 0.75, 1,