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 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
   4  
   5  use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix;
   6  
   7  class PolynomialBestFit extends BestFit
   8  {
   9      /**
  10       * Algorithm type to use for best-fit
  11       * (Name of this Trend class).
  12       *
  13       * @var string
  14       */
  15      protected $bestFitType = 'polynomial';
  16  
  17      /**
  18       * Polynomial order.
  19       *
  20       * @var int
  21       */
  22      protected $order = 0;
  23  
  24      /**
  25       * Return the order of this polynomial.
  26       *
  27       * @return int
  28       */
  29      public function getOrder()
  30      {
  31          return $this->order;
  32      }
  33  
  34      /**
  35       * Return the Y-Value for a specified value of X.
  36       *
  37       * @param float $xValue X-Value
  38       *
  39       * @return float Y-Value
  40       */
  41      public function getValueOfYForX($xValue)
  42      {
  43          $retVal = $this->getIntersect();
  44          $slope = $this->getSlope();
  45          // @phpstan-ignore-next-line
  46          foreach ($slope as $key => $value) {
  47              if ($value != 0.0) {
  48                  $retVal += $value * $xValue ** ($key + 1);
  49              }
  50          }
  51  
  52          return $retVal;
  53      }
  54  
  55      /**
  56       * Return the X-Value for a specified value of Y.
  57       *
  58       * @param float $yValue Y-Value
  59       *
  60       * @return float X-Value
  61       */
  62      public function getValueOfXForY($yValue)
  63      {
  64          return ($yValue - $this->getIntersect()) / $this->getSlope();
  65      }
  66  
  67      /**
  68       * Return the Equation of the best-fit line.
  69       *
  70       * @param int $dp Number of places of decimal precision to display
  71       *
  72       * @return string
  73       */
  74      public function getEquation($dp = 0)
  75      {
  76          $slope = $this->getSlope($dp);
  77          $intersect = $this->getIntersect($dp);
  78  
  79          $equation = 'Y = ' . $intersect;
  80          // @phpstan-ignore-next-line
  81          foreach ($slope as $key => $value) {
  82              if ($value != 0.0) {
  83                  $equation .= ' + ' . $value . ' * X';
  84                  if ($key > 0) {
  85                      $equation .= '^' . ($key + 1);
  86                  }
  87              }
  88          }
  89  
  90          return $equation;
  91      }
  92  
  93      /**
  94       * Return the Slope of the line.
  95       *
  96       * @param int $dp Number of places of decimal precision to display
  97       *
  98       * @return float
  99       */
 100      public function getSlope($dp = 0)
 101      {
 102          if ($dp != 0) {
 103              $coefficients = [];
 104              foreach ($this->slope as $coefficient) {
 105                  $coefficients[] = round($coefficient, $dp);
 106              }
 107  
 108              // @phpstan-ignore-next-line
 109              return $coefficients;
 110          }
 111  
 112          return $this->slope;
 113      }
 114  
 115      public function getCoefficients($dp = 0)
 116      {
 117          // @phpstan-ignore-next-line
 118          return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
 119      }
 120  
 121      /**
 122       * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
 123       *
 124       * @param int $order Order of Polynomial for this regression
 125       * @param float[] $yValues The set of Y-values for this regression
 126       * @param float[] $xValues The set of X-values for this regression
 127       */
 128      private function polynomialRegression($order, $yValues, $xValues): void
 129      {
 130          // calculate sums
 131          $x_sum = array_sum($xValues);
 132          $y_sum = array_sum($yValues);
 133          $xx_sum = $xy_sum = $yy_sum = 0;
 134          for ($i = 0; $i < $this->valueCount; ++$i) {
 135              $xy_sum += $xValues[$i] * $yValues[$i];
 136              $xx_sum += $xValues[$i] * $xValues[$i];
 137              $yy_sum += $yValues[$i] * $yValues[$i];
 138          }
 139          /*
 140           *    This routine uses logic from the PHP port of polyfit version 0.1
 141           *    written by Michael Bommarito and Paul Meagher
 142           *
 143           *    The function fits a polynomial function of order $order through
 144           *    a series of x-y data points using least squares.
 145           *
 146           */
 147          $A = [];
 148          $B = [];
 149          for ($i = 0; $i < $this->valueCount; ++$i) {
 150              for ($j = 0; $j <= $order; ++$j) {
 151                  $A[$i][$j] = $xValues[$i] ** $j;
 152              }
 153          }
 154          for ($i = 0; $i < $this->valueCount; ++$i) {
 155              $B[$i] = [$yValues[$i]];
 156          }
 157          $matrixA = new Matrix($A);
 158          $matrixB = new Matrix($B);
 159          $C = $matrixA->solve($matrixB);
 160  
 161          $coefficients = [];
 162          for ($i = 0; $i < $C->getRowDimension(); ++$i) {
 163              $r = $C->get($i, 0);
 164              if (abs($r) <= 10 ** (-9)) {
 165                  $r = 0;
 166              }
 167              $coefficients[] = $r;
 168          }
 169  
 170          $this->intersect = array_shift($coefficients);
 171          $this->slope = $coefficients;
 172  
 173          $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0);
 174          foreach ($this->xValues as $xKey => $xValue) {
 175              $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
 176          }
 177      }
 178  
 179      /**
 180       * Define the regression and calculate the goodness of fit for a set of X and Y data values.
 181       *
 182       * @param int $order Order of Polynomial for this regression
 183       * @param float[] $yValues The set of Y-values for this regression
 184       * @param float[] $xValues The set of X-values for this regression
 185       */
 186      public function __construct($order, $yValues, $xValues = [])
 187      {
 188          parent::__construct($yValues, $xValues);
 189  
 190          if (!$this->error) {
 191              if ($order < $this->valueCount) {
 192                  $this->bestFitType .= '_' . $order;
 193                  $this->order = $order;
 194                  $this->polynomialRegression($order, $yValues, $xValues);
 195                  if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) {
 196                      $this->error = true;
 197                  }
 198              } else {
 199                  $this->error = true;
 200              }
 201          }
 202      }
 203  }