├── .gitignore ├── Excel Regression.xlsx ├── Tests ├── phpunit.xml └── Regression │ ├── MyReg.csv │ ├── RegressionTest.php │ └── MatrixTest.php ├── src └── Regression │ ├── MatrixException.php │ ├── RegressionException.php │ ├── CsvImport.php │ ├── Regression.php │ └── Matrix.php ├── composer.json ├── README.md ├── bootstrap.php ├── LICENSE └── Demos └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Excel Regression.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blazemeter/PHP-Multivariate-Regression/master/Excel Regression.xlsx -------------------------------------------------------------------------------- /Tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | . 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Regression/MatrixException.php: -------------------------------------------------------------------------------- 1 | 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Demos/index.php: -------------------------------------------------------------------------------- 1 | setX(new Matrix($predictors)); 42 | $regression->setY(new Matrix($predicted)); 43 | $regression->exec(); 44 | 45 | echo "Coefficients:" . PHP_EOL; 46 | print_r($regression->getCoefficients()); 47 | 48 | echo "StdErr:" . PHP_EOL; 49 | print_r($regression->getStandardError()); 50 | 51 | echo "Coef P values:" . PHP_EOL; 52 | print_r($regression->getPValues()); 53 | -------------------------------------------------------------------------------- /src/Regression/CsvImport.php: -------------------------------------------------------------------------------- 1 | 8 | * @author James Pirruccello 9 | */ 10 | 11 | namespace Regression; 12 | 13 | class CsvImport 14 | { 15 | /** 16 | * @example $reg->loadCSV('abc.csv',array(0), array(1,2,3)); 17 | * @param string $file 18 | * @param array $ycol 19 | * @param array $xcol 20 | * @param Boolean $hasHeader 21 | * @return array 22 | */ 23 | static public function loadCsv($file, array $ycol, array $xcol, $hasHeader = true) 24 | { 25 | $xarray = array(); 26 | $yarray = array(); 27 | $handle = fopen($file, "r"); 28 | 29 | //if first row has headers.. ignore 30 | if($hasHeader){ 31 | $data = fgetcsv($handle); 32 | } 33 | //get the data into array 34 | while(($data = fgetcsv($handle)) !== false){ 35 | $rawData[] = array($data); 36 | } 37 | $sampleSize = count($rawData); 38 | 39 | $r = 0; 40 | while($r < $sampleSize){ 41 | $xarray[] = self::getArray($rawData, $xcol, $r); 42 | $yarray[] = self::getArray($rawData, $ycol, $r); //y always has 1 col! 43 | $r++; 44 | } 45 | 46 | return array( 47 | 'x' => $xarray, 48 | 'y' => $yarray, 49 | ); 50 | } 51 | 52 | static public function getArray($rawData, $cols, $r) 53 | { 54 | $arr = array(); 55 | foreach($cols as $val){ 56 | $arr[] = $rawData[$r][0][$val]; 57 | } 58 | return $arr; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/Regression/MyReg.csv: -------------------------------------------------------------------------------- 1 | CollegeGPA,HSGPA,SAT,RECO 2 | 2.04,2.01,1070,5 3 | 2.56,3.4,1254,6 4 | 3.75,3.68,1466,6 5 | 1.1,1.54,706,4 6 | 3,3.32,1160,5 7 | 0.05,0.33,756,3 8 | 1.38,0.36,1058,2 9 | 1.5,1.97,1008,7 10 | 1.38,2.03,1104,4 11 | 4.01,2.05,1200,7 12 | 1.5,2.13,896,7 13 | 1.29,1.34,848,3 14 | 1.9,1.51,958,5 15 | 3.11,3.12,1246,6 16 | 1.92,2.14,1106,4 17 | 0.81,2.6,790,5 18 | 1.01,1.9,954,4 19 | 3.66,3.06,1500,6 20 | 2,1.6,1046,5 21 | 2.05,1.96,1054,4 22 | 2.6,1.96,1198,6 23 | 2.55,1.56,940,3 24 | 0.38,1.6,456,6 25 | 2.48,1.92,1150,7 26 | 2.74,3.09,636,6 27 | 1.77,0.78,744,5 28 | 1.61,2.12,644,5 29 | 0.99,1.85,842,3 30 | 1.62,1.78,852,5 31 | 2.03,1.03,1170,3 32 | 3.5,3.44,1034,10 33 | 3.18,2.42,1202,5 34 | 2.39,1.74,1018,5 35 | 1.48,1.89,1180,5 36 | 1.54,1.43,952,3 37 | 1.57,1.64,1038,4 38 | 2.46,2.69,1090,6 39 | 2.42,1.79,694,5 40 | 2.11,2.72,1096,6 41 | 2.04,2.15,1114,5 42 | 1.68,2.22,1256,6 43 | 1.64,1.55,1208,5 44 | 2.41,2.34,820,6 45 | 2.1,2.92,1222,4 46 | 1.4,2.1,1120,5 47 | 2.03,1.64,886,4 48 | 1.99,2.83,1126,7 49 | 2.24,1.76,1158,4 50 | 0.45,1.81,676,6 51 | 2.31,2.68,1214,7 52 | 2.41,2.55,1136,6 53 | 2.56,2.7,1264,6 54 | 2.5,1.66,1116,3 55 | 2.92,2.23,1292,4 56 | 2.35,2.01,604,5 57 | 2.82,1.24,854,6 58 | 1.8,1.95,814,6 59 | 1.29,1.73,778,3 60 | 1.68,1.08,800,2 61 | 3.44,3.46,1424,7 62 | 1.9,3.01,950,6 63 | 2.06,0.54,1056,3 64 | 3.3,3.2,956,8 65 | 1.8,1.5,1352,5 66 | 2,1.71,852,5 67 | 1.68,1.99,1168,5 68 | 1.94,2.76,970,6 69 | 0.97,1.56,776,4 70 | 1.12,1.78,854,6 71 | 1.31,1.32,1232,5 72 | 1.68,0.87,1140,6 73 | 3.09,1.75,1084,4 74 | 1.87,1.41,954,2 75 | 2,2.77,1000,4 76 | 2.39,1.78,1084,4 77 | 1.5,1.34,1058,4 78 | 1.82,1.52,816,5 79 | 1.8,2.97,1146,7 80 | 2.01,1.75,1000,6 81 | 1.88,1.64,856,4 82 | 1.64,1.8,798,4 83 | 2.42,3.37,1324,6 84 | 0.22,1.15,704,6 85 | 2.31,1.72,1222,5 86 | 0.95,2.27,948,6 87 | 1.99,2.85,1182,8 88 | 1.86,2.21,1000,6 89 | 1.79,1.94,910,6 90 | 3.02,4.25,1374,9 91 | 1.85,1.83,1014,6 92 | 1.98,2.75,1420,7 93 | 2.15,1.71,400,6 94 | 1.46,2.2,998,7 95 | 2.29,2.13,776,6 96 | 2.39,2.38,1134,7 97 | 1.8,1.64,772,4 98 | 2.64,1.87,1304,6 99 | 2.08,2.53,1212,4 100 | 0.7,1.78,818,6 101 | 0.89,1.2,864,2 102 | -------------------------------------------------------------------------------- /src/Regression/Regression.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | * Class for computing multiple linear regression of the form 25 | * y=a+b1x1+b2x2+b3x3... 26 | * 27 | * @author shankar 28 | * @author James Pirruccello 29 | * 30 | */ 31 | 32 | namespace Regression; 33 | 34 | use Regression\Matrix; 35 | use Regression\RegressionException; 36 | 37 | class Regression 38 | { 39 | 40 | protected $SSEScalar; //sum of squares due to error 41 | protected $SSRScalar; //sum of squares due to regression 42 | protected $SSTOScalar; //Total sum of squares 43 | protected $RSquare; //R square 44 | protected $F; //F statistic 45 | protected $stderrors = array(); //standard errror array 46 | protected $tstats = array(); //t statistics array 47 | protected $pvalues = array(); //p values array 48 | protected $coefficients; //regression coefficients Matrix object 49 | protected $covariance; //covariance Matrix object 50 | protected $x; //Matrix object holding independent vars 51 | protected $y; //Matrix object holding dependent vars 52 | 53 | /* 54 | * Prepend a column of 1s to the matrix of independent variables? 55 | */ 56 | protected $generateIntercept = true; 57 | 58 | public function setX(Matrix $x) 59 | { 60 | if($this->generateIntercept){ 61 | $x = $this->generateInterceptColumn($x); 62 | } 63 | $this->x = $x; 64 | } 65 | 66 | public function generateInterceptColumn(Matrix $m) 67 | { 68 | return $m->addColumn(array_fill(0, $m->getNumRows(), 1), 0); 69 | } 70 | 71 | public function setY(Matrix $y) 72 | { 73 | $this->y = $y; 74 | } 75 | 76 | public function getSSE() 77 | { 78 | return $this->SSEScalar; 79 | } 80 | 81 | public function getSSR() 82 | { 83 | return $this->SSRScalar; 84 | } 85 | 86 | public function getSSTO() 87 | { 88 | return $this->SSTOScalar; 89 | } 90 | 91 | public function getRSQUARE() 92 | { 93 | return $this->RSquare; 94 | } 95 | 96 | public function getF() 97 | { 98 | return $this->F; 99 | } 100 | 101 | public function getCoefficients() 102 | { 103 | return $this->coefficients; 104 | } 105 | 106 | public function getStandardError() 107 | { 108 | return $this->stderrors; 109 | } 110 | 111 | public function getTStats() 112 | { 113 | return $this->tstats; 114 | } 115 | 116 | public function getPValues() 117 | { 118 | return $this->pvalues; 119 | } 120 | 121 | public function exec() 122 | { 123 | if(!($this->x instanceof Matrix) 124 | || !($this->y instanceof Matrix)){ 125 | throw new RegressionException('X and Y must be matrices.'); 126 | } 127 | //(X'X)-1 128 | $XtXInv = $this->x->transpose()->multiply($this->x)->invert(); 129 | 130 | //coefficients = b = (X'X)-1 X'Y 131 | $this->coefficients = $XtXInv->multiply($this->x->transpose()->multiply($this->y)); 132 | 133 | //Generate b'X'Y, which we will reuse 134 | // b'X'Y = (Xb)'Y = (predictions)'Y 135 | $btXtY = $this->coefficients 136 | ->transpose() 137 | ->multiply( 138 | $this->x 139 | ->transpose()) 140 | ->multiply($this->y); 141 | 142 | $num_independent = $this->x->getNumColumns(); //note: intercept is included 143 | $sample_size = $this->x->getNumRows(); 144 | $dfTotal = $sample_size - 1; 145 | $dfModel = $num_independent - 1; 146 | $dfResidual = $dfTotal - $dfModel; 147 | 148 | /* 149 | * Create the unit vector, one row per sample 150 | */ 151 | $um = new Matrix(array_fill(0, $sample_size, array(1))); 152 | 153 | //SSR = b'X'Y - (Y'U(U')Y)/n 154 | $this->SSRScalar = $btXtY 155 | ->subtract( 156 | $this->y->transpose() 157 | ->multiply($um) 158 | ->multiply($um->transpose()) 159 | ->multiply($this->y) 160 | ->scalarDivide($sample_size)) 161 | ->getEntry(0, 0); 162 | 163 | //SSE = Y'Y - b'X'Y 164 | $this->SSEScalar = $this->y 165 | ->transpose() 166 | ->multiply($this->y) 167 | ->subtract($btXtY) 168 | ->getEntry(0, 0); 169 | 170 | $this->SSTOScalar = $this->SSRScalar + $this->SSEScalar; 171 | $this->RSquare = $this->SSTOScalar == 0 ? 1 : $this->SSRScalar / $this->SSTOScalar; 172 | $this->F = ($this->SSRScalar / $dfModel) / ($this->SSEScalar / $dfResidual); 173 | 174 | //MSE = SSE/(df) 175 | $MSE = $this->SSEScalar / $dfResidual; 176 | $this->covariance = $XtXInv->scalarMultiply($MSE); 177 | 178 | for($i = 0; $i < $num_independent; $i++){ 179 | //get the diagonal elements of the standard errors 180 | $searray[] = array(sqrt($this->covariance->getEntry($i, $i))); 181 | //compute the t-statistic 182 | $tstat[] = array($this->coefficients->getEntry($i, 0) / $searray[$i][0]); 183 | //compute the student p-value from the t-stat 184 | $pvalue[] = array($this->getStudentPValue($tstat[$i][0], $dfResidual)); 185 | 186 | //convert into 1-d vectors and store 187 | $this->stderrors[] = $searray[$i][0]; 188 | $this->tstats[] = $tstat[$i][0]; 189 | $this->pvalues[] = $pvalue[$i][0]; 190 | } 191 | 192 | return $this; 193 | } 194 | 195 | public function predict(Matrix $m) 196 | { 197 | if(!($this->coefficients instanceof Matrix)){ 198 | throw new RegressionException('Must run exec before calling predict'); 199 | } 200 | 201 | $m = $this->generateInterceptColumn($m); 202 | return $m->multiply($this->coefficients); 203 | } 204 | 205 | /** 206 | * Calculate the standard error for each observation in the training dataset, 207 | * given the covariance matrix. 208 | * 209 | * @return array One row per observation in the training data set 210 | */ 211 | public function computePredictionVariances() 212 | { 213 | $predictionVariances = array(); 214 | 215 | $unitCol = new Matrix(array_fill(0, $this->covariance->getNumColumns(), array(1))); 216 | $unitRow = new Matrix(array_fill(0, $this->covariance->getNumRows(), array(1))); 217 | 218 | $independentVariables = $this->x->getData(); 219 | 220 | foreach($independentVariables AS $k => $v){ 221 | $currentRowX = new Matrix(array($v)); 222 | 223 | //Multiply the elements of the covariance matrix by the square of 224 | //the predictor matrix on an elemental basis 225 | $rowVarianceMatrix = $this->covariance 226 | ->elementMultiply($currentRowX 227 | ->transpose() 228 | ->multiply($currentRowX)); 229 | 230 | //Get the sum of $predictionVariances via matrix math with unit vectors 231 | $predictionVariances[$k] = $unitRow 232 | ->transpose() 233 | ->multiply($rowVarianceMatrix) 234 | ->multiply($unitCol) 235 | ->getEntry(0, 0); 236 | } 237 | 238 | return $predictionVariances; 239 | } 240 | 241 | /** 242 | * @link http://home.ubalt.edu/ntsbarsh/Business-stat/otherapplets/pvalues.htm#rtdist 243 | * @param float $t_stat 244 | * @param float $deg_F 245 | * @return float 246 | */ 247 | protected function getStudentPValue($t_stat, $deg_F) 248 | { 249 | $t_stat = abs($t_stat); 250 | $mw = $t_stat / sqrt($deg_F); 251 | $th = atan2($mw, 1); 252 | if($deg_F == 1){ 253 | return 1.0 - $th / (M_PI / 2.0); 254 | } 255 | $sth = sin($th); 256 | $cth = cos($th); 257 | if($deg_F % 2 == 1){ 258 | return 1.0 - ($th + $sth * $cth * $this->statCom($cth * $cth, 2, $deg_F - 3, -1)) / (M_PI / 2.0); 259 | }else{ 260 | return 1.0 - ($sth * $this->statCom($cth * $cth, 1, $deg_F - 3, -1)); 261 | } 262 | } 263 | 264 | /** 265 | * @link http://home.ubalt.edu/ntsbarsh/Business-stat/otherapplets/pvalues.htm#rtdist 266 | * @param float $q 267 | * @param float $i 268 | * @param float $j 269 | * @param float $b 270 | * @return float 271 | */ 272 | protected function statCom($q, $i, $j, $b) 273 | { 274 | $zz = 1; 275 | $z = $zz; 276 | $k = $i; 277 | while($k <= $j){ 278 | $zz = $zz * $q * $k / ( $k - $b); 279 | $z = $z + $zz; 280 | $k = $k + 2; 281 | } 282 | return $z; 283 | } 284 | 285 | } 286 | -------------------------------------------------------------------------------- /Tests/Regression/RegressionTest.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | * @author shankar 25 | * @author carbocation 26 | * 27 | */ 28 | 29 | namespace Tests\Regression; 30 | 31 | use Regression\CsvImport; 32 | use Regression\Matrix; 33 | use Regression\Regression; 34 | use Regression\RegressionException; 35 | 36 | class RegressionTest extends \PHPUnit_Framework_TestCase 37 | { 38 | 39 | /** 40 | * @link http://davidmlane.com/hyperstat/prediction.html 41 | */ 42 | public function testRegressionUsingCSV() 43 | { 44 | /* @var $reg Regression */ 45 | $reg = new Regression(); 46 | $inputs = CsvImport::loadCsv(__DIR__ . DIRECTORY_SEPARATOR . 'MyReg.csv', array(0), array(1, 2, 3)); 47 | $reg->setX(new Matrix($inputs['x'])); 48 | $reg->setY(new Matrix($inputs['y'])); 49 | $reg->exec(); 50 | $testCoeff = new Matrix(array( 51 | array(-0.1533), 52 | array(0.3764), 53 | array(0.0012), 54 | array(0.0227), 55 | )); 56 | $this->assertEquals($testCoeff, $reg->getCoefficients(), '', .01); 57 | //Test predictions 58 | $correctPred = new Matrix(array(array(1.40379))); 59 | $pred = $reg->predict(new Matrix(array(array(1.2,864,2)))); 60 | $this->assertEquals($correctPred, $pred, '', 0.0001); 61 | //Test multiple predictions at once 62 | $correctPreds = new Matrix(array( 63 | array(1.40379), 64 | array(1.65637), 65 | )); 66 | $preds = $reg->predict(new Matrix(array( 67 | array(1.2,864,2), 68 | array(1.78,818,6), 69 | ))); 70 | $this->assertEquals($correctPreds, $preds, '', 0.0001); 71 | //Test variance 72 | $testPredictionStandardErrors = array( 73 | 0.0038498088711814, 74 | 0.018299433470814, 75 | 0.030324785704026, 76 | 0.011657487032846, 77 | 0.023266235535542, 78 | 0.023456926429428, 79 | 0.030995838187627, 80 | 0.013034457542298, 81 | 0.0075310300882851, 82 | 0.015908893974037, 83 | 0.012089520906566, 84 | 0.01178346958416, 85 | 0.0061310782645288, 86 | 0.012719441143792, 87 | 0.0083160606318891, 88 | 0.016366625342742, 89 | 0.006463074210298, 90 | 0.022087716897928, 91 | 0.0060491511434411, 92 | 0.0066402097332393, 93 | 0.0096630742614536, 94 | 0.01142751247168, 95 | 0.031243999241411, 96 | 0.016573658079668, 97 | 0.036506305518845, 98 | 0.020471526520851, 99 | 0.017249616748212, 100 | 0.016097878718472, 101 | 0.0054792821632401, 102 | 0.019444488579239, 103 | 0.041234608687803, 104 | 0.0071297452106818, 105 | 0.0044251644376152, 106 | 0.006828445589008, 107 | 0.011024204693821, 108 | 0.006148359335231, 109 | 0.0062501708780287, 110 | 0.011459633513013, 111 | 0.0065211702224301, 112 | 0.0044282307634107, 113 | 0.0092795302527959, 114 | 0.012093752314078, 115 | 0.0092727762594761, 116 | 0.022873513812148, 117 | 0.0044972318523185, 118 | 0.0063724437626945, 119 | 0.0092568020303543, 120 | 0.0084228649684506, 121 | 0.01500958450904, 122 | 0.010241561306229, 123 | 0.0055412274854279, 124 | 0.0088051180632777, 125 | 0.013395189576577, 126 | 0.014039866586934, 127 | 0.018804830630234, 128 | 0.016894794503078, 129 | 0.0086587697839662, 130 | 0.016685295483652, 131 | 0.020629539265687, 132 | 0.021540949858001, 133 | 0.01370025116302, 134 | 0.023986529602609, 135 | 0.020253059906565, 136 | 0.022062194062494, 137 | 0.0056329489761745, 138 | 0.0058678520459685, 139 | 0.0086836426669565, 140 | 0.008934823388784, 141 | 0.0085600104598513, 142 | 0.018037260645529, 143 | 0.03571022410914, 144 | 0.006619523074058, 145 | 0.020635863542516, 146 | 0.02035027709489, 147 | 0.0065929688704512, 148 | 0.0086443651336172, 149 | 0.0073940863329166, 150 | 0.010196242290858, 151 | 0.0079221384264438, 152 | 0.0069186706192648, 153 | 0.0093493852111766, 154 | 0.018784517067726, 155 | 0.021603190882945, 156 | 0.010232276146514, 157 | 0.0052373183147573, 158 | 0.016568139527064, 159 | 0.0046548097926346, 160 | 0.0064564783474433, 161 | 0.039090708334733, 162 | 0.0070463222763373, 163 | 0.018616637453404, 164 | 0.036567854663536, 165 | 0.010380252545671, 166 | 0.010112486053719, 167 | 0.0099438523710902, 168 | 0.0093285360027556, 169 | 0.01625372784545, 170 | 0.014681237832734, 171 | 0.0094137632808407, 172 | 0.020011163777876, 173 | ); 174 | $this->assertEquals($testPredictionStandardErrors, $reg->computePredictionVariances(), '', 0.01); 175 | } 176 | 177 | /** 178 | * Tally using attached excel workbook - It computes regression using excel 179 | */ 180 | public function testRegressionPassingArrays() 181 | { 182 | /* 183 | * independent vars.. Note that 1 has *not* been added to the 184 | * first column for computing the intercept. This is done internally 185 | * and automatically for the independent variables. 186 | */ 187 | $x = array( 188 | array(8, 2), 189 | array(40.5, 24.5), 190 | array(4.5, .5), 191 | array(.5, 2), 192 | array(4.5, 4.5), 193 | array(7, 8), 194 | array(24.5, 40.5), 195 | array(4.5, 2), 196 | array(32, 24.5), 197 | array(.5, 4.5), 198 | ); 199 | //dependent matrix..note it is a 2d array 200 | $y = array(array(4.5), 201 | array(22.5), 202 | array(2), 203 | array(.5), 204 | array(18), 205 | array(2), 206 | array(32), 207 | array(4.5), 208 | array(40.5), 209 | array(2)); 210 | 211 | /* @var $reg Regression */ 212 | $reg = new Regression(); 213 | $reg->setX(new Matrix($x)); 214 | $reg->setY(new Matrix($y)); 215 | $reg->exec(); //go! 216 | //all our expected values for the above dataset. 217 | $testSSEScalar = 447.808; 218 | $testSSRScalar = 1448.2166; 219 | $testSSTOScalar = 1896.025; 220 | $testRsquare = 0.76381; 221 | $testF = 11.3190; 222 | $testCoeff = new Matrix(array( 223 | array(1.564), 224 | array(0.3787), 225 | array(0.5747), 226 | )); 227 | $testStdErr = array(3.4901, 0.3279, 0.34303); 228 | $testTstats = array(0.4483, 1.1546, 1.6754); 229 | $testPValues = array(0.6674, 0.2861, 0.1377); 230 | 231 | //confirm our math..note use of foating point accuracy flag in tests! 232 | $this->assertEquals($testSSEScalar, $reg->getSSE(), '', .01); 233 | $this->assertEquals($testSSRScalar, $reg->getSSR(), '', .01); 234 | $this->assertEquals($testSSTOScalar, $reg->getSSTO(), '', .01); 235 | $this->assertEquals($testRsquare, $reg->getRSQUARE(), '', .01); 236 | $this->assertEquals($testF, $reg->getF(), '', .01); 237 | $this->assertEquals($testCoeff, $reg->getCoefficients(), '', .01); 238 | $this->assertEquals($testStdErr, $reg->getStandardError(), '', .01); 239 | $this->assertEquals($testTstats, $reg->getTStats(), '', .01); 240 | $this->assertEquals($testPValues, $reg->getPValues(), '', .01); 241 | } 242 | 243 | /** 244 | * @expectedException Regression\RegressionException 245 | */ 246 | public function testPredictionException() 247 | { 248 | $x = array( 249 | array(8, 2), 250 | array(40.5, 24.5), 251 | array(4.5, .5), 252 | array(.5, 2), 253 | array(4.5, 4.5), 254 | array(7, 8), 255 | array(24.5, 40.5), 256 | array(4.5, 2), 257 | array(32, 24.5), 258 | array(.5, 4.5), 259 | ); 260 | //dependent matrix..note it is a 2d array 261 | $y = array(array(4.5), 262 | array(22.5), 263 | array(2), 264 | array(.5), 265 | array(18), 266 | array(2), 267 | array(32), 268 | array(4.5), 269 | array(40.5), 270 | array(2)); 271 | $reg = new Regression(); 272 | $reg->setX(new Matrix($x)); 273 | $reg->setY(new Matrix($y)); 274 | $reg->predict(new Matrix(array(array(3, 4)))); 275 | 276 | } 277 | 278 | /** 279 | * @expectedException Regression\RegressionException 280 | */ 281 | public function testExecException() 282 | { 283 | $reg = new Regression(); 284 | $reg->exec(); 285 | 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /Tests/Regression/MatrixTest.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | * @author shankar 25 | * @author carbocation 26 | * 27 | */ 28 | 29 | namespace Tests\Regression; 30 | 31 | use Regression\Matrix; 32 | use Regression\MatrixException; 33 | 34 | class MatrixTest extends \PHPUnit_Framework_TestCase 35 | { 36 | 37 | public function testCanCreate2dMatrix() 38 | { 39 | $arr = array( 40 | array(1, 2), 41 | array(3, 4) 42 | ); 43 | $m = new Matrix($arr); 44 | $this->assertInstanceOf('Regression\Matrix', $m); 45 | } 46 | 47 | /** 48 | * test console display function to ensure 100% code coverage 49 | */ 50 | public function testMatrixDisplay() 51 | { 52 | $arr = array( 53 | array(1, 2), 54 | array(3, 4) 55 | ); 56 | $m = new Matrix($arr); 57 | $m->displayMatrix(); 58 | } 59 | 60 | /** 61 | * @expectedException Regression\MatrixException 62 | */ 63 | public function testInvalidMatrixException() 64 | { 65 | $arr = array( 66 | array(2, 3), 67 | array(1) 68 | ); 69 | $m = new Matrix($arr); 70 | } 71 | 72 | public function testNotSquare() 73 | { 74 | $arr = array(array(1, 2), array(3, 4), array(5, 6)); 75 | $m = new Matrix($arr); 76 | $this->assertFalse($m->isSquareMatrix()); 77 | } 78 | 79 | public function testAdd() 80 | { 81 | $arr1 = array( 82 | array(1, 2, 3, 4), 83 | array(4, 5, 6, 7) 84 | ); 85 | $arr2 = array( 86 | array(1, 2, 3, 4), 87 | array(4, 5, 6, 7) 88 | ); 89 | $mat1 = new Matrix($arr1); 90 | $mat2 = new Matrix($arr2); 91 | $ret = $mat1->add($mat2); 92 | 93 | $testRet = array(array(2, 4, 6, 8), array(8, 10, 12, 14)); 94 | $this->assertSame($ret->getData(), $testRet); 95 | } 96 | 97 | /** 98 | * @expectedException Regression\MatrixException 99 | */ 100 | public function testAddException() 101 | { 102 | $arr1 = array( 103 | array(1, 2, 3, 4), 104 | array(4, 5, 6, 7) 105 | ); 106 | $arr2 = array( 107 | array(1, 2, 3) 108 | ); 109 | $mat1 = new Matrix($arr1); 110 | $mat2 = new Matrix($arr2); 111 | $ret = $mat1->add($mat2); 112 | } 113 | 114 | public function testSubtract() 115 | { 116 | $arr1 = array( 117 | array(1, 2, 3, 4), 118 | array(4, 5, 6, 7) 119 | ); 120 | $arr2 = array( 121 | array(1, 2, 3, 4), 122 | array(4, 5, 6, 7) 123 | ); 124 | $mat1 = new Matrix($arr1); 125 | $mat2 = new Matrix($arr2); 126 | $ret = $mat1->subtract($mat2); 127 | 128 | $testRet = array(array(0, 0, 0, 0), array(0, 0, 0, 0)); 129 | $this->assertSame($ret->getData(), $testRet); 130 | } 131 | 132 | /** 133 | * @expectedException Regression\MatrixException 134 | */ 135 | public function testSubtractException() 136 | { 137 | $arr1 = array( 138 | array(1, 2, 3, 4), 139 | array(4, 5, 6, 7) 140 | ); 141 | $arr2 = array( 142 | array(1, 2, 3) 143 | ); 144 | $mat1 = new Matrix($arr1); 145 | $mat2 = new Matrix($arr2); 146 | $ret = $mat1->subtract($mat2); 147 | } 148 | 149 | public function testMultiply() 150 | { 151 | $arr1 = array( 152 | array(2, 0, -1, 1), 153 | array(1, 2, 0, 1) 154 | ); 155 | $arr2 = array( 156 | array(1, 5, -7), 157 | array(1, 1, 0), 158 | array(0, -1, 1), 159 | array(2, 0, 0), 160 | ); 161 | $mat1 = new Matrix($arr1); 162 | $mat2 = new Matrix($arr2); 163 | $ret = $mat1->multiply($mat2); 164 | 165 | $testRet = array(array(4, 11, -15), array(5, 7, -7)); 166 | $this->assertSame($ret->getData(), $testRet); 167 | } 168 | 169 | /** 170 | * @expectedException Regression\MatrixException 171 | */ 172 | public function testMultiplyException() 173 | { 174 | $arr1 = array( 175 | array(2, 0, -1, 1), 176 | array(1, 2, 0, 1) 177 | ); 178 | $arr2 = array( 179 | array(1, 5, -7), 180 | array(1, 1, 0), 181 | array(0, -1, 1) 182 | ); 183 | $mat1 = new Matrix($arr1); 184 | $mat2 = new Matrix($arr2); 185 | $ret = $mat1->multiply($mat2); 186 | } 187 | 188 | public function testDeterminant() 189 | { 190 | $arr1 = array( 191 | array(1, 2), 192 | array(3, 4) 193 | ); 194 | $mat1 = new Matrix($arr1); 195 | $det = $mat1->getDeterminant(); 196 | $this->assertSame(-2, $det); 197 | } 198 | 199 | /** 200 | * @expectedException Regression\MatrixException 201 | */ 202 | public function testDeterminantException() 203 | { 204 | $arr1 = array( 205 | array(1, 2), 206 | array(3, 4), 207 | array(5, 6) 208 | ); 209 | $mat1 = new Matrix($arr1); 210 | $det = $mat1->getDeterminant(); 211 | } 212 | 213 | public function testTranspose() 214 | { 215 | $arr1 = array( 216 | array(1, 2), 217 | array(3, 4) 218 | ); 219 | $mat1 = new Matrix($arr1); 220 | $t = $mat1->transpose(); 221 | $testarr = array(array(1, 3), array(2, 4)); 222 | $this->assertSame($t->getData(), $testarr); 223 | } 224 | 225 | public function testInverse() 226 | { 227 | $arr1 = array( 228 | array(4, 3), 229 | array(3, 2) 230 | ); 231 | $mat1 = new Matrix($arr1); 232 | $t = $mat1->invert()->getData(); 233 | $exp = array( 234 | array(-2, 3), 235 | array(3, -4) 236 | ); 237 | $this->assertSame($exp, $t); 238 | } 239 | 240 | /** 241 | * @expectedException Regression\MatrixException 242 | */ 243 | public function testInverseException() 244 | { 245 | $arr1 = array( 246 | array(4, 3), 247 | array(3, 2), 248 | array(4, 5) 249 | ); 250 | $mat1 = new Matrix($arr1); 251 | $t = $mat1->invert()->getData(); 252 | } 253 | 254 | public function testTrace() 255 | { 256 | $arr = array( 257 | array(1,2,3), 258 | array(4,5,6), 259 | array(7,8,9), 260 | ); 261 | $mat = new Matrix($arr); 262 | $t = $mat->getTrace(); 263 | 264 | $this->assertSame(15, $t); 265 | } 266 | 267 | /** 268 | * @expectedException Regression\MatrixException 269 | */ 270 | public function testNonArrayException() 271 | { 272 | $mat = new Matrix(5); 273 | 274 | } 275 | 276 | /** 277 | * @expectedException Regression\MatrixException 278 | */ 279 | public function testNonArrayOfArraysException() 280 | { 281 | $mat = new Matrix(array(5)); 282 | 283 | } 284 | 285 | /** 286 | * @expectedException Regression\MatrixException 287 | */ 288 | public function testTraceException() 289 | { 290 | $arr = array( 291 | array(1,2,3), 292 | array(4,5,6), 293 | array(7,8,9), 294 | array(10,11,12), 295 | ); 296 | $mat = new Matrix($arr); 297 | $t = $mat->getTrace(); 298 | 299 | } 300 | 301 | public function testCopy() 302 | { 303 | $arr = array( 304 | array(1, 2), 305 | array(3, 4) 306 | ); 307 | $m = new Matrix($arr); 308 | $n = $m->copy(); 309 | $this->assertInstanceOf('Regression\Matrix', $n); 310 | } 311 | 312 | public function testAddRow() 313 | { 314 | $arr = array( 315 | array(1, 2), 316 | array(3, 4), 317 | array(5, 6), 318 | ); 319 | $m = new Matrix($arr); 320 | $n = $m->addRow(array_fill(0, count($arr[0]), 1), 0); 321 | 322 | $arr2 = array( 323 | array(1, 1), 324 | array(1, 2), 325 | array(3, 4), 326 | array(5, 6), 327 | ); 328 | $o = new Matrix($arr2); 329 | 330 | $this->assertEquals($o, $n); 331 | } 332 | 333 | public function testAddColumn() 334 | { 335 | $arr = array( 336 | array(1, 2), 337 | array(3, 4), 338 | array(5, 6), 339 | ); 340 | $m = new Matrix($arr); 341 | $n = $m->addColumn(array_fill(0, count($arr), 1), 0); 342 | 343 | $arr2 = array( 344 | array(1, 1, 2), 345 | array(1, 3, 4), 346 | array(1, 5, 6), 347 | ); 348 | $o = new Matrix($arr2); 349 | 350 | $this->assertEquals($o, $n); 351 | } 352 | 353 | /** 354 | * @expectedException Regression\MatrixException 355 | */ 356 | public function testAddIllegalColumn() 357 | { 358 | $arr = array( 359 | array(1, 2), 360 | array(3, 4), 361 | array(5, 6), 362 | ); 363 | $m = new Matrix($arr); 364 | $m->addColumn(array_fill(0, count($arr)-1, 1), 0); 365 | } 366 | 367 | /** 368 | * @expectedException Regression\MatrixException 369 | */ 370 | public function testAddIllegalRow() 371 | { 372 | $arr = array( 373 | array(1, 2), 374 | array(3, 4), 375 | array(5, 6), 376 | ); 377 | $m = new Matrix($arr); 378 | $n = $m->addRow(array_fill(0, count($arr[0])-1, 1), 0); 379 | } 380 | 381 | public function testElementMultiply() 382 | { 383 | $arr = array( 384 | array(1, 2), 385 | array(3, 4), 386 | ); 387 | $m = new Matrix($arr); 388 | $n = $m->elementMultiply($m); 389 | 390 | $o = new Matrix(array( 391 | array(1, 4), 392 | array(9, 16), 393 | )); 394 | 395 | $this->assertEquals($n, $o); 396 | } 397 | 398 | public function testElementDivide() 399 | { 400 | $arr = array( 401 | array(1, 2), 402 | array(3, 4), 403 | ); 404 | $m = new Matrix($arr); 405 | 406 | $arr2 = array( 407 | array(1, 4), 408 | array(9, 16), 409 | ); 410 | $n = new Matrix($arr2); 411 | 412 | $o = $m->elementDivide($n); 413 | 414 | $p = new Matrix(array( 415 | array(1, 0.5), 416 | array(0.333333, 0.25), 417 | )); 418 | 419 | $this->assertEquals($o, $p, '', 0.0001); 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /src/Regression/Matrix.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | * Simple matrix manipulation library 25 | * 26 | * @author shankar 27 | * @author James Pirruccello 28 | */ 29 | 30 | namespace Regression; 31 | 32 | use Regression\MatrixException; 33 | 34 | class Matrix 35 | { 36 | 37 | //global vars 38 | protected $rows; 39 | protected $columns; 40 | protected $MainMatrix = array(); 41 | 42 | /** 43 | * Matrix Constructor 44 | * 45 | * Initialize the Matrix object. Throw an exception if jagged array is passed. 46 | * 47 | * @param array $array - The array 48 | */ 49 | public function __construct($array) 50 | { 51 | if(!is_array($array)){ 52 | throw new MatrixException('Please provide an array of arrays.'); 53 | } 54 | 55 | if(!is_array($array[0])){ 56 | throw new MatrixException('Please provide an array of arrays.'); 57 | } 58 | 59 | $numRows = count($array); 60 | $numCols = count($array[0]); 61 | 62 | for($i = 0; $i < $numRows; $i++){ 63 | //Do this count on every row to check for jagged matrices 64 | for($j = 0; $j < count($array[$i]); $j++){ 65 | $this->MainMatrix[$i][$j] = $array[$i][$j]; 66 | } 67 | } 68 | $this->rows = $numRows; 69 | $this->columns = $numCols; 70 | if(!$this->isValidMatrix()){ 71 | throw new MatrixException("Invalid matrix"); 72 | } 73 | } 74 | 75 | public function copy() 76 | { 77 | return clone $this; 78 | } 79 | 80 | /** 81 | * Display the matrix 82 | * Formatted display of matrix for debugging. 83 | */ 84 | public function displayMatrix() 85 | { 86 | $rows = $this->rows; 87 | $cols = $this->columns; 88 | echo "Order of the matrix is ($rows rows X $cols columns)\n"; 89 | for($r = 0; $r < $rows; $r++){ 90 | for($c = 0; $c < $cols; $c++){ 91 | echo $this->MainMatrix[$r][$c]; 92 | } 93 | echo PHP_EOL; 94 | } 95 | } 96 | 97 | /** 98 | * Get the inner array stored in matrix object 99 | * 100 | * @return array 101 | */ 102 | public function getData() 103 | { 104 | return $this->MainMatrix; 105 | } 106 | 107 | /** 108 | * Number of rows in the matrix 109 | * @return integer 110 | */ 111 | public function getNumRows() 112 | { 113 | return count($this->MainMatrix); 114 | } 115 | 116 | /** 117 | * Number of columns in the matrix 118 | * @return integer 119 | */ 120 | public function getNumColumns() 121 | { 122 | return count($this->MainMatrix[0]); 123 | } 124 | 125 | /** 126 | * Return element found at location $row, $col. 127 | * 128 | * @param int $row 129 | * @param int $col 130 | * @return object(depends on input) 131 | */ 132 | public function getEntry($row, $col) 133 | { 134 | return $this->MainMatrix[$row][$col]; 135 | } 136 | 137 | /** 138 | * Is this a square matrix? 139 | * 140 | * Determinants and inverses only exist for square matrices! 141 | * 142 | * @return bool 143 | */ 144 | public function isSquareMatrix() 145 | { 146 | if($this->rows == $this->columns){ 147 | return true; 148 | } 149 | 150 | return false; 151 | } 152 | 153 | /** 154 | * Subtract matrix2 from matrix object on which this method is called 155 | * @param Matrix $matrix2 156 | * @return Matrix Note that original matrix is left unchanged 157 | */ 158 | public function subtract(Matrix $matrix2) 159 | { 160 | return $this->elementIterator($this, $matrix2, '-'); 161 | } 162 | 163 | /** 164 | * Add matrix2 to matrix object that calls this method. 165 | * @param Model_Matrix $matrix2 166 | * @return Matrix Note that original matrix is left unchanged 167 | */ 168 | public function add(Matrix $matrix2) 169 | { 170 | return $this->elementIterator($this, $matrix2, '+'); 171 | } 172 | 173 | /** 174 | * Multiply each element of this matrix by the corresponding element 175 | * of matrix2 176 | * @param Matrix $matrix2 177 | * @return \Regression\Matrix 178 | * @throws MatrixException 179 | */ 180 | public function elementMultiply(Matrix $matrix2) 181 | { 182 | return $this->elementIterator($this, $matrix2, '*'); 183 | } 184 | 185 | /** 186 | * Divide each element of this matrix by the corresponding element 187 | * of matrix2 188 | * @param Matrix $matrix2 189 | * @return \Regression\Matrix 190 | * @throws MatrixException 191 | */ 192 | public function elementDivide(Matrix $matrix2) 193 | { 194 | return $this->elementIterator($this, $matrix2, '/'); 195 | } 196 | 197 | /** 198 | * Multiply matrix2 into matrix object that calls this method 199 | * @param Model_Matrix $matrix2 200 | * @return Matrix Note that original matrix is left unaltered 201 | */ 202 | public function multiply(Matrix $matrix2) 203 | { 204 | $newMatrix = array(); 205 | $rows1 = $this->rows; 206 | $columns1 = $this->columns; 207 | 208 | $columns2 = $matrix2->getNumColumns(); 209 | $rows2 = $matrix2->getNumRows(); 210 | if($columns1 != $rows2){ 211 | throw new MatrixException('Incompatible matrix types supplied'); 212 | } 213 | for($i = 0; $i < $rows1; $i++){ 214 | for($j = 0; $j < $columns2; $j++){ 215 | $newMatrix[$i][$j] = 0; 216 | for($ctr = 0; $ctr < $columns1; $ctr++){ 217 | $newMatrix[$i][$j] += $this->MainMatrix[$i][$ctr] * 218 | $matrix2->getEntry($ctr, $j); 219 | } 220 | } 221 | } 222 | return new Matrix($newMatrix); 223 | } 224 | 225 | /** 226 | * Multiply every element of matrix on which this method is called by the scalar 227 | * @param object $scalar 228 | * @return Matrix 229 | */ 230 | public function scalarMultiply($scalar) 231 | { 232 | $newMatrix = array(); 233 | $rows = $this->rows; 234 | $columns = $this->columns; 235 | 236 | $newMatrix = array(); 237 | for($i = 0; $i < $rows; $i++){ 238 | for($j = 0; $j < $columns; $j++){ 239 | $newMatrix[$i][$j] = $this->MainMatrix[$i][$j] * $scalar; 240 | } 241 | } 242 | return new Matrix($newMatrix); 243 | } 244 | 245 | /** 246 | * Divide every element of matrix on which this method is called by the scalar 247 | * @param object $scalar 248 | * @return Matrix 249 | */ 250 | public function scalarDivide($scalar) 251 | { 252 | $newMatrix = array(); 253 | $rows = $this->rows; 254 | $columns = $this->columns; 255 | 256 | for($i = 0; $i < $rows; $i++){ 257 | for($j = 0; $j < $columns; $j++){ 258 | $newMatrix[$i][$j] = $this->MainMatrix[$i][$j] / $scalar; 259 | } 260 | } 261 | return new Matrix($newMatrix); 262 | } 263 | 264 | /** 265 | * Return the sub-matrix after crossing out the $crossx and $crossy row and column respectively 266 | * Part of determinant expansion by minors method 267 | * @param int $crossX 268 | * @param int $crossY 269 | * @return Matrix 270 | */ 271 | public function getSubMatrix($crossX, $crossY) 272 | { 273 | $newMatrix = array(); 274 | $rows = $this->rows; 275 | $columns = $this->columns; 276 | 277 | $p = 0; // submatrix row counter 278 | for($i = 0; $i < $rows; $i++){ 279 | $q = 0; // submatrix col counter 280 | if($crossX != $i){ 281 | for($j = 0; $j < $columns; $j++){ 282 | if($crossY != $j){ 283 | $newMatrix[$p][$q] = $this->getEntry($i, $j); 284 | //$matrix[$i][$j]; 285 | $q++; 286 | } 287 | } 288 | $p++; 289 | } 290 | } 291 | return new Matrix($newMatrix); 292 | } 293 | 294 | /** 295 | * Compute the determinant of the square matrix on which this method is called 296 | * @link http://mathworld.wolfram.com/DeterminantExpansionbyMinors.html 297 | * @return object(depends on input) 298 | */ 299 | public function getDeterminant() 300 | { 301 | if(!$this->isSquareMatrix()){ 302 | throw new MatrixException("Not a square matrix!"); 303 | } 304 | $rows = $this->rows; 305 | $columns = $this->columns; 306 | $determinant = 0; 307 | if($rows == 1 && $columns == 1){ 308 | return $this->MainMatrix[0][0]; 309 | } 310 | if($rows == 2 && $columns == 2){ 311 | $determinant = $this->MainMatrix[0][0] * $this->MainMatrix[1][1] - 312 | $this->MainMatrix[0][1] * $this->MainMatrix[1][0]; 313 | }else{ 314 | for($j = 0; $j < $columns; $j++){ 315 | $subMatrix = $this->getSubMatrix(0, $j); 316 | if(fmod($j, 2) == 0){ 317 | $determinant += $this->MainMatrix[0][$j] * $subMatrix->getDeterminant(); 318 | }else{ 319 | $determinant -= $this->MainMatrix[0][$j] * $subMatrix->getDeterminant(); 320 | } 321 | } 322 | } 323 | return $determinant; 324 | } 325 | 326 | /** 327 | * Compute the transpose of matrix on which this method is called (invert rows and columns) 328 | * @return Matrix 329 | */ 330 | public function transpose() 331 | { 332 | $newArray = array(); 333 | $rows = $this->rows; 334 | $columns = $this->columns; 335 | 336 | for($i = 0; $i < $rows; $i++){ 337 | for($j = 0; $j < $columns; $j++){ 338 | $newArray[$j][$i] = $this->MainMatrix[$i][$j]; 339 | } 340 | } 341 | return new Matrix($newArray); 342 | } 343 | 344 | /** 345 | * Compute the inverse of the matrix on which this method is found (A*A(-1)=I) 346 | * (cofactor(a))T/(det a) 347 | * @link http://www.mathwords.com/i/inverse_of_a_matrix.htm 348 | * @return Matrix 349 | */ 350 | public function invert() 351 | { 352 | if(!$this->isSquareMatrix()){ 353 | throw new MatrixException("Not a square matrix!"); 354 | } 355 | 356 | $newMatrix = array(); 357 | $rows = $this->rows; 358 | $columns = $this->columns; 359 | 360 | for($i = 0; $i < $rows; $i++){ 361 | for($j = 0; $j < $columns; $j++){ 362 | $subMatrix = $this->getSubMatrix($i, $j); 363 | if(fmod($i + $j, 2) == 0){ 364 | $newMatrix[$i][$j] = ($subMatrix->getDeterminant()); 365 | }else{ 366 | $newMatrix[$i][$j] = -($subMatrix->getDeterminant()); 367 | } 368 | } 369 | } 370 | $cofactorMatrix = new Matrix($newMatrix); 371 | return $cofactorMatrix->transpose() 372 | ->scalarDivide($this->getDeterminant()); 373 | } 374 | 375 | public function getTrace() 376 | { 377 | if(!$this->isSquareMatrix()){ 378 | throw new MatrixException("Not a square matrix."); 379 | } 380 | 381 | return array_sum($this->getDiagonal()); 382 | } 383 | 384 | /** 385 | * Returns a copy of the matrix with a new column added before the current 386 | * column $beforeColumn. 387 | * 388 | * Number of rows in newColumn must match the number of rows in this Matrix. 389 | * 390 | * @param array $newColumn 391 | * @param int $beforeColumn 392 | * @return \Regression\Matrix 393 | */ 394 | public function addColumn(array $newColumn, $beforeColumn) 395 | { 396 | if($this->rows != count($newColumn)){ 397 | throw new MatrixException('New column does not have the same number ' 398 | . 'of rows as the current Matrix.'); 399 | } 400 | 401 | $newMatrix = array(); 402 | for($i = 0; $i < $this->rows; $i++){ 403 | $row = $this->MainMatrix[$i]; 404 | $part = array_splice($row, $beforeColumn, $this->columns); 405 | $row = array_merge($row, (array)$newColumn[$i], $part); 406 | 407 | $newMatrix[] = $row; 408 | } 409 | 410 | return new Matrix($newMatrix); 411 | } 412 | 413 | /** 414 | * Returns a copy of the matrix with a new row added before the current 415 | * row $beforeRow. 416 | * 417 | * Number of columns in newRow must match the number of columns in this Matrix. 418 | * 419 | * @param array $newRow 420 | * @param int $beforeRow 421 | * @return \Regression\Matrix 422 | */ 423 | public function addRow(array $newRow, $beforeRow) 424 | { 425 | if($this->columns != count($newRow)){ 426 | throw new MatrixException('New row does not have the same number ' 427 | . 'of columns as the current Matrix.'); 428 | } 429 | 430 | $newMatrix = $this->getData(); 431 | $part = array_splice($newMatrix, $beforeRow, count($newMatrix)); 432 | $newMatrix = array_merge($newMatrix, array($newRow), $part); 433 | 434 | return new Matrix($newMatrix); 435 | } 436 | 437 | protected function elementIterator(Matrix $matrix1, Matrix $matrix2, $operator) 438 | { 439 | $newMatrix = array(); 440 | $rows1 = $matrix1->rows; 441 | $rows2 = $matrix2->getNumRows(); 442 | $columns1 = $this->columns; 443 | $columns2 = $matrix2->getNumColumns(); 444 | 445 | if(($rows1 != $rows2) || ($columns1 != $columns2)){ 446 | throw new MatrixException('Matrices are not the same size!'); 447 | } 448 | 449 | for($i = 0; $i < $rows1; $i++){ 450 | for($j = 0; $j < $columns1; $j++){ 451 | $newMatrix[$i][$j] = $this->elementOperator( 452 | $matrix1->MainMatrix[$i][$j], 453 | $matrix2->getEntry($i, $j), 454 | $operator); 455 | } 456 | } 457 | return new Matrix($newMatrix); 458 | } 459 | 460 | protected function elementOperator($element1, $element2, $operator) 461 | { 462 | switch($operator){ 463 | case '*': 464 | return $element1 * $element2; 465 | break; 466 | case '+': 467 | return $element1 + $element2; 468 | break; 469 | case '-': 470 | return $element1 - $element2; 471 | break; 472 | case '/': 473 | return $element1 / $element2; 474 | break; 475 | default: 476 | throw new MatrixException('Invalid elemental operator specified'); 477 | } 478 | } 479 | 480 | /** 481 | * Is it a valid matrix? 482 | * 483 | * Returns 'False' if it is not a rectangular matrix 484 | * 485 | * @return bool 486 | */ 487 | protected function isValidMatrix() 488 | { 489 | for($i = 0; $i < $this->rows; $i++){ 490 | $numCol = count($this->MainMatrix [$i]); 491 | if($this->columns != $numCol){ 492 | return false; 493 | } 494 | } 495 | return true; 496 | } 497 | 498 | protected function getDiagonal() 499 | { 500 | $diagonal = array(); 501 | 502 | for($i = 0; $i < $this->getNumRows(); $i++){ 503 | for($j = 0; $j < $this->getNumColumns(); $j++){ 504 | if($i == $j){ 505 | $diagonal[] = $this->MainMatrix[$i][$j]; 506 | } 507 | } 508 | } 509 | 510 | return $diagonal; 511 | } 512 | 513 | } 514 | --------------------------------------------------------------------------------