From a79a4ddbabaec1f1865ca2ff4fffa15e20e2cf39 Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Thu, 4 Mar 2021 21:45:56 +0100 Subject: [PATCH] Statistical refactoring - Confidence() and Trend() (#1898) - Move TREND() functions into the Statistical Trends class - Unit tests for TREND() - Create Confidence class for Statistical Confidence functions, and the CONFIDENCE() method --- .../Calculation/Calculation.php | 6 +-- .../Calculation/Statistical.php | 46 ++++++------------- .../Calculation/Statistical/Confidence.php | 41 +++++++++++++++++ .../Calculation/Statistical/Trends.php | 32 +++++++++++++ .../Functions/Statistical/GrowthTest.php | 5 +- .../Functions/Statistical/TrendTest.php | 33 +++++++++++++ tests/PhpSpreadsheetTests/Shared/FontTest.php | 15 +++--- tests/PhpSpreadsheetTests/Style/ColorTest.php | 15 +++--- tests/data/Calculation/Statistical/TREND.php | 34 ++++++++++++++ 9 files changed, 177 insertions(+), 50 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/Statistical/Confidence.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TrendTest.php create mode 100644 tests/data/Calculation/Statistical/TREND.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 04b8ffab..0d06f04e 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -579,12 +579,12 @@ class Calculation ], 'CONFIDENCE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CONFIDENCE'], + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], 'argumentCount' => '3', ], 'CONFIDENCE.NORM' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CONFIDENCE'], + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], 'argumentCount' => '3', ], 'CONFIDENCE.T' => [ @@ -2454,7 +2454,7 @@ class Calculation ], 'TREND' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TREND'], + 'functionCall' => [Statistical\Trends::class, 'TREND'], 'argumentCount' => '1-4', ], 'TRIM' => [ diff --git a/src/PhpSpreadsheet/Calculation/Statistical.php b/src/PhpSpreadsheet/Calculation/Statistical.php index 4b5c8094..8a9e3fea 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical.php +++ b/src/PhpSpreadsheet/Calculation/Statistical.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages; use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Conditional; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Confidence; use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts; use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Maximum; use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum; @@ -832,6 +833,11 @@ class Statistical * * Returns the confidence interval for a population mean * + * @Deprecated 1.18.0 + * + * @see Statistical\Confidence::CONFIDENCE() + * Use the CONFIDENCE() method in the Statistical\Confidence class instead + * * @param float $alpha * @param float $stdDev Standard Deviation * @param float $size @@ -840,23 +846,7 @@ class Statistical */ public static function CONFIDENCE($alpha, $stdDev, $size) { - $alpha = Functions::flattenSingleValue($alpha); - $stdDev = Functions::flattenSingleValue($stdDev); - $size = Functions::flattenSingleValue($size); - - if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) { - $size = floor($size); - if (($alpha <= 0) || ($alpha >= 1)) { - return Functions::NAN(); - } - if (($stdDev <= 0) || ($size < 1)) { - return Functions::NAN(); - } - - return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size); - } - - return Functions::VALUE(); + return Confidence::CONFIDENCE($alpha, $stdDev, $size); } /** @@ -2941,6 +2931,11 @@ class Statistical * * Returns values along a linear Trend * + * @Deprecated 1.18.0 + * + * @see Statistical\Trends::TREND() + * Use the TREND() method in the Statistical\Trends class instead + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @param mixed[] $newValues Values of X for which we want to find Y @@ -2950,22 +2945,7 @@ class Statistical */ public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) { - $yValues = Functions::flattenArray($yValues); - $xValues = Functions::flattenArray($xValues); - $newValues = Functions::flattenArray($newValues); - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); - if (empty($newValues)) { - $newValues = $bestFitLinear->getXValues(); - } - - $returnArray = []; - foreach ($newValues as $xValue) { - $returnArray[0][] = $bestFitLinear->getValueOfYForX($xValue); - } - - return $returnArray; + return Trends::TREND($yValues, $xValues, $newValues, $const); } /** diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php b/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php new file mode 100644 index 00000000..c4c2a7dd --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php @@ -0,0 +1,41 @@ += 1)) { + return Functions::NAN(); + } + if (($stdDev <= 0) || ($size < 1)) { + return Functions::NAN(); + } + + return Statistical::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size); + } + + return Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Trends.php b/src/PhpSpreadsheet/Calculation/Statistical/Trends.php index 23bffced..d06214de 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Trends.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Trends.php @@ -388,4 +388,36 @@ class Trends return $bestFitLinear->getStdevOfResiduals(); } + + /** + * TREND. + * + * Returns values along a linear Trend + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * @param mixed[] $newValues Values of X for which we want to find Y + * @param bool $const a logical value specifying whether to force the intersect to equal 0 + * + * @return array of float + */ + public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) + { + $yValues = Functions::flattenArray($yValues); + $xValues = Functions::flattenArray($xValues); + $newValues = Functions::flattenArray($newValues); + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); + if (empty($newValues)) { + $newValues = $bestFitLinear->getXValues(); + } + + $returnArray = []; + foreach ($newValues as $xValue) { + $returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)]; + } + + return $returnArray; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GrowthTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GrowthTest.php index ce4eef8a..6e1cf7bf 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GrowthTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/GrowthTest.php @@ -17,10 +17,11 @@ class GrowthTest extends TestCase * @dataProvider providerGROWTH * * @param mixed $expectedResult + * @param mixed $yValues */ - public function testGROWTH($expectedResult, ...$args): void + public function testGROWTH($expectedResult, $yValues, ...$args): void { - $result = Statistical::GROWTH(...$args); + $result = Statistical::GROWTH($yValues, ...$args); self::assertEqualsWithDelta($expectedResult, $result[0], 1E-12); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TrendTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TrendTest.php new file mode 100644 index 00000000..b4f756b5 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/TrendTest.php @@ -0,0 +1,33 @@ +