Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
   4  
   5  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 bool Y-Value
 100       */
 101      public function getValueOfYForX($xValue)
 102      {
 103          return false;
 104      }
 105  
 106      /**
 107       * Return the X-Value for a specified value of Y.
 108       *
 109       * @param float $yValue Y-Value
 110       *
 111       * @return bool X-Value
 112       */
 113      public function getValueOfXForY($yValue)
 114      {
 115          return false;
 116      }
 117  
 118      /**
 119       * Return the original set of X-Values.
 120       *
 121       * @return float[] X-Values
 122       */
 123      public function getXValues()
 124      {
 125          return $this->xValues;
 126      }
 127  
 128      /**
 129       * Return the Equation of the best-fit line.
 130       *
 131       * @param int $dp Number of places of decimal precision to display
 132       *
 133       * @return bool
 134       */
 135      public function getEquation($dp = 0)
 136      {
 137          return false;
 138      }
 139  
 140      /**
 141       * Return the Slope of the line.
 142       *
 143       * @param int $dp Number of places of decimal precision to display
 144       *
 145       * @return float
 146       */
 147      public function getSlope($dp = 0)
 148      {
 149          if ($dp != 0) {
 150              return round($this->slope, $dp);
 151          }
 152  
 153          return $this->slope;
 154      }
 155  
 156      /**
 157       * Return the standard error of the Slope.
 158       *
 159       * @param int $dp Number of places of decimal precision to display
 160       *
 161       * @return float
 162       */
 163      public function getSlopeSE($dp = 0)
 164      {
 165          if ($dp != 0) {
 166              return round($this->slopeSE, $dp);
 167          }
 168  
 169          return $this->slopeSE;
 170      }
 171  
 172      /**
 173       * Return the Value of X where it intersects Y = 0.
 174       *
 175       * @param int $dp Number of places of decimal precision to display
 176       *
 177       * @return float
 178       */
 179      public function getIntersect($dp = 0)
 180      {
 181          if ($dp != 0) {
 182              return round($this->intersect, $dp);
 183          }
 184  
 185          return $this->intersect;
 186      }
 187  
 188      /**
 189       * Return the standard error of the Intersect.
 190       *
 191       * @param int $dp Number of places of decimal precision to display
 192       *
 193       * @return float
 194       */
 195      public function getIntersectSE($dp = 0)
 196      {
 197          if ($dp != 0) {
 198              return round($this->intersectSE, $dp);
 199          }
 200  
 201          return $this->intersectSE;
 202      }
 203  
 204      /**
 205       * Return the goodness of fit for this regression.
 206       *
 207       * @param int $dp Number of places of decimal precision to return
 208       *
 209       * @return float
 210       */
 211      public function getGoodnessOfFit($dp = 0)
 212      {
 213          if ($dp != 0) {
 214              return round($this->goodnessOfFit, $dp);
 215          }
 216  
 217          return $this->goodnessOfFit;
 218      }
 219  
 220      /**
 221       * Return the goodness of fit for this regression.
 222       *
 223       * @param int $dp Number of places of decimal precision to return
 224       *
 225       * @return float
 226       */
 227      public function getGoodnessOfFitPercent($dp = 0)
 228      {
 229          if ($dp != 0) {
 230              return round($this->goodnessOfFit * 100, $dp);
 231          }
 232  
 233          return $this->goodnessOfFit * 100;
 234      }
 235  
 236      /**
 237       * Return the standard deviation of the residuals for this regression.
 238       *
 239       * @param int $dp Number of places of decimal precision to return
 240       *
 241       * @return float
 242       */
 243      public function getStdevOfResiduals($dp = 0)
 244      {
 245          if ($dp != 0) {
 246              return round($this->stdevOfResiduals, $dp);
 247          }
 248  
 249          return $this->stdevOfResiduals;
 250      }
 251  
 252      /**
 253       * @param int $dp Number of places of decimal precision to return
 254       *
 255       * @return float
 256       */
 257      public function getSSRegression($dp = 0)
 258      {
 259          if ($dp != 0) {
 260              return round($this->SSRegression, $dp);
 261          }
 262  
 263          return $this->SSRegression;
 264      }
 265  
 266      /**
 267       * @param int $dp Number of places of decimal precision to return
 268       *
 269       * @return float
 270       */
 271      public function getSSResiduals($dp = 0)
 272      {
 273          if ($dp != 0) {
 274              return round($this->SSResiduals, $dp);
 275          }
 276  
 277          return $this->SSResiduals;
 278      }
 279  
 280      /**
 281       * @param int $dp Number of places of decimal precision to return
 282       *
 283       * @return float
 284       */
 285      public function getDFResiduals($dp = 0)
 286      {
 287          if ($dp != 0) {
 288              return round($this->DFResiduals, $dp);
 289          }
 290  
 291          return $this->DFResiduals;
 292      }
 293  
 294      /**
 295       * @param int $dp Number of places of decimal precision to return
 296       *
 297       * @return float
 298       */
 299      public function getF($dp = 0)
 300      {
 301          if ($dp != 0) {
 302              return round($this->f, $dp);
 303          }
 304  
 305          return $this->f;
 306      }
 307  
 308      /**
 309       * @param int $dp Number of places of decimal precision to return
 310       *
 311       * @return float
 312       */
 313      public function getCovariance($dp = 0)
 314      {
 315          if ($dp != 0) {
 316              return round($this->covariance, $dp);
 317          }
 318  
 319          return $this->covariance;
 320      }
 321  
 322      /**
 323       * @param int $dp Number of places of decimal precision to return
 324       *
 325       * @return float
 326       */
 327      public function getCorrelation($dp = 0)
 328      {
 329          if ($dp != 0) {
 330              return round($this->correlation, $dp);
 331          }
 332  
 333          return $this->correlation;
 334      }
 335  
 336      /**
 337       * @return float[]
 338       */
 339      public function getYBestFitValues()
 340      {
 341          return $this->yBestFitValues;
 342      }
 343  
 344      protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
 345      {
 346          $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0;
 347          foreach ($this->xValues as $xKey => $xValue) {
 348              $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
 349  
 350              $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
 351              if ($const) {
 352                  $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
 353              } else {
 354                  $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
 355              }
 356              $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
 357              if ($const) {
 358                  $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
 359              } else {
 360                  $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
 361              }
 362          }
 363  
 364          $this->SSResiduals = $SSres;
 365          $this->DFResiduals = $this->valueCount - 1 - $const;
 366  
 367          if ($this->DFResiduals == 0.0) {
 368              $this->stdevOfResiduals = 0.0;
 369          } else {
 370              $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
 371          }
 372          if (($SStot == 0.0) || ($SSres == $SStot)) {
 373              $this->goodnessOfFit = 1;
 374          } else {
 375              $this->goodnessOfFit = 1 - ($SSres / $SStot);
 376          }
 377  
 378          $this->SSRegression = $this->goodnessOfFit * $SStot;
 379          $this->covariance = $SScov / $this->valueCount;
 380          $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
 381          $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
 382          $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
 383          if ($this->SSResiduals != 0.0) {
 384              if ($this->DFResiduals == 0.0) {
 385                  $this->f = 0.0;
 386              } else {
 387                  $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
 388              }
 389          } else {
 390              if ($this->DFResiduals == 0.0) {
 391                  $this->f = 0.0;
 392              } else {
 393                  $this->f = $this->SSRegression / $this->DFResiduals;
 394              }
 395          }
 396      }
 397  
 398      /**
 399       * @param float[] $yValues
 400       * @param float[] $xValues
 401       * @param bool $const
 402       */
 403      protected function leastSquareFit(array $yValues, array $xValues, $const): void
 404      {
 405          // calculate sums
 406          $x_sum = array_sum($xValues);
 407          $y_sum = array_sum($yValues);
 408          $meanX = $x_sum / $this->valueCount;
 409          $meanY = $y_sum / $this->valueCount;
 410          $mBase = $mDivisor = $xx_sum = $xy_sum = $yy_sum = 0.0;
 411          for ($i = 0; $i < $this->valueCount; ++$i) {
 412              $xy_sum += $xValues[$i] * $yValues[$i];
 413              $xx_sum += $xValues[$i] * $xValues[$i];
 414              $yy_sum += $yValues[$i] * $yValues[$i];
 415  
 416              if ($const) {
 417                  $mBase += ($xValues[$i] - $meanX) * ($yValues[$i] - $meanY);
 418                  $mDivisor += ($xValues[$i] - $meanX) * ($xValues[$i] - $meanX);
 419              } else {
 420                  $mBase += $xValues[$i] * $yValues[$i];
 421                  $mDivisor += $xValues[$i] * $xValues[$i];
 422              }
 423          }
 424  
 425          // calculate slope
 426          $this->slope = $mBase / $mDivisor;
 427  
 428          // calculate intersect
 429          if ($const) {
 430              $this->intersect = $meanY - ($this->slope * $meanX);
 431          } else {
 432              $this->intersect = 0;
 433          }
 434  
 435          $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, $meanX, $meanY, $const);
 436      }
 437  
 438      /**
 439       * Define the regression.
 440       *
 441       * @param float[] $yValues The set of Y-values for this regression
 442       * @param float[] $xValues The set of X-values for this regression
 443       * @param bool $const
 444       */
 445      public function __construct($yValues, $xValues = [], $const = true)
 446      {
 447          //    Calculate number of points
 448          $nY = count($yValues);
 449          $nX = count($xValues);
 450  
 451          //    Define X Values if necessary
 452          if ($nX == 0) {
 453              $xValues = range(1, $nY);
 454          } elseif ($nY != $nX) {
 455              //    Ensure both arrays of points are the same size
 456              $this->error = true;
 457          }
 458  
 459          $this->valueCount = $nY;
 460          $this->xValues = $xValues;
 461          $this->yValues = $yValues;
 462      }
 463  }