Extract Percentile-type functions from Statistics (#1966)
* Extract Percentile-type functions from Statistics (e.g. PERCENTILE(), PERCENTRANK(), QUARTILE(), and RANK()) * Unit test for PERCENTILE() with an empty (of numbers) dataset
This commit is contained in:
parent
b87f93f824
commit
1c92b7611a
|
|
@ -1903,7 +1903,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'PERCENTILE' => [
|
'PERCENTILE' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'PERCENTILE'],
|
'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'],
|
||||||
'argumentCount' => '2',
|
'argumentCount' => '2',
|
||||||
],
|
],
|
||||||
'PERCENTILE.EXC' => [
|
'PERCENTILE.EXC' => [
|
||||||
|
|
@ -1913,12 +1913,12 @@ class Calculation
|
||||||
],
|
],
|
||||||
'PERCENTILE.INC' => [
|
'PERCENTILE.INC' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'PERCENTILE'],
|
'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'],
|
||||||
'argumentCount' => '2',
|
'argumentCount' => '2',
|
||||||
],
|
],
|
||||||
'PERCENTRANK' => [
|
'PERCENTRANK' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'PERCENTRANK'],
|
'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'],
|
||||||
'argumentCount' => '2,3',
|
'argumentCount' => '2,3',
|
||||||
],
|
],
|
||||||
'PERCENTRANK.EXC' => [
|
'PERCENTRANK.EXC' => [
|
||||||
|
|
@ -1928,7 +1928,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'PERCENTRANK.INC' => [
|
'PERCENTRANK.INC' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'PERCENTRANK'],
|
'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'],
|
||||||
'argumentCount' => '2,3',
|
'argumentCount' => '2,3',
|
||||||
],
|
],
|
||||||
'PERMUT' => [
|
'PERMUT' => [
|
||||||
|
|
@ -2018,7 +2018,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'QUARTILE' => [
|
'QUARTILE' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'QUARTILE'],
|
'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'],
|
||||||
'argumentCount' => '2',
|
'argumentCount' => '2',
|
||||||
],
|
],
|
||||||
'QUARTILE.EXC' => [
|
'QUARTILE.EXC' => [
|
||||||
|
|
@ -2028,7 +2028,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'QUARTILE.INC' => [
|
'QUARTILE.INC' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'QUARTILE'],
|
'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'],
|
||||||
'argumentCount' => '2',
|
'argumentCount' => '2',
|
||||||
],
|
],
|
||||||
'QUOTIENT' => [
|
'QUOTIENT' => [
|
||||||
|
|
@ -2058,7 +2058,7 @@ class Calculation
|
||||||
],
|
],
|
||||||
'RANK' => [
|
'RANK' => [
|
||||||
'category' => Category::CATEGORY_STATISTICAL,
|
'category' => Category::CATEGORY_STATISTICAL,
|
||||||
'functionCall' => [Statistical::class, 'RANK'],
|
'functionCall' => [Statistical\Percentiles::class, 'RANK'],
|
||||||
'argumentCount' => '2,3',
|
'argumentCount' => '2,3',
|
||||||
],
|
],
|
||||||
'RANK.AVG' => [
|
'RANK.AVG' => [
|
||||||
|
|
|
||||||
|
|
@ -1665,45 +1665,18 @@ class Statistical
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* PERCENTILE(value1[,value2[, ...]],entry)
|
* PERCENTILE(value1[,value2[, ...]],entry)
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Statistical\Percentiles::PERCENTILE()
|
||||||
|
* Use the PERCENTILE() method in the Statistical\Percentiles class instead
|
||||||
|
*
|
||||||
* @param mixed $args Data values
|
* @param mixed $args Data values
|
||||||
*
|
*
|
||||||
* @return float|string The result, or a string containing an error
|
* @return float|string The result, or a string containing an error
|
||||||
*/
|
*/
|
||||||
public static function PERCENTILE(...$args)
|
public static function PERCENTILE(...$args)
|
||||||
{
|
{
|
||||||
$aArgs = Functions::flattenArray($args);
|
return Statistical\Percentiles::PERCENTILE(...$args);
|
||||||
|
|
||||||
// Calculate
|
|
||||||
$entry = array_pop($aArgs);
|
|
||||||
|
|
||||||
if ((is_numeric($entry)) && (!is_string($entry))) {
|
|
||||||
if (($entry < 0) || ($entry > 1)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
$mArgs = [];
|
|
||||||
foreach ($aArgs as $arg) {
|
|
||||||
// Is it a numeric value?
|
|
||||||
if ((is_numeric($arg)) && (!is_string($arg))) {
|
|
||||||
$mArgs[] = $arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$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 Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1714,6 +1687,11 @@ class Statistical
|
||||||
* rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return
|
* 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
|
* 0.667 rather than 0.666
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Statistical\Percentiles::PERCENTRANK()
|
||||||
|
* Use the PERCENTRANK() method in the Statistical\Percentiles class instead
|
||||||
|
*
|
||||||
* @param mixed (float[]) $valueSet An array of, or a reference to, a list of numbers
|
* @param mixed (float[]) $valueSet An array of, or a reference to, a list of numbers
|
||||||
* @param mixed (int) $value the number whose rank you want to find
|
* @param mixed (int) $value the number whose rank you want to find
|
||||||
* @param mixed (int) $significance the number of significant digits for the returned percentage value
|
* @param mixed (int) $significance the number of significant digits for the returned percentage value
|
||||||
|
|
@ -1722,38 +1700,7 @@ class Statistical
|
||||||
*/
|
*/
|
||||||
public static function PERCENTRANK($valueSet, $value, $significance = 3)
|
public static function PERCENTRANK($valueSet, $value, $significance = 3)
|
||||||
{
|
{
|
||||||
$valueSet = Functions::flattenArray($valueSet);
|
return Statistical\Percentiles::PERCENTRANK($valueSet, $value, $significance);
|
||||||
$value = Functions::flattenSingleValue($value);
|
|
||||||
$significance = ($significance === null) ? 3 : (int) Functions::flattenSingleValue($significance);
|
|
||||||
|
|
||||||
foreach ($valueSet as $key => $valueEntry) {
|
|
||||||
if (!is_numeric($valueEntry)) {
|
|
||||||
unset($valueSet[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort($valueSet, SORT_NUMERIC);
|
|
||||||
$valueCount = count($valueSet);
|
|
||||||
if ($valueCount == 0) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
$valueAdjustor = $valueCount - 1;
|
|
||||||
if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) {
|
|
||||||
return Functions::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($pos / $valueAdjustor, $significance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1811,27 +1758,18 @@ class Statistical
|
||||||
* Excel Function:
|
* Excel Function:
|
||||||
* QUARTILE(value1[,value2[, ...]],entry)
|
* QUARTILE(value1[,value2[, ...]],entry)
|
||||||
*
|
*
|
||||||
|
* @Deprecated 1.18.0
|
||||||
|
*
|
||||||
|
* @see Statistical\Percentiles::QUARTILE()
|
||||||
|
* Use the QUARTILE() method in the Statistical\Percentiles class instead
|
||||||
|
*
|
||||||
* @param mixed $args Data values
|
* @param mixed $args Data values
|
||||||
*
|
*
|
||||||
* @return float|string The result, or a string containing an error
|
* @return float|string The result, or a string containing an error
|
||||||
*/
|
*/
|
||||||
public static function QUARTILE(...$args)
|
public static function QUARTILE(...$args)
|
||||||
{
|
{
|
||||||
$aArgs = Functions::flattenArray($args);
|
return Statistical\Percentiles::QUARTILE(...$args);
|
||||||
$entry = array_pop($aArgs);
|
|
||||||
|
|
||||||
// Calculate
|
|
||||||
if ((is_numeric($entry)) && (!is_string($entry))) {
|
|
||||||
$entry = floor($entry);
|
|
||||||
$entry /= 4;
|
|
||||||
if (($entry < 0) || ($entry > 1)) {
|
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::PERCENTILE($aArgs, $entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1839,36 +1777,20 @@ class Statistical
|
||||||
*
|
*
|
||||||
* Returns the rank of a number in a list of numbers.
|
* Returns the rank of a number in a list of numbers.
|
||||||
*
|
*
|
||||||
* @param int $value the number whose rank you want to find
|
* @Deprecated 1.18.0
|
||||||
* @param float[] $valueSet An array of, or a reference to, a list of numbers
|
*
|
||||||
* @param int $order Order to sort the values in the value set
|
* @see Statistical\Percentiles::RANK()
|
||||||
|
* Use the RANK() method in the Statistical\Percentiles class instead
|
||||||
|
*
|
||||||
|
* @param mixed (float) $value the number whose rank you want to find
|
||||||
|
* @param mixed (float[]) $valueSet An array of, or a reference to, a list of numbers
|
||||||
|
* @param mixed (int) $order Order to sort the values in the value set
|
||||||
*
|
*
|
||||||
* @return float|string The result, or a string containing an error
|
* @return float|string The result, or a string containing an error
|
||||||
*/
|
*/
|
||||||
public static function RANK($value, $valueSet, $order = 0)
|
public static function RANK($value, $valueSet, $order = 0)
|
||||||
{
|
{
|
||||||
$value = Functions::flattenSingleValue($value);
|
return Statistical\Percentiles::RANK($value, $valueSet, $order);
|
||||||
$valueSet = Functions::flattenArray($valueSet);
|
|
||||||
$order = ($order === null) ? 0 : (int) Functions::flattenSingleValue($order);
|
|
||||||
|
|
||||||
foreach ($valueSet as $key => $valueEntry) {
|
|
||||||
if (!is_numeric($valueEntry)) {
|
|
||||||
unset($valueSet[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($order == 0) {
|
|
||||||
sort($valueSet, SORT_NUMERIC);
|
|
||||||
} else {
|
|
||||||
rsort($valueSet, SORT_NUMERIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
$pos = array_search($value, $valueSet);
|
|
||||||
if ($pos === false) {
|
|
||||||
return Functions::NA();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ++$pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
|
||||||
|
trait BaseValidations
|
||||||
|
{
|
||||||
|
protected static function validateFloat($value): float
|
||||||
|
{
|
||||||
|
if (!is_numeric($value)) {
|
||||||
|
throw new Exception(Functions::VALUE());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function validateInt($value): int
|
||||||
|
{
|
||||||
|
if (!is_numeric($value)) {
|
||||||
|
throw new Exception(Functions::VALUE());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) floor($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||||
|
|
||||||
class Confidence
|
class Confidence
|
||||||
{
|
{
|
||||||
|
use BaseValidations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CONFIDENCE.
|
* CONFIDENCE.
|
||||||
*
|
*
|
||||||
|
|
@ -24,18 +27,18 @@ class Confidence
|
||||||
$stdDev = Functions::flattenSingleValue($stdDev);
|
$stdDev = Functions::flattenSingleValue($stdDev);
|
||||||
$size = Functions::flattenSingleValue($size);
|
$size = Functions::flattenSingleValue($size);
|
||||||
|
|
||||||
if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) {
|
try {
|
||||||
$size = floor($size);
|
$alpha = self::validateFloat($alpha);
|
||||||
if (($alpha <= 0) || ($alpha >= 1)) {
|
$stdDev = self::validateFloat($stdDev);
|
||||||
return Functions::NAN();
|
$size = self::validateInt($size);
|
||||||
}
|
} catch (Exception $e) {
|
||||||
if (($stdDev <= 0) || ($size < 1)) {
|
return $e->getMessage();
|
||||||
return Functions::NAN();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Statistical::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Functions::VALUE();
|
if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Statistical::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
|
|
||||||
|
class Percentiles
|
||||||
|
{
|
||||||
|
use BaseValidations;
|
||||||
|
|
||||||
|
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(...$args)
|
||||||
|
{
|
||||||
|
$aArgs = Functions::flattenArray($args);
|
||||||
|
|
||||||
|
// Calculate
|
||||||
|
$entry = array_pop($aArgs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$entry = self::validateFloat($entry);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($entry < 0) || ($entry > 1)) {
|
||||||
|
return Functions::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 Functions::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 (float[]) $valueSet An array of, or a reference to, a list of numbers
|
||||||
|
* @param mixed (int) $value the number whose rank you want to find
|
||||||
|
* @param mixed (int) $significance the number of significant digits for the returned percentage value
|
||||||
|
*
|
||||||
|
* @return float|string (string if result is an error)
|
||||||
|
*/
|
||||||
|
public static function PERCENTRANK($valueSet, $value, $significance = 3)
|
||||||
|
{
|
||||||
|
$valueSet = Functions::flattenArray($valueSet);
|
||||||
|
$value = Functions::flattenSingleValue($value);
|
||||||
|
$significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$value = self::validateFloat($value);
|
||||||
|
$significance = self::validateInt($significance);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$valueSet = self::rankFilterValues($valueSet);
|
||||||
|
$valueCount = count($valueSet);
|
||||||
|
if ($valueCount == 0) {
|
||||||
|
return Functions::NA();
|
||||||
|
}
|
||||||
|
sort($valueSet, SORT_NUMERIC);
|
||||||
|
|
||||||
|
$valueAdjustor = $valueCount - 1;
|
||||||
|
if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) {
|
||||||
|
return Functions::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($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(...$args)
|
||||||
|
{
|
||||||
|
$aArgs = Functions::flattenArray($args);
|
||||||
|
$entry = array_pop($aArgs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$entry = self::validateFloat($entry);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry = floor($entry);
|
||||||
|
$entry /= 4;
|
||||||
|
if (($entry < 0) || ($entry > 1)) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::PERCENTILE($aArgs, $entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RANK.
|
||||||
|
*
|
||||||
|
* Returns the rank of a number in a list of numbers.
|
||||||
|
*
|
||||||
|
* @param mixed (float) $value the number whose rank you want to find
|
||||||
|
* @param mixed (float[]) $valueSet An array of, or a reference to, a list of numbers
|
||||||
|
* @param mixed (int) $order Order to sort the values in the value set
|
||||||
|
*
|
||||||
|
* @return float|string The result, or a string containing an error
|
||||||
|
*/
|
||||||
|
public static function RANK($value, $valueSet, $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 = self::validateFloat($value);
|
||||||
|
$order = self::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 Functions::NA();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ++$pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function percentileFilterValues(array $dataSet)
|
||||||
|
{
|
||||||
|
return array_filter(
|
||||||
|
$dataSet,
|
||||||
|
function ($value): bool {
|
||||||
|
return is_numeric($value) && !is_string($value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function rankFilterValues(array $dataSet)
|
||||||
|
{
|
||||||
|
return array_filter(
|
||||||
|
$dataSet,
|
||||||
|
function ($value): bool {
|
||||||
|
return is_numeric($value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||||
|
|
||||||
class Permutations
|
class Permutations
|
||||||
{
|
{
|
||||||
|
use BaseValidations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PERMUT.
|
* PERMUT.
|
||||||
*
|
*
|
||||||
|
|
@ -26,16 +29,18 @@ class Permutations
|
||||||
$numObjs = Functions::flattenSingleValue($numObjs);
|
$numObjs = Functions::flattenSingleValue($numObjs);
|
||||||
$numInSet = Functions::flattenSingleValue($numInSet);
|
$numInSet = Functions::flattenSingleValue($numInSet);
|
||||||
|
|
||||||
if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
|
try {
|
||||||
$numInSet = floor($numInSet);
|
$numObjs = self::validateInt($numObjs);
|
||||||
if ($numObjs < $numInSet) {
|
$numInSet = self::validateInt($numInSet);
|
||||||
return Functions::NAN();
|
} catch (Exception $e) {
|
||||||
}
|
return $e->getMessage();
|
||||||
|
|
||||||
return round(MathTrig\Fact::funcFact($numObjs) / MathTrig\Fact::funcFact($numObjs - $numInSet));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Functions::VALUE();
|
if ($numObjs < $numInSet) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return round(MathTrig\Fact::funcFact($numObjs) / MathTrig\Fact::funcFact($numObjs - $numInSet));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,16 +59,17 @@ class Permutations
|
||||||
$numObjs = Functions::flattenSingleValue($numObjs);
|
$numObjs = Functions::flattenSingleValue($numObjs);
|
||||||
$numInSet = Functions::flattenSingleValue($numInSet);
|
$numInSet = Functions::flattenSingleValue($numInSet);
|
||||||
|
|
||||||
if ((is_numeric($numObjs)) && (is_numeric($numInSet))) {
|
try {
|
||||||
$numObjs = floor($numObjs);
|
$numObjs = self::validateInt($numObjs);
|
||||||
$numInSet = floor($numInSet);
|
$numInSet = self::validateInt($numInSet);
|
||||||
if ($numObjs < 0 || $numInSet < 0) {
|
} catch (Exception $e) {
|
||||||
return Functions::NAN();
|
return $e->getMessage();
|
||||||
}
|
|
||||||
|
|
||||||
return $numObjs ** $numInSet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Functions::VALUE();
|
if ($numObjs < 0 || $numInSet < 0) {
|
||||||
|
return Functions::NAN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $numObjs ** $numInSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend;
|
||||||
|
|
||||||
class Trends
|
class Trends
|
||||||
{
|
{
|
||||||
|
use BaseValidations;
|
||||||
|
|
||||||
private static function filterTrendValues(array &$array1, array &$array2): void
|
private static function filterTrendValues(array &$array1, array &$array2): void
|
||||||
{
|
{
|
||||||
foreach ($array1 as $key => $value) {
|
foreach ($array1 as $key => $value) {
|
||||||
|
|
@ -116,11 +118,9 @@ class Trends
|
||||||
public static function FORECAST($xValue, $yValues, $xValues)
|
public static function FORECAST($xValue, $yValues, $xValues)
|
||||||
{
|
{
|
||||||
$xValue = Functions::flattenSingleValue($xValue);
|
$xValue = Functions::flattenSingleValue($xValue);
|
||||||
if (!is_numeric($xValue)) {
|
|
||||||
return Functions::VALUE();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$xValue = self::validateFloat($xValue);
|
||||||
self::checkTrendArrays($yValues, $xValues);
|
self::checkTrendArrays($yValues, $xValues);
|
||||||
self::validateTrendArrays($yValues, $xValues);
|
self::validateTrendArrays($yValues, $xValues);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,34 @@ return [
|
||||||
48.4,
|
48.4,
|
||||||
[10.5, 7.2, 200, 5.4, 8.1, 0.8],
|
[10.5, 7.2, 200, 5.4, 8.1, 0.8],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
[2, 1, 6, 4, 3, 5, 0.2],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4,
|
||||||
|
[2, 1, 6, 4, 3, 5, 0.6],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.5,
|
||||||
|
[2, 1, 6, 4, 3, 5, 0.5],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5.75,
|
||||||
|
[2, 1, 6, 4, 3, 5, 0.95],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
[1, 2, 3, 4, -0.3],
|
[1, 2, 3, 4, -0.3],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[1, 2, 3, 4, 1.5],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
['A', 'B', 0.5],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#VALUE!',
|
'#VALUE!',
|
||||||
[1, 2, 3, 4, 'NaN'],
|
[1, 2, 3, 4, 'NaN'],
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,15 @@ return [
|
||||||
2,
|
2,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'#NUM!',
|
'#VALUE!',
|
||||||
['A', 'B', 'C', 'D'],
|
['A', 'B', 'C', 'D'],
|
||||||
'E',
|
'E',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#N/A',
|
||||||
|
['A', 'B', 'C', 'D'],
|
||||||
|
3,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#N/A',
|
'#N/A',
|
||||||
[1, 2, 3, 4],
|
[1, 2, 3, 4],
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,14 @@ return [
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
-1, 2,
|
-1, 2,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
1, -2,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
'NaN', 31,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#VALUE!',
|
'#VALUE!',
|
||||||
49, 'NaN',
|
49, 'NaN',
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ return [
|
||||||
9.25,
|
9.25,
|
||||||
[7, 8, 9, 10, 3],
|
[7, 8, 9, 10, 3],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#NUM!',
|
||||||
|
[7, 8, 9, 10, -1],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#NUM!',
|
'#NUM!',
|
||||||
[7, 8, 9, 10, 5],
|
[7, 8, 9, 10, 5],
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,53 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
3.5,
|
||||||
|
[7, 3.5, 3.5, 1, 2],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
7,
|
||||||
|
[7, 3.5, 3.5, 1, 2],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
3,
|
3,
|
||||||
3.5,
|
3.5,
|
||||||
[7, 3.5, 3.5, 1, 2],
|
[7, 3.5, 3.5, 1, 2],
|
||||||
|
1,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
5,
|
5,
|
||||||
7,
|
7,
|
||||||
[7, 3.5, 3.5, 1, 2],
|
[7, 3.5, 3.5, 1, 2],
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
3.5,
|
|
||||||
[7, 3.5, 3.5, 1, 2],
|
|
||||||
1,
|
1,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
1,
|
4,
|
||||||
7,
|
|
||||||
[7, 3.5, 3.5, 1, 2],
|
|
||||||
1,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
7.2,
|
7.2,
|
||||||
[10.5, 7.2, 200, 5.4, 8.1],
|
[10.5, 7.2, 200, 5.4, 8.1],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
3,
|
2,
|
||||||
3.5,
|
3.5,
|
||||||
[7, 3.5, 'ONE', 'UNO', 3.5, 1, 2],
|
[7, 3.5, 'ONE', 'UNO', 3.5, 1, 2],
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'#VALUE!',
|
||||||
|
'Uno',
|
||||||
|
[1, 'Deux', 2, 'Uno', 3, 4],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
[1, 'Deux', 2, 'Uno', 3, 4],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
[1, 'Deux', 2, 'Uno', 3, 4],
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'#N/A',
|
'#N/A',
|
||||||
1.5,
|
1.5,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue