init
This commit is contained in:
commit
72a26edcff
22092 changed files with 2101903 additions and 0 deletions
59
lib/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php
Normal file
59
lib/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
abstract class AggregateBase
|
||||
{
|
||||
/**
|
||||
* MS Excel does not count Booleans if passed as cell values, but they are counted if passed as literals.
|
||||
* OpenOffice Calc always counts Booleans.
|
||||
* Gnumeric never counts Booleans.
|
||||
*/
|
||||
protected static function testAcceptedBoolean(mixed $arg, mixed $k): mixed
|
||||
{
|
||||
if (!is_bool($arg)) {
|
||||
return $arg;
|
||||
}
|
||||
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_GNUMERIC) {
|
||||
return $arg;
|
||||
}
|
||||
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
return (int) $arg;
|
||||
}
|
||||
if (!Functions::isCellValue($k)) {
|
||||
return (int) $arg;
|
||||
}
|
||||
/*if (
|
||||
(is_bool($arg)) &&
|
||||
((!Functions::isCellValue($k) && (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_EXCEL)) ||
|
||||
(Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE))
|
||||
) {
|
||||
$arg = (int) $arg;
|
||||
}*/
|
||||
|
||||
return $arg;
|
||||
}
|
||||
|
||||
protected static function isAcceptedCountable(mixed $arg, mixed $k, bool $countNull = false): bool
|
||||
{
|
||||
if ($countNull && $arg === null && !Functions::isCellValue($k) && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC) {
|
||||
return true;
|
||||
}
|
||||
if (!is_numeric($arg)) {
|
||||
return false;
|
||||
}
|
||||
if (!is_string($arg)) {
|
||||
return true;
|
||||
}
|
||||
if (!Functions::isCellValue($k) && Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
return true;
|
||||
}
|
||||
if (!Functions::isCellValue($k) && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
259
lib/PhpSpreadsheet/Calculation/Statistical/Averages.php
Normal file
259
lib/PhpSpreadsheet/Calculation/Statistical/Averages.php
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Averages extends AggregateBase
|
||||
{
|
||||
/**
|
||||
* AVEDEV.
|
||||
*
|
||||
* Returns the average of the absolute deviations of data points from their mean.
|
||||
* AVEDEV is a measure of the variability in a data set.
|
||||
*
|
||||
* Excel Function:
|
||||
* AVEDEV(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
*/
|
||||
public static function averageDeviations(mixed ...$args): string|float
|
||||
{
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
|
||||
// Return value
|
||||
$returnValue = 0.0;
|
||||
|
||||
$aMean = self::average(...$args);
|
||||
if ($aMean === ExcelError::DIV0()) {
|
||||
return ExcelError::NAN();
|
||||
} elseif ($aMean === ExcelError::VALUE()) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$aCount = 0;
|
||||
foreach ($aArgs as $k => $arg) {
|
||||
$arg = self::testAcceptedBoolean($arg, $k);
|
||||
// Is it a numeric value?
|
||||
// Strings containing numeric values are only counted if they are string literals (not cell values)
|
||||
// and then only in MS Excel and in Open Office, not in Gnumeric
|
||||
if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
if (self::isAcceptedCountable($arg, $k)) {
|
||||
$returnValue += abs($arg - $aMean);
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
if ($aCount === 0) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
return $returnValue / $aCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* AVERAGE.
|
||||
*
|
||||
* Returns the average (arithmetic mean) of the arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* AVERAGE(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|int|string (string if result is an error)
|
||||
*/
|
||||
public static function average(mixed ...$args): string|int|float
|
||||
{
|
||||
$returnValue = $aCount = 0;
|
||||
|
||||
// Loop through arguments
|
||||
foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
|
||||
$arg = self::testAcceptedBoolean($arg, $k);
|
||||
// Is it a numeric value?
|
||||
// Strings containing numeric values are only counted if they are string literals (not cell values)
|
||||
// and then only in MS Excel and in Open Office, not in Gnumeric
|
||||
if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
if (self::isAcceptedCountable($arg, $k)) {
|
||||
$returnValue += $arg;
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
if ($aCount > 0) {
|
||||
return $returnValue / $aCount;
|
||||
}
|
||||
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
/**
|
||||
* AVERAGEA.
|
||||
*
|
||||
* Returns the average of its arguments, including numbers, text, and logical values
|
||||
*
|
||||
* Excel Function:
|
||||
* AVERAGEA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|int|string (string if result is an error)
|
||||
*/
|
||||
public static function averageA(mixed ...$args): string|int|float
|
||||
{
|
||||
$returnValue = null;
|
||||
|
||||
$aCount = 0;
|
||||
// Loop through arguments
|
||||
foreach (Functions::flattenArrayIndexed($args) as $k => $arg) {
|
||||
if (is_numeric($arg)) {
|
||||
// do nothing
|
||||
} elseif (is_bool($arg)) {
|
||||
$arg = (int) $arg;
|
||||
} elseif (!Functions::isMatrixValue($k)) {
|
||||
$arg = 0;
|
||||
} else {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
$returnValue += $arg;
|
||||
++$aCount;
|
||||
}
|
||||
|
||||
if ($aCount > 0) {
|
||||
return $returnValue / $aCount;
|
||||
}
|
||||
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
/**
|
||||
* MEDIAN.
|
||||
*
|
||||
* Returns the median of the given numbers. The median is the number in the middle of a set of numbers.
|
||||
*
|
||||
* Excel Function:
|
||||
* MEDIAN(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function median(mixed ...$args): float|string
|
||||
{
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
|
||||
$returnValue = ExcelError::NAN();
|
||||
|
||||
$aArgs = self::filterArguments($aArgs);
|
||||
$valueCount = count($aArgs);
|
||||
if ($valueCount > 0) {
|
||||
sort($aArgs, SORT_NUMERIC);
|
||||
$valueCount = $valueCount / 2;
|
||||
if ($valueCount == floor($valueCount)) {
|
||||
$returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2;
|
||||
} else {
|
||||
$valueCount = floor($valueCount);
|
||||
$returnValue = $aArgs[$valueCount];
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* MODE.
|
||||
*
|
||||
* Returns the most frequently occurring, or repetitive, value in an array or range of data
|
||||
*
|
||||
* Excel Function:
|
||||
* MODE(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function mode(mixed ...$args): float|string
|
||||
{
|
||||
$returnValue = ExcelError::NA();
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
$aArgs = self::filterArguments($aArgs);
|
||||
|
||||
if (!empty($aArgs)) {
|
||||
return self::modeCalc($aArgs);
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
protected static function filterArguments(array $args): array
|
||||
{
|
||||
return array_filter(
|
||||
$args,
|
||||
function ($value): bool {
|
||||
// Is it a numeric value?
|
||||
return is_numeric($value) && (!is_string($value));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Special variant of array_count_values that isn't limited to strings and integers,
|
||||
* but can work with floating point numbers as values.
|
||||
*/
|
||||
private static function modeCalc(array $data): float|string
|
||||
{
|
||||
$frequencyArray = [];
|
||||
$index = 0;
|
||||
$maxfreq = 0;
|
||||
$maxfreqkey = '';
|
||||
$maxfreqdatum = '';
|
||||
foreach ($data as $datum) {
|
||||
$found = false;
|
||||
++$index;
|
||||
foreach ($frequencyArray as $key => $value) {
|
||||
if ((string) $value['value'] == (string) $datum) {
|
||||
++$frequencyArray[$key]['frequency'];
|
||||
$freq = $frequencyArray[$key]['frequency'];
|
||||
if ($freq > $maxfreq) {
|
||||
$maxfreq = $freq;
|
||||
$maxfreqkey = $key;
|
||||
$maxfreqdatum = $datum;
|
||||
} elseif ($freq == $maxfreq) {
|
||||
if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) {
|
||||
$maxfreqkey = $key;
|
||||
$maxfreqdatum = $datum;
|
||||
}
|
||||
}
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found === false) {
|
||||
$frequencyArray[] = [
|
||||
'value' => $datum,
|
||||
'frequency' => 1,
|
||||
'index' => $index,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($maxfreq <= 1) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
return $maxfreqdatum;
|
||||
}
|
||||
}
|
||||
126
lib/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php
Normal file
126
lib/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum;
|
||||
|
||||
class Mean
|
||||
{
|
||||
/**
|
||||
* GEOMEAN.
|
||||
*
|
||||
* Returns the geometric mean of an array or range of positive data. For example, you
|
||||
* can use GEOMEAN to calculate average growth rate given compound interest with
|
||||
* variable rates.
|
||||
*
|
||||
* Excel Function:
|
||||
* GEOMEAN(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function geometric(mixed ...$args): float|int|string
|
||||
{
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
|
||||
$aMean = MathTrig\Operations::product($aArgs);
|
||||
if (is_numeric($aMean) && ($aMean > 0)) {
|
||||
$aCount = Counts::COUNT($aArgs);
|
||||
if (Minimum::min($aArgs) > 0) {
|
||||
return $aMean ** (1 / $aCount);
|
||||
}
|
||||
}
|
||||
|
||||
return ExcelError::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
|
||||
*/
|
||||
public static function harmonic(mixed ...$args): string|float|int
|
||||
{
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
if (Minimum::min($aArgs) < 0) {
|
||||
return ExcelError::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 ExcelError::NAN();
|
||||
}
|
||||
$returnValue += (1 / $arg);
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
if ($aCount > 0) {
|
||||
return 1 / ($returnValue / $aCount);
|
||||
}
|
||||
|
||||
return ExcelError::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
|
||||
*/
|
||||
public static function trim(mixed ...$args): float|string
|
||||
{
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
|
||||
// Calculate
|
||||
$percent = array_pop($aArgs);
|
||||
|
||||
if ((is_numeric($percent)) && (!is_string($percent))) {
|
||||
if (($percent < 0) || ($percent > 1)) {
|
||||
return ExcelError::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 ExcelError::VALUE();
|
||||
}
|
||||
}
|
||||
293
lib/PhpSpreadsheet/Calculation/Statistical/Conditional.php
Normal file
293
lib/PhpSpreadsheet/Calculation/Statistical/Conditional.php
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Database\DAverage;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Database\DCount;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMax;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Database\DMin;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Database\DSum;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Conditional
|
||||
{
|
||||
private const CONDITION_COLUMN_NAME = 'CONDITION';
|
||||
private const VALUE_COLUMN_NAME = 'VALUE';
|
||||
private const CONDITIONAL_COLUMN_NAME = 'CONDITIONAL %d';
|
||||
|
||||
/**
|
||||
* AVERAGEIF.
|
||||
*
|
||||
* Returns the average value from a range of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* AVERAGEIF(range,condition[, average_range])
|
||||
*
|
||||
* @param mixed $range Data values
|
||||
* @param null|array|string $condition the criteria that defines which cells will be checked
|
||||
* @param mixed $averageRange Data values
|
||||
*/
|
||||
public static function AVERAGEIF(mixed $range, null|array|string $condition, mixed $averageRange = []): null|int|float|string
|
||||
{
|
||||
if (!is_array($range) || !is_array($averageRange) || array_key_exists(0, $range) || array_key_exists(0, $averageRange)) {
|
||||
throw new CalcException('Must specify range of cells, not any kind of literal');
|
||||
}
|
||||
$database = self::databaseFromRangeAndValue($range, $averageRange);
|
||||
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
|
||||
|
||||
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* AVERAGEIFS.
|
||||
*
|
||||
* Counts the number of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* AVERAGEIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
|
||||
*
|
||||
* @param mixed $args Pairs of Ranges and Criteria
|
||||
*/
|
||||
public static function AVERAGEIFS(mixed ...$args): null|int|float|string
|
||||
{
|
||||
if (empty($args)) {
|
||||
return 0.0;
|
||||
} elseif (count($args) === 3) {
|
||||
return self::AVERAGEIF($args[1], $args[2], $args[0]);
|
||||
}
|
||||
foreach ($args as $arg) {
|
||||
if (is_array($arg) && array_key_exists(0, $arg)) {
|
||||
throw new CalcException('Must specify range of cells, not any kind of literal');
|
||||
}
|
||||
}
|
||||
|
||||
$conditions = self::buildConditionSetForValueRange(...$args);
|
||||
$database = self::buildDatabaseWithValueRange(...$args);
|
||||
|
||||
return DAverage::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* COUNTIF.
|
||||
*
|
||||
* Counts the number of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* COUNTIF(range,condition)
|
||||
*
|
||||
* @param mixed[] $range Data values
|
||||
* @param null|array|string $condition the criteria that defines which cells will be counted
|
||||
*/
|
||||
public static function COUNTIF(array $range, null|array|string $condition): string|int
|
||||
{
|
||||
// Filter out any empty values that shouldn't be included in a COUNT
|
||||
$range = array_filter(
|
||||
Functions::flattenArray($range),
|
||||
fn ($value): bool => $value !== null && $value !== ''
|
||||
);
|
||||
|
||||
$range = array_merge([[self::CONDITION_COLUMN_NAME]], array_chunk($range, 1));
|
||||
$condition = array_merge([[self::CONDITION_COLUMN_NAME]], [[$condition]]);
|
||||
|
||||
return DCount::evaluate($range, null, $condition, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* COUNTIFS.
|
||||
*
|
||||
* Counts the number of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…)
|
||||
*
|
||||
* @param mixed $args Pairs of Ranges and Criteria
|
||||
*/
|
||||
public static function COUNTIFS(mixed ...$args): int|string
|
||||
{
|
||||
if (empty($args)) {
|
||||
return 0;
|
||||
} elseif (count($args) === 2) {
|
||||
return self::COUNTIF(...$args);
|
||||
}
|
||||
|
||||
$database = self::buildDatabase(...$args);
|
||||
$conditions = self::buildConditionSet(...$args);
|
||||
|
||||
return DCount::evaluate($database, null, $conditions, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* MAXIFS.
|
||||
*
|
||||
* Returns the maximum value within a range of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
|
||||
*
|
||||
* @param mixed $args Pairs of Ranges and Criteria
|
||||
*/
|
||||
public static function MAXIFS(mixed ...$args): null|float|string
|
||||
{
|
||||
if (empty($args)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$conditions = self::buildConditionSetForValueRange(...$args);
|
||||
$database = self::buildDatabaseWithValueRange(...$args);
|
||||
|
||||
return DMax::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* MINIFS.
|
||||
*
|
||||
* Returns the minimum value within a range of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
|
||||
*
|
||||
* @param mixed $args Pairs of Ranges and Criteria
|
||||
*/
|
||||
public static function MINIFS(mixed ...$args): null|float|string
|
||||
{
|
||||
if (empty($args)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$conditions = self::buildConditionSetForValueRange(...$args);
|
||||
$database = self::buildDatabaseWithValueRange(...$args);
|
||||
|
||||
return DMin::evaluate($database, self::VALUE_COLUMN_NAME, $conditions, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* SUMIF.
|
||||
*
|
||||
* Totals the values of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* SUMIF(range, criteria, [sum_range])
|
||||
*
|
||||
* @param array $range Data values
|
||||
*/
|
||||
public static function SUMIF(array $range, mixed $condition, array $sumRange = []): null|float|string
|
||||
{
|
||||
$database = self::databaseFromRangeAndValue($range, $sumRange);
|
||||
$condition = [[self::CONDITION_COLUMN_NAME, self::VALUE_COLUMN_NAME], [$condition, null]];
|
||||
|
||||
return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* SUMIFS.
|
||||
*
|
||||
* Counts the number of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* SUMIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2]…)
|
||||
*
|
||||
* @param mixed $args Pairs of Ranges and Criteria
|
||||
*/
|
||||
public static function SUMIFS(mixed ...$args): null|float|string
|
||||
{
|
||||
if (empty($args)) {
|
||||
return 0.0;
|
||||
} elseif (count($args) === 3) {
|
||||
return self::SUMIF($args[1], $args[2], $args[0]);
|
||||
}
|
||||
|
||||
$conditions = self::buildConditionSetForValueRange(...$args);
|
||||
$database = self::buildDatabaseWithValueRange(...$args);
|
||||
|
||||
return DSum::evaluate($database, self::VALUE_COLUMN_NAME, $conditions);
|
||||
}
|
||||
|
||||
/** @param array $args */
|
||||
private static function buildConditionSet(...$args): array
|
||||
{
|
||||
$conditions = self::buildConditions(1, ...$args);
|
||||
|
||||
return array_map(null, ...$conditions);
|
||||
}
|
||||
|
||||
/** @param array $args */
|
||||
private static function buildConditionSetForValueRange(...$args): array
|
||||
{
|
||||
$conditions = self::buildConditions(2, ...$args);
|
||||
|
||||
if (count($conditions) === 1) {
|
||||
return array_map(
|
||||
fn ($value): array => [$value],
|
||||
$conditions[0]
|
||||
);
|
||||
}
|
||||
|
||||
return array_map(null, ...$conditions);
|
||||
}
|
||||
|
||||
/** @param array $args */
|
||||
private static function buildConditions(int $startOffset, ...$args): array
|
||||
{
|
||||
$conditions = [];
|
||||
|
||||
$pairCount = 1;
|
||||
$argumentCount = count($args);
|
||||
for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
|
||||
$conditions[] = array_merge([sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)], [$args[$argument]]);
|
||||
++$pairCount;
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
/** @param array $args */
|
||||
private static function buildDatabase(...$args): array
|
||||
{
|
||||
$database = [];
|
||||
|
||||
return self::buildDataSet(0, $database, ...$args);
|
||||
}
|
||||
|
||||
/** @param array $args */
|
||||
private static function buildDatabaseWithValueRange(...$args): array
|
||||
{
|
||||
$database = [];
|
||||
$database[] = array_merge(
|
||||
[self::VALUE_COLUMN_NAME],
|
||||
Functions::flattenArray($args[0])
|
||||
);
|
||||
|
||||
return self::buildDataSet(1, $database, ...$args);
|
||||
}
|
||||
|
||||
/** @param array $args */
|
||||
private static function buildDataSet(int $startOffset, array $database, ...$args): array
|
||||
{
|
||||
$pairCount = 1;
|
||||
$argumentCount = count($args);
|
||||
for ($argument = $startOffset; $argument < $argumentCount; $argument += 2) {
|
||||
$database[] = array_merge(
|
||||
[sprintf(self::CONDITIONAL_COLUMN_NAME, $pairCount)],
|
||||
Functions::flattenArray($args[$argument])
|
||||
);
|
||||
++$pairCount;
|
||||
}
|
||||
|
||||
return array_map(null, ...$database);
|
||||
}
|
||||
|
||||
private static function databaseFromRangeAndValue(array $range, array $valueRange = []): array
|
||||
{
|
||||
$range = Functions::flattenArray($range);
|
||||
|
||||
$valueRange = Functions::flattenArray($valueRange);
|
||||
if (empty($valueRange)) {
|
||||
$valueRange = $range;
|
||||
}
|
||||
|
||||
$database = array_map(null, array_merge([self::CONDITION_COLUMN_NAME], $range), array_merge([self::VALUE_COLUMN_NAME], $valueRange));
|
||||
|
||||
return $database;
|
||||
}
|
||||
}
|
||||
51
lib/PhpSpreadsheet/Calculation/Statistical/Confidence.php
Normal file
51
lib/PhpSpreadsheet/Calculation/Statistical/Confidence.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Confidence
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* CONFIDENCE.
|
||||
*
|
||||
* Returns the confidence interval for a population mean
|
||||
*
|
||||
* @param mixed $alpha As a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $stdDev Standard Deviation as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $size As an integer
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function CONFIDENCE(mixed $alpha, mixed $stdDev, mixed $size)
|
||||
{
|
||||
if (is_array($alpha) || is_array($stdDev) || is_array($size)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $alpha, $stdDev, $size);
|
||||
}
|
||||
|
||||
try {
|
||||
$alpha = StatisticalValidations::validateFloat($alpha);
|
||||
$stdDev = StatisticalValidations::validateFloat($stdDev);
|
||||
$size = StatisticalValidations::validateInt($size);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
/** @var float $temp */
|
||||
$temp = Distributions\StandardNormal::inverse(1 - $alpha / 2);
|
||||
|
||||
return Functions::scalar($temp * $stdDev / sqrt($size));
|
||||
}
|
||||
}
|
||||
96
lib/PhpSpreadsheet/Calculation/Statistical/Counts.php
Normal file
96
lib/PhpSpreadsheet/Calculation/Statistical/Counts.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Counts extends AggregateBase
|
||||
{
|
||||
/**
|
||||
* COUNT.
|
||||
*
|
||||
* Counts the number of cells that contain numbers within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* COUNT(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function COUNT(mixed ...$args): int
|
||||
{
|
||||
$returnValue = 0;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
foreach ($aArgs as $k => $arg) {
|
||||
$arg = self::testAcceptedBoolean($arg, $k);
|
||||
// Is it a numeric value?
|
||||
// Strings containing numeric values are only counted if they are string literals (not cell values)
|
||||
// and then only in MS Excel and in Open Office, not in Gnumeric
|
||||
if (self::isAcceptedCountable($arg, $k, true)) {
|
||||
++$returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* COUNTA.
|
||||
*
|
||||
* Counts the number of cells that are not empty within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* COUNTA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function COUNTA(mixed ...$args): int
|
||||
{
|
||||
$returnValue = 0;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
foreach ($aArgs as $k => $arg) {
|
||||
// Nulls are counted if literals, but not if cell values
|
||||
if ($arg !== null || (!Functions::isCellValue($k))) {
|
||||
++$returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* COUNTBLANK.
|
||||
*
|
||||
* Counts the number of empty cells within the list of arguments
|
||||
*
|
||||
* Excel Function:
|
||||
* COUNTBLANK(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed $range Data values
|
||||
*/
|
||||
public static function COUNTBLANK(mixed $range): int
|
||||
{
|
||||
if ($range === null) {
|
||||
return 1;
|
||||
}
|
||||
if (!is_array($range) || array_key_exists(0, $range)) {
|
||||
throw new CalcException('Must specify range of cells, not any kind of literal');
|
||||
}
|
||||
$returnValue = 0;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($range);
|
||||
foreach ($aArgs as $arg) {
|
||||
// Is it a blank cell?
|
||||
if (($arg === null) || ((is_string($arg)) && ($arg == ''))) {
|
||||
++$returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
}
|
||||
138
lib/PhpSpreadsheet/Calculation/Statistical/Deviations.php
Normal file
138
lib/PhpSpreadsheet/Calculation/Statistical/Deviations.php
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Deviations
|
||||
{
|
||||
/**
|
||||
* DEVSQ.
|
||||
*
|
||||
* Returns the sum of squares of deviations of data points from their sample mean.
|
||||
*
|
||||
* Excel Function:
|
||||
* DEVSQ(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function sumSquares(mixed ...$args): string|float
|
||||
{
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
|
||||
$aMean = Averages::average($aArgs);
|
||||
if (!is_numeric($aMean)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Return value
|
||||
$returnValue = 0.0;
|
||||
$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))) {
|
||||
$returnValue += ($arg - $aMean) ** 2;
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
|
||||
return $aCount === 0 ? ExcelError::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
|
||||
*/
|
||||
public static function kurtosis(...$args): string|int|float
|
||||
{
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
$mean = Averages::average($aArgs);
|
||||
if (!is_numeric($mean)) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
$stdDev = (float) 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 ExcelError::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|int|string The result, or a string containing an error
|
||||
*/
|
||||
public static function skew(...$args): string|int|float
|
||||
{
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
$mean = Averages::average($aArgs);
|
||||
if (!is_numeric($mean)) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
$stdDev = StandardDeviations::STDEV($aArgs);
|
||||
if ($stdDev === 0.0 || is_string($stdDev)) {
|
||||
return ExcelError::DIV0();
|
||||
}
|
||||
|
||||
$count = $summer = 0;
|
||||
// Loop through arguments
|
||||
foreach ($aArgs as $k => $arg) {
|
||||
if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) {
|
||||
} elseif (!is_numeric($arg)) {
|
||||
return ExcelError::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 ExcelError::DIV0();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Beta
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
private const MAX_ITERATIONS = 256;
|
||||
|
||||
private const LOG_GAMMA_X_MAX_VALUE = 2.55e305;
|
||||
|
||||
private const XMININ = 2.23e-308;
|
||||
|
||||
/**
|
||||
* BETADIST.
|
||||
*
|
||||
* Returns the beta distribution.
|
||||
*
|
||||
* @param mixed $value Float value at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $alpha Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $beta Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $rMin as an float
|
||||
* Or can be an array of values
|
||||
* @param mixed $rMax as an float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $alpha, mixed $beta, mixed $rMin = 0.0, mixed $rMax = 1.0): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($alpha) || is_array($beta) || is_array($rMin) || is_array($rMax)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $alpha, $beta, $rMin, $rMax);
|
||||
}
|
||||
|
||||
$rMin = $rMin ?? 0.0;
|
||||
$rMax = $rMax ?? 1.0;
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$alpha = DistributionValidations::validateFloat($alpha);
|
||||
$beta = DistributionValidations::validateFloat($beta);
|
||||
$rMax = DistributionValidations::validateFloat($rMax);
|
||||
$rMin = DistributionValidations::validateFloat($rMin);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($rMin > $rMax) {
|
||||
$tmp = $rMin;
|
||||
$rMin = $rMax;
|
||||
$rMax = $tmp;
|
||||
}
|
||||
if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$value -= $rMin;
|
||||
$value /= ($rMax - $rMin);
|
||||
|
||||
return self::incompleteBeta($value, $alpha, $beta);
|
||||
}
|
||||
|
||||
/**
|
||||
* BETAINV.
|
||||
*
|
||||
* Returns the inverse of the Beta distribution.
|
||||
*
|
||||
* @param mixed $probability Float probability at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $alpha Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $beta Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $rMin Minimum value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $rMax Maximum value as a float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $probability, mixed $alpha, mixed $beta, mixed $rMin = 0.0, mixed $rMax = 1.0): array|string|float
|
||||
{
|
||||
if (is_array($probability) || is_array($alpha) || is_array($beta) || is_array($rMin) || is_array($rMax)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta, $rMin, $rMax);
|
||||
}
|
||||
|
||||
$rMin = $rMin ?? 0.0;
|
||||
$rMax = $rMax ?? 1.0;
|
||||
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$alpha = DistributionValidations::validateFloat($alpha);
|
||||
$beta = DistributionValidations::validateFloat($beta);
|
||||
$rMax = DistributionValidations::validateFloat($rMax);
|
||||
$rMin = DistributionValidations::validateFloat($rMin);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($rMin > $rMax) {
|
||||
$tmp = $rMin;
|
||||
$rMin = $rMax;
|
||||
$rMax = $tmp;
|
||||
}
|
||||
if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::calculateInverse($probability, $alpha, $beta, $rMin, $rMax);
|
||||
}
|
||||
|
||||
private static function calculateInverse(float $probability, float $alpha, float $beta, float $rMin, float $rMax): string|float
|
||||
{
|
||||
$a = 0;
|
||||
$b = 2;
|
||||
$guess = ($a + $b) / 2;
|
||||
|
||||
$i = 0;
|
||||
while ((($b - $a) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
|
||||
$guess = ($a + $b) / 2;
|
||||
$result = self::distribution($guess, $alpha, $beta);
|
||||
if (($result === $probability) || ($result === 0.0)) {
|
||||
$b = $a;
|
||||
} elseif ($result > $probability) {
|
||||
$b = $guess;
|
||||
} else {
|
||||
$a = $guess;
|
||||
}
|
||||
}
|
||||
|
||||
if ($i === self::MAX_ITERATIONS) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
return round($rMin + $guess * ($rMax - $rMin), 12);
|
||||
}
|
||||
|
||||
/**
|
||||
* Incomplete beta function.
|
||||
*
|
||||
* @author Jaco van Kooten
|
||||
* @author Paul Meagher
|
||||
*
|
||||
* The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992).
|
||||
*
|
||||
* @param float $x require 0<=x<=1
|
||||
* @param float $p require p>0
|
||||
* @param float $q require q>0
|
||||
*
|
||||
* @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow
|
||||
*/
|
||||
public static function incompleteBeta(float $x, float $p, float $q): float
|
||||
{
|
||||
if ($x <= 0.0) {
|
||||
return 0.0;
|
||||
} elseif ($x >= 1.0) {
|
||||
return 1.0;
|
||||
} elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x));
|
||||
if ($x < ($p + 1.0) / ($p + $q + 2.0)) {
|
||||
return $beta_gam * self::betaFraction($x, $p, $q) / $p;
|
||||
}
|
||||
|
||||
return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q);
|
||||
}
|
||||
|
||||
// Function cache for logBeta function
|
||||
|
||||
private static float $logBetaCacheP = 0.0;
|
||||
|
||||
private static float $logBetaCacheQ = 0.0;
|
||||
|
||||
private static float $logBetaCacheResult = 0.0;
|
||||
|
||||
/**
|
||||
* The natural logarithm of the beta function.
|
||||
*
|
||||
* @param float $p require p>0
|
||||
* @param float $q require q>0
|
||||
*
|
||||
* @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow
|
||||
*
|
||||
* @author Jaco van Kooten
|
||||
*/
|
||||
private static function logBeta(float $p, float $q): float
|
||||
{
|
||||
if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) {
|
||||
self::$logBetaCacheP = $p;
|
||||
self::$logBetaCacheQ = $q;
|
||||
if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) {
|
||||
self::$logBetaCacheResult = 0.0;
|
||||
} else {
|
||||
self::$logBetaCacheResult = Gamma::logGamma($p) + Gamma::logGamma($q) - Gamma::logGamma($p + $q);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$logBetaCacheResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates of continued fraction part of incomplete beta function.
|
||||
* Based on an idea from Numerical Recipes (W.H. Press et al, 1992).
|
||||
*
|
||||
* @author Jaco van Kooten
|
||||
*/
|
||||
private static function betaFraction(float $x, float $p, float $q): float
|
||||
{
|
||||
$c = 1.0;
|
||||
$sum_pq = $p + $q;
|
||||
$p_plus = $p + 1.0;
|
||||
$p_minus = $p - 1.0;
|
||||
$h = 1.0 - $sum_pq * $x / $p_plus;
|
||||
if (abs($h) < self::XMININ) {
|
||||
$h = self::XMININ;
|
||||
}
|
||||
$h = 1.0 / $h;
|
||||
$frac = $h;
|
||||
$m = 1;
|
||||
$delta = 0.0;
|
||||
while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) {
|
||||
$m2 = 2 * $m;
|
||||
// even index for d
|
||||
$d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2));
|
||||
$h = 1.0 + $d * $h;
|
||||
if (abs($h) < self::XMININ) {
|
||||
$h = self::XMININ;
|
||||
}
|
||||
$h = 1.0 / $h;
|
||||
$c = 1.0 + $d / $c;
|
||||
if (abs($c) < self::XMININ) {
|
||||
$c = self::XMININ;
|
||||
}
|
||||
$frac *= $h * $c;
|
||||
// odd index for d
|
||||
$d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2));
|
||||
$h = 1.0 + $d * $h;
|
||||
if (abs($h) < self::XMININ) {
|
||||
$h = self::XMININ;
|
||||
}
|
||||
$h = 1.0 / $h;
|
||||
$c = 1.0 + $d / $c;
|
||||
if (abs($c) < self::XMININ) {
|
||||
$c = self::XMININ;
|
||||
}
|
||||
$delta = $h * $c;
|
||||
$frac *= $delta;
|
||||
++$m;
|
||||
}
|
||||
|
||||
return $frac;
|
||||
}
|
||||
|
||||
/*
|
||||
private static function betaValue(float $a, float $b): float
|
||||
{
|
||||
return (Gamma::gammaValue($a) * Gamma::gammaValue($b)) /
|
||||
Gamma::gammaValue($a + $b);
|
||||
}
|
||||
|
||||
private static function regularizedIncompleteBeta(float $value, float $a, float $b): float
|
||||
{
|
||||
return self::incompleteBeta($value, $a, $b) / self::betaValue($a, $b);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations;
|
||||
|
||||
class Binomial
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* 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 $value Integer number of successes in trials
|
||||
* Or can be an array of values
|
||||
* @param mixed $trials Integer umber of trials
|
||||
* Or can be an array of values
|
||||
* @param mixed $probability Probability of success on each trial as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $trials, mixed $probability, mixed $cumulative)
|
||||
{
|
||||
if (is_array($value) || is_array($trials) || is_array($probability) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $trials, $probability, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateInt($value);
|
||||
$trials = DistributionValidations::validateInt($trials);
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value < 0) || ($value > $trials)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative) {
|
||||
return self::calculateCumulativeBinomial($value, $trials, $probability);
|
||||
}
|
||||
/** @var float $comb */
|
||||
$comb = Combinations::withoutRepetition($trials, $value);
|
||||
|
||||
return $comb * $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 $trials Integer number of trials
|
||||
* Or can be an array of values
|
||||
* @param mixed $probability Probability of success on each trial as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $successes The integer number of successes in trials
|
||||
* Or can be an array of values
|
||||
* @param mixed $limit Upper limit for successes in trials as null, or an integer
|
||||
* If null, then this will indicate the same as the number of Successes
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function range(mixed $trials, mixed $probability, mixed $successes, mixed $limit = null): array|string|float|int
|
||||
{
|
||||
if (is_array($trials) || is_array($probability) || is_array($successes) || is_array($limit)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $successes, $limit);
|
||||
}
|
||||
|
||||
$limit = $limit ?? $successes;
|
||||
|
||||
try {
|
||||
$trials = DistributionValidations::validateInt($trials);
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$successes = DistributionValidations::validateInt($successes);
|
||||
$limit = DistributionValidations::validateInt($limit);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($successes < 0) || ($successes > $trials)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if (($limit < 0) || ($limit > $trials) || $limit < $successes) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$summer = 0;
|
||||
for ($i = $successes; $i <= $limit; ++$i) {
|
||||
/** @var float $comb */
|
||||
$comb = Combinations::withoutRepetition($trials, $i);
|
||||
$summer += $comb * $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 $failures Number of Failures as an integer
|
||||
* Or can be an array of values
|
||||
* @param mixed $successes Threshold number of Successes as an integer
|
||||
* Or can be an array of values
|
||||
* @param mixed $probability Probability of success on each trial as a float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*
|
||||
* 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(mixed $failures, mixed $successes, mixed $probability): array|string|float
|
||||
{
|
||||
if (is_array($failures) || is_array($successes) || is_array($probability)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $failures, $successes, $probability);
|
||||
}
|
||||
|
||||
try {
|
||||
$failures = DistributionValidations::validateInt($failures);
|
||||
$successes = DistributionValidations::validateInt($successes);
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($failures < 0) || ($successes < 1)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
||||
if (($failures + $successes - 1) <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
}
|
||||
/** @var float $comb */
|
||||
$comb = Combinations::withoutRepetition($failures + $successes - 1, $successes - 1);
|
||||
|
||||
return $comb
|
||||
* ($probability ** $successes) * ((1 - $probability) ** $failures);
|
||||
}
|
||||
|
||||
/**
|
||||
* BINOM.INV.
|
||||
*
|
||||
* Returns the smallest value for which the cumulative binomial distribution is greater
|
||||
* than or equal to a criterion value
|
||||
*
|
||||
* @param mixed $trials number of Bernoulli trials as an integer
|
||||
* Or can be an array of values
|
||||
* @param mixed $probability probability of a success on each trial as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $alpha criterion value as a float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $trials, mixed $probability, mixed $alpha): array|string|int
|
||||
{
|
||||
if (is_array($trials) || is_array($probability) || is_array($alpha)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $alpha);
|
||||
}
|
||||
|
||||
try {
|
||||
$trials = DistributionValidations::validateInt($trials);
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$alpha = DistributionValidations::validateFloat($alpha);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($trials < 0) {
|
||||
return ExcelError::NAN();
|
||||
} elseif (($alpha < 0.0) || ($alpha > 1.0)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$successes = 0;
|
||||
while ($successes <= $trials) {
|
||||
$result = self::calculateCumulativeBinomial($successes, $trials, $probability);
|
||||
if ($result >= $alpha) {
|
||||
break;
|
||||
}
|
||||
++$successes;
|
||||
}
|
||||
|
||||
return $successes;
|
||||
}
|
||||
|
||||
private static function calculateCumulativeBinomial(int $value, int $trials, float $probability): float|int
|
||||
{
|
||||
$summer = 0;
|
||||
for ($i = 0; $i <= $value; ++$i) {
|
||||
/** @var float $comb */
|
||||
$comb = Combinations::withoutRepetition($trials, $i);
|
||||
$summer += $comb * $probability ** $i
|
||||
* (1 - $probability) ** ($trials - $i);
|
||||
}
|
||||
|
||||
return $summer;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class ChiSquared
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
private const EPS = 2.22e-16;
|
||||
|
||||
/**
|
||||
* CHIDIST.
|
||||
*
|
||||
* Returns the one-tailed probability of the chi-squared distribution.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $degrees Integer degrees of freedom
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distributionRightTail(mixed $value, mixed $degrees): array|string|int|float
|
||||
{
|
||||
if (is_array($value) || is_array($degrees)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$degrees = DistributionValidations::validateInt($degrees);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($degrees < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if ($value < 0) {
|
||||
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) / Gamma::gammaValue($degrees / 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* CHIDIST.
|
||||
*
|
||||
* Returns the one-tailed probability of the chi-squared distribution.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $degrees Integer degrees of freedom
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distributionLeftTail(mixed $value, mixed $degrees, mixed $cumulative): array|string|int|float
|
||||
{
|
||||
if (is_array($value) || is_array($degrees) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$degrees = DistributionValidations::validateInt($degrees);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($degrees < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if ($value < 0) {
|
||||
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative === true) {
|
||||
$temp = self::distributionRightTail($value, $degrees);
|
||||
|
||||
return 1 - (is_numeric($temp) ? $temp : 0);
|
||||
}
|
||||
|
||||
return ($value ** (($degrees / 2) - 1) * exp(-$value / 2))
|
||||
/ ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* CHIINV.
|
||||
*
|
||||
* Returns the inverse of the right-tailed probability of the chi-squared distribution.
|
||||
*
|
||||
* @param mixed $probability Float probability at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $degrees Integer degrees of freedom
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverseRightTail(mixed $probability, mixed $degrees)
|
||||
{
|
||||
if (is_array($probability) || is_array($degrees)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
|
||||
}
|
||||
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$degrees = DistributionValidations::validateInt($degrees);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($degrees < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$callback = function ($value) use ($degrees): float {
|
||||
return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2)
|
||||
/ Gamma::gammaValue($degrees / 2));
|
||||
};
|
||||
|
||||
$newtonRaphson = new NewtonRaphson($callback);
|
||||
|
||||
return $newtonRaphson->execute($probability);
|
||||
}
|
||||
|
||||
/**
|
||||
* CHIINV.
|
||||
*
|
||||
* Returns the inverse of the left-tailed probability of the chi-squared distribution.
|
||||
*
|
||||
* @param mixed $probability Float probability at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $degrees Integer degrees of freedom
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverseLeftTail(mixed $probability, mixed $degrees): array|string|float
|
||||
{
|
||||
if (is_array($probability) || is_array($degrees)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
|
||||
}
|
||||
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$degrees = DistributionValidations::validateInt($degrees);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($degrees < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::inverseLeftTailCalculation($probability, $degrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* CHITEST.
|
||||
*
|
||||
* Uses the chi-square test to calculate the probability that the differences between two supplied data sets
|
||||
* (of observed and expected frequencies), are likely to be simply due to sampling error,
|
||||
* or if they are likely to be real.
|
||||
*
|
||||
* @param mixed $actual an array of observed frequencies
|
||||
* @param mixed $expected an array of expected frequencies
|
||||
*/
|
||||
public static function test(mixed $actual, mixed $expected): float|string
|
||||
{
|
||||
$rows = count($actual);
|
||||
$actual = Functions::flattenArray($actual);
|
||||
$expected = Functions::flattenArray($expected);
|
||||
$columns = intdiv(count($actual), $rows);
|
||||
|
||||
$countActuals = count($actual);
|
||||
$countExpected = count($expected);
|
||||
if ($countActuals !== $countExpected || $countActuals === 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$result = 0.0;
|
||||
for ($i = 0; $i < $countActuals; ++$i) {
|
||||
if ($expected[$i] == 0.0) {
|
||||
return ExcelError::DIV0();
|
||||
} elseif ($expected[$i] < 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
$result += (($actual[$i] - $expected[$i]) ** 2) / $expected[$i];
|
||||
}
|
||||
|
||||
$degrees = self::degrees($rows, $columns);
|
||||
|
||||
$result = Functions::scalar(self::distributionRightTail($result, $degrees));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected static function degrees(int $rows, int $columns): int
|
||||
{
|
||||
if ($rows === 1) {
|
||||
return $columns - 1;
|
||||
} elseif ($columns === 1) {
|
||||
return $rows - 1;
|
||||
}
|
||||
|
||||
return ($columns - 1) * ($rows - 1);
|
||||
}
|
||||
|
||||
private static function inverseLeftTailCalculation(float $probability, int $degrees): float
|
||||
{
|
||||
// bracket the root
|
||||
$min = 0;
|
||||
$sd = sqrt(2.0 * $degrees);
|
||||
$max = 2 * $sd;
|
||||
$s = -1;
|
||||
|
||||
while ($s * self::pchisq($max, $degrees) > $probability * $s) {
|
||||
$min = $max;
|
||||
$max += 2 * $sd;
|
||||
}
|
||||
|
||||
// Find root using bisection
|
||||
$chi2 = 0.5 * ($min + $max);
|
||||
|
||||
while (($max - $min) > self::EPS * $chi2) {
|
||||
if ($s * self::pchisq($chi2, $degrees) > $probability * $s) {
|
||||
$min = $chi2;
|
||||
} else {
|
||||
$max = $chi2;
|
||||
}
|
||||
$chi2 = 0.5 * ($min + $max);
|
||||
}
|
||||
|
||||
return $chi2;
|
||||
}
|
||||
|
||||
private static function pchisq(float $chi2, int $degrees): float
|
||||
{
|
||||
return self::gammp($degrees, 0.5 * $chi2);
|
||||
}
|
||||
|
||||
private static function gammp(int $n, float $x): float
|
||||
{
|
||||
if ($x < 0.5 * $n + 1) {
|
||||
return self::gser($n, $x);
|
||||
}
|
||||
|
||||
return 1 - self::gcf($n, $x);
|
||||
}
|
||||
|
||||
// Return the incomplete gamma function P(n/2,x) evaluated by
|
||||
// series representation. Algorithm from numerical recipe.
|
||||
// Assume that n is a positive integer and x>0, won't check arguments.
|
||||
// Relative error controlled by the eps parameter
|
||||
private static function gser(int $n, float $x): float
|
||||
{
|
||||
/** @var float $gln */
|
||||
$gln = Gamma::ln($n / 2);
|
||||
$a = 0.5 * $n;
|
||||
$ap = $a;
|
||||
$sum = 1.0 / $a;
|
||||
$del = $sum;
|
||||
for ($i = 1; $i < 101; ++$i) {
|
||||
++$ap;
|
||||
$del = $del * $x / $ap;
|
||||
$sum += $del;
|
||||
if ($del < $sum * self::EPS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $sum * exp(-$x + $a * log($x) - $gln);
|
||||
}
|
||||
|
||||
// Return the incomplete gamma function Q(n/2,x) evaluated by
|
||||
// its continued fraction representation. Algorithm from numerical recipe.
|
||||
// Assume that n is a postive integer and x>0, won't check arguments.
|
||||
// Relative error controlled by the eps parameter
|
||||
private static function gcf(int $n, float $x): float
|
||||
{
|
||||
/** @var float $gln */
|
||||
$gln = Gamma::ln($n / 2);
|
||||
$a = 0.5 * $n;
|
||||
$b = $x + 1 - $a;
|
||||
$fpmin = 1.e-300;
|
||||
$c = 1 / $fpmin;
|
||||
$d = 1 / $b;
|
||||
$h = $d;
|
||||
for ($i = 1; $i < 101; ++$i) {
|
||||
$an = -$i * ($i - $a);
|
||||
$b += 2;
|
||||
$d = $an * $d + $b;
|
||||
if (abs($d) < $fpmin) {
|
||||
$d = $fpmin;
|
||||
}
|
||||
$c = $b + $an / $c;
|
||||
if (abs($c) < $fpmin) {
|
||||
$c = $fpmin;
|
||||
}
|
||||
$d = 1 / $d;
|
||||
$del = $d * $c;
|
||||
$h = $h * $del;
|
||||
if (abs($del - 1) < self::EPS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $h * exp(-$x + $a * log($x) - $gln);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StatisticalValidations;
|
||||
|
||||
class DistributionValidations extends StatisticalValidations
|
||||
{
|
||||
public static function validateProbability(mixed $probability): float
|
||||
{
|
||||
$probability = self::validateFloat($probability);
|
||||
|
||||
if ($probability < 0.0 || $probability > 1.0) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $probability;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Exponential
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* EXPONDIST.
|
||||
*
|
||||
* Returns the exponential distribution. Use EXPONDIST to model the time between events,
|
||||
* such as how long an automated bank teller takes to deliver cash. For example, you can
|
||||
* use EXPONDIST to determine the probability that the process takes at most 1 minute.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $lambda The parameter value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $lambda, mixed $cumulative): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($lambda) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $lambda, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$lambda = DistributionValidations::validateFloat($lambda);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value < 0) || ($lambda < 0)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative === true) {
|
||||
return 1 - exp(0 - $value * $lambda);
|
||||
}
|
||||
|
||||
return $lambda * exp(0 - $value * $lambda);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class F
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* F.DIST.
|
||||
*
|
||||
* Returns the F probability distribution.
|
||||
* You can use this function to determine whether two data sets have different degrees of diversity.
|
||||
* For example, you can examine the test scores of men and women entering high school, and determine
|
||||
* if the variability in the females is different from that found in the males.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $u The numerator degrees of freedom as an integer
|
||||
* Or can be an array of values
|
||||
* @param mixed $v The denominator degrees of freedom as an integer
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $u, mixed $v, mixed $cumulative): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($u) || is_array($v) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $u, $v, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$u = DistributionValidations::validateInt($u);
|
||||
$v = DistributionValidations::validateInt($v);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($value < 0 || $u < 1 || $v < 1) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative) {
|
||||
$adjustedValue = ($u * $value) / ($u * $value + $v);
|
||||
|
||||
return Beta::incompleteBeta($adjustedValue, $u / 2, $v / 2);
|
||||
}
|
||||
|
||||
return (Gamma::gammaValue(($v + $u) / 2)
|
||||
/ (Gamma::gammaValue($u / 2) * Gamma::gammaValue($v / 2)))
|
||||
* (($u / $v) ** ($u / 2))
|
||||
* (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Fisher
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* FISHER.
|
||||
*
|
||||
* Returns the Fisher transformation at x. This transformation produces a function that
|
||||
* is normally distributed rather than skewed. Use this function to perform hypothesis
|
||||
* testing on the correlation coefficient.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value): array|string|float
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
|
||||
}
|
||||
|
||||
try {
|
||||
DistributionValidations::validateFloat($value);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value <= -1) || ($value >= 1)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return 0.5 * log((1 + $value) / (1 - $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* FISHERINV.
|
||||
*
|
||||
* Returns the inverse of the Fisher transformation. Use this transformation when
|
||||
* analyzing correlations between ranges or arrays of data. If y = FISHER(x), then
|
||||
* FISHERINV(y) = x.
|
||||
*
|
||||
* @param mixed $probability Float probability at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $probability): array|string|float
|
||||
{
|
||||
if (is_array($probability)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $probability);
|
||||
}
|
||||
|
||||
try {
|
||||
DistributionValidations::validateFloat($probability);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return (exp(2 * $probability) - 1) / (exp(2 * $probability) + 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Gamma extends GammaBase
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* GAMMA.
|
||||
*
|
||||
* Return the gamma function value.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function gamma(mixed $value): array|string|float
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ((((int) $value) == ((float) $value)) && $value <= 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::gammaValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* GAMMADIST.
|
||||
*
|
||||
* Returns the gamma distribution.
|
||||
*
|
||||
* @param mixed $value Float Value at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $a Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $b Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $a, mixed $b, mixed $cumulative)
|
||||
{
|
||||
if (is_array($value) || is_array($a) || is_array($b) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $a, $b, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$a = DistributionValidations::validateFloat($a);
|
||||
$b = DistributionValidations::validateFloat($b);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value < 0) || ($a <= 0) || ($b <= 0)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::calculateDistribution($value, $a, $b, $cumulative);
|
||||
}
|
||||
|
||||
/**
|
||||
* GAMMAINV.
|
||||
*
|
||||
* Returns the inverse of the Gamma distribution.
|
||||
*
|
||||
* @param mixed $probability Float probability at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $alpha Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $beta Parameter to the distribution as a float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $probability, mixed $alpha, mixed $beta)
|
||||
{
|
||||
if (is_array($probability) || is_array($alpha) || is_array($beta)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta);
|
||||
}
|
||||
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$alpha = DistributionValidations::validateFloat($alpha);
|
||||
$beta = DistributionValidations::validateFloat($beta);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($alpha <= 0.0) || ($beta <= 0.0)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::calculateInverse($probability, $alpha, $beta);
|
||||
}
|
||||
|
||||
/**
|
||||
* GAMMALN.
|
||||
*
|
||||
* Returns the natural logarithm of the gamma function.
|
||||
*
|
||||
* @param mixed $value Float Value at which you want to evaluate the distribution
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function ln(mixed $value): array|string|float
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($value <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return log(self::gammaValue($value));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,388 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
abstract class GammaBase
|
||||
{
|
||||
private const LOG_GAMMA_X_MAX_VALUE = 2.55e305;
|
||||
|
||||
private const EPS = 2.22e-16;
|
||||
|
||||
private const MAX_VALUE = 1.2e308;
|
||||
|
||||
private const SQRT2PI = 2.5066282746310005024157652848110452530069867406099;
|
||||
|
||||
private const MAX_ITERATIONS = 256;
|
||||
|
||||
protected static function calculateDistribution(float $value, float $a, float $b, bool $cumulative): float
|
||||
{
|
||||
if ($cumulative) {
|
||||
return self::incompleteGamma($a, $value / $b) / self::gammaValue($a);
|
||||
}
|
||||
|
||||
return (1 / ($b ** $a * self::gammaValue($a))) * $value ** ($a - 1) * exp(0 - ($value / $b));
|
||||
}
|
||||
|
||||
/** @return float|string */
|
||||
protected static function calculateInverse(float $probability, float $alpha, float $beta)
|
||||
{
|
||||
$xLo = 0;
|
||||
$xHi = $alpha * $beta * 5;
|
||||
|
||||
$dx = 1024;
|
||||
$x = $xNew = 1;
|
||||
$i = 0;
|
||||
|
||||
while ((abs($dx) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
|
||||
// Apply Newton-Raphson step
|
||||
$result = self::calculateDistribution($x, $alpha, $beta, true);
|
||||
if (!is_float($result)) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
$error = $result - $probability;
|
||||
|
||||
if ($error == 0.0) {
|
||||
$dx = 0;
|
||||
} elseif ($error < 0.0) {
|
||||
$xLo = $x;
|
||||
} else {
|
||||
$xHi = $x;
|
||||
}
|
||||
|
||||
$pdf = self::calculateDistribution($x, $alpha, $beta, false);
|
||||
// Avoid division by zero
|
||||
if (!is_float($pdf)) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
if ($pdf !== 0.0) {
|
||||
$dx = $error / $pdf;
|
||||
$xNew = $x - $dx;
|
||||
}
|
||||
|
||||
// If the NR fails to converge (which for example may be the
|
||||
// case if the initial guess is too rough) we apply a bisection
|
||||
// step to determine a more narrow interval around the root.
|
||||
if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) {
|
||||
$xNew = ($xLo + $xHi) / 2;
|
||||
$dx = $xNew - $x;
|
||||
}
|
||||
$x = $xNew;
|
||||
}
|
||||
|
||||
if ($i === self::MAX_ITERATIONS) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of the incomplete Gamma function
|
||||
//
|
||||
public static function incompleteGamma(float $a, float $x): float
|
||||
{
|
||||
static $max = 32;
|
||||
$summer = 0;
|
||||
for ($n = 0; $n <= $max; ++$n) {
|
||||
$divisor = $a;
|
||||
for ($i = 1; $i <= $n; ++$i) {
|
||||
$divisor *= ($a + $i);
|
||||
}
|
||||
$summer += ($x ** $n / $divisor);
|
||||
}
|
||||
|
||||
return $x ** $a * exp(0 - $x) * $summer;
|
||||
}
|
||||
|
||||
//
|
||||
// Implementation of the Gamma function
|
||||
//
|
||||
public static function gammaValue(float $value): float
|
||||
{
|
||||
if ($value == 0.0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static $p0 = 1.000000000190015;
|
||||
static $p = [
|
||||
1 => 76.18009172947146,
|
||||
2 => -86.50532032941677,
|
||||
3 => 24.01409824083091,
|
||||
4 => -1.231739572450155,
|
||||
5 => 1.208650973866179e-3,
|
||||
6 => -5.395239384953e-6,
|
||||
];
|
||||
|
||||
$y = $x = $value;
|
||||
$tmp = $x + 5.5;
|
||||
$tmp -= ($x + 0.5) * log($tmp);
|
||||
|
||||
$summer = $p0;
|
||||
for ($j = 1; $j <= 6; ++$j) {
|
||||
$summer += ($p[$j] / ++$y);
|
||||
}
|
||||
|
||||
return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x));
|
||||
}
|
||||
|
||||
private const LG_D1 = -0.5772156649015328605195174;
|
||||
|
||||
private const LG_D2 = 0.4227843350984671393993777;
|
||||
|
||||
private const LG_D4 = 1.791759469228055000094023;
|
||||
|
||||
private const LG_P1 = [
|
||||
4.945235359296727046734888,
|
||||
201.8112620856775083915565,
|
||||
2290.838373831346393026739,
|
||||
11319.67205903380828685045,
|
||||
28557.24635671635335736389,
|
||||
38484.96228443793359990269,
|
||||
26377.48787624195437963534,
|
||||
7225.813979700288197698961,
|
||||
];
|
||||
|
||||
private const LG_P2 = [
|
||||
4.974607845568932035012064,
|
||||
542.4138599891070494101986,
|
||||
15506.93864978364947665077,
|
||||
184793.2904445632425417223,
|
||||
1088204.76946882876749847,
|
||||
3338152.967987029735917223,
|
||||
5106661.678927352456275255,
|
||||
3074109.054850539556250927,
|
||||
];
|
||||
|
||||
private const LG_P4 = [
|
||||
14745.02166059939948905062,
|
||||
2426813.369486704502836312,
|
||||
121475557.4045093227939592,
|
||||
2663432449.630976949898078,
|
||||
29403789566.34553899906876,
|
||||
170266573776.5398868392998,
|
||||
492612579337.743088758812,
|
||||
560625185622.3951465078242,
|
||||
];
|
||||
|
||||
private const LG_Q1 = [
|
||||
67.48212550303777196073036,
|
||||
1113.332393857199323513008,
|
||||
7738.757056935398733233834,
|
||||
27639.87074403340708898585,
|
||||
54993.10206226157329794414,
|
||||
61611.22180066002127833352,
|
||||
36351.27591501940507276287,
|
||||
8785.536302431013170870835,
|
||||
];
|
||||
|
||||
private const LG_Q2 = [
|
||||
183.0328399370592604055942,
|
||||
7765.049321445005871323047,
|
||||
133190.3827966074194402448,
|
||||
1136705.821321969608938755,
|
||||
5267964.117437946917577538,
|
||||
13467014.54311101692290052,
|
||||
17827365.30353274213975932,
|
||||
9533095.591844353613395747,
|
||||
];
|
||||
|
||||
private const LG_Q4 = [
|
||||
2690.530175870899333379843,
|
||||
639388.5654300092398984238,
|
||||
41355999.30241388052042842,
|
||||
1120872109.61614794137657,
|
||||
14886137286.78813811542398,
|
||||
101680358627.2438228077304,
|
||||
341747634550.7377132798597,
|
||||
446315818741.9713286462081,
|
||||
];
|
||||
|
||||
private const LG_C = [
|
||||
-0.001910444077728,
|
||||
8.4171387781295e-4,
|
||||
-5.952379913043012e-4,
|
||||
7.93650793500350248e-4,
|
||||
-0.002777777777777681622553,
|
||||
0.08333333333333333331554247,
|
||||
0.0057083835261,
|
||||
];
|
||||
|
||||
// Rough estimate of the fourth root of logGamma_xBig
|
||||
private const LG_FRTBIG = 2.25e76;
|
||||
|
||||
private const PNT68 = 0.6796875;
|
||||
|
||||
// Function cache for logGamma
|
||||
|
||||
private static float $logGammaCacheResult = 0.0;
|
||||
|
||||
private static float $logGammaCacheX = 0.0;
|
||||
|
||||
/**
|
||||
* logGamma function.
|
||||
*
|
||||
* Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
|
||||
*
|
||||
* The natural logarithm of the gamma function. <br />
|
||||
* Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz <br />
|
||||
* Applied Mathematics Division <br />
|
||||
* Argonne National Laboratory <br />
|
||||
* Argonne, IL 60439 <br />
|
||||
* <p>
|
||||
* References:
|
||||
* <ol>
|
||||
* <li>W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
|
||||
* Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.</li>
|
||||
* <li>K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.</li>
|
||||
* <li>Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
* <p>
|
||||
* From the original documentation:
|
||||
* </p>
|
||||
* <p>
|
||||
* This routine calculates the LOG(GAMMA) function for a positive real argument X.
|
||||
* Computation is based on an algorithm outlined in references 1 and 2.
|
||||
* The program uses rational functions that theoretically approximate LOG(GAMMA)
|
||||
* to at least 18 significant decimal digits. The approximation for X > 12 is from
|
||||
* reference 3, while approximations for X < 12.0 are similar to those in reference
|
||||
* 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
|
||||
* the compiler, the intrinsic functions, and proper selection of the
|
||||
* machine-dependent constants.
|
||||
* </p>
|
||||
* <p>
|
||||
* Error returns: <br />
|
||||
* The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
|
||||
* The computation is believed to be free of underflow and overflow.
|
||||
* </p>
|
||||
*
|
||||
* @version 1.1
|
||||
*
|
||||
* @author Jaco van Kooten
|
||||
*
|
||||
* @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
|
||||
*/
|
||||
public static function logGamma(float $x): float
|
||||
{
|
||||
if ($x == self::$logGammaCacheX) {
|
||||
return self::$logGammaCacheResult;
|
||||
}
|
||||
|
||||
$y = $x;
|
||||
if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) {
|
||||
if ($y <= self::EPS) {
|
||||
$res = -log($y);
|
||||
} elseif ($y <= 1.5) {
|
||||
$res = self::logGamma1($y);
|
||||
} elseif ($y <= 4.0) {
|
||||
$res = self::logGamma2($y);
|
||||
} elseif ($y <= 12.0) {
|
||||
$res = self::logGamma3($y);
|
||||
} else {
|
||||
$res = self::logGamma4($y);
|
||||
}
|
||||
} else {
|
||||
// --------------------------
|
||||
// Return for bad arguments
|
||||
// --------------------------
|
||||
$res = self::MAX_VALUE;
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Final adjustments and return
|
||||
// ------------------------------
|
||||
self::$logGammaCacheX = $x;
|
||||
self::$logGammaCacheResult = $res;
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
private static function logGamma1(float $y): float
|
||||
{
|
||||
// ---------------------
|
||||
// EPS .LT. X .LE. 1.5
|
||||
// ---------------------
|
||||
if ($y < self::PNT68) {
|
||||
$corr = -log($y);
|
||||
$xm1 = $y;
|
||||
} else {
|
||||
$corr = 0.0;
|
||||
$xm1 = $y - 1.0;
|
||||
}
|
||||
|
||||
$xden = 1.0;
|
||||
$xnum = 0.0;
|
||||
if ($y <= 0.5 || $y >= self::PNT68) {
|
||||
for ($i = 0; $i < 8; ++$i) {
|
||||
$xnum = $xnum * $xm1 + self::LG_P1[$i];
|
||||
$xden = $xden * $xm1 + self::LG_Q1[$i];
|
||||
}
|
||||
|
||||
return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden));
|
||||
}
|
||||
|
||||
$xm2 = $y - 1.0;
|
||||
for ($i = 0; $i < 8; ++$i) {
|
||||
$xnum = $xnum * $xm2 + self::LG_P2[$i];
|
||||
$xden = $xden * $xm2 + self::LG_Q2[$i];
|
||||
}
|
||||
|
||||
return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
|
||||
}
|
||||
|
||||
private static function logGamma2(float $y): float
|
||||
{
|
||||
// ---------------------
|
||||
// 1.5 .LT. X .LE. 4.0
|
||||
// ---------------------
|
||||
$xm2 = $y - 2.0;
|
||||
$xden = 1.0;
|
||||
$xnum = 0.0;
|
||||
for ($i = 0; $i < 8; ++$i) {
|
||||
$xnum = $xnum * $xm2 + self::LG_P2[$i];
|
||||
$xden = $xden * $xm2 + self::LG_Q2[$i];
|
||||
}
|
||||
|
||||
return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
|
||||
}
|
||||
|
||||
protected static function logGamma3(float $y): float
|
||||
{
|
||||
// ----------------------
|
||||
// 4.0 .LT. X .LE. 12.0
|
||||
// ----------------------
|
||||
$xm4 = $y - 4.0;
|
||||
$xden = -1.0;
|
||||
$xnum = 0.0;
|
||||
for ($i = 0; $i < 8; ++$i) {
|
||||
$xnum = $xnum * $xm4 + self::LG_P4[$i];
|
||||
$xden = $xden * $xm4 + self::LG_Q4[$i];
|
||||
}
|
||||
|
||||
return self::LG_D4 + $xm4 * ($xnum / $xden);
|
||||
}
|
||||
|
||||
protected static function logGamma4(float $y): float
|
||||
{
|
||||
// ---------------------------------
|
||||
// Evaluate for argument .GE. 12.0
|
||||
// ---------------------------------
|
||||
$res = 0.0;
|
||||
if ($y <= self::LG_FRTBIG) {
|
||||
$res = self::LG_C[6];
|
||||
$ysq = $y * $y;
|
||||
for ($i = 0; $i < 6; ++$i) {
|
||||
$res = $res / $ysq + self::LG_C[$i];
|
||||
}
|
||||
$res /= $y;
|
||||
$corr = log($y);
|
||||
$res = $res + log(self::SQRT2PI) - 0.5 * $corr;
|
||||
$res += $y * ($corr - 1.0);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig\Combinations;
|
||||
|
||||
class HyperGeometric
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* HYPGEOMDIST.
|
||||
*
|
||||
* Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of
|
||||
* sample successes, given the sample size, population successes, and population size.
|
||||
*
|
||||
* @param mixed $sampleSuccesses Integer number of successes in the sample
|
||||
* Or can be an array of values
|
||||
* @param mixed $sampleNumber Integer size of the sample
|
||||
* Or can be an array of values
|
||||
* @param mixed $populationSuccesses Integer number of successes in the population
|
||||
* Or can be an array of values
|
||||
* @param mixed $populationNumber Integer population size
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $sampleSuccesses, mixed $sampleNumber, mixed $populationSuccesses, mixed $populationNumber): array|string|float
|
||||
{
|
||||
if (
|
||||
is_array($sampleSuccesses) || is_array($sampleNumber)
|
||||
|| is_array($populationSuccesses) || is_array($populationNumber)
|
||||
) {
|
||||
return self::evaluateArrayArguments(
|
||||
[self::class, __FUNCTION__],
|
||||
$sampleSuccesses,
|
||||
$sampleNumber,
|
||||
$populationSuccesses,
|
||||
$populationNumber
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$sampleSuccesses = DistributionValidations::validateInt($sampleSuccesses);
|
||||
$sampleNumber = DistributionValidations::validateInt($sampleNumber);
|
||||
$populationSuccesses = DistributionValidations::validateInt($populationSuccesses);
|
||||
$populationNumber = DistributionValidations::validateInt($populationNumber);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$successesPopulationAndSample = (float) Combinations::withoutRepetition($populationSuccesses, $sampleSuccesses);
|
||||
$numbersPopulationAndSample = (float) Combinations::withoutRepetition($populationNumber, $sampleNumber);
|
||||
$adjustedPopulationAndSample = (float) Combinations::withoutRepetition(
|
||||
$populationNumber - $populationSuccesses,
|
||||
$sampleNumber - $sampleSuccesses
|
||||
);
|
||||
|
||||
return $successesPopulationAndSample * $adjustedPopulationAndSample / $numbersPopulationAndSample;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class LogNormal
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* LOGNORMDIST.
|
||||
*
|
||||
* Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed
|
||||
* with parameters mean and standard_dev.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $mean Mean value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $stdDev Standard Deviation as a float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function cumulative(mixed $value, mixed $mean, mixed $stdDev)
|
||||
{
|
||||
if (is_array($value) || is_array($mean) || is_array($stdDev)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$mean = DistributionValidations::validateFloat($mean);
|
||||
$stdDev = DistributionValidations::validateFloat($stdDev);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value <= 0) || ($stdDev <= 0)) {
|
||||
return ExcelError::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 $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $mean Mean value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $stdDev Standard Deviation as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $mean, mixed $stdDev, mixed $cumulative = false)
|
||||
{
|
||||
if (is_array($value) || is_array($mean) || is_array($stdDev) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$mean = DistributionValidations::validateFloat($mean);
|
||||
$stdDev = DistributionValidations::validateFloat($stdDev);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value <= 0) || ($stdDev <= 0)) {
|
||||
return ExcelError::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 lognormal cumulative distribution
|
||||
*
|
||||
* @param mixed $probability Float probability for which we want the value
|
||||
* Or can be an array of values
|
||||
* @param mixed $mean Mean Value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $stdDev Standard Deviation as a float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*
|
||||
* @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(mixed $probability, mixed $mean, mixed $stdDev): array|string|float
|
||||
{
|
||||
if (is_array($probability) || is_array($mean) || is_array($stdDev)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev);
|
||||
}
|
||||
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$mean = DistributionValidations::validateFloat($mean);
|
||||
$stdDev = DistributionValidations::validateFloat($stdDev);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($stdDev <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
/** @var float $inverse */
|
||||
$inverse = StandardNormal::inverse($probability);
|
||||
|
||||
return exp($mean + $stdDev * $inverse);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class NewtonRaphson
|
||||
{
|
||||
private const MAX_ITERATIONS = 256;
|
||||
|
||||
/** @var callable */
|
||||
protected $callback;
|
||||
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
public function execute(float $probability): string|int|float
|
||||
{
|
||||
$xLo = 100;
|
||||
$xHi = 0;
|
||||
|
||||
$dx = 1;
|
||||
$x = $xNew = 1;
|
||||
$i = 0;
|
||||
|
||||
while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) {
|
||||
// Apply Newton-Raphson step
|
||||
$result = call_user_func($this->callback, $x);
|
||||
$error = $result - $probability;
|
||||
|
||||
if ($error == 0.0) {
|
||||
$dx = 0;
|
||||
} elseif ($error < 0.0) {
|
||||
$xLo = $x;
|
||||
} else {
|
||||
$xHi = $x;
|
||||
}
|
||||
|
||||
// Avoid division by zero
|
||||
if ($result != 0.0) {
|
||||
$dx = $error / $result;
|
||||
$xNew = $x - $dx;
|
||||
}
|
||||
|
||||
// If the NR fails to converge (which for example may be the
|
||||
// case if the initial guess is too rough) we apply a bisection
|
||||
// step to determine a more narrow interval around the root.
|
||||
if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
|
||||
$xNew = ($xLo + $xHi) / 2;
|
||||
$dx = $xNew - $x;
|
||||
}
|
||||
$x = $xNew;
|
||||
}
|
||||
|
||||
if ($i == self::MAX_ITERATIONS) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
return $x;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Normal
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
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 $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $mean Mean value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $stdDev Standard Deviation as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $mean, mixed $stdDev, mixed $cumulative): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($mean) || is_array($stdDev) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$mean = DistributionValidations::validateFloat($mean);
|
||||
$stdDev = DistributionValidations::validateFloat($stdDev);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($stdDev < 0) {
|
||||
return ExcelError::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 $probability Float probability for which we want the value
|
||||
* Or can be an array of values
|
||||
* @param mixed $mean Mean Value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $stdDev Standard Deviation as a float
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $probability, mixed $mean, mixed $stdDev): array|string|float
|
||||
{
|
||||
if (is_array($probability) || is_array($mean) || is_array($stdDev)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev);
|
||||
}
|
||||
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$mean = DistributionValidations::validateFloat($mean);
|
||||
$stdDev = DistributionValidations::validateFloat($stdDev);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($stdDev < 0) {
|
||||
return ExcelError::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(float $p): float
|
||||
{
|
||||
// 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,66 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
class Poisson
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* POISSON.
|
||||
*
|
||||
* Returns the Poisson distribution. A common application of the Poisson distribution
|
||||
* is predicting the number of events over a specific time, such as the number of
|
||||
* cars arriving at a toll plaza in 1 minute.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $mean Mean value as a float
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $mean, mixed $cumulative): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($mean) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$mean = DistributionValidations::validateFloat($mean);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value < 0) || ($mean < 0)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative) {
|
||||
$summer = 0;
|
||||
$floor = floor($value);
|
||||
for ($i = 0; $i <= $floor; ++$i) {
|
||||
/** @var float $fact */
|
||||
$fact = MathTrig\Factorial::fact($i);
|
||||
$summer += $mean ** $i / $fact;
|
||||
}
|
||||
|
||||
return exp(0 - $mean) * $summer;
|
||||
}
|
||||
/** @var float $fact */
|
||||
$fact = MathTrig\Factorial::fact($value);
|
||||
|
||||
return (exp(0 - $mean) * $mean ** $value) / $fact;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
|
||||
|
||||
class StandardNormal
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* NOTE: We don't need to check for arrays to array-enable this function, because that is already
|
||||
* handled by the logic in Normal::distribution()
|
||||
* All we need to do is pass the value through as scalar or as array.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function cumulative(mixed $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.
|
||||
*
|
||||
* NOTE: We don't need to check for arrays to array-enable this function, because that is already
|
||||
* handled by the logic in Normal::distribution()
|
||||
* All we need to do is pass the value and cumulative through as scalar or as array.
|
||||
*
|
||||
* @param mixed $value Float value for which we want the probability
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $cumulative)
|
||||
{
|
||||
return Normal::distribution($value, 0, 1, $cumulative);
|
||||
}
|
||||
|
||||
/**
|
||||
* NORMSINV.
|
||||
*
|
||||
* Returns the inverse of the standard normal cumulative distribution
|
||||
*
|
||||
* @param mixed $value float probability for which we want the value
|
||||
* Or can be an array of values
|
||||
*
|
||||
* NOTE: We don't need to check for arrays to array-enable this function, because that is already
|
||||
* handled by the logic in Normal::inverse()
|
||||
* All we need to do is pass the value through as scalar or as array
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $value)
|
||||
{
|
||||
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 Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function gauss(mixed $value): array|string|float
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
|
||||
}
|
||||
|
||||
if (!is_numeric($value)) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
/** @var float $dist */
|
||||
$dist = self::distribution($value, true);
|
||||
|
||||
return $dist - 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $dataSet The dataset should be an array of float values for the observations
|
||||
* @param mixed $m0 Alpha Parameter
|
||||
* Or can be an array of values
|
||||
* @param mixed $sigma A null or float value for the Beta (Standard Deviation) Parameter;
|
||||
* if null, we use the standard deviation of the dataset
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string (string if result is an error)
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function zTest(mixed $dataSet, mixed $m0, mixed $sigma = null)
|
||||
{
|
||||
if (is_array($m0) || is_array($sigma)) {
|
||||
return self::evaluateArrayArgumentsSubsetFrom([self::class, __FUNCTION__], 1, $dataSet, $m0, $sigma);
|
||||
}
|
||||
|
||||
$dataSet = Functions::flattenArrayIndexed($dataSet);
|
||||
|
||||
if (!is_numeric($m0) || ($sigma !== null && !is_numeric($sigma))) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
if ($sigma === null) {
|
||||
/** @var float $sigma */
|
||||
$sigma = StandardDeviations::STDEV($dataSet);
|
||||
}
|
||||
$n = count($dataSet);
|
||||
|
||||
$sub1 = Averages::average($dataSet);
|
||||
|
||||
if (!is_numeric($sub1)) {
|
||||
return $sub1;
|
||||
}
|
||||
|
||||
$temp = self::cumulative(($sub1 - $m0) / ($sigma / sqrt($n)));
|
||||
|
||||
return 1 - (is_numeric($temp) ? $temp : 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class StudentT
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* TDIST.
|
||||
*
|
||||
* Returns the probability of Student's T distribution.
|
||||
*
|
||||
* @param mixed $value Float value for the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $degrees Integer value for degrees of freedom
|
||||
* Or can be an array of values
|
||||
* @param mixed $tails Integer value for the number of tails (1 or 2)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $degrees, mixed $tails)
|
||||
{
|
||||
if (is_array($value) || is_array($degrees) || is_array($tails)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $tails);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$degrees = DistributionValidations::validateInt($degrees);
|
||||
$tails = DistributionValidations::validateInt($tails);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::calculateDistribution($value, $degrees, $tails);
|
||||
}
|
||||
|
||||
/**
|
||||
* TINV.
|
||||
*
|
||||
* Returns the one-tailed probability of the chi-squared distribution.
|
||||
*
|
||||
* @param mixed $probability Float probability for the function
|
||||
* Or can be an array of values
|
||||
* @param mixed $degrees Integer value for degrees of freedom
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string The result, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function inverse(mixed $probability, mixed $degrees)
|
||||
{
|
||||
if (is_array($probability) || is_array($degrees)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees);
|
||||
}
|
||||
|
||||
try {
|
||||
$probability = DistributionValidations::validateProbability($probability);
|
||||
$degrees = DistributionValidations::validateInt($degrees);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($degrees <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$callback = fn ($value) => self::distribution($value, $degrees, 2);
|
||||
|
||||
$newtonRaphson = new NewtonRaphson($callback);
|
||||
|
||||
return $newtonRaphson->execute($probability);
|
||||
}
|
||||
|
||||
private static function calculateDistribution(float $value, int $degrees, int $tails): float
|
||||
{
|
||||
// tdist, which finds the probability that corresponds to a given value
|
||||
// of t with k degrees of freedom. This algorithm is translated from a
|
||||
// pascal function on p81 of "Statistical Computing in Pascal" by D
|
||||
// Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd:
|
||||
// London). The above Pascal algorithm is itself a translation of the
|
||||
// fortran algoritm "AS 3" by B E Cooper of the Atlas Computer
|
||||
// Laboratory as reported in (among other places) "Applied Statistics
|
||||
// Algorithms", editied by P Griffiths and I D Hill (1985; Ellis
|
||||
// Horwood Ltd.; W. Sussex, England).
|
||||
$tterm = $degrees;
|
||||
$ttheta = atan2($value, sqrt($tterm));
|
||||
$tc = cos($ttheta);
|
||||
$ts = sin($ttheta);
|
||||
|
||||
if (($degrees % 2) === 1) {
|
||||
$ti = 3;
|
||||
$tterm = $tc;
|
||||
} else {
|
||||
$ti = 2;
|
||||
$tterm = 1;
|
||||
}
|
||||
|
||||
$tsum = $tterm;
|
||||
while ($ti < $degrees) {
|
||||
$tterm *= $tc * $tc * ($ti - 1) / $ti;
|
||||
$tsum += $tterm;
|
||||
$ti += 2;
|
||||
}
|
||||
|
||||
$tsum *= $ts;
|
||||
if (($degrees % 2) == 1) {
|
||||
$tsum = Functions::M_2DIVPI * ($tsum + $ttheta);
|
||||
}
|
||||
|
||||
$tValue = 0.5 * (1 + $tsum);
|
||||
if ($tails == 1) {
|
||||
return 1 - abs($tValue);
|
||||
}
|
||||
|
||||
return 1 - abs((1 - $tValue) - $tValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Weibull
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* WEIBULL.
|
||||
*
|
||||
* Returns the Weibull distribution. Use this distribution in reliability
|
||||
* analysis, such as calculating a device's mean time to failure.
|
||||
*
|
||||
* @param mixed $value Float value for the distribution
|
||||
* Or can be an array of values
|
||||
* @param mixed $alpha Float alpha Parameter
|
||||
* Or can be an array of values
|
||||
* @param mixed $beta Float beta Parameter
|
||||
* Or can be an array of values
|
||||
* @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string (string if result is an error)
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function distribution(mixed $value, mixed $alpha, mixed $beta, mixed $cumulative): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($alpha) || is_array($beta) || is_array($cumulative)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $alpha, $beta, $cumulative);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = DistributionValidations::validateFloat($value);
|
||||
$alpha = DistributionValidations::validateFloat($alpha);
|
||||
$beta = DistributionValidations::validateFloat($beta);
|
||||
$cumulative = DistributionValidations::validateBool($cumulative);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
if ($cumulative) {
|
||||
return 1 - exp(0 - ($value / $beta) ** $alpha);
|
||||
}
|
||||
|
||||
return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha);
|
||||
}
|
||||
}
|
||||
17
lib/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php
Normal file
17
lib/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
abstract class MaxMinBase
|
||||
{
|
||||
protected static function datatypeAdjustmentAllowStrings(mixed $value): mixed
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return (int) $value;
|
||||
} elseif (is_string($value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
85
lib/PhpSpreadsheet/Calculation/Statistical/Maximum.php
Normal file
85
lib/PhpSpreadsheet/Calculation/Statistical/Maximum.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
|
||||
|
||||
class Maximum extends MaxMinBase
|
||||
{
|
||||
/**
|
||||
* MAX.
|
||||
*
|
||||
* MAX returns the value of the element of the values passed that has the highest value,
|
||||
* with negative numbers considered smaller than positive numbers.
|
||||
*
|
||||
* Excel Function:
|
||||
* MAX(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function max(mixed ...$args): float|int|string
|
||||
{
|
||||
$returnValue = null;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
foreach ($aArgs as $arg) {
|
||||
if (ErrorValue::isError($arg)) {
|
||||
$returnValue = $arg;
|
||||
|
||||
break;
|
||||
}
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) && (!is_string($arg))) {
|
||||
if (($returnValue === null) || ($arg > $returnValue)) {
|
||||
$returnValue = $arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnValue === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* MAXA.
|
||||
*
|
||||
* Returns the greatest value in a list of arguments, including numbers, text, and logical values
|
||||
*
|
||||
* Excel Function:
|
||||
* MAXA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function maxA(mixed ...$args): float|int|string
|
||||
{
|
||||
$returnValue = null;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
foreach ($aArgs as $arg) {
|
||||
if (ErrorValue::isError($arg)) {
|
||||
$returnValue = $arg;
|
||||
|
||||
break;
|
||||
}
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
|
||||
$arg = self::datatypeAdjustmentAllowStrings($arg);
|
||||
if (($returnValue === null) || ($arg > $returnValue)) {
|
||||
$returnValue = $arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnValue === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
}
|
||||
85
lib/PhpSpreadsheet/Calculation/Statistical/Minimum.php
Normal file
85
lib/PhpSpreadsheet/Calculation/Statistical/Minimum.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
|
||||
|
||||
class Minimum extends MaxMinBase
|
||||
{
|
||||
/**
|
||||
* MIN.
|
||||
*
|
||||
* MIN returns the value of the element of the values passed that has the smallest value,
|
||||
* with negative numbers considered smaller than positive numbers.
|
||||
*
|
||||
* Excel Function:
|
||||
* MIN(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function min(mixed ...$args): float|int|string
|
||||
{
|
||||
$returnValue = null;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
foreach ($aArgs as $arg) {
|
||||
if (ErrorValue::isError($arg)) {
|
||||
$returnValue = $arg;
|
||||
|
||||
break;
|
||||
}
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) && (!is_string($arg))) {
|
||||
if (($returnValue === null) || ($arg < $returnValue)) {
|
||||
$returnValue = $arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnValue === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* MINA.
|
||||
*
|
||||
* Returns the smallest value in a list of arguments, including numbers, text, and logical values
|
||||
*
|
||||
* Excel Function:
|
||||
* MINA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function minA(mixed ...$args): float|int|string
|
||||
{
|
||||
$returnValue = null;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
foreach ($aArgs as $arg) {
|
||||
if (ErrorValue::isError($arg)) {
|
||||
$returnValue = $arg;
|
||||
|
||||
break;
|
||||
}
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
|
||||
$arg = self::datatypeAdjustmentAllowStrings($arg);
|
||||
if (($returnValue === null) || ($arg < $returnValue)) {
|
||||
$returnValue = $arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($returnValue === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
}
|
||||
202
lib/PhpSpreadsheet/Calculation/Statistical/Percentiles.php
Normal file
202
lib/PhpSpreadsheet/Calculation/Statistical/Percentiles.php
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Percentiles
|
||||
{
|
||||
public const RANK_SORT_DESCENDING = 0;
|
||||
|
||||
public const RANK_SORT_ASCENDING = 1;
|
||||
|
||||
/**
|
||||
* PERCENTILE.
|
||||
*
|
||||
* Returns the nth percentile of values in a range..
|
||||
*
|
||||
* Excel Function:
|
||||
* PERCENTILE(value1[,value2[, ...]],entry)
|
||||
*
|
||||
* @param mixed $args Data values
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function PERCENTILE(mixed ...$args)
|
||||
{
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
|
||||
// Calculate
|
||||
$entry = array_pop($aArgs);
|
||||
|
||||
try {
|
||||
$entry = StatisticalValidations::validateFloat($entry);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($entry < 0) || ($entry > 1)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$mArgs = self::percentileFilterValues($aArgs);
|
||||
$mValueCount = count($mArgs);
|
||||
if ($mValueCount > 0) {
|
||||
sort($mArgs);
|
||||
$count = Counts::COUNT($mArgs);
|
||||
$index = $entry * ($count - 1);
|
||||
$iBase = floor($index);
|
||||
if ($index == $iBase) {
|
||||
return $mArgs[$index];
|
||||
}
|
||||
$iNext = $iBase + 1;
|
||||
$iProportion = $index - $iBase;
|
||||
|
||||
return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion);
|
||||
}
|
||||
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
/**
|
||||
* PERCENTRANK.
|
||||
*
|
||||
* Returns the rank of a value in a data set as a percentage of the data set.
|
||||
* Note that the returned rank is simply rounded to the appropriate significant digits,
|
||||
* rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return
|
||||
* 0.667 rather than 0.666
|
||||
*
|
||||
* @param mixed $valueSet An array of (float) values, or a reference to, a list of numbers
|
||||
* @param mixed $value The number whose rank you want to find
|
||||
* @param mixed $significance The (integer) number of significant digits for the returned percentage value
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
*/
|
||||
public static function PERCENTRANK(mixed $valueSet, mixed $value, mixed $significance = 3): string|float
|
||||
{
|
||||
$valueSet = Functions::flattenArray($valueSet);
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
$significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance);
|
||||
|
||||
try {
|
||||
$value = StatisticalValidations::validateFloat($value);
|
||||
$significance = StatisticalValidations::validateInt($significance);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$valueSet = self::rankFilterValues($valueSet);
|
||||
$valueCount = count($valueSet);
|
||||
if ($valueCount == 0) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
sort($valueSet, SORT_NUMERIC);
|
||||
|
||||
$valueAdjustor = $valueCount - 1;
|
||||
if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
$pos = array_search($value, $valueSet);
|
||||
if ($pos === false) {
|
||||
$pos = 0;
|
||||
$testValue = $valueSet[0];
|
||||
while ($testValue < $value) {
|
||||
$testValue = $valueSet[++$pos];
|
||||
}
|
||||
--$pos;
|
||||
$pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos]));
|
||||
}
|
||||
|
||||
return round(((float) $pos) / $valueAdjustor, $significance);
|
||||
}
|
||||
|
||||
/**
|
||||
* QUARTILE.
|
||||
*
|
||||
* Returns the quartile of a data set.
|
||||
*
|
||||
* Excel Function:
|
||||
* QUARTILE(value1[,value2[, ...]],entry)
|
||||
*
|
||||
* @param mixed $args Data values
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function QUARTILE(mixed ...$args)
|
||||
{
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
$entry = array_pop($aArgs);
|
||||
|
||||
try {
|
||||
$entry = StatisticalValidations::validateFloat($entry);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$entry = floor($entry);
|
||||
$entry /= 4;
|
||||
if (($entry < 0) || ($entry > 1)) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return self::PERCENTILE($aArgs, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* RANK.
|
||||
*
|
||||
* Returns the rank of a number in a list of numbers.
|
||||
*
|
||||
* @param mixed $value The number whose rank you want to find
|
||||
* @param mixed $valueSet An array of float values, or a reference to, a list of numbers
|
||||
* @param mixed $order Order to sort the values in the value set
|
||||
*
|
||||
* @return float|string The result, or a string containing an error (0 = Descending, 1 = Ascending)
|
||||
*/
|
||||
public static function RANK(mixed $value, mixed $valueSet, mixed $order = self::RANK_SORT_DESCENDING)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
$valueSet = Functions::flattenArray($valueSet);
|
||||
$order = ($order === null) ? self::RANK_SORT_DESCENDING : Functions::flattenSingleValue($order);
|
||||
|
||||
try {
|
||||
$value = StatisticalValidations::validateFloat($value);
|
||||
$order = StatisticalValidations::validateInt($order);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$valueSet = self::rankFilterValues($valueSet);
|
||||
if ($order === self::RANK_SORT_DESCENDING) {
|
||||
rsort($valueSet, SORT_NUMERIC);
|
||||
} else {
|
||||
sort($valueSet, SORT_NUMERIC);
|
||||
}
|
||||
|
||||
$pos = array_search($value, $valueSet);
|
||||
if ($pos === false) {
|
||||
return ExcelError::NA();
|
||||
}
|
||||
|
||||
return ++$pos;
|
||||
}
|
||||
|
||||
protected static function percentileFilterValues(array $dataSet): array
|
||||
{
|
||||
return array_filter(
|
||||
$dataSet,
|
||||
fn ($value): bool => is_numeric($value) && !is_string($value)
|
||||
);
|
||||
}
|
||||
|
||||
protected static function rankFilterValues(array $dataSet): array
|
||||
{
|
||||
return array_filter(
|
||||
$dataSet,
|
||||
fn ($value): bool => is_numeric($value)
|
||||
);
|
||||
}
|
||||
}
|
||||
99
lib/PhpSpreadsheet/Calculation/Statistical/Permutations.php
Normal file
99
lib/PhpSpreadsheet/Calculation/Statistical/Permutations.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\IntOrFloat;
|
||||
|
||||
class Permutations
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* PERMUT.
|
||||
*
|
||||
* Returns the number of permutations for a given number of objects that can be
|
||||
* selected from number objects. A permutation is any set or subset of objects or
|
||||
* events where internal order is significant. Permutations are different from
|
||||
* combinations, for which the internal order is not significant. Use this function
|
||||
* for lottery-style probability calculations.
|
||||
*
|
||||
* @param mixed $numObjs Integer number of different objects
|
||||
* Or can be an array of values
|
||||
* @param mixed $numInSet Integer number of objects in each permutation
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|int|string Number of permutations, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function PERMUT(mixed $numObjs, mixed $numInSet)
|
||||
{
|
||||
if (is_array($numObjs) || is_array($numInSet)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet);
|
||||
}
|
||||
|
||||
try {
|
||||
$numObjs = StatisticalValidations::validateInt($numObjs);
|
||||
$numInSet = StatisticalValidations::validateInt($numInSet);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($numObjs < $numInSet) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
$result1 = MathTrig\Factorial::fact($numObjs);
|
||||
if (is_string($result1)) {
|
||||
return $result1;
|
||||
}
|
||||
$result2 = MathTrig\Factorial::fact($numObjs - $numInSet);
|
||||
if (is_string($result2)) {
|
||||
return $result2;
|
||||
}
|
||||
// phpstan thinks result1 and result2 can be arrays; they can't.
|
||||
$result = round($result1 / $result2); // @phpstan-ignore-line
|
||||
|
||||
return IntOrFloat::evaluate($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* PERMUTATIONA.
|
||||
*
|
||||
* Returns the number of permutations for a given number of objects (with repetitions)
|
||||
* that can be selected from the total objects.
|
||||
*
|
||||
* @param mixed $numObjs Integer number of different objects
|
||||
* Or can be an array of values
|
||||
* @param mixed $numInSet Integer number of objects in each permutation
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|int|string Number of permutations, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function PERMUTATIONA(mixed $numObjs, mixed $numInSet)
|
||||
{
|
||||
if (is_array($numObjs) || is_array($numInSet)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet);
|
||||
}
|
||||
|
||||
try {
|
||||
$numObjs = StatisticalValidations::validateInt($numObjs);
|
||||
$numInSet = StatisticalValidations::validateInt($numInSet);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($numObjs < 0 || $numInSet < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
$result = $numObjs ** $numInSet;
|
||||
|
||||
return IntOrFloat::evaluate($result);
|
||||
}
|
||||
}
|
||||
97
lib/PhpSpreadsheet/Calculation/Statistical/Size.php
Normal file
97
lib/PhpSpreadsheet/Calculation/Statistical/Size.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Size
|
||||
{
|
||||
/**
|
||||
* LARGE.
|
||||
*
|
||||
* Returns the nth largest value in a data set. You can use this function to
|
||||
* select a value based on its relative standing.
|
||||
*
|
||||
* Excel Function:
|
||||
* LARGE(value1[,value2[, ...]],entry)
|
||||
*
|
||||
* @param mixed $args Data values
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function large(mixed ...$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 ($count === 0 || $entry < 0 || $entry >= $count) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
rsort($mArgs);
|
||||
|
||||
return $mArgs[$entry];
|
||||
}
|
||||
|
||||
return ExcelError::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(mixed ...$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 ($count === 0 || $entry < 0 || $entry >= $count) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
sort($mArgs);
|
||||
|
||||
return $mArgs[$entry];
|
||||
}
|
||||
|
||||
return ExcelError::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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
class StandardDeviations
|
||||
{
|
||||
/**
|
||||
* STDEV.
|
||||
*
|
||||
* Estimates standard deviation based on a sample. The standard deviation is a measure of how
|
||||
* widely values are dispersed from the average value (the mean).
|
||||
*
|
||||
* Excel Function:
|
||||
* STDEV(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function STDEV(mixed ...$args)
|
||||
{
|
||||
$result = Variances::VAR(...$args);
|
||||
if (!is_numeric($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return sqrt((float) $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* STDEVA.
|
||||
*
|
||||
* Estimates standard deviation based on a sample, including numbers, text, and logical values
|
||||
*
|
||||
* Excel Function:
|
||||
* STDEVA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function STDEVA(mixed ...$args): float|string
|
||||
{
|
||||
$result = Variances::VARA(...$args);
|
||||
if (!is_numeric($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return sqrt((float) $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* STDEVP.
|
||||
*
|
||||
* Calculates standard deviation based on the entire population
|
||||
*
|
||||
* Excel Function:
|
||||
* STDEVP(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function STDEVP(mixed ...$args): float|string
|
||||
{
|
||||
$result = Variances::VARP(...$args);
|
||||
if (!is_numeric($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return sqrt((float) $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* STDEVPA.
|
||||
*
|
||||
* Calculates standard deviation based on the entire population, including numbers, text, and logical values
|
||||
*
|
||||
* Excel Function:
|
||||
* STDEVPA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*/
|
||||
public static function STDEVPA(mixed ...$args): float|string
|
||||
{
|
||||
$result = Variances::VARPA(...$args);
|
||||
if (!is_numeric($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return sqrt((float) $result);
|
||||
}
|
||||
}
|
||||
49
lib/PhpSpreadsheet/Calculation/Statistical/Standardize.php
Normal file
49
lib/PhpSpreadsheet/Calculation/Statistical/Standardize.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Standardize extends StatisticalValidations
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* STANDARDIZE.
|
||||
*
|
||||
* Returns a normalized value from a distribution characterized by mean and standard_dev.
|
||||
*
|
||||
* @param array|float $value Value to normalize
|
||||
* Or can be an array of values
|
||||
* @param array|float $mean Mean Value
|
||||
* Or can be an array of values
|
||||
* @param array|float $stdDev Standard Deviation
|
||||
* Or can be an array of values
|
||||
*
|
||||
* @return array|float|string Standardized value, or a string containing an error
|
||||
* If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function execute($value, $mean, $stdDev): array|string|float
|
||||
{
|
||||
if (is_array($value) || is_array($mean) || is_array($stdDev)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev);
|
||||
}
|
||||
|
||||
try {
|
||||
$value = self::validateFloat($value);
|
||||
$mean = self::validateFloat($mean);
|
||||
$stdDev = self::validateFloat($stdDev);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($stdDev <= 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
return ($value - $mean) / $stdDev;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class StatisticalValidations
|
||||
{
|
||||
public static function validateFloat(mixed $value): float
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
|
||||
public static function validateInt(mixed $value): int
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (int) floor((float) $value);
|
||||
}
|
||||
|
||||
public static function validateBool(mixed $value): bool
|
||||
{
|
||||
if (!is_bool($value) && !is_numeric($value)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (bool) $value;
|
||||
}
|
||||
}
|
||||
425
lib/PhpSpreadsheet/Calculation/Statistical/Trends.php
Normal file
425
lib/PhpSpreadsheet/Calculation/Statistical/Trends.php
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend;
|
||||
|
||||
class Trends
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
private static function filterTrendValues(array &$array1, array &$array2): void
|
||||
{
|
||||
foreach ($array1 as $key => $value) {
|
||||
if ((is_bool($value)) || (is_string($value)) || ($value === null)) {
|
||||
unset($array1[$key], $array2[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $array1 should be array, but scalar is made into one
|
||||
* @param mixed $array2 should be array, but scalar is made into one
|
||||
*/
|
||||
private static function checkTrendArrays(mixed &$array1, mixed &$array2): void
|
||||
{
|
||||
if (!is_array($array1)) {
|
||||
$array1 = [$array1];
|
||||
}
|
||||
if (!is_array($array2)) {
|
||||
$array2 = [$array2];
|
||||
}
|
||||
|
||||
$array1 = Functions::flattenArray($array1);
|
||||
$array2 = Functions::flattenArray($array2);
|
||||
|
||||
self::filterTrendValues($array1, $array2);
|
||||
self::filterTrendValues($array2, $array1);
|
||||
|
||||
// Reset the array indexes
|
||||
$array1 = array_merge($array1);
|
||||
$array2 = array_merge($array2);
|
||||
}
|
||||
|
||||
protected static function validateTrendArrays(array $yValues, array $xValues): void
|
||||
{
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount === 0) || ($yValueCount !== $xValueCount)) {
|
||||
throw new Exception(ExcelError::NA());
|
||||
} elseif ($yValueCount === 1) {
|
||||
throw new Exception(ExcelError::DIV0());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CORREL.
|
||||
*
|
||||
* Returns covariance, the average of the products of deviations for each data point pair.
|
||||
*
|
||||
* @param mixed $yValues array of mixed Data Series Y
|
||||
* @param null|mixed $xValues array of mixed Data Series X
|
||||
*/
|
||||
public static function CORREL(mixed $yValues, $xValues = null): float|string
|
||||
{
|
||||
if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getCorrelation();
|
||||
}
|
||||
|
||||
/**
|
||||
* COVAR.
|
||||
*
|
||||
* Returns covariance, the average of the products of deviations for each data point pair.
|
||||
*
|
||||
* @param mixed[] $yValues array of mixed Data Series Y
|
||||
* @param mixed[] $xValues array of mixed Data Series X
|
||||
*/
|
||||
public static function COVAR(array $yValues, array $xValues): float|string
|
||||
{
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getCovariance();
|
||||
}
|
||||
|
||||
/**
|
||||
* FORECAST.
|
||||
*
|
||||
* Calculates, or predicts, a future value by using existing values.
|
||||
* The predicted value is a y-value for a given x-value.
|
||||
*
|
||||
* @param mixed $xValue Float value of X for which we want to find Y
|
||||
* Or can be an array of values
|
||||
* @param mixed[] $yValues array of mixed Data Series Y
|
||||
* @param mixed[] $xValues array of mixed Data Series X
|
||||
*
|
||||
* @return array|bool|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
|
||||
* with the same dimensions
|
||||
*/
|
||||
public static function FORECAST(mixed $xValue, array $yValues, array $xValues)
|
||||
{
|
||||
if (is_array($xValue)) {
|
||||
return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $xValue, $yValues, $xValues);
|
||||
}
|
||||
|
||||
try {
|
||||
$xValue = StatisticalValidations::validateFloat($xValue);
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getValueOfYForX($xValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* GROWTH.
|
||||
*
|
||||
* Returns values along a predicted exponential Trend
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
* @param mixed[] $newValues Values of X for which we want to find Y
|
||||
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
|
||||
*
|
||||
* @return float[]
|
||||
*/
|
||||
public static function GROWTH(array $yValues, array $xValues = [], array $newValues = [], mixed $const = true): array
|
||||
{
|
||||
$yValues = Functions::flattenArray($yValues);
|
||||
$xValues = Functions::flattenArray($xValues);
|
||||
$newValues = Functions::flattenArray($newValues);
|
||||
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
|
||||
|
||||
$bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
|
||||
if (empty($newValues)) {
|
||||
$newValues = $bestFitExponential->getXValues();
|
||||
}
|
||||
|
||||
$returnArray = [];
|
||||
foreach ($newValues as $xValue) {
|
||||
$returnArray[0][] = [$bestFitExponential->getValueOfYForX($xValue)];
|
||||
}
|
||||
|
||||
return $returnArray; //* @phpstan-ignore-line
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERCEPT.
|
||||
*
|
||||
* Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values.
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
*/
|
||||
public static function INTERCEPT(array $yValues, array $xValues): float|string
|
||||
{
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getIntersect();
|
||||
}
|
||||
|
||||
/**
|
||||
* LINEST.
|
||||
*
|
||||
* Calculates the statistics for a line by using the "least squares" method to calculate a straight line
|
||||
* that best fits your data, and then returns an array that describes the line.
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param null|mixed[] $xValues Data Series X
|
||||
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
|
||||
* @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics
|
||||
*
|
||||
* @return array|string The result, or a string containing an error
|
||||
*/
|
||||
public static function LINEST(array $yValues, ?array $xValues = null, mixed $const = true, mixed $stats = false): string|array
|
||||
{
|
||||
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
|
||||
$stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
|
||||
if ($xValues === null) {
|
||||
$xValues = $yValues;
|
||||
}
|
||||
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const);
|
||||
|
||||
if ($stats === true) {
|
||||
return [
|
||||
[
|
||||
$bestFitLinear->getSlope(),
|
||||
$bestFitLinear->getIntersect(),
|
||||
],
|
||||
[
|
||||
$bestFitLinear->getSlopeSE(),
|
||||
($const === false) ? ExcelError::NA() : $bestFitLinear->getIntersectSE(),
|
||||
],
|
||||
[
|
||||
$bestFitLinear->getGoodnessOfFit(),
|
||||
$bestFitLinear->getStdevOfResiduals(),
|
||||
],
|
||||
[
|
||||
$bestFitLinear->getF(),
|
||||
$bestFitLinear->getDFResiduals(),
|
||||
],
|
||||
[
|
||||
$bestFitLinear->getSSRegression(),
|
||||
$bestFitLinear->getSSResiduals(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
$bestFitLinear->getSlope(),
|
||||
$bestFitLinear->getIntersect(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* LOGEST.
|
||||
*
|
||||
* Calculates an exponential curve that best fits the X and Y data series,
|
||||
* and then returns an array that describes the line.
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param null|mixed[] $xValues Data Series X
|
||||
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
|
||||
* @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics
|
||||
*
|
||||
* @return array|string The result, or a string containing an error
|
||||
*/
|
||||
public static function LOGEST(array $yValues, ?array $xValues = null, mixed $const = true, mixed $stats = false): string|array
|
||||
{
|
||||
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
|
||||
$stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
|
||||
if ($xValues === null) {
|
||||
$xValues = $yValues;
|
||||
}
|
||||
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
foreach ($yValues as $value) {
|
||||
if ($value < 0.0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
}
|
||||
|
||||
$bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
|
||||
|
||||
if ($stats === true) {
|
||||
return [
|
||||
[
|
||||
$bestFitExponential->getSlope(),
|
||||
$bestFitExponential->getIntersect(),
|
||||
],
|
||||
[
|
||||
$bestFitExponential->getSlopeSE(),
|
||||
($const === false) ? ExcelError::NA() : $bestFitExponential->getIntersectSE(),
|
||||
],
|
||||
[
|
||||
$bestFitExponential->getGoodnessOfFit(),
|
||||
$bestFitExponential->getStdevOfResiduals(),
|
||||
],
|
||||
[
|
||||
$bestFitExponential->getF(),
|
||||
$bestFitExponential->getDFResiduals(),
|
||||
],
|
||||
[
|
||||
$bestFitExponential->getSSRegression(),
|
||||
$bestFitExponential->getSSResiduals(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
$bestFitExponential->getSlope(),
|
||||
$bestFitExponential->getIntersect(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* RSQ.
|
||||
*
|
||||
* Returns the square of the Pearson product moment correlation coefficient through data points
|
||||
* in known_y's and known_x's.
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function RSQ(array $yValues, array $xValues)
|
||||
{
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getGoodnessOfFit();
|
||||
}
|
||||
|
||||
/**
|
||||
* SLOPE.
|
||||
*
|
||||
* Returns the slope of the linear regression line through data points in known_y's and known_x's.
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
*
|
||||
* @return float|string The result, or a string containing an error
|
||||
*/
|
||||
public static function SLOPE(array $yValues, array $xValues)
|
||||
{
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getSlope();
|
||||
}
|
||||
|
||||
/**
|
||||
* STEYX.
|
||||
*
|
||||
* Returns the standard error of the predicted y-value for each x in the regression.
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
*/
|
||||
public static function STEYX(array $yValues, array $xValues): float|string
|
||||
{
|
||||
try {
|
||||
self::checkTrendArrays($yValues, $xValues);
|
||||
self::validateTrendArrays($yValues, $xValues);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getStdevOfResiduals();
|
||||
}
|
||||
|
||||
/**
|
||||
* TREND.
|
||||
*
|
||||
* Returns values along a linear Trend
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
* @param mixed[] $newValues Values of X for which we want to find Y
|
||||
* @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not
|
||||
*
|
||||
* @return float[]
|
||||
*/
|
||||
public static function TREND(array $yValues, array $xValues = [], array $newValues = [], mixed $const = true): array
|
||||
{
|
||||
$yValues = Functions::flattenArray($yValues);
|
||||
$xValues = Functions::flattenArray($xValues);
|
||||
$newValues = Functions::flattenArray($newValues);
|
||||
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const);
|
||||
if (empty($newValues)) {
|
||||
$newValues = $bestFitLinear->getXValues();
|
||||
}
|
||||
|
||||
$returnArray = [];
|
||||
foreach ($newValues as $xValue) {
|
||||
$returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)];
|
||||
}
|
||||
|
||||
return $returnArray; //* @phpstan-ignore-line
|
||||
}
|
||||
}
|
||||
28
lib/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php
Normal file
28
lib/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
abstract class VarianceBase
|
||||
{
|
||||
protected static function datatypeAdjustmentAllowStrings(mixed $value): mixed
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return (int) $value;
|
||||
} elseif (is_string($value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected static function datatypeAdjustmentBooleans(mixed $value): mixed
|
||||
{
|
||||
if (is_bool($value) && (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
186
lib/PhpSpreadsheet/Calculation/Statistical/Variances.php
Normal file
186
lib/PhpSpreadsheet/Calculation/Statistical/Variances.php
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Variances extends VarianceBase
|
||||
{
|
||||
/**
|
||||
* VAR.
|
||||
*
|
||||
* Estimates variance based on a sample.
|
||||
*
|
||||
* Excel Function:
|
||||
* VAR(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
*/
|
||||
public static function VAR(mixed ...$args): float|string
|
||||
{
|
||||
$returnValue = ExcelError::DIV0();
|
||||
|
||||
$summerA = $summerB = 0.0;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
$aCount = 0;
|
||||
foreach ($aArgs as $arg) {
|
||||
$arg = self::datatypeAdjustmentBooleans($arg);
|
||||
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) && (!is_string($arg))) {
|
||||
$summerA += ($arg * $arg);
|
||||
$summerB += $arg;
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aCount > 1) {
|
||||
$summerA *= $aCount;
|
||||
$summerB *= $summerB;
|
||||
|
||||
return ($summerA - $summerB) / ($aCount * ($aCount - 1));
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* VARA.
|
||||
*
|
||||
* Estimates variance based on a sample, including numbers, text, and logical values
|
||||
*
|
||||
* Excel Function:
|
||||
* VARA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
*/
|
||||
public static function VARA(mixed ...$args): string|float
|
||||
{
|
||||
$returnValue = ExcelError::DIV0();
|
||||
|
||||
$summerA = $summerB = 0.0;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
$aCount = 0;
|
||||
foreach ($aArgs as $k => $arg) {
|
||||
if ((is_string($arg)) && (Functions::isValue($k))) {
|
||||
return ExcelError::VALUE();
|
||||
} elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) {
|
||||
} else {
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
|
||||
$arg = self::datatypeAdjustmentAllowStrings($arg);
|
||||
$summerA += ($arg * $arg);
|
||||
$summerB += $arg;
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($aCount > 1) {
|
||||
$summerA *= $aCount;
|
||||
$summerB *= $summerB;
|
||||
|
||||
return ($summerA - $summerB) / ($aCount * ($aCount - 1));
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* VARP.
|
||||
*
|
||||
* Calculates variance based on the entire population
|
||||
*
|
||||
* Excel Function:
|
||||
* VARP(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
*/
|
||||
public static function VARP(mixed ...$args): float|string
|
||||
{
|
||||
// Return value
|
||||
$returnValue = ExcelError::DIV0();
|
||||
|
||||
$summerA = $summerB = 0.0;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
$aCount = 0;
|
||||
foreach ($aArgs as $arg) {
|
||||
$arg = self::datatypeAdjustmentBooleans($arg);
|
||||
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) && (!is_string($arg))) {
|
||||
$summerA += ($arg * $arg);
|
||||
$summerB += $arg;
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
|
||||
if ($aCount > 0) {
|
||||
$summerA *= $aCount;
|
||||
$summerB *= $summerB;
|
||||
|
||||
return ($summerA - $summerB) / ($aCount * $aCount);
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* VARPA.
|
||||
*
|
||||
* Calculates variance based on the entire population, including numbers, text, and logical values
|
||||
*
|
||||
* Excel Function:
|
||||
* VARPA(value1[,value2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
*/
|
||||
public static function VARPA(mixed ...$args): string|float
|
||||
{
|
||||
$returnValue = ExcelError::DIV0();
|
||||
|
||||
$summerA = $summerB = 0.0;
|
||||
|
||||
// Loop through arguments
|
||||
$aArgs = Functions::flattenArrayIndexed($args);
|
||||
$aCount = 0;
|
||||
foreach ($aArgs as $k => $arg) {
|
||||
if ((is_string($arg)) && (Functions::isValue($k))) {
|
||||
return ExcelError::VALUE();
|
||||
} elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) {
|
||||
} else {
|
||||
// Is it a numeric value?
|
||||
if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
|
||||
$arg = self::datatypeAdjustmentAllowStrings($arg);
|
||||
$summerA += ($arg * $arg);
|
||||
$summerB += $arg;
|
||||
++$aCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($aCount > 0) {
|
||||
$summerA *= $aCount;
|
||||
$summerB *= $summerB;
|
||||
|
||||
return ($summerA - $summerB) / ($aCount * $aCount);
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue