mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-05 06:58:15 +00:00
Fix Some Functions Related to Student-T Distribution
Fix #4167. That problem was initially reported as a problem with TINV, however, at its root, it was actually a problem with TDIST (which TINV needs to call). Both of these are deprecated by Microsoft in favor of some newer functions. PhpSpreadsheet incorrectly treated T.INV as an alias for TINV, and did not implement T.INV.2T, which actually is an alias for TINV. To solve this problem, I adapted the Perl CPAN module `Statistics::Disttributions`, written by Michael Kospach around 2003. Unit test results have been manually checked against Excel; a few had to be changed because they were wrong (see the original issue). I believe that all of TDIST, T.DIST.2T, T.DIST.RT, TINV, T.INV, and T.INV.2T now function correctly, at least most of the time. I have not found code or an algorithm that I can use for T.DIST nor for T.TEST, both of which remain unimplemented.
This commit is contained in:
@@ -498,10 +498,10 @@ STDEVP | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Sta
|
||||
STDEVPA | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations::STDEVPA
|
||||
STEYX | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends::STEYX
|
||||
T.DIST | **Not yet Implemented**
|
||||
T.DIST.2T | **Not yet Implemented**
|
||||
T.DIST.RT | **Not yet Implemented**
|
||||
T.INV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::inverse
|
||||
T.INV.2T | **Not yet Implemented**
|
||||
T.DIST.2T | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::tDotDistDot2T
|
||||
T.DIST.RT | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::tDotDistDotRT
|
||||
T.INV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::tDotInv
|
||||
T.INV.2T | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::inverse
|
||||
T.TEST | **Not yet Implemented**
|
||||
TDIST | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::distribution
|
||||
TINV | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::inverse
|
||||
|
||||
@@ -556,10 +556,10 @@ Excel Function | Category | PhpSpreadsheet Function
|
||||
-------------------------|-----------------------|--------------------------------------
|
||||
T | TEXT_AND_DATA | TextData\Text::test
|
||||
T.DIST | STATISTICAL | **Not yet Implemented**
|
||||
T.DIST.2T | STATISTICAL | **Not yet Implemented**
|
||||
T.DIST.RT | STATISTICAL | **Not yet Implemented**
|
||||
T.INV | STATISTICAL | Statistical\Distributions\StudentT::inverse
|
||||
T.INV.2T | STATISTICAL | **Not yet Implemented**
|
||||
T.DIST.2T | STATISTICAL | Statistical\Distributions\StudentT::tDotDistDot2T
|
||||
T.DIST.RT | STATISTICAL | Statistical\Distributions\StudentT::tDotDistDotRT
|
||||
T.INV | STATISTICAL | Statistical\Distributions\StudentT::tDotInv
|
||||
T.INV.2T | STATISTICAL | Statistical\Distributions\StudentT::inverse
|
||||
T.TEST | STATISTICAL | **Not yet Implemented**
|
||||
TAKE | LOOKUP_AND_REFERENCE | LookupRef\ChooseRowsEtc::take
|
||||
TAN | MATH_AND_TRIG | MathTrig\Trig\Tangent::tan
|
||||
|
||||
@@ -552,10 +552,10 @@ Excel Function | Category | PhpSpreadsheet Funct
|
||||
-------------------------|--------------------------------|--------------------------------------
|
||||
T | CATEGORY_TEXT_AND_DATA | \PhpOffice\PhpSpreadsheet\Calculation\TextData\Text::test
|
||||
T.DIST | CATEGORY_STATISTICAL | **Not yet Implemented**
|
||||
T.DIST.2T | CATEGORY_STATISTICAL | **Not yet Implemented**
|
||||
T.DIST.RT | CATEGORY_STATISTICAL | **Not yet Implemented**
|
||||
T.INV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::inverse
|
||||
T.INV.2T | CATEGORY_STATISTICAL | **Not yet Implemented**
|
||||
T.DIST.2T | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::tDotDistDot2T
|
||||
T.DIST.RT | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::tDotDistDotRT
|
||||
T.INV | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::tDotInv
|
||||
T.INV.2T | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\StudentT::inverse
|
||||
T.TEST | CATEGORY_STATISTICAL | **Not yet Implemented**
|
||||
TAKE | CATEGORY_LOOKUP_AND_REFERENCE | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\ChooseRowsEtc::take
|
||||
TAN | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Trig\Tangent::tan
|
||||
|
||||
@@ -2341,12 +2341,12 @@ class FunctionArray extends CalculationBase
|
||||
],
|
||||
'T.DIST.2T' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'functionCall' => [Statistical\Distributions\StudentT::class, 'tDotDistDot2T'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'T.DIST.RT' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'functionCall' => [Statistical\Distributions\StudentT::class, 'tDotDistDotRT'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'TEXT' => [
|
||||
@@ -2426,12 +2426,12 @@ class FunctionArray extends CalculationBase
|
||||
],
|
||||
'T.INV' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'],
|
||||
'functionCall' => [Statistical\Distributions\StudentT::class, 'tDotInv'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'T.INV.2T' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Functions::class, 'DUMMY'],
|
||||
'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'TODAY' => [
|
||||
|
||||
@@ -4,9 +4,15 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
/**
|
||||
* Some of this code is drived from Perl CPAN Statistical::Distributions.
|
||||
* Its copyright statement is:
|
||||
* Copyright 2003 Michael Kospach. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
|
||||
*/
|
||||
class StudentT
|
||||
{
|
||||
use ArrayEnabled;
|
||||
@@ -28,9 +34,33 @@ class StudentT
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $degrees, mixed $tails)
|
||||
{
|
||||
return self::calcDistribution($value, $degrees, $tails, self::distribution(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>|float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function tDotDistDot2T(mixed $value, mixed $degrees)
|
||||
{
|
||||
return self::calcDistribution($value, $degrees, 2, self::distribution(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>|float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function tDotDistDotRT(mixed $value, mixed $degrees)
|
||||
{
|
||||
return self::calcDistribution($value, $degrees, 1, self::distribution(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>|float|string The result, or a string containing an error
|
||||
*/
|
||||
private static function calcDistribution(mixed $value, mixed $degrees, mixed $tails, callable $callback)
|
||||
{
|
||||
if (is_array($value) || is_array($degrees) || is_array($tails)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $tails);
|
||||
return self::evaluateArrayArguments($callback, $value, $degrees, $tails);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -45,7 +75,31 @@ class StudentT
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::calculateDistribution($value, $degrees, $tails);
|
||||
return self::subTProb($value, $degrees, $tails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on code from Perl CPAN Statistical::Distributions.
|
||||
*/
|
||||
private static function subTProb(float $x, int $n, int $tails): float
|
||||
{
|
||||
$w = atan2($x / sqrt($n), 1);
|
||||
$z = cos($w) ** 2;
|
||||
$y = 1;
|
||||
|
||||
for ($i = $n - 2; $i >= 2; $i -= 2) {
|
||||
$y = 1 + ($i - 1) / $i * $z * $y;
|
||||
}
|
||||
|
||||
if ($n % 2 == 0) {
|
||||
$a = sin($w) / 2;
|
||||
$b = 0.5;
|
||||
} else {
|
||||
$a = ($n == 1) ? 0 : (sin($w) * cos($w) / M_PI);
|
||||
$b = 0.5 + $w / M_PI;
|
||||
}
|
||||
|
||||
return $tails * max(0, 1 - $b - $a * $y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,9 +117,17 @@ class StudentT
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $probability, mixed $degrees)
|
||||
{
|
||||
return self::calcInverse($probability, $degrees, 2, self::inverse(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>|float|string The result, or a string containing an error
|
||||
*/
|
||||
private static function calcInverse(mixed $probability, mixed $degrees, int $tails, callable $callback2)
|
||||
{
|
||||
if (is_array($probability) || is_array($degrees)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
|
||||
return self::evaluateArrayArguments($callback2, $probability, $degrees, $tails);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -79,54 +141,107 @@ class StudentT
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$callback = fn ($value) => self::distribution($value, $degrees, 2);
|
||||
$callback = fn ($value) => self::distribution($value, $degrees, $tails);
|
||||
|
||||
$newtonRaphson = new NewtonRaphson($callback);
|
||||
|
||||
return $newtonRaphson->execute($probability);
|
||||
$result = $newtonRaphson->execute($probability);
|
||||
if (is_numeric($result) && $tails === 1) {
|
||||
$result = -$result; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function calculateDistribution(float $value, int $degrees, int $tails): float
|
||||
/**
|
||||
* @return array<mixed>|float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function xtDotInv(mixed $probability, mixed $degrees)
|
||||
{
|
||||
// tdist, which finds the probability that corresponds to a given value
|
||||
// of t with k degrees of freedom. This algorithm is translated from a
|
||||
// pascal function on p81 of "Statistical Computing in Pascal" by D
|
||||
// Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
|
||||
// London). The above Pascal algorithm is itself a translation of the
|
||||
// fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
|
||||
// Laboratory as reported in (among other places) "Applied Statistics
|
||||
// Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
|
||||
// Horwood Ltd.; W. Sussex, England).
|
||||
$tterm = $degrees;
|
||||
$ttheta = atan2($value, sqrt($tterm));
|
||||
$tc = cos($ttheta);
|
||||
$ts = sin($ttheta);
|
||||
return self::calcInverse($probability, $degrees, 1, self::tDotInv(...));
|
||||
}
|
||||
|
||||
if (($degrees % 2) === 1) {
|
||||
$ti = 3;
|
||||
$tterm = $tc;
|
||||
} else {
|
||||
$ti = 2;
|
||||
$tterm = 1;
|
||||
/**
|
||||
* Based on code from Perl CPAN Statistical::Distributions.
|
||||
*
|
||||
* @return array<mixed>|float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function tDotInv(mixed $probability, mixed $degrees)
|
||||
{
|
||||
if (is_array($probability) || is_array($degrees)) {
|
||||
return self::evaluateArrayArguments(self::tDotInv(...), $probability, $degrees);
|
||||
}
|
||||
|
||||
$tsum = $tterm;
|
||||
while ($ti < $degrees) {
|
||||
$tterm *= $tc * $tc * ($ti - 1) / $ti;
|
||||
$tsum += $tterm;
|
||||
$ti += 2;
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$degrees = DistributionValidations::validateInt($degrees);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$tsum *= $ts;
|
||||
if (($degrees % 2) == 1) {
|
||||
$tsum = Functions::M_2DIVPI * ($tsum + $ttheta);
|
||||
if ($degrees < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if ($probability == 0.5) {
|
||||
return 0.0;
|
||||
}
|
||||
if ($probability < 0.5) {
|
||||
$result = self::tDotInv(1.0 - $probability, $degrees);
|
||||
|
||||
return is_numeric($result) ? -$result : $result;
|
||||
}
|
||||
$p = $probability;
|
||||
$n = $degrees;
|
||||
$u = self::subU($p);
|
||||
$u2 = $u ** 2;
|
||||
|
||||
$a = ($u2 + 1) / 4;
|
||||
$b = ((5 * $u2 + 16) * $u2 + 3) / 96;
|
||||
$c = (((3 * $u2 + 19) * $u2 + 17) * $u2 - 15) / 384;
|
||||
$d = ((((79 * $u2 + 776) * $u2 + 1482) * $u2 - 1920) * $u2 - 945) / 92160;
|
||||
$e = (((((27 * $u2 + 339) * $u2 + 930) * $u2 - 1782) * $u2 - 765) * $u2 + 17955) / 368640;
|
||||
|
||||
$x = $u * (1 + ($a + ($b + ($c + ($d + $e / $n) / $n) / $n) / $n) / $n);
|
||||
|
||||
if ($n <= log10($p) ** 2 + 3) {
|
||||
do {
|
||||
$p1 = self::subTProb($x, $n, 1);
|
||||
$n1 = $n + 1;
|
||||
$delta = ($p1 - $p)
|
||||
/ exp(($n1 * log($n1 / ($n + $x * $x))
|
||||
+ log($n / $n1 / 2 / M_PI) - 1
|
||||
+ (1 / $n1 - 1 / $n) / 6) / 2);
|
||||
$x += $delta;
|
||||
$round = sprintf('%.' . abs((int) (log10(abs($x)) - 4)) . 'f', $delta);
|
||||
} while (($x) && ($round != 0));
|
||||
}
|
||||
|
||||
$tValue = 0.5 * (1 + $tsum);
|
||||
if ($tails == 1) {
|
||||
return 1 - abs($tValue);
|
||||
return -$x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on code from Perl CPAN Statistical::Distributions.
|
||||
*/
|
||||
private static function subU(float $p): float
|
||||
{
|
||||
$y = -log(4 * $p * (1 - $p));
|
||||
$x = sqrt(
|
||||
$y * (1.570796288
|
||||
+ $y * (.03706987906
|
||||
+ $y * (-.8364353589E-3
|
||||
+ $y * (-.2250947176E-3
|
||||
+ $y * (.6841218299E-5
|
||||
+ $y * (0.5824238515E-5
|
||||
+ $y * (-.104527497E-5
|
||||
+ $y * (.8360937017E-7
|
||||
+ $y * (-.3231081277E-8
|
||||
+ $y * (.3657763036E-10
|
||||
+ $y * .6936233982E-12))))))))))
|
||||
);
|
||||
if ($p > 0.5) {
|
||||
$x = -$x;
|
||||
}
|
||||
|
||||
return 1 - abs((1 - $tValue) - $tValue);
|
||||
return $x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
class TDist2TTest extends AllSetupTeardown
|
||||
{
|
||||
#[DataProvider('providerTDIST2T')]
|
||||
public function testTDIST2T(mixed $expectedResult, mixed $value, mixed $degrees): void
|
||||
{
|
||||
$this->runTestCaseReference('T.DIST.2T', $expectedResult, $value, $degrees);
|
||||
}
|
||||
|
||||
public static function providerTDIST2T(): array
|
||||
{
|
||||
return require 'tests/data/Calculation/Statistical/TDIST2T.php';
|
||||
}
|
||||
|
||||
#[DataProvider('providerTDistArray')]
|
||||
public function testTDist2TArray(array $expectedResult, string $values, string $degrees): void
|
||||
{
|
||||
$calculation = Calculation::getInstance();
|
||||
|
||||
$formula = "=T.DIST.2T({$values}, {$degrees})";
|
||||
$result = $calculation->calculateFormula($formula);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-6);
|
||||
}
|
||||
|
||||
public static function providerTDistArray(): array
|
||||
{
|
||||
return [
|
||||
'row/column vectors' => [
|
||||
[
|
||||
[0.295167, 0.13932596855884327, 0.08051623795726259],
|
||||
],
|
||||
'2',
|
||||
'{1.5, 3.5, 8}',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
class TDistRTTest extends AllSetupTeardown
|
||||
{
|
||||
#[DataProvider('providerTDISTRT')]
|
||||
public function testTDISTRT(mixed $expectedResult, mixed $value, mixed $degrees): void
|
||||
{
|
||||
$this->runTestCaseReference('T.DIST.RT', $expectedResult, $value, $degrees);
|
||||
}
|
||||
|
||||
public static function providerTDISTRT(): array
|
||||
{
|
||||
return require 'tests/data/Calculation/Statistical/TDISTRT.php';
|
||||
}
|
||||
|
||||
#[DataProvider('providerTDistRTArray')]
|
||||
public function testTDistRTArray(array $expectedResult, string $values, string $degrees): void
|
||||
{
|
||||
$calculation = Calculation::getInstance();
|
||||
|
||||
$formula = "=T.DIST.RT({$values}, {$degrees})";
|
||||
$result = $calculation->calculateFormula($formula);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-6);
|
||||
}
|
||||
|
||||
public static function providerTDistRTArray(): array
|
||||
{
|
||||
return [
|
||||
'row/column vectors' => [
|
||||
[
|
||||
[0.147584, 0.06966298427942164, 0.040258118978631297],
|
||||
],
|
||||
'2',
|
||||
'{1.5, 3.5, 8}',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class TDistTest extends AllSetupTeardown
|
||||
|
||||
$formula = "=TDIST({$values}, {$degrees}, {$tails})";
|
||||
$result = $calculation->calculateFormula($formula);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-6);
|
||||
}
|
||||
|
||||
public static function providerTDistArray(): array
|
||||
@@ -34,8 +34,8 @@ class TDistTest extends AllSetupTeardown
|
||||
return [
|
||||
'row/column vectors' => [
|
||||
[
|
||||
[0.020259663176916964, 0.06966298427942164, 0.040258118978631297],
|
||||
[0.04051932635383393, 0.13932596855884327, 0.08051623795726259],
|
||||
[0.147584, 0.06966298427942164, 0.040258118978631297],
|
||||
[0.295167, 0.13932596855884327, 0.08051623795726259],
|
||||
],
|
||||
'2',
|
||||
'{1.5, 3.5, 8}',
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
// Note that TINV and T.INV.2T should return same results,
|
||||
// so this member just borrows the TINV data.
|
||||
class TDotInvDot2TTest extends AllSetupTeardown
|
||||
{
|
||||
#[DataProvider('providerTINV')]
|
||||
public function testTdotINV(mixed $expectedResult, mixed $probability, mixed $degrees): void
|
||||
{
|
||||
$this->runTestCaseReference('T.INV.2T', $expectedResult, $probability, $degrees);
|
||||
}
|
||||
|
||||
public static function providerTINV(): array
|
||||
{
|
||||
return require 'tests/data/Calculation/Statistical/TINV.php';
|
||||
}
|
||||
|
||||
#[DataProvider('providerTInvArray')]
|
||||
public function testTInvArray(array $expectedResult, string $values, string $degrees): void
|
||||
{
|
||||
$calculation = Calculation::getInstance();
|
||||
|
||||
$formula = "=T.INV.2T({$values}, {$degrees})";
|
||||
$result = $calculation->calculateFormula($formula);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-9);
|
||||
}
|
||||
|
||||
public static function providerTInvArray(): array
|
||||
{
|
||||
return [
|
||||
'row vector' => [
|
||||
[
|
||||
[0.612800788, 0.5023133547575189, 0.4713169827948964],
|
||||
],
|
||||
'0.65',
|
||||
'{1.5, 3.5, 8}',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
class TDotInvTest extends AllSetupTeardown
|
||||
{
|
||||
#[DataProvider('providerTdotINV')]
|
||||
public function testTdotINV(mixed $expectedResult, mixed $probability, mixed $degrees): void
|
||||
{
|
||||
$this->runTestCaseReference('T.INV', $expectedResult, $probability, $degrees);
|
||||
}
|
||||
|
||||
public static function providerTdotINV(): array
|
||||
{
|
||||
return require 'tests/data/Calculation/Statistical/TdotINV.php';
|
||||
}
|
||||
|
||||
#[DataProvider('providerTInvArray')]
|
||||
public function testTInvArray(array $expectedResult, string $values, string $degrees): void
|
||||
{
|
||||
$calculation = Calculation::getInstance();
|
||||
|
||||
$formula = "=T.INV({$values}, {$degrees})";
|
||||
$result = $calculation->calculateFormula($formula);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-6);
|
||||
}
|
||||
|
||||
public static function providerTInvArray(): array
|
||||
{
|
||||
return [
|
||||
'row vector' => [
|
||||
[
|
||||
[0.509525, 0.424202, 0.399469],
|
||||
],
|
||||
'0.65',
|
||||
'{1.5, 3.5, 8}',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class TinvTest extends AllSetupTeardown
|
||||
|
||||
$formula = "=TINV({$values}, {$degrees})";
|
||||
$result = $calculation->calculateFormula($formula);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-9);
|
||||
}
|
||||
|
||||
public static function providerTInvArray(): array
|
||||
@@ -34,7 +34,7 @@ class TinvTest extends AllSetupTeardown
|
||||
return [
|
||||
'row vector' => [
|
||||
[
|
||||
[0.29001075058679815, 0.5023133547575189, 0.4713169827948964],
|
||||
[0.612800788, 0.5023133547575189, 0.4713169827948964],
|
||||
],
|
||||
'0.65',
|
||||
'{1.5, 3.5, 8}',
|
||||
|
||||
@@ -27,6 +27,18 @@ return [
|
||||
0.056475980427,
|
||||
2, 25, 2,
|
||||
],
|
||||
'test 1 derived from Perl suite 1-tail' => [
|
||||
0.0041301,
|
||||
6.251, 3, 1,
|
||||
],
|
||||
'test 2 derived from Perl suite 2-tail' => [
|
||||
0.0082602,
|
||||
6.251, 3, 2,
|
||||
],
|
||||
'issue 4167' => [
|
||||
0.31730337488351257,
|
||||
1.8373862310371, 1, 2,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
'NaN', 10, 2,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
[
|
||||
0.054644929976,
|
||||
1.959999998, 60,
|
||||
],
|
||||
[
|
||||
0.340893132302,
|
||||
1, 10,
|
||||
],
|
||||
[
|
||||
0.056475980427,
|
||||
2, 25,
|
||||
],
|
||||
'test 2 derived from Perl suite 2-tail' => [
|
||||
0.0082602,
|
||||
6.251, 3,
|
||||
],
|
||||
'issue 4167' => [
|
||||
0.31730337488351257,
|
||||
1.8373862310371, 1,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
'NaN', 10,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
1, 'NaN',
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
-1, 10,
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
1, 0,
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
[
|
||||
0.027322464988,
|
||||
1.959999998, 60,
|
||||
],
|
||||
[
|
||||
0.170446566151,
|
||||
1, 10,
|
||||
],
|
||||
[
|
||||
0.028237990213,
|
||||
2, 25,
|
||||
],
|
||||
'test 1 derived from Perl suite 1-tail' => [
|
||||
0.0041301,
|
||||
6.251, 3,
|
||||
],
|
||||
'issue 4167' => [
|
||||
0.15865168744175628,
|
||||
1.8373862310371, 1,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
'NaN', 10,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
1, 'NaN',
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
-1, 10,
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
1, 0,
|
||||
],
|
||||
];
|
||||
@@ -15,6 +15,10 @@ return [
|
||||
0.699812061312,
|
||||
0.5, 10,
|
||||
],
|
||||
'issue 4167' => [
|
||||
12.706204736174712,
|
||||
0.05, 1,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
'NaN', 10,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
[
|
||||
-1.625583529294314,
|
||||
0.05464, 60,
|
||||
],
|
||||
[
|
||||
-0.699812061,
|
||||
0.25, 10,
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.5, 10,
|
||||
],
|
||||
'issue 4167' => [
|
||||
-6.313746407466989,
|
||||
0.05, 1,
|
||||
],
|
||||
[
|
||||
0.5095256881847339,
|
||||
0.65, 1,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
'NaN', 10,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
0.5, 'NaN',
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
-0.5, 10,
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
0.5, 0,
|
||||
],
|
||||
];
|
||||
Reference in New Issue
Block a user