Extract Binomial Distribution functions from Statistical (#1974)
* Extract Binomial Distribution functions from Statistical Replace the old MS algorithm for CRITBINOM() (which has now been replaced with te BINOM.INV() function) with a brute force approach - I'll look to refine it later. The MS algorithm is no longer documented, and the implementation produced erroneous results anyway * Exract the NEGBINOMDIST() function as well; still need to add a cumulative flag to support the additional argument for the newer NEGBINOM.DIST() function * Rationalise validation of probability arguments
This commit is contained in:
parent
df93577ef8
commit
029f345987
|
|
@ -418,22 +418,22 @@ class Calculation
|
||||||
],
|
],
|
||||||
'BINOMDIST' => [
|
'BINOMDIST' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'BINOMDIST'],
|
'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
|
||||||
'argumentCount' => '4',
|
'argumentCount' => '4',
|
||||||
],
|
],
|
||||||
'BINOM.DIST' => [
|
'BINOM.DIST' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'BINOMDIST'],
|
'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
|
||||||
'argumentCount' => '4',
|
'argumentCount' => '4',
|
||||||
],
|
],
|
||||||
'BINOM.DIST.RANGE' => [
|
'BINOM.DIST.RANGE' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Functions::class, 'DUMMY'],
|
'functionCall' => [Statistical\Distributions\Binomial::class, 'range'],
|
||||||
'argumentCount' => '3,4',
|
'argumentCount' => '3,4',
|
||||||
],
|
],
|
||||||
'BINOM.INV' => [
|
'BINOM.INV' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Functions::class, 'DUMMY'],
|
'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
|
||||||
'argumentCount' => '3',
|
'argumentCount' => '3',
|
||||||
],
|
],
|
||||||
'BITAND' => [
|
'BITAND' => [
|
||||||
|
|
@ -695,7 +695,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'CRITBINOM' => [
|
'CRITBINOM' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'CRITBINOM'],
|
'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
|
||||||
'argumentCount' => '3',
|
'argumentCount' => '3',
|
||||||
],
|
],
|
||||||
'CSC' => [
|
'CSC' => [
|
||||||
|
|
@ -1751,7 +1751,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'NEGBINOMDIST' => [
|
'NEGBINOMDIST' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'NEGBINOMDIST'],
|
'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'],
|
||||||
'argumentCount' => '3',
|
'argumentCount' => '3',
|
||||||
],
|
],
|
||||||
'NEGBINOM.DIST' => [
|
'NEGBINOM.DIST' => [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
|
||||||
|
|
||||||
|
class Single
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -251,6 +251,11 @@ class Statistical
|
||||||
* experiment. For example, BINOMDIST can calculate the probability that two of the next three
|
* experiment. For example, BINOMDIST can calculate the probability that two of the next three
|
||||||
* babies born are male.
|
* babies born are male.
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Statistical\Distributions\Binomial::distribution()
|
||||||
|
* Use the distribution() method in the Statistical\Distributions\Binomial class instead
|
||||||
|
*
|
||||||
* @param mixed (float) $value Number of successes in trials
|
* @param mixed (float) $value Number of successes in trials
|
||||||
* @param mixed (float) $trials Number of trials
|
* @param mixed (float) $trials Number of trials
|
||||||
* @param mixed (float) $probability Probability of success on each trial
|
* @param mixed (float) $probability Probability of success on each trial
|
||||||
|
|
@ -260,34 +265,7 @@ class Statistical
|
||||||
*/
|
*/
|
||||||
public static function BINOMDIST($value, $trials, $probability, $cumulative)
|
public static function BINOMDIST($value, $trials, $probability, $cumulative)
|
||||||
{
|
{
|
||||||
$value = Functions::flattenSingleValue($value);
|
return Statistical\Distributions\Binomial::distribution($value, $trials, $probability, $cumulative);
|
||||||
$trials = Functions::flattenSingleValue($trials);
|
|
||||||
$probability = Functions::flattenSingleValue($probability);
|
|
||||||
|
|
||||||
if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) {
|
|
||||||
$value = floor($value);
|
|
||||||
$trials = floor($trials);
|
|
||||||
if (($value < 0) || ($value > $trials)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
if (($probability < 0) || ($probability > 1)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
if ((is_numeric($cumulative)) || (is_bool($cumulative))) {
|
|
||||||
if ($cumulative) {
|
|
||||||
$summer = 0;
|
|
||||||
for ($i = 0; $i <= $value; ++$i) {
|
|
||||||
$summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $summer;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MathTrig::COMBIN($trials, $value) * $probability ** $value * (1 - $probability) ** ($trials - $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -510,6 +488,11 @@ class Statistical
|
||||||
*
|
*
|
||||||
* See https://support.microsoft.com/en-us/help/828117/ for details of the algorithm used
|
* See https://support.microsoft.com/en-us/help/828117/ for details of the algorithm used
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Statistical\Distributions\Binomial::inverse()
|
||||||
|
* Use the inverse() method in the Statistical\Distributions\Binomial class instead
|
||||||
|
*
|
||||||
* @param float $trials number of Bernoulli trials
|
* @param float $trials number of Bernoulli trials
|
||||||
* @param float $probability probability of a success on each trial
|
* @param float $probability probability of a success on each trial
|
||||||
* @param float $alpha criterion value
|
* @param float $alpha criterion value
|
||||||
|
|
@ -523,110 +506,7 @@ class Statistical
|
||||||
*/
|
*/
|
||||||
public static function CRITBINOM($trials, $probability, $alpha)
|
public static function CRITBINOM($trials, $probability, $alpha)
|
||||||
{
|
{
|
||||||
$trials = floor(Functions::flattenSingleValue($trials));
|
return Statistical\Distributions\Binomial::inverse($trials, $probability, $alpha);
|
||||||
$probability = Functions::flattenSingleValue($probability);
|
|
||||||
$alpha = Functions::flattenSingleValue($alpha);
|
|
||||||
|
|
||||||
if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) {
|
|
||||||
$trials = (int) $trials;
|
|
||||||
if ($trials < 0) {
|
|
||||||
return Functions::NAN();
|
|
||||||
} elseif (($probability < 0.0) || ($probability > 1.0)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
} elseif (($alpha < 0.0) || ($alpha > 1.0)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($alpha <= 0.5) {
|
|
||||||
$t = sqrt(log(1 / ($alpha * $alpha)));
|
|
||||||
$trialsApprox = 0 - ($t + (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t));
|
|
||||||
} else {
|
|
||||||
$t = sqrt(log(1 / (1 - $alpha) ** 2));
|
|
||||||
$trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t);
|
|
||||||
}
|
|
||||||
|
|
||||||
$Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability)));
|
|
||||||
if ($Guess < 0) {
|
|
||||||
$Guess = 0;
|
|
||||||
} elseif ($Guess > $trials) {
|
|
||||||
$Guess = $trials;
|
|
||||||
}
|
|
||||||
|
|
||||||
$TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0;
|
|
||||||
$EssentiallyZero = 10e-12;
|
|
||||||
|
|
||||||
$m = floor($trials * $probability);
|
|
||||||
++$TotalUnscaledProbability;
|
|
||||||
if ($m == $Guess) {
|
|
||||||
++$UnscaledPGuess;
|
|
||||||
}
|
|
||||||
if ($m <= $Guess) {
|
|
||||||
++$UnscaledCumPGuess;
|
|
||||||
}
|
|
||||||
|
|
||||||
$PreviousValue = 1;
|
|
||||||
$Done = false;
|
|
||||||
$k = $m + 1;
|
|
||||||
while ((!$Done) && ($k <= $trials)) {
|
|
||||||
$CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability));
|
|
||||||
$TotalUnscaledProbability += $CurrentValue;
|
|
||||||
if ($k == $Guess) {
|
|
||||||
$UnscaledPGuess += $CurrentValue;
|
|
||||||
}
|
|
||||||
if ($k <= $Guess) {
|
|
||||||
$UnscaledCumPGuess += $CurrentValue;
|
|
||||||
}
|
|
||||||
if ($CurrentValue <= $EssentiallyZero) {
|
|
||||||
$Done = true;
|
|
||||||
}
|
|
||||||
$PreviousValue = $CurrentValue;
|
|
||||||
++$k;
|
|
||||||
}
|
|
||||||
|
|
||||||
$PreviousValue = 1;
|
|
||||||
$Done = false;
|
|
||||||
$k = $m - 1;
|
|
||||||
while ((!$Done) && ($k >= 0)) {
|
|
||||||
$CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability);
|
|
||||||
$TotalUnscaledProbability += $CurrentValue;
|
|
||||||
if ($k == $Guess) {
|
|
||||||
$UnscaledPGuess += $CurrentValue;
|
|
||||||
}
|
|
||||||
if ($k <= $Guess) {
|
|
||||||
$UnscaledCumPGuess += $CurrentValue;
|
|
||||||
}
|
|
||||||
if ($CurrentValue <= $EssentiallyZero) {
|
|
||||||
$Done = true;
|
|
||||||
}
|
|
||||||
$PreviousValue = $CurrentValue;
|
|
||||||
--$k;
|
|
||||||
}
|
|
||||||
|
|
||||||
$PGuess = $UnscaledPGuess / $TotalUnscaledProbability;
|
|
||||||
$CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability;
|
|
||||||
|
|
||||||
$CumPGuessMinus1 = $CumPGuess - 1;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) {
|
|
||||||
return $Guess;
|
|
||||||
} elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) {
|
|
||||||
$PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability);
|
|
||||||
$CumPGuessMinus1 = $CumPGuess;
|
|
||||||
$CumPGuess = $CumPGuess + $PGuessPlus1;
|
|
||||||
$PGuess = $PGuessPlus1;
|
|
||||||
++$Guess;
|
|
||||||
} elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) {
|
|
||||||
$PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability;
|
|
||||||
$CumPGuess = $CumPGuessMinus1;
|
|
||||||
$CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess;
|
|
||||||
$PGuess = $PGuessMinus1;
|
|
||||||
--$Guess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1502,6 +1382,11 @@ class Statistical
|
||||||
* distribution, except that the number of successes is fixed, and the number of trials is
|
* distribution, except that the number of successes is fixed, and the number of trials is
|
||||||
* variable. Like the binomial, trials are assumed to be independent.
|
* variable. Like the binomial, trials are assumed to be independent.
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Statistical\Distributions\Binomial::negative::mode()
|
||||||
|
* Use the negative() method in the Statistical\Distributions\Binomial class instead
|
||||||
|
*
|
||||||
* @param mixed (float) $failures Number of Failures
|
* @param mixed (float) $failures Number of Failures
|
||||||
* @param mixed (float) $successes Threshold number of Successes
|
* @param mixed (float) $successes Threshold number of Successes
|
||||||
* @param mixed (float) $probability Probability of success on each trial
|
* @param mixed (float) $probability Probability of success on each trial
|
||||||
|
|
@ -1510,26 +1395,7 @@ class Statistical
|
||||||
*/
|
*/
|
||||||
public static function NEGBINOMDIST($failures, $successes, $probability)
|
public static function NEGBINOMDIST($failures, $successes, $probability)
|
||||||
{
|
{
|
||||||
$failures = floor(Functions::flattenSingleValue($failures));
|
return Statistical\Distributions\Binomial::negative($failures, $successes, $probability);
|
||||||
$successes = floor(Functions::flattenSingleValue($successes));
|
|
||||||
$probability = Functions::flattenSingleValue($probability);
|
|
||||||
|
|
||||||
if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) {
|
|
||||||
if (($failures < 0) || ($successes < 1)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
} elseif (($probability < 0) || ($probability > 1)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
|
||||||
if (($failures + $successes - 1) <= 0) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (MathTrig::COMBIN($failures + $successes - 1, $successes - 1)) * ($probability ** $successes) * ((1 - $probability) ** $failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,15 @@ trait BaseValidations
|
||||||
|
|
||||||
return (bool) $value;
|
return (bool) $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static function validateProbability($probability)
|
||||||
|
{
|
||||||
|
$probability = self::validateFloat($probability);
|
||||||
|
|
||||||
|
if ($probability < 0.0 || $probability > 1.0) {
|
||||||
|
throw new Exception(Functions::NAN());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $probability;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class Beta
|
||||||
$rMax = Functions::flattenSingleValue($rMax);
|
$rMax = Functions::flattenSingleValue($rMax);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$probability = self::validateFloat($probability);
|
$probability = self::validateProbability($probability);
|
||||||
$alpha = self::validateFloat($alpha);
|
$alpha = self::validateFloat($alpha);
|
||||||
$beta = self::validateFloat($beta);
|
$beta = self::validateFloat($beta);
|
||||||
$rMax = self::validateFloat($rMax);
|
$rMax = self::validateFloat($rMax);
|
||||||
|
|
@ -97,7 +97,7 @@ class Beta
|
||||||
$rMin = $rMax;
|
$rMin = $rMax;
|
||||||
$rMax = $tmp;
|
$rMax = $tmp;
|
||||||
}
|
}
|
||||||
if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) {
|
if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) {
|
||||||
return Functions::NAN();
|
return Functions::NAN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||||
|
|
||||||
|
class Binomial
|
||||||
|
{
|
||||||
|
use BaseValidations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BINOMDIST.
|
||||||
|
*
|
||||||
|
* Returns the individual term binomial distribution probability. Use BINOMDIST in problems with
|
||||||
|
* a fixed number of tests or trials, when the outcomes of any trial are only success or failure,
|
||||||
|
* when trials are independent, and when the probability of success is constant throughout the
|
||||||
|
* experiment. For example, BINOMDIST can calculate the probability that two of the next three
|
||||||
|
* babies born are male.
|
||||||
|
*
|
||||||
|
* @param mixed (int) $value Number of successes in trials
|
||||||
|
* @param mixed (int) $trials Number of trials
|
||||||
|
* @param mixed (float) $probability Probability of success on each trial
|
||||||
|
* @param mixed (bool) $cumulative
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function distribution($value, $trials, $probability, $cumulative)
|
||||||
|
{
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$trials = Functions::flattenSingleValue($trials);
|
||||||
|
$probability = Functions::flattenSingleValue($probability);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$value = self::validateInt($value);
|
||||||
|
$trials = self::validateInt($trials);
|
||||||
|
$probability = self::validateProbability($probability);
|
||||||
|
$cumulative = self::validateBool($cumulative);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($value < 0) || ($value > $trials)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cumulative) {
|
||||||
|
return self::calculateCumulativeBinomial($value, $trials, $probability);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MathTrig::COMBIN($trials, $value) * $probability ** $value * (1 - $probability) ** ($trials - $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BINOM.DIST.RANGE.
|
||||||
|
*
|
||||||
|
* Returns returns the Binomial Distribution probability for the number of successes from a specified number
|
||||||
|
* of trials falling into a specified range.
|
||||||
|
*
|
||||||
|
* @param mixed (int) $trials Number of trials
|
||||||
|
* @param mixed (float) $probability Probability of success on each trial
|
||||||
|
* @param mixed (int) $successes The number of successes in trials
|
||||||
|
* @param mixed (int) $limit Upper limit for successes in trials
|
||||||
|
*
|
||||||
|
* @return float|string
|
||||||
|
*/
|
||||||
|
public static function range($trials, $probability, $successes, $limit = null)
|
||||||
|
{
|
||||||
|
$trials = Functions::flattenSingleValue($trials);
|
||||||
|
$probability = Functions::flattenSingleValue($probability);
|
||||||
|
$successes = Functions::flattenSingleValue($successes);
|
||||||
|
$limit = ($limit === null) ? $successes : Functions::flattenSingleValue($limit);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$trials = self::validateInt($trials);
|
||||||
|
$probability = self::validateProbability($probability);
|
||||||
|
$successes = self::validateInt($successes);
|
||||||
|
$limit = self::validateInt($limit);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($successes < 0) || ($successes > $trials)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
if (($limit < 0) || ($limit > $trials) || $limit < $successes) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
$summer = 0;
|
||||||
|
for ($i = $successes; $i <= $limit; ++$i) {
|
||||||
|
$summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $summer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NEGBINOMDIST.
|
||||||
|
*
|
||||||
|
* Returns the negative binomial distribution. NEGBINOMDIST returns the probability that
|
||||||
|
* there will be number_f failures before the number_s-th success, when the constant
|
||||||
|
* probability of a success is probability_s. This function is similar to the binomial
|
||||||
|
* distribution, except that the number of successes is fixed, and the number of trials is
|
||||||
|
* variable. Like the binomial, trials are assumed to be independent.
|
||||||
|
*
|
||||||
|
* @param mixed (float) $failures Number of Failures
|
||||||
|
* @param mixed (float) $successes Threshold number of Successes
|
||||||
|
* @param mixed (float) $probability Probability of success on each trial
|
||||||
|
*
|
||||||
|
* @return float|string The result, or a string containing an error
|
||||||
|
*
|
||||||
|
* TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST
|
||||||
|
* The cumulative default should be false to reflect the behaviour of NEGBINOMDIST
|
||||||
|
*/
|
||||||
|
public static function negative($failures, $successes, $probability)
|
||||||
|
{
|
||||||
|
$failures = Functions::flattenSingleValue($failures);
|
||||||
|
$successes = Functions::flattenSingleValue($successes);
|
||||||
|
$probability = Functions::flattenSingleValue($probability);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$failures = self::validateInt($failures);
|
||||||
|
$successes = self::validateInt($successes);
|
||||||
|
$probability = self::validateProbability($probability);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($failures < 0) || ($successes < 1)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
||||||
|
if (($failures + $successes - 1) <= 0) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (MathTrig::COMBIN($failures + $successes - 1, $successes - 1)) *
|
||||||
|
($probability ** $successes) * ((1 - $probability) ** $failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRITBINOM.
|
||||||
|
*
|
||||||
|
* Returns the smallest value for which the cumulative binomial distribution is greater
|
||||||
|
* than or equal to a criterion value
|
||||||
|
*
|
||||||
|
* @param float $trials number of Bernoulli trials
|
||||||
|
* @param float $probability probability of a success on each trial
|
||||||
|
* @param float $alpha criterion value
|
||||||
|
*
|
||||||
|
* @return int|string
|
||||||
|
*/
|
||||||
|
public static function inverse($trials, $probability, $alpha)
|
||||||
|
{
|
||||||
|
$trials = Functions::flattenSingleValue($trials);
|
||||||
|
$probability = Functions::flattenSingleValue($probability);
|
||||||
|
$alpha = Functions::flattenSingleValue($alpha);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$trials = self::validateInt($trials);
|
||||||
|
$probability = self::validateProbability($probability);
|
||||||
|
$alpha = self::validateFloat($alpha);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($trials < 0) {
|
||||||
|
return Functions::NAN();
|
||||||
|
} elseif (($alpha < 0.0) || ($alpha > 1.0)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
$successes = 0;
|
||||||
|
while (true && $successes <= $trials) {
|
||||||
|
$result = self::calculateCumulativeBinomial($successes, $trials, $probability);
|
||||||
|
if ($result >= $alpha) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++$successes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $successes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return float|int
|
||||||
|
*/
|
||||||
|
private static function calculateCumulativeBinomial(int $value, int $trials, float $probability)
|
||||||
|
{
|
||||||
|
$summer = 0;
|
||||||
|
for ($i = 0; $i <= $value; ++$i) {
|
||||||
|
$summer += MathTrig::COMBIN($trials, $i) * $probability ** $i * (1 - $probability) ** ($trials - $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $summer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -109,13 +109,13 @@ class ChiSquared
|
||||||
$degrees = Functions::flattenSingleValue($degrees);
|
$degrees = Functions::flattenSingleValue($degrees);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$probability = self::validateFloat($probability);
|
$probability = self::validateProbability($probability);
|
||||||
$degrees = self::validateInt($degrees);
|
$degrees = self::validateInt($degrees);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($probability < 0.0 || $probability > 1.0 || $degrees < 1) {
|
if ($degrees < 1) {
|
||||||
return Functions::NAN();
|
return Functions::NAN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,13 +145,13 @@ class ChiSquared
|
||||||
$degrees = Functions::flattenSingleValue($degrees);
|
$degrees = Functions::flattenSingleValue($degrees);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$probability = self::validateFloat($probability);
|
$probability = self::validateProbability($probability);
|
||||||
$degrees = self::validateInt($degrees);
|
$degrees = self::validateInt($degrees);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($probability < 0.0 || $probability > 1.0 || $degrees < 1) {
|
if ($degrees < 1) {
|
||||||
return Functions::NAN();
|
return Functions::NAN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,14 +87,14 @@ class Gamma extends GammaBase
|
||||||
$beta = Functions::flattenSingleValue($beta);
|
$beta = Functions::flattenSingleValue($beta);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$probability = self::validateFloat($probability);
|
$probability = self::validateProbability($probability);
|
||||||
$alpha = self::validateFloat($alpha);
|
$alpha = self::validateFloat($alpha);
|
||||||
$beta = self::validateFloat($beta);
|
$beta = self::validateFloat($beta);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($alpha <= 0.0) || ($beta <= 0.0) || ($probability < 0.0) || ($probability > 1.0)) {
|
if (($alpha <= 0.0) || ($beta <= 0.0)) {
|
||||||
return Functions::NAN();
|
return Functions::NAN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class NewtonRaphson
|
||||||
$this->callback = $callback;
|
$this->callback = $callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute($probability)
|
public function execute(float $probability)
|
||||||
{
|
{
|
||||||
$xLo = 100;
|
$xLo = 100;
|
||||||
$xHi = 0;
|
$xHi = 0;
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,13 @@ class StudentT
|
||||||
$degrees = Functions::flattenSingleValue($degrees);
|
$degrees = Functions::flattenSingleValue($degrees);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$probability = self::validateFloat($probability);
|
$probability = self::validateProbability($probability);
|
||||||
$degrees = self::validateInt($degrees);
|
$degrees = self::validateInt($degrees);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($probability < 0.0 || $probability > 1.0 || $degrees <= 0) {
|
if ($degrees <= 0) {
|
||||||
return Functions::NAN();
|
return Functions::NAN();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class BinomDistRangeTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider providerBINOMDISTRANGE
|
||||||
|
*
|
||||||
|
* @param mixed $expectedResult
|
||||||
|
*/
|
||||||
|
public function testBINOMDISTRANGE($expectedResult, ...$args): void
|
||||||
|
{
|
||||||
|
$result = Statistical\Distributions\Binomial::range(...$args);
|
||||||
|
self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function providerBINOMDISTRANGE()
|
||||||
|
{
|
||||||
|
return require 'tests/data/Calculation/Statistical/BINOMDISTRANGE.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Statistical;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class BinomInvTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider providerBINOMINV
|
||||||
|
*
|
||||||
|
* @param mixed $expectedResult
|
||||||
|
*/
|
||||||
|
public function testBINOMINV($expectedResult, ...$args): void
|
||||||
|
{
|
||||||
|
$result = Statistical::CRITBINOM(...$args);
|
||||||
|
self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function providerBINOMINV()
|
||||||
|
{
|
||||||
|
return require 'tests/data/Calculation/Statistical/BINOMINV.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,12 +49,32 @@ return [
|
||||||
'#VALUE!',
|
'#VALUE!',
|
||||||
'NAN', 100, 0.5, true,
|
'NAN', 100, 0.5, true,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
65, 'NAN', 0.5, true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
65, 100, 'NAN', true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
65, 100, 0.5, 'NAN',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
-5, 100, 0.5, true,
|
-5, 100, 0.5, true,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
5, 100, 1.5, true,
|
105, 100, 0.5, true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
65, 100, -0.5, true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
65, 100, 1.5, true,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
0.083974967429,
|
||||||
|
60, 0.75, 48,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.5236297934719,
|
||||||
|
60, 0.75, 45, 50,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.0284439668205,
|
||||||
|
100, 0.5, 0, 40,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.728746975926,
|
||||||
|
100, 0.5, 45, 55,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.539794618694,
|
||||||
|
100, 0.5, 50, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.079589237387,
|
||||||
|
100, 0.5, 50,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
'NaN', 0.5, 50, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
100, 'NaN', 50, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
100, 0.5, 'NaN', 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
100, 0.5, 50, 'NaN',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, -0.5, 50, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 1.5, 50, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 0.5, -5, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
10, 0.5, 50, 100,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 0.5, 50, -10,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 0.5, 50, -110,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 0.5, 50, 40,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
32,
|
||||||
|
100, 0.3, 0.7,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
6, 0.5, 0.75,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7,
|
||||||
|
12, 0.5, 0.75,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
46,
|
||||||
|
100, 0.5, 0.2,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50,
|
||||||
|
100, 0.5, 0.5,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
56,
|
||||||
|
100, 0.5, 0.9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
'NaN', 0.5, 0.9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
100, 'NaN', 0.9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
100, 0.5, 'NaN',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
-1, 0.5, 0.9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, -0.5, 0.9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 1.5, 0.9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 0.5, -0.9,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
100, 0.5, 1.9,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
4,
|
|
||||||
6, 0.5, 0.75,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
7,
|
|
||||||
12, 0.5, 0.75,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
46,
|
|
||||||
100, 0.5, 0.2,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
50,
|
|
||||||
100, 0.5, 0.5,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
56,
|
|
||||||
100, 0.5, 0.9,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
@ -17,6 +17,14 @@ return [
|
||||||
0.057564377784729004,
|
0.057564377784729004,
|
||||||
15, 12, 0.5,
|
15, 12, 0.5,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
'NaN', 12, 0.5,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
15, 'NaN', 0.5,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#VALUE!',
|
'#VALUE!',
|
||||||
15, 12, 'NaN',
|
15, 12, 'NaN',
|
||||||
|
|
@ -33,4 +41,12 @@ return [
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
1, 0.5, 0.25,
|
1, 0.5, 0.25,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
6, 12, -0.25,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
1, 12, 1.25,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue