Extract Normal and Standard Normal Distributions from the Statistical Class (#1981)
* Extract Normal and Standard Normal Distributions from the Statistical Class * Extract ZTest from the Statistical Class, and move it to the Standard Normal Distribution class Additional unit tests for NORMINV() * Extract LogNormal distribution functions from Statistical
This commit is contained in:
parent
a4982fd9fe
commit
a2bb825bc5
|
|
@ -1586,22 +1586,22 @@ class Calculation
|
|||
],
|
||||
'LOGINV' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'LOGINV'],
|
||||
'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'LOGNORMDIST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'LOGNORMDIST'],
|
||||
'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'LOGNORM.DIST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'LOGNORMDIST2'],
|
||||
'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'],
|
||||
'argumentCount' => '4',
|
||||
],
|
||||
'LOGNORM.INV' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'LOGINV'],
|
||||
'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'LOOKUP' => [
|
||||
|
|
@ -1776,42 +1776,42 @@ class Calculation
|
|||
],
|
||||
'NORMDIST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMDIST'],
|
||||
'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'],
|
||||
'argumentCount' => '4',
|
||||
],
|
||||
'NORM.DIST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMDIST'],
|
||||
'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'],
|
||||
'argumentCount' => '4',
|
||||
],
|
||||
'NORMINV' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMINV'],
|
||||
'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'NORM.INV' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMINV'],
|
||||
'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'NORMSDIST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMSDIST'],
|
||||
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'NORM.S.DIST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMSDIST2'],
|
||||
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'],
|
||||
'argumentCount' => '1,2',
|
||||
],
|
||||
'NORMSINV' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMSINV'],
|
||||
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'NORM.S.INV' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'NORMSINV'],
|
||||
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'],
|
||||
'argumentCount' => '1',
|
||||
],
|
||||
'NOT' => [
|
||||
|
|
@ -2651,12 +2651,12 @@ class Calculation
|
|||
],
|
||||
'ZTEST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'ZTEST'],
|
||||
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'],
|
||||
'argumentCount' => '2-3',
|
||||
],
|
||||
'Z.TEST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'ZTEST'],
|
||||
'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'],
|
||||
'argumentCount' => '2-3',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -21,90 +21,6 @@ class Statistical
|
|||
const MAX_ITERATIONS = 256;
|
||||
const SQRT2PI = 2.5066282746310005024157652848110452530069867406099;
|
||||
|
||||
/*
|
||||
* inverse_ncdf.php
|
||||
* -------------------
|
||||
* begin : Friday, January 16, 2004
|
||||
* copyright : (C) 2004 Michael Nickerson
|
||||
* email : nickersonm@yahoo.com
|
||||
*
|
||||
*/
|
||||
private static function inverseNcdf($p)
|
||||
{
|
||||
// Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
|
||||
// PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
|
||||
// a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
|
||||
// I have not checked the accuracy of this implementation. Be aware that PHP
|
||||
// will truncate the coeficcients to 14 digits.
|
||||
|
||||
// You have permission to use and distribute this function freely for
|
||||
// whatever purpose you want, but please show common courtesy and give credit
|
||||
// where credit is due.
|
||||
|
||||
// Input paramater is $p - probability - where 0 < p < 1.
|
||||
|
||||
// Coefficients in rational approximations
|
||||
static $a = [
|
||||
1 => -3.969683028665376e+01,
|
||||
2 => 2.209460984245205e+02,
|
||||
3 => -2.759285104469687e+02,
|
||||
4 => 1.383577518672690e+02,
|
||||
5 => -3.066479806614716e+01,
|
||||
6 => 2.506628277459239e+00,
|
||||
];
|
||||
|
||||
static $b = [
|
||||
1 => -5.447609879822406e+01,
|
||||
2 => 1.615858368580409e+02,
|
||||
3 => -1.556989798598866e+02,
|
||||
4 => 6.680131188771972e+01,
|
||||
5 => -1.328068155288572e+01,
|
||||
];
|
||||
|
||||
static $c = [
|
||||
1 => -7.784894002430293e-03,
|
||||
2 => -3.223964580411365e-01,
|
||||
3 => -2.400758277161838e+00,
|
||||
4 => -2.549732539343734e+00,
|
||||
5 => 4.374664141464968e+00,
|
||||
6 => 2.938163982698783e+00,
|
||||
];
|
||||
|
||||
static $d = [
|
||||
1 => 7.784695709041462e-03,
|
||||
2 => 3.224671290700398e-01,
|
||||
3 => 2.445134137142996e+00,
|
||||
4 => 3.754408661907416e+00,
|
||||
];
|
||||
|
||||
// Define lower and upper region break-points.
|
||||
$p_low = 0.02425; //Use lower region approx. below this
|
||||
$p_high = 1 - $p_low; //Use upper region approx. above this
|
||||
|
||||
if (0 < $p && $p < $p_low) {
|
||||
// Rational approximation for lower region.
|
||||
$q = sqrt(-2 * log($p));
|
||||
|
||||
return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
|
||||
(((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
|
||||
} elseif ($p_low <= $p && $p <= $p_high) {
|
||||
// Rational approximation for central region.
|
||||
$q = $p - 0.5;
|
||||
$r = $q * $q;
|
||||
|
||||
return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q /
|
||||
((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
|
||||
} elseif ($p_high < $p && $p < 1) {
|
||||
// Rational approximation for upper region.
|
||||
$q = sqrt(-2 * log(1 - $p));
|
||||
|
||||
return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
|
||||
(((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
|
||||
}
|
||||
// If 0 < p < 1, return a null value
|
||||
return Functions::NULL();
|
||||
}
|
||||
|
||||
/**
|
||||
* AVEDEV.
|
||||
*
|
||||
|
|
@ -766,7 +682,7 @@ class Statistical
|
|||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return self::NORMDIST($value, 0, 1, true) - 0.5;
|
||||
return Statistical\Distributions\Normal::distribution($value, 0, 1, true) - 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1048,6 +964,11 @@ class Statistical
|
|||
*
|
||||
* Returns the inverse of the normal cumulative distribution
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\LogNormal::inverse()
|
||||
* Use the inverse() method in the Statistical\Distributions\LogNormal class instead
|
||||
*
|
||||
* @param float $probability
|
||||
* @param float $mean
|
||||
* @param float $stdDev
|
||||
|
|
@ -1060,19 +981,7 @@ class Statistical
|
|||
*/
|
||||
public static function LOGINV($probability, $mean, $stdDev)
|
||||
{
|
||||
$probability = Functions::flattenSingleValue($probability);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
|
||||
if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
|
||||
if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return exp($mean + $stdDev * self::NORMSINV($probability));
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
return Statistical\Distributions\LogNormal::inverse($probability, $mean, $stdDev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1081,6 +990,11 @@ class Statistical
|
|||
* Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
|
||||
* with parameters mean and standard_dev.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\LogNormal::cumulative()
|
||||
* Use the cumulative() method in the Statistical\Distributions\LogNormal class instead
|
||||
*
|
||||
* @param float $value
|
||||
* @param float $mean
|
||||
* @param float $stdDev
|
||||
|
|
@ -1089,19 +1003,7 @@ class Statistical
|
|||
*/
|
||||
public static function LOGNORMDIST($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 (($value <= 0) || ($stdDev <= 0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return self::NORMSDIST((log($value) - $mean) / $stdDev);
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
return Statistical\Distributions\LogNormal::cumulative($value, $mean, $stdDev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1110,6 +1012,11 @@ class Statistical
|
|||
* Returns the lognormal distribution of x, where ln(x) is normally distributed
|
||||
* with parameters mean and standard_dev.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\LogNormal::distribution()
|
||||
* Use the distribution() method in the Statistical\Distributions\LogNormal class instead
|
||||
*
|
||||
* @param float $value
|
||||
* @param float $mean
|
||||
* @param float $stdDev
|
||||
|
|
@ -1119,25 +1026,7 @@ class Statistical
|
|||
*/
|
||||
public static function LOGNORMDIST2($value, $mean, $stdDev, $cumulative = false)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
$cumulative = (bool) Functions::flattenSingleValue($cumulative);
|
||||
|
||||
if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
|
||||
if (($value <= 0) || ($stdDev <= 0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative === true) {
|
||||
return self::NORMSDIST2((log($value) - $mean) / $stdDev, true);
|
||||
}
|
||||
|
||||
return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) *
|
||||
exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2)));
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
return Statistical\Distributions\LogNormal::distribution($value, $mean, $stdDev, $cumulative);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1329,7 +1218,7 @@ class Statistical
|
|||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\Binomial::negative::mode()
|
||||
* @see Statistical\Distributions\Binomial::negative()
|
||||
* Use the negative() method in the Statistical\Distributions\Binomial class instead
|
||||
*
|
||||
* @param mixed (float) $failures Number of Failures
|
||||
|
|
@ -1350,6 +1239,11 @@ class Statistical
|
|||
* function has a very wide range of applications in statistics, including hypothesis
|
||||
* testing.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\Normal::distribution()
|
||||
* Use the distribution() method in the Statistical\Distributions\Normal class instead
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
* @param mixed (float) $mean Mean Value
|
||||
* @param mixed (float) $stdDev Standard Deviation
|
||||
|
|
@ -1359,24 +1253,7 @@ class Statistical
|
|||
*/
|
||||
public static function NORMDIST($value, $mean, $stdDev, $cumulative)
|
||||
{
|
||||
$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();
|
||||
}
|
||||
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
|
||||
if ($cumulative) {
|
||||
return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2))));
|
||||
}
|
||||
|
||||
return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev))));
|
||||
}
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
return Statistical\Distributions\Normal::distribution($value, $mean, $stdDev, $cumulative);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1384,6 +1261,11 @@ class Statistical
|
|||
*
|
||||
* Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\Normal::inverse()
|
||||
* Use the inverse() method in the Statistical\Distributions\Normal class instead
|
||||
*
|
||||
* @param mixed (float) $probability
|
||||
* @param mixed (float) $mean Mean Value
|
||||
* @param mixed (float) $stdDev Standard Deviation
|
||||
|
|
@ -1392,22 +1274,7 @@ class Statistical
|
|||
*/
|
||||
public static function NORMINV($probability, $mean, $stdDev)
|
||||
{
|
||||
$probability = Functions::flattenSingleValue($probability);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
|
||||
if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) {
|
||||
if (($probability < 0) || ($probability > 1)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
if ($stdDev < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (self::inverseNcdf($probability) * $stdDev) + $mean;
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
return Statistical\Distributions\Normal::inverse($probability, $mean, $stdDev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1417,18 +1284,18 @@ class Statistical
|
|||
* a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
|
||||
* table of standard normal curve areas.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\StandardNormal::cumulative()
|
||||
* Use the cumulative() method in the Statistical\Distributions\StandardNormal class instead
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function NORMSDIST($value)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
if (!is_numeric($value)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return self::NORMDIST($value, 0, 1, true);
|
||||
return Statistical\Distributions\StandardNormal::cumulative($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1438,6 +1305,11 @@ class Statistical
|
|||
* a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
|
||||
* table of standard normal curve areas.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\StandardNormal::distribution()
|
||||
* Use the distribution() method in the Statistical\Distributions\StandardNormal class instead
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
* @param mixed (bool) $cumulative
|
||||
*
|
||||
|
|
@ -1445,13 +1317,7 @@ class Statistical
|
|||
*/
|
||||
public static function NORMSDIST2($value, $cumulative)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
if (!is_numeric($value)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$cumulative = (bool) Functions::flattenSingleValue($cumulative);
|
||||
|
||||
return self::NORMDIST($value, 0, 1, $cumulative);
|
||||
return Statistical\Distributions\StandardNormal::distribution($value, $cumulative);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1459,13 +1325,18 @@ class Statistical
|
|||
*
|
||||
* Returns the inverse of the standard normal cumulative distribution
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\StandardNormal::inverse()
|
||||
* Use the inverse() method in the Statistical\Distributions\StandardNormal class instead
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function NORMSINV($value)
|
||||
{
|
||||
return self::NORMINV($value, 0, 1);
|
||||
return Statistical\Distributions\StandardNormal::inverse($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2098,6 +1969,11 @@ class Statistical
|
|||
* For a given hypothesized population mean, x, Z.TEST returns the probability that the sample mean would be
|
||||
* greater than the average of observations in the data set (array) — that is, the observed sample mean.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Distributions\StandardNormal::zTest()
|
||||
* Use the zTest() method in the Statistical\Distributions\StandardNormal class instead
|
||||
*
|
||||
* @param float $dataSet
|
||||
* @param float $m0 Alpha Parameter
|
||||
* @param float $sigma Beta Parameter
|
||||
|
|
@ -2106,15 +1982,6 @@ class Statistical
|
|||
*/
|
||||
public static function ZTEST($dataSet, $m0, $sigma = null)
|
||||
{
|
||||
$dataSet = Functions::flattenArrayIndexed($dataSet);
|
||||
$m0 = Functions::flattenSingleValue($m0);
|
||||
$sigma = Functions::flattenSingleValue($sigma);
|
||||
|
||||
if ($sigma === null) {
|
||||
$sigma = StandardDeviations::STDEV($dataSet);
|
||||
}
|
||||
$n = count($dataSet);
|
||||
|
||||
return 1 - self::NORMSDIST((Averages::average($dataSet) - $m0) / ($sigma / sqrt($n)));
|
||||
return Statistical\Distributions\StandardNormal::zTest($dataSet, $m0, $sigma);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
class Confidence
|
||||
{
|
||||
|
|
@ -39,6 +38,6 @@ class Confidence
|
|||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return Statistical::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
|
||||
return Distributions\StandardNormal::inverse(1 - $alpha / 2) * $stdDev / sqrt($size);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class LogNormal
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* LOGNORMDIST.
|
||||
*
|
||||
* Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
|
||||
* with parameters mean and standard_dev.
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
* @param mixed (float) $mean
|
||||
* @param mixed (float) $stdDev
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function cumulative($value, $mean, $stdDev)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
|
||||
try {
|
||||
$value = self::validateFloat($value);
|
||||
$mean = self::validateFloat($mean);
|
||||
$stdDev = self::validateFloat($stdDev);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value <= 0) || ($stdDev <= 0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return StandardNormal::cumulative((log($value) - $mean) / $stdDev);
|
||||
}
|
||||
|
||||
/**
|
||||
* LOGNORM.DIST.
|
||||
*
|
||||
* Returns the lognormal distribution of x, where ln(x) is normally distributed
|
||||
* with parameters mean and standard_dev.
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
* @param mixed (float) $mean
|
||||
* @param mixed (float) $stdDev
|
||||
* @param mixed (bool) $cumulative
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function distribution($value, $mean, $stdDev, $cumulative = false)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
$cumulative = Functions::flattenSingleValue($cumulative);
|
||||
|
||||
try {
|
||||
$value = self::validateFloat($value);
|
||||
$mean = self::validateFloat($mean);
|
||||
$stdDev = self::validateFloat($stdDev);
|
||||
$cumulative = self::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value <= 0) || ($stdDev <= 0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative === true) {
|
||||
return StandardNormal::distribution((log($value) - $mean) / $stdDev, true);
|
||||
}
|
||||
|
||||
return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) *
|
||||
exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* LOGINV.
|
||||
*
|
||||
* Returns the inverse of the normal cumulative distribution
|
||||
*
|
||||
* @param mixed (float) $probability
|
||||
* @param mixed (float) $mean
|
||||
* @param mixed (float) $stdDev
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*
|
||||
* @TODO Try implementing P J Acklam's refinement algorithm for greater
|
||||
* accuracy if I can get my head round the mathematics
|
||||
* (as described at) http://home.online.no/~pjacklam/notes/invnorm/
|
||||
*/
|
||||
public static function inverse($probability, $mean, $stdDev)
|
||||
{
|
||||
$probability = Functions::flattenSingleValue($probability);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
|
||||
try {
|
||||
$probability = self::validateProbability($probability);
|
||||
$mean = self::validateFloat($mean);
|
||||
$stdDev = self::validateFloat($stdDev);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($stdDev <= 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return exp($mean + $stdDev * StandardNormal::inverse($probability));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Normal
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
public const SQRT2PI = 2.5066282746310005024157652848110452530069867406099;
|
||||
|
||||
/**
|
||||
* NORMDIST.
|
||||
*
|
||||
* Returns the normal distribution for the specified mean and standard deviation. This
|
||||
* function has a very wide range of applications in statistics, including hypothesis
|
||||
* testing.
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
* @param mixed (float) $mean Mean Value
|
||||
* @param mixed (float) $stdDev Standard Deviation
|
||||
* @param mixed (bool) $cumulative
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function distribution($value, $mean, $stdDev, $cumulative)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
|
||||
try {
|
||||
$value = self::validateFloat($value);
|
||||
$mean = self::validateFloat($mean);
|
||||
$stdDev = self::validateFloat($stdDev);
|
||||
$cumulative = self::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($stdDev < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative) {
|
||||
return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2))));
|
||||
}
|
||||
|
||||
return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev))));
|
||||
}
|
||||
|
||||
/**
|
||||
* NORMINV.
|
||||
*
|
||||
* Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
|
||||
*
|
||||
* @param mixed (float) $probability
|
||||
* @param mixed (float) $mean Mean Value
|
||||
* @param mixed (float) $stdDev Standard Deviation
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function inverse($probability, $mean, $stdDev)
|
||||
{
|
||||
$probability = Functions::flattenSingleValue($probability);
|
||||
$mean = Functions::flattenSingleValue($mean);
|
||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||
|
||||
try {
|
||||
$probability = self::validateProbability($probability);
|
||||
$mean = self::validateFloat($mean);
|
||||
$stdDev = self::validateFloat($stdDev);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($stdDev < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (self::inverseNcdf($probability) * $stdDev) + $mean;
|
||||
}
|
||||
|
||||
/*
|
||||
* inverse_ncdf.php
|
||||
* -------------------
|
||||
* begin : Friday, January 16, 2004
|
||||
* copyright : (C) 2004 Michael Nickerson
|
||||
* email : nickersonm@yahoo.com
|
||||
*
|
||||
*/
|
||||
private static function inverseNcdf($p)
|
||||
{
|
||||
// Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
|
||||
// PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
|
||||
// a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
|
||||
// I have not checked the accuracy of this implementation. Be aware that PHP
|
||||
// will truncate the coeficcients to 14 digits.
|
||||
|
||||
// You have permission to use and distribute this function freely for
|
||||
// whatever purpose you want, but please show common courtesy and give credit
|
||||
// where credit is due.
|
||||
|
||||
// Input paramater is $p - probability - where 0 < p < 1.
|
||||
|
||||
// Coefficients in rational approximations
|
||||
static $a = [
|
||||
1 => -3.969683028665376e+01,
|
||||
2 => 2.209460984245205e+02,
|
||||
3 => -2.759285104469687e+02,
|
||||
4 => 1.383577518672690e+02,
|
||||
5 => -3.066479806614716e+01,
|
||||
6 => 2.506628277459239e+00,
|
||||
];
|
||||
|
||||
static $b = [
|
||||
1 => -5.447609879822406e+01,
|
||||
2 => 1.615858368580409e+02,
|
||||
3 => -1.556989798598866e+02,
|
||||
4 => 6.680131188771972e+01,
|
||||
5 => -1.328068155288572e+01,
|
||||
];
|
||||
|
||||
static $c = [
|
||||
1 => -7.784894002430293e-03,
|
||||
2 => -3.223964580411365e-01,
|
||||
3 => -2.400758277161838e+00,
|
||||
4 => -2.549732539343734e+00,
|
||||
5 => 4.374664141464968e+00,
|
||||
6 => 2.938163982698783e+00,
|
||||
];
|
||||
|
||||
static $d = [
|
||||
1 => 7.784695709041462e-03,
|
||||
2 => 3.224671290700398e-01,
|
||||
3 => 2.445134137142996e+00,
|
||||
4 => 3.754408661907416e+00,
|
||||
];
|
||||
|
||||
// Define lower and upper region break-points.
|
||||
$p_low = 0.02425; //Use lower region approx. below this
|
||||
$p_high = 1 - $p_low; //Use upper region approx. above this
|
||||
|
||||
if (0 < $p && $p < $p_low) {
|
||||
// Rational approximation for lower region.
|
||||
$q = sqrt(-2 * log($p));
|
||||
|
||||
return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
|
||||
(((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
|
||||
} elseif ($p_high < $p && $p < 1) {
|
||||
// Rational approximation for upper region.
|
||||
$q = sqrt(-2 * log(1 - $p));
|
||||
|
||||
return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
|
||||
(((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
|
||||
}
|
||||
|
||||
// Rational approximation for central region.
|
||||
$q = $p - 0.5;
|
||||
$r = $q * $q;
|
||||
|
||||
return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q /
|
||||
((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
|
||||
|
||||
class StandardNormal
|
||||
{
|
||||
/**
|
||||
* NORMSDIST.
|
||||
*
|
||||
* Returns the standard normal cumulative distribution function. The distribution has
|
||||
* a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
|
||||
* table of standard normal curve areas.
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function cumulative($value)
|
||||
{
|
||||
return Normal::distribution($value, 0, 1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* NORM.S.DIST.
|
||||
*
|
||||
* Returns the standard normal cumulative distribution function. The distribution has
|
||||
* a mean of 0 (zero) and a standard deviation of one. Use this function in place of a
|
||||
* table of standard normal curve areas.
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
* @param mixed (bool) $cumulative
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function distribution($value, $cumulative)
|
||||
{
|
||||
return Normal::distribution($value, 0, 1, $cumulative);
|
||||
}
|
||||
|
||||
/**
|
||||
* NORMSINV.
|
||||
*
|
||||
* Returns the inverse of the standard normal cumulative distribution
|
||||
*
|
||||
* @param mixed (float) $value
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function inverse($value)
|
||||
{
|
||||
return Normal::inverse($value, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* ZTEST.
|
||||
*
|
||||
* Returns the one-tailed P-value of a z-test.
|
||||
*
|
||||
* For a given hypothesized population mean, x, Z.TEST returns the probability that the sample mean would be
|
||||
* greater than the average of observations in the data set (array) — that is, the observed sample mean.
|
||||
*
|
||||
* @param mixed (float) $dataSet
|
||||
* @param mixed (float) $m0 Alpha Parameter
|
||||
* @param mixed (null|float) $sigma Beta Parameter
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
*/
|
||||
public static function zTest($dataSet, $m0, $sigma = null)
|
||||
{
|
||||
$dataSet = Functions::flattenArrayIndexed($dataSet);
|
||||
$m0 = Functions::flattenSingleValue($m0);
|
||||
$sigma = Functions::flattenSingleValue($sigma);
|
||||
|
||||
if (!is_numeric($m0) || ($sigma !== null && !is_numeric($sigma))) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
if ($sigma === null) {
|
||||
$sigma = StandardDeviations::STDEV($dataSet);
|
||||
}
|
||||
$n = count($dataSet);
|
||||
|
||||
return 1 - self::cumulative((Averages::average($dataSet) - $m0) / ($sigma / sqrt($n)));
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ class NormInvTest extends TestCase
|
|||
public function testNORMINV($expectedResult, ...$args): void
|
||||
{
|
||||
$result = Statistical::NORMINV(...$args);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
|
||||
}
|
||||
|
||||
public function providerNORMINV()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ZTestTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider providerZTEST
|
||||
*
|
||||
* @param mixed $expectedResult
|
||||
* @param mixed $value
|
||||
* @param mixed $dataSet
|
||||
* @param null|mixed $sigma
|
||||
*/
|
||||
public function testZTEST($expectedResult, $dataSet, $value, $sigma = null): void
|
||||
{
|
||||
$result = Statistical::ZTEST($dataSet, $value, $sigma);
|
||||
self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
|
||||
}
|
||||
|
||||
public function providerZTEST()
|
||||
{
|
||||
return require 'tests/data/Calculation/Statistical/ZTEST.php';
|
||||
}
|
||||
}
|
||||
|
|
@ -15,10 +15,26 @@ return [
|
|||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
1.1, 2.2, 'NAN',
|
||||
'NaN', 2.2, 3.3,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
0.1, 'NaN', 3.3,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
0.1, 2.2, 'NaN',
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
-0.1, 2.2, 3.3,
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
1.1, 2.2, 3.3,
|
||||
],
|
||||
[
|
||||
'#NUM!',
|
||||
0.1, 2.2, -3.3,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
return[
|
||||
[0.0390835557068, 4, 3.5, 1.2],
|
||||
[0.066417114799, 12, 10, 5],
|
||||
['#NUM!', -1.1, -2.2, 3.3],
|
||||
['#NUM!', 1.1, -2.2, -3.3],
|
||||
['#VALUE!', 'NAN', 0.1, 0.2],
|
||||
['#NUM!', -1.1, 2.2, 3.3],
|
||||
['#NUM!', 1.1, 2.2, -3.3],
|
||||
['#VALUE!', 'NaN', 0.1, 0.2],
|
||||
['#VALUE!', 1.1, 'NAN', 0.2],
|
||||
['#VALUE!', 1.1, 0.1, 'NAN'],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ return[
|
|||
[0.0896756593248, 1.1, 2.2, 3.3, false],
|
||||
[0.7566441984111, 1.1, -2.2, 3.3, true],
|
||||
[0.0862879718374, 1.1, -2.2, 3.3, false],
|
||||
['#NUM!', -1.1, -2.2, 3.3, true],
|
||||
['#NUM!', 1.1, -2.2, -3.3, true],
|
||||
['#VALUE!', 'NAN', 0.1, 0.2, true],
|
||||
['#NUM!', -1.1, 2.2, 3.3, true],
|
||||
['#NUM!', 1.1, 2.2, -3.3, true],
|
||||
['#VALUE!', 'NaN', 0.1, 0.2, true],
|
||||
['#VALUE!', 1.1, 'NaN', 0.2, true],
|
||||
['#VALUE!', 1.1, 0.1, 'NaN', true],
|
||||
['#VALUE!', 1.1, 0.1, 0.2, 'NaN'],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -7,5 +7,8 @@ return [
|
|||
[0.2524925375469, 0.8, 1, 0.3, true],
|
||||
[0.8413447460685, 68, 65.5, 2.5, true],
|
||||
['#NUM!', 42, 40, -1.5, true],
|
||||
['#VALUE!', 42, 'ALPHA', 1.5, true],
|
||||
['#VALUE!', 'NaN', 40, 1.5, true],
|
||||
['#VALUE!', 42, 'NaN', 1.5, true],
|
||||
['#VALUE!', 42, 40, 'NaN', true],
|
||||
['#VALUE!', 42, 40, 1.5, 'NaN'],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@ return [
|
|||
[42.000002008416, 0.908789, 40, 1.5],
|
||||
[5.50669420572, 0.6, 5, 2],
|
||||
[63.813775624441, 0.25, 65.5, 2.5],
|
||||
[7.074774056039, 0.015, 12.5, 2.5],
|
||||
[17.925225943961, 0.985, 12.5, 2.5],
|
||||
['#NUM!', -0.5, 2.2, 3.3],
|
||||
['#NUM!', 1.5, 2.2, 3.3],
|
||||
['#NUM!', 0.5, 2.2, -3.3],
|
||||
['#VALUE!', 'NAN', 0.1, 0.2],
|
||||
['#VALUE!', 'NaN', 2.5, 0.2],
|
||||
['#VALUE!', 0.25, 'NaN', 0.2],
|
||||
['#VALUE!', 0.25, 2.5, 'NaN'],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ return [
|
|||
[0.066807201269, -1.5],
|
||||
[0.5, 0],
|
||||
[0.989275889978, 2.3],
|
||||
['#VALUE!', 'NAN'],
|
||||
['#VALUE!', 'NaN'],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -13,5 +13,6 @@ return [
|
|||
[0.398942280401, 0, false],
|
||||
[0.989275889978, 2.3, true],
|
||||
[0.028327037742, 2.3, false],
|
||||
['#VALUE!', 'NAN', true],
|
||||
['#VALUE!', 'NaN', true],
|
||||
['#VALUE!', 2.3, 'NaN'],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,4 +5,7 @@ return [
|
|||
[-0.67448975022342, 0.25],
|
||||
[0.12566134687610, 0.55],
|
||||
[1.28155156414015, 0.9],
|
||||
['#VALUE!', 'NaN'],
|
||||
['#NUM!', -0.9],
|
||||
['#NUM!', 1.9],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
[
|
||||
0.090574196851,
|
||||
[3, 6, 7, 8, 6, 5, 4, 2, 1, 9],
|
||||
4,
|
||||
],
|
||||
[
|
||||
0.863043389130,
|
||||
[3, 6, 7, 8, 6, 5, 4, 2, 1, 9],
|
||||
6,
|
||||
],
|
||||
[
|
||||
0.371103278559,
|
||||
[4, 5, 2, 5, 8, 9, 3, 2, 3, 8, 9, 5],
|
||||
5,
|
||||
],
|
||||
[
|
||||
0.83812918702,
|
||||
[4, 5, 2, 5, 8, 9, 3, 2, 3, 8, 9, 5],
|
||||
6,
|
||||
],
|
||||
[
|
||||
0.412070447871,
|
||||
[1, 2, 3, 3, 4, 4, 8, 10, 12],
|
||||
5,
|
||||
3,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
[1, 2, 3, 3, 4, 4, 8, 10, 12],
|
||||
'NaN',
|
||||
3,
|
||||
],
|
||||
[
|
||||
'#VALUE!',
|
||||
[1, 2, 3, 3, 4, 4, 8, 10, 12],
|
||||
5,
|
||||
'NaN',
|
||||
],
|
||||
];
|
||||
Loading…
Reference in New Issue