BestFit.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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 float
  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 float
  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 float
  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 float
  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. /**
  182. * Return the goodness of fit for this regression.
  183. *
  184. * @param int $dp Number of places of decimal precision to return
  185. *
  186. * @return float
  187. */
  188. public function getGoodnessOfFitPercent($dp = 0)
  189. {
  190. if ($dp != 0) {
  191. return round($this->goodnessOfFit * 100, $dp);
  192. }
  193. return $this->goodnessOfFit * 100;
  194. }
  195. /**
  196. * Return the standard deviation of the residuals for this regression.
  197. *
  198. * @param int $dp Number of places of decimal precision to return
  199. *
  200. * @return float
  201. */
  202. public function getStdevOfResiduals($dp = 0)
  203. {
  204. if ($dp != 0) {
  205. return round($this->stdevOfResiduals, $dp);
  206. }
  207. return $this->stdevOfResiduals;
  208. }
  209. /**
  210. * @param int $dp Number of places of decimal precision to return
  211. *
  212. * @return float
  213. */
  214. public function getSSRegression($dp = 0)
  215. {
  216. if ($dp != 0) {
  217. return round($this->SSRegression, $dp);
  218. }
  219. return $this->SSRegression;
  220. }
  221. /**
  222. * @param int $dp Number of places of decimal precision to return
  223. *
  224. * @return float
  225. */
  226. public function getSSResiduals($dp = 0)
  227. {
  228. if ($dp != 0) {
  229. return round($this->SSResiduals, $dp);
  230. }
  231. return $this->SSResiduals;
  232. }
  233. /**
  234. * @param int $dp Number of places of decimal precision to return
  235. *
  236. * @return float
  237. */
  238. public function getDFResiduals($dp = 0)
  239. {
  240. if ($dp != 0) {
  241. return round($this->DFResiduals, $dp);
  242. }
  243. return $this->DFResiduals;
  244. }
  245. /**
  246. * @param int $dp Number of places of decimal precision to return
  247. *
  248. * @return float
  249. */
  250. public function getF($dp = 0)
  251. {
  252. if ($dp != 0) {
  253. return round($this->f, $dp);
  254. }
  255. return $this->f;
  256. }
  257. /**
  258. * @param int $dp Number of places of decimal precision to return
  259. *
  260. * @return float
  261. */
  262. public function getCovariance($dp = 0)
  263. {
  264. if ($dp != 0) {
  265. return round($this->covariance, $dp);
  266. }
  267. return $this->covariance;
  268. }
  269. /**
  270. * @param int $dp Number of places of decimal precision to return
  271. *
  272. * @return float
  273. */
  274. public function getCorrelation($dp = 0)
  275. {
  276. if ($dp != 0) {
  277. return round($this->correlation, $dp);
  278. }
  279. return $this->correlation;
  280. }
  281. /**
  282. * @return float[]
  283. */
  284. public function getYBestFitValues()
  285. {
  286. return $this->yBestFitValues;
  287. }
  288. protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const)
  289. {
  290. $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0;
  291. foreach ($this->xValues as $xKey => $xValue) {
  292. $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
  293. $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
  294. if ($const) {
  295. $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
  296. } else {
  297. $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
  298. }
  299. $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
  300. if ($const) {
  301. $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
  302. } else {
  303. $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
  304. }
  305. }
  306. $this->SSResiduals = $SSres;
  307. $this->DFResiduals = $this->valueCount - 1 - $const;
  308. if ($this->DFResiduals == 0.0) {
  309. $this->stdevOfResiduals = 0.0;
  310. } else {
  311. $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
  312. }
  313. if (($SStot == 0.0) || ($SSres == $SStot)) {
  314. $this->goodnessOfFit = 1;
  315. } else {
  316. $this->goodnessOfFit = 1 - ($SSres / $SStot);
  317. }
  318. $this->SSRegression = $this->goodnessOfFit * $SStot;
  319. $this->covariance = $SScov / $this->valueCount;
  320. $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - pow($sumX, 2)) * ($this->valueCount * $sumY2 - pow($sumY, 2)));
  321. $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
  322. $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
  323. if ($this->SSResiduals != 0.0) {
  324. if ($this->DFResiduals == 0.0) {
  325. $this->f = 0.0;
  326. } else {
  327. $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
  328. }
  329. } else {
  330. if ($this->DFResiduals == 0.0) {
  331. $this->f = 0.0;
  332. } else {
  333. $this->f = $this->SSRegression / $this->DFResiduals;
  334. }
  335. }
  336. }
  337. /**
  338. * @param float[] $yValues
  339. * @param float[] $xValues
  340. * @param bool $const
  341. */
  342. protected function leastSquareFit(array $yValues, array $xValues, $const)
  343. {
  344. // calculate sums
  345. $x_sum = array_sum($xValues);
  346. $y_sum = array_sum($yValues);
  347. $meanX = $x_sum / $this->valueCount;
  348. $meanY = $y_sum / $this->valueCount;
  349. $mBase = $mDivisor = $xx_sum = $xy_sum = $yy_sum = 0.0;
  350. for ($i = 0; $i < $this->valueCount; ++$i) {
  351. $xy_sum += $xValues[$i] * $yValues[$i];
  352. $xx_sum += $xValues[$i] * $xValues[$i];
  353. $yy_sum += $yValues[$i] * $yValues[$i];
  354. if ($const) {
  355. $mBase += ($xValues[$i] - $meanX) * ($yValues[$i] - $meanY);
  356. $mDivisor += ($xValues[$i] - $meanX) * ($xValues[$i] - $meanX);
  357. } else {
  358. $mBase += $xValues[$i] * $yValues[$i];
  359. $mDivisor += $xValues[$i] * $xValues[$i];
  360. }
  361. }
  362. // calculate slope
  363. $this->slope = $mBase / $mDivisor;
  364. // calculate intersect
  365. if ($const) {
  366. $this->intersect = $meanY - ($this->slope * $meanX);
  367. } else {
  368. $this->intersect = 0;
  369. }
  370. $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, $meanX, $meanY, $const);
  371. }
  372. /**
  373. * Define the regression.
  374. *
  375. * @param float[] $yValues The set of Y-values for this regression
  376. * @param float[] $xValues The set of X-values for this regression
  377. * @param bool $const
  378. */
  379. public function __construct($yValues, $xValues = [], $const = true)
  380. {
  381. // Calculate number of points
  382. $nY = count($yValues);
  383. $nX = count($xValues);
  384. // Define X Values if necessary
  385. if ($nX == 0) {
  386. $xValues = range(1, $nY);
  387. } elseif ($nY != $nX) {
  388. // Ensure both arrays of points are the same size
  389. $this->error = true;
  390. }
  391. $this->valueCount = $nY;
  392. $this->xValues = $xValues;
  393. $this->yValues = $yValues;
  394. }
  395. }