Move the trend functions from Statistical and into their own group class (#1890)
* Move the trend functions from Statistical and into their own group class * Additional LINEST()/LOGEST() tests, and fix for the returned array
This commit is contained in:
parent
04e7c30758
commit
70e371189c
|
|
@ -599,7 +599,7 @@ class Calculation
|
|||
],
|
||||
'CORREL' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'CORREL'],
|
||||
'functionCall' => [Statistical\Trends::class, 'CORREL'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'COS' => [
|
||||
|
|
@ -679,12 +679,12 @@ class Calculation
|
|||
],
|
||||
'COVAR' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'COVAR'],
|
||||
'functionCall' => [Statistical\Trends::class, 'COVAR'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'COVARIANCE.P' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'COVAR'],
|
||||
'functionCall' => [Statistical\Trends::class, 'COVAR'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'COVARIANCE.S' => [
|
||||
|
|
@ -1084,7 +1084,7 @@ class Calculation
|
|||
],
|
||||
'FORECAST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'FORECAST'],
|
||||
'functionCall' => [Statistical\Trends::class, 'FORECAST'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'FORECAST.ETS' => [
|
||||
|
|
@ -1109,7 +1109,7 @@ class Calculation
|
|||
],
|
||||
'FORECAST.LINEAR' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'FORECAST'],
|
||||
'functionCall' => [Statistical\Trends::class, 'FORECAST'],
|
||||
'argumentCount' => '3',
|
||||
],
|
||||
'FORMULATEXT' => [
|
||||
|
|
@ -1423,7 +1423,7 @@ class Calculation
|
|||
],
|
||||
'INTERCEPT' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'INTERCEPT'],
|
||||
'functionCall' => [Statistical\Trends::class, 'INTERCEPT'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'INTRATE' => [
|
||||
|
|
@ -1560,7 +1560,7 @@ class Calculation
|
|||
],
|
||||
'LINEST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'LINEST'],
|
||||
'functionCall' => [Statistical\Trends::class, 'LINEST'],
|
||||
'argumentCount' => '1-4',
|
||||
],
|
||||
'LN' => [
|
||||
|
|
@ -1580,7 +1580,7 @@ class Calculation
|
|||
],
|
||||
'LOGEST' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'LOGEST'],
|
||||
'functionCall' => [Statistical\Trends::class, 'LOGEST'],
|
||||
'argumentCount' => '1-4',
|
||||
],
|
||||
'LOGINV' => [
|
||||
|
|
@ -2143,7 +2143,7 @@ class Calculation
|
|||
],
|
||||
'RSQ' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'RSQ'],
|
||||
'functionCall' => [Statistical\Trends::class, 'RSQ'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'RTD' => [
|
||||
|
|
@ -2228,7 +2228,7 @@ class Calculation
|
|||
],
|
||||
'SLOPE' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'SLOPE'],
|
||||
'functionCall' => [Statistical\Trends::class, 'SLOPE'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'SMALL' => [
|
||||
|
|
@ -2293,7 +2293,7 @@ class Calculation
|
|||
],
|
||||
'STEYX' => [
|
||||
'category' => Category::CATEGORY_STATISTICAL,
|
||||
'functionCall' => [Statistical::class, 'STEYX'],
|
||||
'functionCall' => [Statistical\Trends::class, 'STEYX'],
|
||||
'argumentCount' => '2',
|
||||
],
|
||||
'SUBSTITUTE' => [
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Maximum;
|
|||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Permutations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend;
|
||||
|
||||
|
|
@ -21,33 +22,6 @@ class Statistical
|
|||
const MAX_ITERATIONS = 256;
|
||||
const SQRT2PI = 2.5066282746310005024157652848110452530069867406099;
|
||||
|
||||
private static function checkTrendArrays(&$array1, &$array2)
|
||||
{
|
||||
if (!is_array($array1)) {
|
||||
$array1 = [$array1];
|
||||
}
|
||||
if (!is_array($array2)) {
|
||||
$array2 = [$array2];
|
||||
}
|
||||
|
||||
$array1 = Functions::flattenArray($array1);
|
||||
$array2 = Functions::flattenArray($array2);
|
||||
foreach ($array1 as $key => $value) {
|
||||
if ((is_bool($value)) || (is_string($value)) || ($value === null)) {
|
||||
unset($array1[$key], $array2[$key]);
|
||||
}
|
||||
}
|
||||
foreach ($array2 as $key => $value) {
|
||||
if ((is_bool($value)) || (is_string($value)) || ($value === null)) {
|
||||
unset($array1[$key], $array2[$key]);
|
||||
}
|
||||
}
|
||||
$array1 = array_merge($array1);
|
||||
$array2 = array_merge($array2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incomplete beta function.
|
||||
*
|
||||
|
|
@ -890,6 +864,11 @@ class Statistical
|
|||
*
|
||||
* Returns covariance, the average of the products of deviations for each data point pair.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::CORREL()
|
||||
* Use the CORREL() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param mixed $yValues array of mixed Data Series Y
|
||||
* @param null|mixed $xValues array of mixed Data Series X
|
||||
*
|
||||
|
|
@ -897,24 +876,7 @@ class Statistical
|
|||
*/
|
||||
public static function CORREL($yValues, $xValues = null)
|
||||
{
|
||||
if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getCorrelation();
|
||||
return Trends::CORREL($xValues, $yValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1033,6 +995,11 @@ class Statistical
|
|||
*
|
||||
* Returns covariance, the average of the products of deviations for each data point pair.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::COVAR()
|
||||
* Use the COVAR() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param mixed $yValues array of mixed Data Series Y
|
||||
* @param mixed $xValues array of mixed Data Series X
|
||||
*
|
||||
|
|
@ -1040,21 +1007,7 @@ class Statistical
|
|||
*/
|
||||
public static function COVAR($yValues, $xValues)
|
||||
{
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getCovariance();
|
||||
return Trends::COVAR($yValues, $xValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1380,6 +1333,11 @@ class Statistical
|
|||
*
|
||||
* Calculates, or predicts, a future value by using existing values. The predicted value is a y-value for a given x-value.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::FORECAST()
|
||||
* Use the FORECAST() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param float $xValue Value of X for which we want to find Y
|
||||
* @param mixed $yValues array of mixed Data Series Y
|
||||
* @param mixed $xValues of mixed Data Series X
|
||||
|
|
@ -1388,24 +1346,7 @@ class Statistical
|
|||
*/
|
||||
public static function FORECAST($xValue, $yValues, $xValues)
|
||||
{
|
||||
$xValue = Functions::flattenSingleValue($xValue);
|
||||
if (!is_numeric($xValue)) {
|
||||
return Functions::VALUE();
|
||||
} elseif (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getValueOfYForX($xValue);
|
||||
return Trends::FORECAST($xValue, $yValues, $xValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1722,6 +1663,11 @@ class Statistical
|
|||
*
|
||||
* Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::INTERCEPT()
|
||||
* Use the INTERCEPT() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
*
|
||||
|
|
@ -1729,21 +1675,7 @@ class Statistical
|
|||
*/
|
||||
public static function INTERCEPT($yValues, $xValues)
|
||||
{
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getIntersect();
|
||||
return Trends::INTERCEPT($yValues, $xValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1838,6 +1770,11 @@ class Statistical
|
|||
* 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.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::LINEST()
|
||||
* Use the LINEST() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param null|mixed[] $xValues Data Series X
|
||||
* @param bool $const a logical value specifying whether to force the intersect to equal 0
|
||||
|
|
@ -1847,48 +1784,7 @@ class Statistical
|
|||
*/
|
||||
public static function LINEST($yValues, $xValues = null, $const = true, $stats = false)
|
||||
{
|
||||
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
|
||||
$stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
|
||||
if ($xValues === null) {
|
||||
$xValues = range(1, count(Functions::flattenArray($yValues)));
|
||||
}
|
||||
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const);
|
||||
if ($stats) {
|
||||
return [
|
||||
[
|
||||
$bestFitLinear->getSlope(),
|
||||
$bestFitLinear->getSlopeSE(),
|
||||
$bestFitLinear->getGoodnessOfFit(),
|
||||
$bestFitLinear->getF(),
|
||||
$bestFitLinear->getSSRegression(),
|
||||
],
|
||||
[
|
||||
$bestFitLinear->getIntersect(),
|
||||
$bestFitLinear->getIntersectSE(),
|
||||
$bestFitLinear->getStdevOfResiduals(),
|
||||
$bestFitLinear->getDFResiduals(),
|
||||
$bestFitLinear->getSSResiduals(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
$bestFitLinear->getSlope(),
|
||||
$bestFitLinear->getIntersect(),
|
||||
];
|
||||
return Trends::LINEST($yValues, $xValues, $const, $stats);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1897,6 +1793,11 @@ class Statistical
|
|||
* Calculates an exponential curve that best fits the X and Y data series,
|
||||
* and then returns an array that describes the line.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::LOGEST()
|
||||
* Use the LOGEST() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param null|mixed[] $xValues Data Series X
|
||||
* @param bool $const a logical value specifying whether to force the intersect to equal 0
|
||||
|
|
@ -1906,54 +1807,7 @@ class Statistical
|
|||
*/
|
||||
public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false)
|
||||
{
|
||||
$const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const);
|
||||
$stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats);
|
||||
if ($xValues === null) {
|
||||
$xValues = range(1, count(Functions::flattenArray($yValues)));
|
||||
}
|
||||
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
foreach ($yValues as $value) {
|
||||
if ($value <= 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
}
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
|
||||
if ($stats) {
|
||||
return [
|
||||
[
|
||||
$bestFitExponential->getSlope(),
|
||||
$bestFitExponential->getSlopeSE(),
|
||||
$bestFitExponential->getGoodnessOfFit(),
|
||||
$bestFitExponential->getF(),
|
||||
$bestFitExponential->getSSRegression(),
|
||||
],
|
||||
[
|
||||
$bestFitExponential->getIntersect(),
|
||||
$bestFitExponential->getIntersectSE(),
|
||||
$bestFitExponential->getStdevOfResiduals(),
|
||||
$bestFitExponential->getDFResiduals(),
|
||||
$bestFitExponential->getSSResiduals(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
$bestFitExponential->getSlope(),
|
||||
$bestFitExponential->getIntersect(),
|
||||
];
|
||||
return Trends::LOGEST($yValues, $xValues, $const, $stats);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2711,6 +2565,11 @@ class Statistical
|
|||
*
|
||||
* Returns the square of the Pearson product moment correlation coefficient through data points in known_y's and known_x's.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::RSQ()
|
||||
* Use the RSQ() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
*
|
||||
|
|
@ -2718,21 +2577,7 @@ class Statistical
|
|||
*/
|
||||
public static function RSQ($yValues, $xValues)
|
||||
{
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getGoodnessOfFit();
|
||||
return Trends::RSQ($yValues, $xValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2784,6 +2629,11 @@ class Statistical
|
|||
*
|
||||
* Returns the slope of the linear regression line through data points in known_y's and known_x's.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::SLOPE()
|
||||
* Use the SLOPE() method in the Statistical\Trends class instead
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
* @param mixed[] $xValues Data Series X
|
||||
*
|
||||
|
|
@ -2791,21 +2641,7 @@ class Statistical
|
|||
*/
|
||||
public static function SLOPE($yValues, $xValues)
|
||||
{
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getSlope();
|
||||
return Trends::SLOPE($yValues, $xValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2971,6 +2807,11 @@ class Statistical
|
|||
/**
|
||||
* STEYX.
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Statistical\Trends::STEYX()
|
||||
* Use the STEYX() method in the Statistical\Trends class instead
|
||||
*
|
||||
* Returns the standard error of the predicted y-value for each x in the regression.
|
||||
*
|
||||
* @param mixed[] $yValues Data Series Y
|
||||
|
|
@ -2980,21 +2821,7 @@ class Statistical
|
|||
*/
|
||||
public static function STEYX($yValues, $xValues)
|
||||
{
|
||||
if (!self::checkTrendArrays($yValues, $xValues)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$yValueCount = count($yValues);
|
||||
$xValueCount = count($xValues);
|
||||
|
||||
if (($yValueCount == 0) || ($yValueCount != $xValueCount)) {
|
||||
return Functions::NA();
|
||||
} elseif ($yValueCount == 1) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
$bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues);
|
||||
|
||||
return $bestFitLinear->getStdevOfResiduals();
|
||||
return Trends::STEYX($yValues, $xValues);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,359 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend;
|
||||
|
||||
class Trends
|
||||
{
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function checkTrendArrays(&$array1, &$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(Functions::NA());
|
||||
} elseif ($yValueCount === 1) {
|
||||
throw new Exception(Functions::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
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function CORREL($yValues, $xValues = null)
|
||||
{
|
||||
if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) {
|
||||
return Functions::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
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function COVAR($yValues, $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->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 float $xValue Value of X for which we want to find Y
|
||||
* @param mixed $yValues array of mixed Data Series Y
|
||||
* @param mixed $xValues of mixed Data Series X
|
||||
*
|
||||
* @return bool|float|string
|
||||
*/
|
||||
public static function FORECAST($xValue, $yValues, $xValues)
|
||||
{
|
||||
$xValue = Functions::flattenSingleValue($xValue);
|
||||
if (!is_numeric($xValue)) {
|
||||
return Functions::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->getValueOfYForX($xValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function INTERCEPT($yValues, $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->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 bool $const a logical value specifying whether to force the intersect to equal 0
|
||||
* @param bool $stats a logical value specifying whether to return additional regression statistics
|
||||
*
|
||||
* @return array|int|string The result, or a string containing an error
|
||||
*/
|
||||
public static function LINEST($yValues, $xValues = null, $const = true, $stats = false)
|
||||
{
|
||||
$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(),
|
||||
$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 bool $const a logical value specifying whether to force the intersect to equal 0
|
||||
* @param bool $stats a logical value specifying whether to return additional regression statistics
|
||||
*
|
||||
* @return array|int|string The result, or a string containing an error
|
||||
*/
|
||||
public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false)
|
||||
{
|
||||
$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 Functions::NAN();
|
||||
}
|
||||
}
|
||||
|
||||
$bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const);
|
||||
|
||||
if ($stats === true) {
|
||||
return [
|
||||
[
|
||||
$bestFitExponential->getSlope(),
|
||||
$bestFitExponential->getIntersect(),
|
||||
],
|
||||
[
|
||||
$bestFitExponential->getSlopeSE(),
|
||||
$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($yValues, $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($yValues, $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
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function STEYX($yValues, $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->getStdevOfResiduals();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,4 +8,42 @@ return [
|
|||
true,
|
||||
false,
|
||||
],
|
||||
[
|
||||
[
|
||||
[56.837944664032, 11704.347826086974],
|
||||
[54.403741747077, 131988.39973671117],
|
||||
[0.108159322123, 13123.598915116556],
|
||||
[1.091488562082, 9],
|
||||
[187985818.1818185, 1550059636.363636],
|
||||
],
|
||||
[142000, 144000, 151000, 150000, 139000, 169000, 126000, 142900, 163000, 169000, 149000],
|
||||
[2310, 2333, 2356, 2379, 2402, 2425, 2448, 2471, 2494, 2517, 2540],
|
||||
true,
|
||||
true,
|
||||
],
|
||||
// [
|
||||
// [
|
||||
// [-234.2371645, 2553.21066, 12529.76817, 27.64138737, 52317.83051],
|
||||
// [13.26801148, 530.6691519, 400.0668382, 5.429374042, 12237.3616],
|
||||
// [0.996747993, 970.5784629, '#N/A', '#N/A', '#N/A'],
|
||||
// [459.7536742, 6, '#N/A', '#N/A', '#N/A'],
|
||||
// [1732393319, 5652135.316, '#N/A', '#N/A', '#N/A'],
|
||||
// ],
|
||||
// [142000, 144000, 151000, 150000, 139000, 169000, 126000, 142900, 163000, 169000, 149000],
|
||||
// [
|
||||
// [2310, 2, 2, 20],
|
||||
// [2333, 2, 2, 12],
|
||||
// [2356, 3, 1.5, 33],
|
||||
// [2379, 3, 2, 43],
|
||||
// [2402, 2, 3, 53],
|
||||
// [2425, 4, 2, 23],
|
||||
// [2448, 2, 1.5, 99],
|
||||
// [2471, 2, 2, 34],
|
||||
// [2494, 3, 3, 23],
|
||||
// [2517, 4, 4, 55],
|
||||
// [2540, 2, 3, 22],
|
||||
// ],
|
||||
// true,
|
||||
// true,
|
||||
// ],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -8,4 +8,11 @@ return [
|
|||
true,
|
||||
false,
|
||||
],
|
||||
[
|
||||
[1.482939830687, 2.257475168225],
|
||||
[2, 3, 6, 8, 12, 15, 25, 34, 50],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||
true,
|
||||
false,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue