Implemented the CHISQ.DIST() Statistical function. (#1961)

* Implementation of the CHISQ.DIST() statistical function for left tail distribution
This commit is contained in:
Mark Baker 2021-03-28 16:13:00 +02:00 committed by GitHub
parent aff1d35e4a
commit e2ff14fe89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 183 additions and 29 deletions

View File

@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Added ### 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) - Support for ActiveSheet and SelectedCells in the ODS Reader and Writer. [PR #1908](https://github.com/PHPOffice/PhpSpreadsheet/pull/1908)
### Changed ### Changed

View File

@ -488,22 +488,22 @@ class Calculation
], ],
'CHIDIST' => [ 'CHIDIST' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distribution'], 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'CHISQ.DIST' => [ 'CHISQ.DIST' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Functions::class, 'DUMMY'], 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'],
'argumentCount' => '3', 'argumentCount' => '3',
], ],
'CHISQ.DIST.RT' => [ 'CHISQ.DIST.RT' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distribution'], 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'CHIINV' => [ 'CHIINV' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverse'], 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'CHISQ.INV' => [ 'CHISQ.INV' => [
@ -513,7 +513,7 @@ class Calculation
], ],
'CHISQ.INV.RT' => [ 'CHISQ.INV.RT' => [
'category' => Category::CATEGORY_STATISTICAL, 'category' => Category::CATEGORY_STATISTICAL,
'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverse'], 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'CHITEST' => [ 'CHITEST' => [

View File

@ -297,8 +297,8 @@ class Statistical
* *
* @Deprecated 1.18.0 * @Deprecated 1.18.0
* *
* @see Statistical\Distributions\ChiSquared::distribution() * @see Statistical\Distributions\ChiSquared::distributionRightTail()
* Use the distribution() method in the Statistical\Distributions\ChiSquared class instead * Use the distributionRightTail() method in the Statistical\Distributions\ChiSquared class instead
* *
* @param float $value Value for the function * @param float $value Value for the function
* @param float $degrees degrees of freedom * @param float $degrees degrees of freedom
@ -307,7 +307,7 @@ class Statistical
*/ */
public static function CHIDIST($value, $degrees) 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 * @Deprecated 1.18.0
* *
* @see Statistical\Distributions\ChiSquared::inverse() * @see Statistical\Distributions\ChiSquared::inverseRightTail()
* Use the inverse() method in the Statistical\Distributions\ChiSquared class instead * Use the inverseRightTail() method in the Statistical\Distributions\ChiSquared class instead
* *
* @param float $probability Probability for the function * @param float $probability Probability for the function
* @param float $degrees degrees of freedom * @param float $degrees degrees of freedom
@ -327,7 +327,7 @@ class Statistical
*/ */
public static function CHIINV($probability, $degrees) 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. * 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 * @Deprecated 1.18.0
* *

View File

@ -21,7 +21,7 @@ class ChiSquared
* *
* @return float|string * @return float|string
*/ */
public static function distribution($value, $degrees) public static function distributionRightTail($value, $degrees)
{ {
$value = Functions::flattenSingleValue($value); $value = Functions::flattenSingleValue($value);
$degrees = Functions::flattenSingleValue($degrees); $degrees = Functions::flattenSingleValue($degrees);
@ -48,16 +48,60 @@ class ChiSquared
} }
/** /**
* CHIINV. * CHIDIST.
* *
* Returns the one-tailed probability of the chi-squared distribution. * 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 (float) $probability Probability for the function
* @param mixed (int) $degrees degrees of freedom * @param mixed (int) $degrees degrees of freedom
* *
* @return float|string * @return float|string
*/ */
public static function inverse($probability, $degrees) public static function inverseRightTail($probability, $degrees)
{ {
$probability = Functions::flattenSingleValue($probability); $probability = Functions::flattenSingleValue($probability);
$degrees = Functions::flattenSingleValue($degrees); $degrees = Functions::flattenSingleValue($degrees);
@ -108,7 +152,7 @@ class ChiSquared
$degrees = self::degrees($rows, $columns); $degrees = self::degrees($rows, $columns);
$result = self::distribution($result, $degrees); $result = self::distributionRightTail($result, $degrees);
return $result; return $result;
} }

View File

@ -30,14 +30,15 @@ abstract class GammaBase
$xLo = 0; $xLo = 0;
$xHi = $alpha * $beta * 5; $xHi = $alpha * $beta * 5;
$x = $xNew = 1;
$dx = 1024; $dx = 1024;
$x = $xNew = 1;
$i = 0; $i = 0;
while ((abs($dx) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { while ((abs($dx) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
// Apply Newton-Raphson step // Apply Newton-Raphson step
$result = self::calculateDistribution($x, $alpha, $beta, true); $result = self::calculateDistribution($x, $alpha, $beta, true);
$error = $result - $probability; $error = $result - $probability;
if ($error == 0.0) { if ($error == 0.0) {
$dx = 0; $dx = 0;
} elseif ($error < 0.0) { } elseif ($error < 0.0) {

View File

@ -20,8 +20,8 @@ class NewtonRaphson
$xLo = 100; $xLo = 100;
$xHi = 0; $xHi = 0;
$x = $xNew = 1;
$dx = 1; $dx = 1;
$x = $xNew = 1;
$i = 0; $i = 0;
while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) {

View File

@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical; use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ChiInvTest extends TestCase class ChiDistLeftTailTest extends TestCase
{ {
protected function setUp(): void protected function setUp(): void
{ {
@ -14,18 +14,18 @@ class ChiInvTest extends TestCase
} }
/** /**
* @dataProvider providerCHIINV * @dataProvider providerCHIDIST
* *
* @param mixed $expectedResult * @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); 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';
} }
} }

View File

@ -6,7 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical; use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ChiDistTest extends TestCase class ChiDistRightTailTest extends TestCase
{ {
protected function setUp(): void protected function setUp(): void
{ {
@ -26,6 +26,6 @@ class ChiDistTest extends TestCase
public function providerCHIDIST() public function providerCHIDIST()
{ {
return require 'tests/data/Calculation/Statistical/CHIDIST.php'; return require 'tests/data/Calculation/Statistical/CHIDISTRightTail.php';
} }
} }

View File

@ -0,0 +1,37 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
use PHPUnit\Framework\TestCase;
class ChiInvRightTailTest extends TestCase
{
protected function setUp(): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
}
/**
* @dataProvider providerCHIINV
*
* @param mixed $expectedResult
* @param mixed $probability
* @param mixed $degrees
*/
public function testCHIINV($expectedResult, $probability, $degrees): void
{
$result = Statistical::CHIINV($probability, $degrees);
if (!is_string($expectedResult)) {
$reverse = Statistical\Distributions\ChiSquared::distributionRightTail($result, $degrees);
self::assertEqualsWithDelta($probability, $reverse, 1E-12);
}
self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
}
public function providerCHIINV()
{
return require 'tests/data/Calculation/Statistical/CHIINVRightTail.php';
}
}

View File

@ -0,0 +1,64 @@
<?php
return [
[
0.520499877813,
0.5, 1, true,
],
[
0.207553748710,
2, 3, false,
],
[
0.111565080074,
3, 2, false,
],
[
0.776869839852,
3, 2, true,
],
[
0.039646370521,
3, 9, false,
],
[
0.035705027315,
3, 9, true,
],
[
0.103349469094,
7.5, 8, false,
],
[
0.516232618446,
7.5, 8, true,
],
[
0.020666985354,
8, 3, false,
],
[
0.953988294311,
8, 3, true,
],
[
'#VALUE!',
'NaN', 3, true,
],
[
'#VALUE!',
2, 'NaN', true,
],
[
'#VALUE!',
2, 3, 'NaN',
],
'Value < 0' => [
'#NUM!',
-8, 3, true,
],
'Degrees < 1' => [
'#NUM!',
8, 0, true,
],
];

View File

@ -10,13 +10,21 @@ return [
0.75, 10, 0.75, 10,
], ],
[ [
18.30697345702, 0.007716715545,
0.050001, 10, 0.93, 1,
],
[
1.021651247532,
0.6, 2,
], ],
[ [
0.45493642312, 0.45493642312,
0.5, 1, 0.5, 1,
], ],
[
4.351460191096,
0.5, 5,
],
[ [
0.101531044268, 0.101531044268,
0.75, 1, 0.75, 1,