BestFit.php 10 KB

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