From e4973fa0418effba52641b5989be0c0f75afcad5 Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Thu, 29 Apr 2021 14:34:50 +0200 Subject: [PATCH] Start work on refactoring the last of the Excel Statistical functions (#2033) * Refactoring the last of the Excel Statistical functions --- phpstan-baseline.neon | 5 - .../Calculation/Calculation.php | 26 +- .../Calculation/Statistical.php | 299 ++++-------------- .../Calculation/Statistical/Averages/Mean.php | 131 ++++++++ .../Calculation/Statistical/Deviations.php | 141 +++++++++ .../Distributions/StandardNormal.php | 20 ++ .../Calculation/Statistical/Size.php | 96 ++++++ .../Calculation/Statistical/Standardize.php | 41 +++ .../Functions/Statistical/DevSqTest.php | 31 ++ tests/data/Calculation/Statistical/DEVSQ.php | 20 ++ .../data/Calculation/Statistical/HARMEAN.php | 4 + tests/data/Calculation/Statistical/KURT.php | 8 + tests/data/Calculation/Statistical/SKEW.php | 8 + .../Calculation/Statistical/STANDARDIZE.php | 2 + 14 files changed, 581 insertions(+), 251 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php create mode 100644 src/PhpSpreadsheet/Calculation/Statistical/Deviations.php create mode 100644 src/PhpSpreadsheet/Calculation/Statistical/Size.php create mode 100644 src/PhpSpreadsheet/Calculation/Statistical/Standardize.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/DevSqTest.php create mode 100644 tests/data/Calculation/Statistical/DEVSQ.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e07db455..89355400 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1085,11 +1085,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php - - - message: "#^Binary operation \"\\-\" between float\\|int and float\\|string results in an error\\.$#" - count: 4 - path: src/PhpSpreadsheet/Calculation/Statistical.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:MAXIFS\\(\\) should return float but returns float\\|string\\|null\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 1a0fb6d7..54d9a6e8 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -848,7 +848,7 @@ class Calculation ], 'DEVSQ' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'DEVSQ'], + 'functionCall' => [Statistical\Deviations::class, 'sumSquares'], 'argumentCount' => '1+', ], 'DGET' => [ @@ -1185,7 +1185,7 @@ class Calculation ], 'GAUSS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAUSS'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'gauss'], 'argumentCount' => '1', ], 'GCD' => [ @@ -1195,7 +1195,7 @@ class Calculation ], 'GEOMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GEOMEAN'], + 'functionCall' => [Statistical\Averages\Mean::class, 'geometric'], 'argumentCount' => '1+', ], 'GESTEP' => [ @@ -1215,7 +1215,7 @@ class Calculation ], 'HARMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'HARMEAN'], + 'functionCall' => [Statistical\Averages\Mean::class, 'harmonic'], 'argumentCount' => '1+', ], 'HEX2BIN' => [ @@ -1529,12 +1529,12 @@ class Calculation ], 'KURT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'KURT'], + 'functionCall' => [Statistical\Deviations::class, 'kurtosis'], 'argumentCount' => '1+', ], 'LARGE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LARGE'], + 'functionCall' => [Statistical\Size::class, 'large'], 'argumentCount' => '2', ], 'LCM' => [ @@ -2071,7 +2071,7 @@ class Calculation ], 'RANK.EQ' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'RANK'], + 'functionCall' => [Statistical\Percentiles::class, 'RANK'], 'argumentCount' => '2,3', ], 'RATE' => [ @@ -2218,7 +2218,7 @@ class Calculation ], 'SKEW' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'SKEW'], + 'functionCall' => [Statistical\Deviations::class, 'skew'], 'argumentCount' => '1+', ], 'SKEW.P' => [ @@ -2238,7 +2238,7 @@ class Calculation ], 'SMALL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'SMALL'], + 'functionCall' => [Statistical\Size::class, 'small'], 'argumentCount' => '2', ], 'SORT' => [ @@ -2263,7 +2263,7 @@ class Calculation ], 'STANDARDIZE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STANDARDIZE'], + 'functionCall' => [Statistical\Standardize::class, 'execute'], 'argumentCount' => '3', ], 'STDEV' => [ @@ -2288,12 +2288,12 @@ class Calculation ], 'STDEVP' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVP'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], 'argumentCount' => '1+', ], 'STDEVPA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVPA'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVPA'], 'argumentCount' => '1+', ], 'STEYX' => [ @@ -2469,7 +2469,7 @@ class Calculation ], 'TRIMMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TRIMMEAN'], + 'functionCall' => [Statistical\Averages\Mean::class, 'trim'], 'argumentCount' => '2', ], 'TRUE' => [ diff --git a/src/PhpSpreadsheet/Calculation/Statistical.php b/src/PhpSpreadsheet/Calculation/Statistical.php index 0f5355b9..e34c0c1e 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical.php +++ b/src/PhpSpreadsheet/Calculation/Statistical.php @@ -13,12 +13,14 @@ use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations; use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends; use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances; +/** + * @deprecated 1.18.0 + */ class Statistical { const LOG_GAMMA_X_MAX_VALUE = 2.55e305; const EPS = 2.22e-16; const MAX_VALUE = 1.2e308; - const MAX_ITERATIONS = 256; const SQRT2PI = 2.5066282746310005024157652848110452530069867406099; /** @@ -428,48 +430,18 @@ class Statistical * Excel Function: * DEVSQ(value1[,value2[, ...]]) * + * @Deprecated 1.18.0 + * + * @see Statistical\Deviations::sumSquares() + * Use the sumSquares() method in the Statistical\Deviations class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function DEVSQ(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - // Return value - $returnValue = null; - - $aMean = Averages::average($aArgs); - if ($aMean != Functions::DIV0()) { - $aCount = -1; - foreach ($aArgs as $k => $arg) { - // Is it a numeric value? - if ( - (is_bool($arg)) && - ((!Functions::isCellValue($k)) || - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) - ) { - $arg = (int) $arg; - } - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = ($arg - $aMean) ** 2; - } else { - $returnValue += ($arg - $aMean) ** 2; - } - ++$aCount; - } - } - - // Return - if ($returnValue === null) { - return Functions::NAN(); - } - - return $returnValue; - } - - return Functions::NA(); + return Statistical\Deviations::sumSquares(...$args); } /** @@ -671,18 +643,18 @@ class Statistical * Calculates the probability that a member of a standard normal population will fall between * the mean and z standard deviations from the mean. * + * @Deprecated 1.18.0 + * + * @see Statistical\Distributions\StandardNormal::gauss() + * Use the gauss() method in the Statistical\Distributions\StandardNormal class instead + * * @param float $value * * @return float|string The result, or a string containing an error */ public static function GAUSS($value) { - $value = Functions::flattenSingleValue($value); - if (!is_numeric($value)) { - return Functions::VALUE(); - } - - return Statistical\Distributions\Normal::distribution($value, 0, 1, true) - 0.5; + return Statistical\Distributions\StandardNormal::gauss($value); } /** @@ -695,23 +667,18 @@ class Statistical * Excel Function: * GEOMEAN(value1[,value2[, ...]]) * + * @Deprecated 1.18.0 + * + * @see Statistical\Averages\Mean::geometric() + * Use the geometric() method in the Statistical\Averages\Mean class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function GEOMEAN(...$args) { - $aArgs = Functions::flattenArray($args); - - $aMean = MathTrig\Product::evaluate($aArgs); - if (is_numeric($aMean) && ($aMean > 0)) { - $aCount = Counts::COUNT($aArgs); - if (Minimum::MIN($aArgs) > 0) { - return $aMean ** (1 / $aCount); - } - } - - return Functions::NAN(); + return Statistical\Averages\Mean::geometric(...$args); } /** @@ -745,38 +712,18 @@ class Statistical * Excel Function: * HARMEAN(value1[,value2[, ...]]) * + * @Deprecated 1.18.0 + * + * @see Statistical\Averages\Mean::harmonic() + * Use the harmonic() method in the Statistical\Averages\Mean class instead + * * @param mixed ...$args Data values * * @return float|string */ public static function HARMEAN(...$args) { - // Return value - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - if (Minimum::MIN($aArgs) < 0) { - return Functions::NAN(); - } - $aCount = 0; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($arg <= 0) { - return Functions::NAN(); - } - $returnValue += (1 / $arg); - ++$aCount; - } - } - - // Return - if ($aCount > 0) { - return 1 / ($returnValue / $aCount); - } - - return Functions::NA(); + return Statistical\Averages\Mean::harmonic(...$args); } /** @@ -835,40 +782,18 @@ class Statistical * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a * relatively flat distribution. * + * @Deprecated 1.18.0 + * + * @see Statistical\Deviations::kurtosis() + * Use the kurtosis() method in the Statistical\Deviations class instead + * * @param array ...$args Data Series * * @return float|string */ public static function KURT(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - $mean = Averages::average($aArgs); - $stdDev = StandardDeviations::STDEV($aArgs); - - if ($stdDev > 0) { - $count = $summer = 0; - // Loop through arguments - foreach ($aArgs as $k => $arg) { - if ( - (is_bool($arg)) && - (!Functions::isMatrixValue($k)) - ) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summer += (($arg - $mean) / $stdDev) ** 4; - ++$count; - } - } - } - - // Return - if ($count > 3) { - return $summer * ($count * ($count + 1) / (($count - 1) * ($count - 2) * ($count - 3))) - (3 * ($count - 1) ** 2 / (($count - 2) * ($count - 3))); - } - } - - return Functions::DIV0(); + return Statistical\Deviations::kurtosis(...$args); } /** @@ -880,37 +805,18 @@ class Statistical * Excel Function: * LARGE(value1[,value2[, ...]],entry) * + * @Deprecated 1.18.0 + * + * @see Statistical\Size::large() + * Use the large() method in the Statistical\Size class instead + * * @param mixed $args Data values * * @return float|string The result, or a string containing an error */ public static function LARGE(...$args) { - $aArgs = Functions::flattenArray($args); - $entry = array_pop($aArgs); - - if ((is_numeric($entry)) && (!is_string($entry))) { - $entry = (int) floor($entry); - - // Calculate - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - $count = Counts::COUNT($mArgs); - --$entry; - if (($entry < 0) || ($entry >= $count) || ($count == 0)) { - return Functions::NAN(); - } - rsort($mArgs); - - return $mArgs[$entry]; - } - - return Functions::VALUE(); + return Statistical\Size::large(...$args); } /** @@ -1503,40 +1409,18 @@ class Statistical * asymmetric tail extending toward more positive values. Negative skewness indicates a * distribution with an asymmetric tail extending toward more negative values. * + * @Deprecated 1.18.0 + * + * @see Statistical\Deviations::skew() + * Use the skew() method in the Statistical\Deviations class instead + * * @param array ...$args Data Series * * @return float|string The result, or a string containing an error */ public static function SKEW(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - $mean = Averages::average($aArgs); - $stdDev = StandardDeviations::STDEV($aArgs); - - if ($stdDev === 0.0 || is_string($stdDev)) { - return Functions::DIV0(); - } - - $count = $summer = 0; - // Loop through arguments - foreach ($aArgs as $k => $arg) { - if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { - } elseif (!is_numeric($arg)) { - return Functions::VALUE(); - } else { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summer += (($arg - $mean) / $stdDev) ** 3; - ++$count; - } - } - } - - if ($count > 2) { - return $summer * ($count / (($count - 1) * ($count - 2))); - } - - return Functions::DIV0(); + return Statistical\Deviations::skew(...$args); } /** @@ -1568,38 +1452,18 @@ class Statistical * Excel Function: * SMALL(value1[,value2[, ...]],entry) * + * @Deprecated 1.18.0 + * + * @see Statistical\Size::small() + * Use the small() method in the Statistical\Size class instead + * * @param mixed $args Data values * * @return float|string The result, or a string containing an error */ public static function SMALL(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $entry = array_pop($aArgs); - - if ((is_numeric($entry)) && (!is_string($entry))) { - $entry = (int) floor($entry); - - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - $count = Counts::COUNT($mArgs); - --$entry; - if (($entry < 0) || ($entry >= $count) || ($count == 0)) { - return Functions::NAN(); - } - sort($mArgs); - - return $mArgs[$entry]; - } - - return Functions::VALUE(); + return Statistical\Size::small(...$args); } /** @@ -1607,6 +1471,11 @@ class Statistical * * Returns a normalized value from a distribution characterized by mean and standard_dev. * + * @Deprecated 1.18.0 + * + * @see Statistical\Standardize::execute() + * Use the execute() method in the Statistical\Standardize class instead + * * @param float $value Value to normalize * @param float $mean Mean Value * @param float $stdDev Standard Deviation @@ -1615,19 +1484,7 @@ class Statistical */ public static function STANDARDIZE($value, $mean, $stdDev) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if ($stdDev <= 0) { - return Functions::NAN(); - } - - return ($value - $mean) / $stdDev; - } - - return Functions::VALUE(); + return Statistical\Standardize::execute($value, $mean, $stdDev); } /** @@ -1812,42 +1669,18 @@ class Statistical * Excel Function: * TRIMEAN(value1[,value2[, ...]], $discard) * + * @Deprecated 1.18.0 + * + *@see Statistical\Averages\Mean::trim() + * Use the trim() method in the Statistical\Averages\Mean class instead + * * @param mixed $args Data values * * @return float|string */ public static function TRIMMEAN(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $percent = array_pop($aArgs); - - if ((is_numeric($percent)) && (!is_string($percent))) { - if (($percent < 0) || ($percent > 1)) { - return Functions::NAN(); - } - - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - - $discard = floor(Counts::COUNT($mArgs) * $percent / 2); - sort($mArgs); - - for ($i = 0; $i < $discard; ++$i) { - array_pop($mArgs); - array_shift($mArgs); - } - - return Averages::average($mArgs); - } - - return Functions::VALUE(); + return Statistical\Averages\Mean::trim(...$args); } /** @@ -1860,12 +1693,12 @@ class Statistical * * @Deprecated 1.17.0 * + *@see Statistical\Variances::VAR() + * Use the VAR() method in the Statistical\Variances class instead + * * @param mixed ...$args Data values * * @return float|string (string if result is an error) - * - *@see Statistical\Variances::VAR() - * Use the VAR() method in the Statistical\Variances class instead */ public static function VARFunc(...$args) { diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php b/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php new file mode 100644 index 00000000..e39a3ff2 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php @@ -0,0 +1,131 @@ + 0)) { + $aCount = Counts::COUNT($aArgs); + if (Minimum::MIN($aArgs) > 0) { + return $aMean ** (1 / $aCount); + } + } + + return Functions::NAN(); + } + + /** + * HARMEAN. + * + * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the + * arithmetic mean of reciprocals. + * + * Excel Function: + * HARMEAN(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string + */ + public static function harmonic(...$args) + { + // Loop through arguments + $aArgs = Functions::flattenArray($args); + if (Minimum::MIN($aArgs) < 0) { + return Functions::NAN(); + } + + $returnValue = 0; + $aCount = 0; + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + if ($arg <= 0) { + return Functions::NAN(); + } + $returnValue += (1 / $arg); + ++$aCount; + } + } + + // Return + if ($aCount > 0) { + return 1 / ($returnValue / $aCount); + } + + return Functions::NA(); + } + + /** + * TRIMMEAN. + * + * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean + * taken by excluding a percentage of data points from the top and bottom tails + * of a data set. + * + * Excel Function: + * TRIMEAN(value1[,value2[, ...]], $discard) + * + * @param mixed $args Data values + * + * @return float|string + */ + public static function trim(...$args) + { + $aArgs = Functions::flattenArray($args); + + // Calculate + $percent = array_pop($aArgs); + + if ((is_numeric($percent)) && (!is_string($percent))) { + if (($percent < 0) || ($percent > 1)) { + return Functions::NAN(); + } + + $mArgs = []; + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $mArgs[] = $arg; + } + } + + $discard = floor(Counts::COUNT($mArgs) * $percent / 2); + sort($mArgs); + + for ($i = 0; $i < $discard; ++$i) { + array_pop($mArgs); + array_shift($mArgs); + } + + return Averages::average($mArgs); + } + + return Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php b/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php new file mode 100644 index 00000000..1b64fe2c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php @@ -0,0 +1,141 @@ + $arg) { + // Is it a numeric value? + if ( + (is_bool($arg)) && + ((!Functions::isCellValue($k)) || + (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) + ) { + $arg = (int) $arg; + } + if ((is_numeric($arg)) && (!is_string($arg))) { + $returnValue += ($arg - $aMean) ** 2; + ++$aCount; + } + } + + return $aCount === 0 ? Functions::VALUE() : $returnValue; + } + + /** + * KURT. + * + * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness + * or flatness of a distribution compared with the normal distribution. Positive + * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a + * relatively flat distribution. + * + * @param array ...$args Data Series + * + * @return float|string + */ + public static function kurtosis(...$args) + { + $aArgs = Functions::flattenArrayIndexed($args); + $mean = Averages::average($aArgs); + if (!is_numeric($mean)) { + return Functions::DIV0(); + } + $stdDev = StandardDeviations::STDEV($aArgs); + + if ($stdDev > 0) { + $count = $summer = 0; + + foreach ($aArgs as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summer += (($arg - $mean) / $stdDev) ** 4; + ++$count; + } + } + } + + if ($count > 3) { + return $summer * ($count * ($count + 1) / + (($count - 1) * ($count - 2) * ($count - 3))) - (3 * ($count - 1) ** 2 / + (($count - 2) * ($count - 3))); + } + } + + return Functions::DIV0(); + } + + /** + * SKEW. + * + * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry + * of a distribution around its mean. Positive skewness indicates a distribution with an + * asymmetric tail extending toward more positive values. Negative skewness indicates a + * distribution with an asymmetric tail extending toward more negative values. + * + * @param array ...$args Data Series + * + * @return float|string The result, or a string containing an error + */ + public static function skew(...$args) + { + $aArgs = Functions::flattenArrayIndexed($args); + $mean = Averages::average($aArgs); + if (!is_numeric($mean)) { + return Functions::DIV0(); + } + $stdDev = StandardDeviations::STDEV($aArgs); + if ($stdDev === 0.0 || is_string($stdDev)) { + return Functions::DIV0(); + } + + $count = $summer = 0; + // Loop through arguments + foreach ($aArgs as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } elseif (!is_numeric($arg)) { + return Functions::VALUE(); + } else { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summer += (($arg - $mean) / $stdDev) ** 3; + ++$count; + } + } + } + + if ($count > 2) { + return $summer * ($count / (($count - 1) * ($count - 2))); + } + + return Functions::DIV0(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php index 0dde2006..d10f02a5 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php @@ -55,6 +55,26 @@ class StandardNormal return Normal::inverse($value, 0, 1); } + /** + * GAUSS. + * + * Calculates the probability that a member of a standard normal population will fall between + * the mean and z standard deviations from the mean. + * + * @param mixed $value + * + * @return float|string The result, or a string containing an error + */ + public static function gauss($value) + { + $value = Functions::flattenSingleValue($value); + if (!is_numeric($value)) { + return Functions::VALUE(); + } + + return self::distribution($value, true) - 0.5; + } + /** * ZTEST. * diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Size.php b/src/PhpSpreadsheet/Calculation/Statistical/Size.php new file mode 100644 index 00000000..de4b6d6c --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Size.php @@ -0,0 +1,96 @@ += $count) || ($count == 0)) { + return Functions::NAN(); + } + rsort($mArgs); + + return $mArgs[$entry]; + } + + return Functions::VALUE(); + } + + /** + * SMALL. + * + * Returns the nth smallest value in a data set. You can use this function to + * select a value based on its relative standing. + * + * Excel Function: + * SMALL(value1[,value2[, ...]],entry) + * + * @param mixed $args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function small(...$args) + { + $aArgs = Functions::flattenArray($args); + + $entry = array_pop($aArgs); + + if ((is_numeric($entry)) && (!is_string($entry))) { + $entry = (int) floor($entry); + + $mArgs = self::filter($aArgs); + $count = Counts::COUNT($mArgs); + --$entry; + if (($entry < 0) || ($entry >= $count) || ($count == 0)) { + return Functions::NAN(); + } + sort($mArgs); + + return $mArgs[$entry]; + } + + return Functions::VALUE(); + } + + /** + * @param mixed[] $args Data values + */ + protected static function filter(array $args): array + { + $mArgs = []; + + foreach ($args as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $mArgs[] = $arg; + } + } + + return $mArgs; + } +} diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php b/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php new file mode 100644 index 00000000..2f3c58e7 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php @@ -0,0 +1,41 @@ +getMessage(); + } + + if ($stdDev <= 0) { + return Functions::NAN(); + } + + return ($value - $mean) / $stdDev; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/DevSqTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/DevSqTest.php new file mode 100644 index 00000000..2db29300 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Statistical/DevSqTest.php @@ -0,0 +1,31 @@ +