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:
Mark Baker 2021-03-29 12:59:46 +02:00 committed by GitHub
parent b87f93f824
commit 1c92b7611a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 378 additions and 157 deletions

View File

@ -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' => [

View File

@ -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;
} }
/** /**

View File

@ -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);
}
}

View File

@ -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) {
return $e->getMessage();
} }
if (($stdDev <= 0) || ($size < 1)) {
if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) {
return Functions::NAN(); return Functions::NAN();
} }
return Statistical::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size); return Statistical::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size);
} }
return Functions::VALUE();
}
} }

View File

@ -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);
}
);
}
}

View File

@ -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,8 +29,13 @@ 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);
$numInSet = self::validateInt($numInSet);
} catch (Exception $e) {
return $e->getMessage();
}
if ($numObjs < $numInSet) { if ($numObjs < $numInSet) {
return Functions::NAN(); return Functions::NAN();
} }
@ -35,9 +43,6 @@ class Permutations
return round(MathTrig\Fact::funcFact($numObjs) / MathTrig\Fact::funcFact($numObjs - $numInSet)); return round(MathTrig\Fact::funcFact($numObjs) / MathTrig\Fact::funcFact($numObjs - $numInSet));
} }
return Functions::VALUE();
}
/** /**
* PERMUTATIONA. * PERMUTATIONA.
* *
@ -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);
} catch (Exception $e) {
return $e->getMessage();
}
if ($numObjs < 0 || $numInSet < 0) { if ($numObjs < 0 || $numInSet < 0) {
return Functions::NAN(); return Functions::NAN();
} }
return $numObjs ** $numInSet; return $numObjs ** $numInSet;
} }
return Functions::VALUE();
}
} }

View File

@ -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) {

View File

@ -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'],

View File

@ -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],

View File

@ -21,6 +21,14 @@ return [
'#NUM!', '#NUM!',
-1, 2, -1, 2,
], ],
[
'#NUM!',
1, -2,
],
[
'#VALUE!',
'NaN', 31,
],
[ [
'#VALUE!', '#VALUE!',
49, 'NaN', 49, 'NaN',

View File

@ -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],

View File

@ -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,