Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
   4  
   5  abstract class BestFit
   6  {
   7      /**
   8       * Indicator flag for a calculation error.
   9       *
  10       * @var bool
  11       */
  12      protected $error = false;
  13  
  14      /**
  15       * Algorithm type to use for best-fit.
  16       *
  17       * @var string
  18       */
  19      protected $bestFitType = 'undetermined';
  20  
  21      /**
  22       * Number of entries in the sets of x- and y-value arrays.
  23       *
  24       * @var int
  25       */
  26      protected $valueCount = 0;
  27  
  28      /**
  29       * X-value dataseries of values.
  30       *
  31       * @var float[]
  32       */
  33      protected $xValues = [];
  34  
  35      /**
  36       * Y-value dataseries of values.
  37       *
  38       * @var float[]
  39       */
  40      protected $yValues = [];
  41  
  42      /**
  43       * Flag indicating whether values should be adjusted to Y=0.
  44       *
  45       * @var bool
  46       */
  47      protected $adjustToZero = false;
  48  
  49      /**
  50       * Y-value series of best-fit values.
  51       *
  52       * @var float[]
  53       */
  54      protected $yBestFitValues = [];
  55  
  56      protected $goodnessOfFit = 1;
  57  
  58      protected $stdevOfResiduals = 0;
  59  
  60      protected $covariance = 0;
  61  
  62      protected $correlation = 0;
  63  
  64      protected $SSRegression = 0;
  65  
  66      protected $SSResiduals = 0;
  67  
  68      protected $DFResiduals = 0;
  69  
  70      protected $f = 0;
  71  
  72      protected $slope = 0;
  73  
  74      protected $slopeSE = 0;
  75  
  76      protected $intersect = 0;
  77  
  78      protected $intersectSE = 0;
  79  
  80      protected $xOffset = 0;
  81  
  82      protected $yOffset = 0;
  83  
  84      public function getError()
  85      {
  86          return $this->error;
  87      }
  88  
  89      public function getBestFitType()
  90      {
  91          return $this->bestFitType;
  92      }
  93  
  94      /**
  95       * Return the Y-Value for a specified value of X.
  96       *
  97       * @param float $xValue X-Value
  98       *
  99       * @return float Y-Value
 100       */
 101      abstract public function getValueOfYForX($xValue);
 102  
 103      /**
 104       * Return the X-Value for a specified value of Y.
 105       *
 106       * @param float $yValue Y-Value
 107       *
 108       * @return float X-Value
 109       */
 110      abstract public function getValueOfXForY($yValue);
 111  
 112      /**
 113       * Return the original set of X-Values.
 114       *
 115       * @return float[] X-Values
 116       */
 117      public function getXValues()
 118      {
 119          return $this->xValues;
 120      }
 121  
 122      /**
 123       * Return the Equation of the best-fit line.
 124       *
 125       * @param int $dp Number of places of decimal precision to display
 126       *
 127       * @return string
 128       */
 129      abstract public function getEquation($dp = 0);
 130  
 131      /**
 132       * Return the Slope of the line.
 133       *
 134       * @param int $dp Number of places of decimal precision to display
 135       *
 136       * @return float
 137       */
 138      public function getSlope($dp = 0)
 139      {
 140          if ($dp != 0) {
 141              return round($this->slope, $dp);
 142          }
 143  
 144          return $this->slope;
 145      }
 146  
 147      /**
 148       * Return the standard error of the Slope.
 149       *
 150       * @param int $dp Number of places of decimal precision to display
 151       *
 152       * @return float
 153       */
 154      public function getSlopeSE($dp = 0)
 155      {
 156          if ($dp != 0) {
 157              return round($this->slopeSE, $dp);
 158          }
 159  
 160          return $this->slopeSE;
 161      }
 162  
 163      /**
 164       * Return the Value of X where it intersects Y = 0.
 165       *
 166       * @param int $dp Number of places of decimal precision to display
 167       *
 168       * @return float
 169       */
 170      public function getIntersect($dp = 0)
 171      {
 172          if ($dp != 0) {
 173              return round($this->intersect, $dp);
 174          }
 175  
 176          return $this->intersect;
 177      }
 178  
 179      /**
 180       * Return the standard error of the Intersect.
 181       *
 182       * @param int $dp Number of places of decimal precision to display
 183       *
 184       * @return float
 185       */
 186      public function getIntersectSE($dp = 0)
 187      {
 188          if ($dp != 0) {
 189              return round($this->intersectSE, $dp);
 190          }
 191  
 192          return $this->intersectSE;
 193      }
 194  
 195      /**
 196       * Return the goodness of fit for this regression.
 197       *
 198       * @param int $dp Number of places of decimal precision to return
 199       *
 200       * @return float
 201       */
 202      public function getGoodnessOfFit($dp = 0)
 203      {
 204          if ($dp != 0) {
 205              return round($this->goodnessOfFit, $dp);
 206          }
 207  
 208          return $this->goodnessOfFit;
 209      }
 210  
 211      /**
 212       * Return the goodness of fit for this regression.
 213       *
 214       * @param int $dp Number of places of decimal precision to return
 215       *
 216       * @return float
 217       */
 218      public function getGoodnessOfFitPercent($dp = 0)
 219      {
 220          if ($dp != 0) {
 221              return round($this->goodnessOfFit * 100, $dp);
 222          }
 223  
 224          return $this->goodnessOfFit * 100;
 225      }
 226  
 227      /**
 228       * Return the standard deviation of the residuals for this regression.
 229       *
 230       * @param int $dp Number of places of decimal precision to return
 231       *
 232       * @return float
 233       */
 234      public function getStdevOfResiduals($dp = 0)
 235      {
 236          if ($dp != 0) {
 237              return round($this->stdevOfResiduals, $dp);
 238          }
 239  
 240          return $this->stdevOfResiduals;
 241      }
 242  
 243      /**
 244       * @param int $dp Number of places of decimal precision to return
 245       *
 246       * @return float
 247       */
 248      public function getSSRegression($dp = 0)
 249      {
 250          if ($dp != 0) {
 251              return round($this->SSRegression, $dp);
 252          }
 253  
 254          return $this->SSRegression;
 255      }
 256  
 257      /**
 258       * @param int $dp Number of places of decimal precision to return
 259       *
 260       * @return float
 261       */
 262      public function getSSResiduals($dp = 0)
 263      {
 264          if ($dp != 0) {
 265              return round($this->SSResiduals, $dp);
 266          }
 267  
 268          return $this->SSResiduals;
 269      }
 270  
 271      /**
 272       * @param int $dp Number of places of decimal precision to return
 273       *
 274       * @return float
 275       */
 276      public function getDFResiduals($dp = 0)
 277      {
 278          if ($dp != 0) {
 279              return round($this->DFResiduals, $dp);
 280          }
 281  
 282          return $this->DFResiduals;
 283      }
 284  
 285      /**
 286       * @param int $dp Number of places of decimal precision to return
 287       *
 288       * @return float
 289       */
 290      public function getF($dp = 0)
 291      {
 292          if ($dp != 0) {
 293              return round($this->f, $dp);
 294          }
 295  
 296          return $this->f;
 297      }
 298  
 299      /**
 300       * @param int $dp Number of places of decimal precision to return
 301       *
 302       * @return float
 303       */
 304      public function getCovariance($dp = 0)
 305      {
 306          if ($dp != 0) {
 307              return round($this->covariance, $dp);
 308          }
 309  
 310          return $this->covariance;
 311      }
 312  
 313      /**
 314       * @param int $dp Number of places of decimal precision to return
 315       *
 316       * @return float
 317       */
 318      public function getCorrelation($dp = 0)
 319      {
 320          if ($dp != 0) {
 321              return round($this->correlation, $dp);
 322          }
 323  
 324          return $this->correlation;
 325      }
 326  
 327      /**
 328       * @return float[]
 329       */
 330      public function getYBestFitValues()
 331      {
 332          return $this->yBestFitValues;
 333      }
 334  
 335      protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
 336      {
 337          $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0;
 338          foreach ($this->xValues as $xKey => $xValue) {
 339              $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
 340  
 341              $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
 342              if ($const === true) {
 343                  $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
 344              } else {
 345                  $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
 346              }
 347              $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
 348              if ($const === true) {
 349                  $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
 350              } else {
 351                  $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
 352              }
 353          }
 354  
 355          $this->SSResiduals = $SSres;
 356          $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0);
 357  
 358          if ($this->DFResiduals == 0.0) {
 359              $this->stdevOfResiduals = 0.0;
 360          } else {
 361              $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
 362          }
 363          if (($SStot == 0.0) || ($SSres == $SStot)) {
 364              $this->goodnessOfFit = 1;
 365          } else {
 366              $this->goodnessOfFit = 1 - ($SSres / $SStot);
 367          }
 368  
 369          $this->SSRegression = $this->goodnessOfFit * $SStot;
 370          $this->covariance = $SScov / $this->valueCount;
 371          $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
 372          $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
 373          $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
 374          if ($this->SSResiduals != 0.0) {
 375              if ($this->DFResiduals == 0.0) {
 376                  $this->f = 0.0;
 377              } else {
 378                  $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
 379              }
 380          } else {
 381              if ($this->DFResiduals == 0.0) {
 382                  $this->f = 0.0;
 383              } else {
 384                  $this->f = $this->SSRegression / $this->DFResiduals;
 385              }
 386          }
 387      }
 388  
 389      private function sumSquares(array $values)
 390      {
 391          return array_sum(
 392              array_map(
 393                  function ($value) {
 394                      return $value ** 2;
 395                  },
 396                  $values
 397              )
 398          );
 399      }
 400  
 401      /**
 402       * @param float[] $yValues
 403       * @param float[] $xValues
 404       */
 405      protected function leastSquareFit(array $yValues, array $xValues, bool $const): void
 406      {
 407          // calculate sums
 408          $sumValuesX = array_sum($xValues);
 409          $sumValuesY = array_sum($yValues);
 410          $meanValueX = $sumValuesX / $this->valueCount;
 411          $meanValueY = $sumValuesY / $this->valueCount;
 412          $sumSquaresX = $this->sumSquares($xValues);
 413          $sumSquaresY = $this->sumSquares($yValues);
 414          $mBase = $mDivisor = 0.0;
 415          $xy_sum = 0.0;
 416          for ($i = 0; $i < $this->valueCount; ++$i) {
 417              $xy_sum += $xValues[$i] * $yValues[$i];
 418  
 419              if ($const === true) {
 420                  $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY);
 421                  $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX);
 422              } else {
 423                  $mBase += $xValues[$i] * $yValues[$i];
 424                  $mDivisor += $xValues[$i] * $xValues[$i];
 425              }
 426          }
 427  
 428          // calculate slope
 429          $this->slope = $mBase / $mDivisor;
 430  
 431          // calculate intersect
 432          $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0;
 433  
 434          $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const);
 435      }
 436  
 437      /**
 438       * Define the regression.
 439       *
 440       * @param float[] $yValues The set of Y-values for this regression
 441       * @param float[] $xValues The set of X-values for this regression
 442       */
 443      public function __construct($yValues, $xValues = [])
 444      {
 445          //    Calculate number of points
 446          $yValueCount = count($yValues);
 447          $xValueCount = count($xValues);
 448  
 449          //    Define X Values if necessary
 450          if ($xValueCount === 0) {
 451              $xValues = range(1, $yValueCount);
 452          } elseif ($yValueCount !== $xValueCount) {
 453              //    Ensure both arrays of points are the same size
 454              $this->error = true;
 455          }
 456  
 457          $this->valueCount = $yValueCount;
 458          $this->xValues = $xValues;
 459          $this->yValues = $yValues;
 460      }
 461  }