diff --git a/src/PhpSpreadsheet/Shared/Trend/BestFit.php b/src/PhpSpreadsheet/Shared/Trend/BestFit.php index 6d6f62838a..4efb109d0b 100644 --- a/src/PhpSpreadsheet/Shared/Trend/BestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/BestFit.php @@ -2,7 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared\Trend; -class BestFit +abstract class BestFit { /** * Indicator flag for a calculation error. @@ -39,13 +39,6 @@ class BestFit */ protected $yValues = []; - /** - * Flag indicating whether values should be adjusted to Y=0. - * - * @var bool - */ - protected $adjustToZero = false; - /** * Y-value series of best-fit values. * @@ -96,24 +89,18 @@ public function getBestFitType() * * @param float $xValue X-Value * - * @return bool Y-Value + * @return float Y-Value */ - public function getValueOfYForX($xValue) - { - return false; - } + abstract public function getValueOfYForX($xValue); /** * Return the X-Value for a specified value of Y. * * @param float $yValue Y-Value * - * @return bool X-Value + * @return float X-Value */ - public function getValueOfXForY($yValue) - { - return false; - } + abstract public function getValueOfXForY($yValue); /** * Return the original set of X-Values. @@ -130,12 +117,9 @@ public function getXValues() * * @param int $dp Number of places of decimal precision to display * - * @return bool + * @return string */ - public function getEquation($dp = 0) - { - return false; - } + abstract public function getEquation($dp = 0); /** * Return the Slope of the line. @@ -278,16 +262,10 @@ public function getSSResiduals($dp = 0) } /** - * @param int $dp Number of places of decimal precision to return - * - * @return float + * @return int */ - public function getDFResiduals($dp = 0) + public function getDFResiduals() { - if ($dp != 0) { - return round($this->DFResiduals, $dp); - } - return $this->DFResiduals; } @@ -341,70 +319,68 @@ public function getYBestFitValues() return $this->yBestFitValues; } - protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void + private function sumSquares(array $values): float { - $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0; - foreach ($this->xValues as $xKey => $xValue) { - $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); - - $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY); - if ($const === true) { - $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY); - } else { - $SStot += $this->yValues[$xKey] * $this->yValues[$xKey]; - } - $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY); - if ($const === true) { - $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX); - } else { - $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey]; - } - } + return array_sum( + array_map( + function ($value) { + return $value ** 2; + }, + $values + ) + ); + } - $this->SSResiduals = $SSres; - $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0); + /** + * @param $sumXY + * @param $sumValuesX + * @param $sumValuesY + */ + protected function calculateGoodnessOfFit(float $SStot, $sumXY, $sumValuesX, $sumValuesY, float $SSsex): void + { + $sumSquaresX = $this->sumSquares($this->xValues); + $sumSquaresY = $this->sumSquares($this->yValues); - if ($this->DFResiduals == 0.0) { - $this->stdevOfResiduals = 0.0; - } else { - $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals); - } - if (($SStot == 0.0) || ($SSres == $SStot)) { - $this->goodnessOfFit = 1; - } else { - $this->goodnessOfFit = 1 - ($SSres / $SStot); - } + $this->stdevOfResiduals = ($this->DFResiduals == 0.0) ? 0.0 : sqrt($this->SSResiduals / $this->DFResiduals); + $this->goodnessOfFit = (($SStot == 0.0) || ($this->SSResiduals == $SStot)) + ? 1.0 + : 1.0 - ($this->SSResiduals / $SStot); $this->SSRegression = $this->goodnessOfFit * $SStot; - $this->covariance = $SScov / $this->valueCount; - $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2)); + $this->correlation = ($this->valueCount * $sumXY - $sumValuesX * $sumValuesY) / + sqrt(($this->valueCount * $sumSquaresX - $sumValuesX ** 2) * ($this->valueCount * $sumSquaresY - $sumValuesY ** 2)); $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex); - $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2)); + $this->intersectSE = $this->stdevOfResiduals * + sqrt(1 / ($this->valueCount - ($sumValuesX * $sumValuesX) / $sumSquaresX)); + if ($this->SSResiduals != 0.0) { - if ($this->DFResiduals == 0.0) { - $this->f = 0.0; - } else { - $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals); - } + $this->f = ($this->DFResiduals == 0.0) ? 0.0 : $this->SSRegression / ($this->SSResiduals / $this->DFResiduals); } else { - if ($this->DFResiduals == 0.0) { - $this->f = 0.0; - } else { - $this->f = $this->SSRegression / $this->DFResiduals; - } + $this->f = ($this->DFResiduals == 0.0) ? 0.0 : $this->SSRegression / $this->DFResiduals; } } - private function sumSquares(array $values) + protected function calculateResiduals($sumValuesX, $sumValuesY, $sumXY, $meanValueX, $meanYmeanValueY, $const): void { - return array_sum( - array_map( - function ($value) { - return $value ** 2; - }, - $values - ) - ); + $SSresiduals = $SScovariance = $SStot = $SSsex = 0.0; + foreach ($this->xValues as $xKey => $xValue) { + $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); + + $SSresiduals += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY); + $SStot += ($const === true) + ? ($this->yValues[$xKey] - $meanYmeanValueY) * ($this->yValues[$xKey] - $meanYmeanValueY) + : $this->yValues[$xKey] * $this->yValues[$xKey]; + $SScovariance += ($this->xValues[$xKey] - $meanValueX) * ($this->yValues[$xKey] - $meanYmeanValueY); + $SSsex += ($const === true) + ? ($this->xValues[$xKey] - $meanValueX) * ($this->xValues[$xKey] - $meanValueX) + : $this->xValues[$xKey] * $this->xValues[$xKey]; + } + + $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0); + $this->SSResiduals = $SSresiduals; + $this->covariance = $SScovariance / $this->valueCount; + + $this->calculateGoodnessOfFit($SStot, $sumXY, $sumValuesX, $sumValuesY, $SSsex); } /** @@ -418,20 +394,18 @@ protected function leastSquareFit(array $yValues, array $xValues, bool $const): $sumValuesY = array_sum($yValues); $meanValueX = $sumValuesX / $this->valueCount; $meanValueY = $sumValuesY / $this->valueCount; - $sumSquaresX = $this->sumSquares($xValues); - $sumSquaresY = $this->sumSquares($yValues); + $mBase = $mDivisor = 0.0; $xy_sum = 0.0; for ($i = 0; $i < $this->valueCount; ++$i) { $xy_sum += $xValues[$i] * $yValues[$i]; - if ($const === true) { - $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY); - $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX); - } else { - $mBase += $xValues[$i] * $yValues[$i]; - $mDivisor += $xValues[$i] * $xValues[$i]; - } + $mBase += ($const === true) + ? ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY) + : $xValues[$i] * $yValues[$i]; + $mDivisor += ($const === true) + ? ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX) + : $xValues[$i] * $xValues[$i]; } // calculate slope @@ -440,7 +414,7 @@ protected function leastSquareFit(array $yValues, array $xValues, bool $const): // calculate intersect $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0; - $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const); + $this->calculateResiduals($sumValuesX, $sumValuesY, $xy_sum, $meanValueX, $meanValueY, $const); } /** diff --git a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php index 1d34e81c19..feb382ab91 100644 --- a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php @@ -166,7 +166,7 @@ private function polynomialRegression($order, $yValues, $xValues): void $this->intersect = array_shift($coefficients); $this->slope = $coefficients; - $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0); + $this->calculateResiduals($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0); foreach ($this->xValues as $xKey => $xValue) { $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); } diff --git a/src/PhpSpreadsheet/Shared/Trend/Trend.php b/src/PhpSpreadsheet/Shared/Trend/Trend.php index d0a117cbde..fc8e9a2cc0 100644 --- a/src/PhpSpreadsheet/Shared/Trend/Trend.php +++ b/src/PhpSpreadsheet/Shared/Trend/Trend.php @@ -96,7 +96,7 @@ public static function calculate($trendType = self::TREND_BEST_FIT, $yValues = [ $bestFit[$trendMethod] = new $className($yValues, $xValues, $const); $bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit(); } - if ($trendType != self::TREND_BEST_FIT_NO_POLY) { + if ($trendType !== self::TREND_BEST_FIT_NO_POLY) { foreach (self::$trendTypePolynomialOrders as $trendMethod) { $order = substr($trendMethod, -1); $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues);