├── .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 |
--------------------------------------------------------------------------------