C++ matrix implementation
Matrix.hpp
Go to the documentation of this file.
1 
7 #ifndef MACHINE_LEARNING_MATRIX_HPP
8 #define MACHINE_LEARNING_MATRIX_HPP
9 
10 #include <vector>
11 #include <functional>
12 #include <stdexcept>
13 #include <algorithm>
14 #include <iostream>
15 #include <iomanip>
16 #include <cmath>
17 #include <set>
18 #include <complex>
19 #include "include/nr3/nr3.h"
20 #include "include/nr3/eigen_unsym.h"
21 #include "include/csv_reader/CSVReader.hpp"
22 
23 using namespace std;
24 
27 template<
28  typename T,
29  typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
30 class Matrix {
31  private:
32  size_t mRows;
33  size_t mCols;
34  std::vector<T> mData;
35 
40  void validateIndexes(size_t row, size_t col) const {
41  if (row < 0 or row >= mRows)
42  throw invalid_argument(
43  "Invalid row index (" + to_string(row) + "): should be between 0 and " + to_string(mRows - 1));
44  if (col < 0 or col >= mCols)
45  throw invalid_argument(
46  "Invalid column index (" + to_string(col) + "): should be between 0 and " + to_string(mCols - 1));
47  }
48 
54  static pair<Matrix, Matrix> eigsort(Matrix eigenvalues, Matrix eigenvectors) {
55 // if (eigenvalues.mCols != eigenvectors.mRows)
56 // throw runtime_error("Incompatible number of eigenvalues and eigenvectors");
57 
58  Matrix eigval(eigenvalues.mRows, eigenvalues.mCols, eigenvalues.mData);
59  Matrix eigvec(eigenvectors.mRows, eigenvectors.mCols, eigenvectors.mData);
60 
61  // keep the order of eigenvalues in this vector
62  vector<size_t> newOrder;
63  for (size_t i = 0; i < eigenvalues.nRows(); i++) {
64  int position = 0;
65  for (int j = 0; j < newOrder.size(); j++)
66  if (eigenvalues(i, 0) < eigenvalues(newOrder[j], 0))
67  position++;
68  newOrder.insert(newOrder.begin() + position, i);
69  }
70 
71  // order eigenvalues and eigenvectors by the value of the eigenvalues
72  for (size_t i = 0; i < newOrder.size(); i++) {
73  eigval(i, 0) = eigenvalues(newOrder[i], 0);
74 
75  for (int j = 0; j < eigenvectors.nRows(); j++) {
76  eigvec(static_cast<size_t>(j), i) = eigenvectors(j, newOrder[i]);
77  }
78  }
79 
80  return make_pair(eigval, eigvec);
81  }
82 
83  public:
84 
85  enum Axis { ALL, ROWS, COLUMNS };
86 
87  size_t nCols() const { return mCols; }
88 
89  size_t nRows() const { return mRows; }
90 
91  //region Constructors
92 
94  Matrix() {
95  mRows = mCols = 0;
96  }
97 
100  Matrix(size_t dimension) {
101  Matrix(dimension, dimension);
102  }
103 
107  Matrix(size_t rows, size_t cols)
108  : mRows(rows),
109  mCols(cols),
110  mData(rows * cols) {
111  }
112 
117  Matrix(size_t rows, size_t cols, const vector<T> &data)
118  : mRows(rows),
119  mCols(cols) {
120  if (data.size() != rows * cols)
121  throw invalid_argument("Matrix dimension incompatible with its initializing vector.");
122  mData = data;
123  }
124 
125  template<std::size_t N>
126  Matrix(size_t rows, size_t cols, T (&data)[N]) {
127  if (N != rows * cols)
128  throw invalid_argument("Matrix dimension incompatible with its initializing vector.");
129  vector<T> v(data, data + N);
130  Matrix(rows, cols, v);
131  }
132  //endregion
133 
134  //region Operators
135 
136  //region Scalar operators
137 
142  friend Matrix operator+(const Matrix &m, double value) {
143  Matrix result(m.mRows, m.mCols);
144 
145  #pragma omp parallel for collapse(2)
146  for (size_t i = 0; i < m.mRows; i++) {
147  for (size_t j = 0; j < m.mCols; j++) {
148  result(i, j) = value + m(i, j);
149  }
150  }
151 
152  return result;
153  }
154 
159  friend Matrix operator+(double value, const Matrix &m) {
160  return m + value;
161  }
162 
167  friend Matrix operator-(const Matrix &m, double value) {
168  return m + (-value);
169  }
170 
175  friend Matrix operator-(double value, const Matrix &m) {
176  return m - value;
177  }
178 
183  friend Matrix operator*(const Matrix &m, double value) {
184  Matrix result(m.mRows, m.mCols);
185 
186  #pragma omp parallel for collapse(2)
187  for (size_t i = 0; i < m.mRows; i++) {
188  for (size_t j = 0; j < m.mCols; j++) {
189  result(i, j) = value * m(i, j);
190  }
191  }
192 
193  return result;
194  }
195 
200  friend Matrix operator*(double value, const Matrix &m) {
201  return m * value;
202  }
203 
208  friend Matrix operator/(const Matrix &m, double value) {
209  Matrix result(m.mRows, m.mCols);
210 
211  #pragma omp parallel for collapse(2)
212  for (size_t i = 0; i < m.mRows; i++) {
213  for (size_t j = 0; j < m.mCols; j++) {
214  result(i, j) = m(i, j) / value;
215  }
216  }
217 
218  return result;
219  }
220 
225  friend Matrix operator/(double value, const Matrix &m) {
226  // division is not commutative, so a new method is implemented
227  Matrix result(m.mRows, m.mCols);
228 
229  #pragma omp parallel for collapse(2)
230  for (size_t i = 0; i < m.mRows; i++) {
231  for (size_t j = 0; j < m.mCols; j++) {
232  result(i, j) = value / m(i, j);
233  }
234  }
235 
236  return result;
237  }
238 
239  Matrix operator+=(double value) {
240  #pragma omp parallel for
241  for (int i = 0; i < mData.size(); i++)
242  mData[i] += value;
243  return *this;
244  }
245 
246  Matrix operator-=(double value) {
247  #pragma omp parallel for
248  for (int i = 0; i < mData.size(); i++)
249  mData[i] -= value;
250  return *this;
251  }
252 
253  Matrix operator*=(double value) {
254  #pragma omp parallel for
255  for (int i = 0; i < mData.size(); i++)
256  mData[i] *= value;
257  return *this;
258  }
259 
260  Matrix operator/=(double value) {
261  #pragma omp parallel for
262  for (int i = 0; i < mData.size(); i++)
263  mData[i] /= value;
264  return *this;
265  }
266  //endregion
267 
268  //region Matrix operators
269 
273  Matrix operator+(const Matrix &b) {
274  if (mRows != b.mRows || mCols != b.mCols)
275  throw invalid_argument("Cannot add these matrices: L = " + to_string(mRows) + "x" + to_string(mCols) + ", R = "
276  + to_string(b.mRows) + "x" + to_string(b.mCols));
277 
278  Matrix result(mRows, mCols);
279 
280  #pragma omp parallel for collapse(2)
281  for (size_t i = 0; i < mRows; i++) {
282  for (size_t j = 0; j < mCols; j++) {
283  result(i, j) = operator()(i, j) + b(i, j);
284  }
285  }
286 
287  return result;
288  }
289 
293  Matrix operator-(const Matrix &b) {
294  if (mRows != b.mRows || mCols != b.mCols)
295  throw invalid_argument(
296  "Cannot subtract these matrices: L = " + to_string(mRows) + "x" + to_string(mCols) + ", R = "
297  + to_string(b.mRows) + "x" + to_string(b.mCols));
298 
299  Matrix result(mRows, mCols);
300 
301  #pragma omp parallel for collapse(2)
302  for (size_t i = 0; i < mRows; i++) {
303  for (size_t j = 0; j < mCols; j++) {
304  result(i, j) = operator()(i, j) - b(i, j);
305  }
306  }
307 
308  return result;
309  }
310 
314  Matrix operator*(const Matrix &b) const {
315  if (mCols != b.mRows)
316  throw invalid_argument(
317  "Cannot multiply these matrices: L = " + to_string(this->mRows) + "x" +
318  to_string(this->mCols) + ", R = " + to_string(b.mRows) + "x" + to_string(b.mCols));
319 
320  Matrix result = zeros(mRows, b.mCols);
321 
322  #pragma omp parallel for if(result.mRows * result.mCols > 250)
323  for (size_t i = 0; i < result.mRows; i++) {
324  for (size_t k = 0; k < mCols; k++) {
325  double tmp = operator()(i, k);
326  for (size_t j = 0; j < result.mCols; j++) {
327  result(i, j) += tmp * b(k, j);
328  }
329  }
330  }
331 
332  return result;
333  }
334 
335  Matrix &operator+=(const Matrix &other) {
336  if (mRows != other.mRows || mCols != other.mCols)
337  throw invalid_argument("Cannot add these matrices: L = " + to_string(mRows) + "x" + to_string(mCols) + ", R = "
338  + to_string(other.mRows) + "x" + to_string(other.mCols));
339  #pragma omp parallel for collapse(2)
340  for (size_t i = 0; i < other.mRows; i++) {
341  for (size_t j = 0; j < other.mCols; j++) {
342  operator()(i, j) += other(i, j);
343  }
344  }
345 
346  return *this;
347  }
348 
349  Matrix &operator-=(const Matrix &other) {
350  if (mRows != other.mRows || mCols != other.mCols)
351  throw invalid_argument(
352  "Cannot subtract these matrices: L = " + to_string(mRows) + "x" + to_string(mCols) + ", R = "
353  + to_string(other.mRows) + "x" + to_string(other.mCols));
354 
355  #pragma omp parallel for collapse(2)
356  for (size_t i = 0; i < other.mRows; i++) {
357  for (size_t j = 0; j < other.mCols; j++) {
358  operator()(i, j) -= other(i, j);
359  }
360  }
361 
362  return *this;
363  }
364 
365  Matrix &operator*=(const Matrix &other) {
366  if (mCols != other.mRows)
367  throw invalid_argument(
368  "Cannot multiply these matrices: L " + to_string(mRows) + "x" +
369  to_string(mCols) + ", R " + to_string(other.mRows) + "x" + to_string(other.mCols));
370 
371  Matrix result(mRows, other.mCols);
372 
373  #pragma omp parallel for collapse(2)
374  // two loops iterate through every cell of the new matrix
375  for (size_t i = 0; i < result.mRows; i++) {
376  for (size_t j = 0; j < result.mCols; j++) {
377  // here we calculate the value of a single cell in our new matrix
378  result(i, j) = 0;
379  for (size_t ii = 0; ii < mCols; ii++)
380  result(i, j) += operator()(i, ii) * other(ii, j);
381  }
382  }
383 
384  mRows = result.mRows;
385  mCols = result.mCols;
386  mData = result.mData;
387  return *this;
388  }
389  //endregion
390 
391  //region Equality operators
392 
393  Matrix<int> operator==(const T &value) {
394  Matrix<int> result(mRows, mCols);
395 
396  #pragma omp parallel for collapse(2)
397  for (size_t i = 0; i < mRows; i++) {
398  for (size_t j = 0; j < mCols; j++) {
399  result(i, j) = operator()(i, j) == value;
400  }
401  }
402 
403  return result;
404  }
405 
406  bool operator==(const Matrix &other) {
407  if (mData.size() != other.mData.size() || mRows != other.mRows || mCols != other.mCols)
408  return false;
409 
410  for (int k = 0; k < mData.size(); k++) {
411  if (mData[k] != other.mData[k])return false;
412  }
413 
414  return true;
415  }
416 
417  Matrix operator!=(const double &value) {
418  // subtract 1 from everything: 0s become -1s, 1s become 0s
419  // negate everything: 0s remains 0s, -1s becomes 1s
420  return -((*this == value) - 1);
421  }
422 
423  bool operator!=(const Matrix &other) {
424  // subtract 1 from everything: 0s become -1s, 1s become 0s
425  // negate everything: 0s remains 0s, -1s becomes 1s
426  return !(*this == other);
427  }
428  //endregion
429 
433  Matrix result(this->mRows, this->mCols);
434 
435  #pragma omp parallel for collapse(2)
436  for (size_t i = 0; i < mCols; i++) {
437  for (size_t j = 0; j < mRows; j++) {
438  result(i, j) = -operator()(i, j);
439  }
440  }
441 
442  return result;
443  }
444 
445  //region Functors
446 
451  T &operator()(size_t i, size_t j) {
452  validateIndexes(i, j);
453  return mData[i * mCols + j];
454  }
455 
460  T operator()(size_t i, size_t j) const {
461  validateIndexes(i, j);
462  return mData[i * mCols + j];
463  }
464  //endregion
465  //endregion
466 
472  static Matrix fill(size_t rows, size_t cols, double value) {
473  Matrix result(rows, cols, vector<T>(rows * cols, value));
474  return result;
475  }
476 
481  static Matrix diagonal(size_t size, double value) {
482  Matrix result = zeros(size, size);
483  for (size_t i = 0; i < size; i++)
484  result(i, i) = value;
485 
486  return result;
487  }
488 
489  bool isSquare() const {
490  return mCols == mRows;
491  }
492 
495  if (!isSquare()) {
496  throw runtime_error("Can't get the diagonal, not a square matrix");
497  }
498 
499  Matrix result(mRows, 1);
500 
501  #pragma omp parallel
502  for (size_t i = 0; i < mRows; i++)
503  result(i, 0) = operator()(i, i);
504 
505  return result;
506  }
507 
511  static Matrix identity(size_t size) {
512  return diagonal(size, 1);
513  }
514 
519  static Matrix ones(size_t rows, size_t cols) {
520  return fill(rows, cols, 1);
521  }
522 
527  static Matrix zeros(size_t rows, size_t cols) {
528  return fill(rows, cols, 0);
529  }
530 
534  Matrix hadamard(const Matrix &b) {
535  if (mCols != b.mCols || mRows != b.mRows)
536  throw invalid_argument(
537  "Cannot multiply these matrices element-wise: L = " + to_string(mRows) + "x" +
538  to_string(mCols) + ", R = " + to_string(b.mRows) + "x" + to_string(b.mCols));
539 
540  Matrix result(mRows, mCols);
541 
542  #pragma omp parallel for collapse(2)
543  for (size_t i = 0; i < mRows; i++) {
544  for (size_t j = 0; j < mCols; j++) {
545  result(i, j) = operator()(i, j) * b(i, j);
546  }
547  }
548 
549  return result;
550  }
551 
556  Matrix submatrix(size_t row, size_t column) const {
557  Matrix result(mRows - 1, mCols - 1);
558 
559  size_t subi = 0;
560 
561  #pragma omp parallel for
562  for (size_t i = 0; i < mRows; i++) {
563  size_t subj = 0;
564  if (i == row) continue;
565  for (size_t j = 0; j < mCols; j++) {
566  if (j == column) continue;
567  result(subi, subj) = operator()(i, j);
568  subj++;
569  }
570  subi++;
571  }
572 
573  return result;
574  }
575 
581  double getMinor(size_t row, size_t column) const {
582 // the minor of a 2x2 a b is d c
583 // c d b a
584  if (mRows == 2 and mCols == 2) {
585  Matrix result(2, 2);
586  result(0, 0) = operator()(1, 1);
587  result(0, 1) = operator()(1, 0);
588  result(1, 0) = operator()(0, 1);
589  result(1, 1) = operator()(0, 0);
590  return result.determinant();
591  }
592 
593  return submatrix(row, column).determinant();
594  }
595 
600  double cofactor(size_t row, size_t column) const {
601  double minor;
602 
603  // special case for when our matrix is 2x2
604  if (mRows == 2 and mCols == 2) {
605  if (row == 0 and column == 0)
606  minor = operator()(1, 1);
607  else if (row == 1 and column == 1)
608  minor = operator()(0, 0);
609  else if (row == 0 and column == 1)
610  minor = operator()(1, 0);
611  else if (row == 1 and column == 0)
612  minor = operator()(0, 1);
613  } else
614  minor = this->getMinor(row, column);
615  return (row + column) % 2 == 0 ? minor : -minor;
616  }
617 
621  Matrix result(mRows, mCols);
622 
623  #pragma omp parallel for collapse(2)
624  for (size_t i = 0; i < mRows; i++) {
625  for (size_t j = 0; j < mCols; j++) {
626  result(i, j) = cofactor(i, j);
627  }
628  }
629  return result;
630  }
631 
634  Matrix adjugate() const {
635  return cofactorMatrix().transpose();
636  }
637 
641  Matrix inverse() const {
642  if (!isSquare())
643  throw runtime_error("Cannot invert a non-square matrix");
644 
645  double det = determinant();
646 
647  if (det == 0)
648  throw runtime_error("Matrix is singular");
649 
650  Matrix adj = adjugate();
651  return adjugate() / det;
652  };
653 
656  double determinant() const {
657  if (!isSquare()) {
658  throw runtime_error("Cannot calculate the determinant of a non-square matrix");
659  }
660 
661  size_t n = mRows;
662  double d = 0;
663  if (n == 2) {
664  return ((operator()(0, 0) * operator()(1, 1)) -
665  (operator()(1, 0) * operator()(0, 1)));
666  } else {
667  #pragma omp parallel for reduction (+:d)
668  for (size_t c = 0; c < n; c++) {
669  d += pow(-1, c) * operator()(0, c) * submatrix(0, c).determinant();
670  }
671  return d;
672  }
673  }
674 
677  Matrix transpose() const {
678  Matrix result(mCols, mRows);
679 
680  #pragma omp parallel for collapse(2)
681  for (size_t i = 0; i < mRows; i++) {
682  for (size_t j = 0; j < mCols; j++) {
683  result(j, i) = operator()(i, j);
684  }
685  }
686 
687  return result;
688  }
689 
692  void addColumn(Matrix values) {
693  addColumn(values, mCols);
694  }
695 
698  void addRow(Matrix values) {
699  addRow(values, mRows);
700  }
701 
706  void addColumn(Matrix values, size_t position) {
707  if (!isEmpty() and values.nRows() != mRows)
708  throw invalid_argument("Wrong number of values passed for new column");
709  if (values.nCols() != 1)
710  throw invalid_argument("Can't add multiple columns at once");
711 
712  if (isEmpty()) {
713  mRows = values.mRows;
714  mCols = values.mCols;
715  mData = values.mData;
716  return;
717  }
718 
719  vector<T> newData(mData.size() + values.mRows);
720 
721  size_t newData_mCols = mCols + 1;
722  for (size_t i = 0; i < mRows; i++) {
723  for (size_t j = 0; j < newData_mCols; j++) {
724  if (j == position)
725  newData[i * newData_mCols + j] = values(i, 0);
726  else {
727  int a = j > position;
728  newData[i * newData_mCols + j] = operator()(i, j - (j > position));
729  }
730  }
731  }
732  mCols += 1;
733  mData = newData;
734  }
735 
740  void addRow(Matrix values, size_t position) {
741  if (!isEmpty() and values.mRows != mCols)
742  throw invalid_argument("Wrong number of values passed for new row");
743  if (values.mCols != 1)
744  throw invalid_argument("Can't add multiple rows at once");
745 
746  if (isEmpty()) {
747  mRows = values.mCols;
748  mCols = values.mRows;
749  mData = values.mData;
750  return;
751  }
752 
753  // TODO addColumn with same logic was wrong, must check this one
754 
755  vector<T> newData(mData.size() + values.mRows);
756 
757  mRows += 1;
758  for (size_t i = 0; i < mRows; i++) {
759  for (size_t j = 0; j < mCols; j++) {
760  if (i == position)
761  newData[i * mCols + j] = values(j, 0);
762  else
763  newData[i * mCols + j] = operator()(i - (i > position), j);
764  }
765  }
766 
767  mData = newData;
768  }
769 
772  void removeColumn(int position) {
773  // this is how you stop a reverse for loop with unsigned integers
774  for (size_t i = mRows - 1; i != (size_t) -1; i--)
775  mData.erase(mData.begin() + (i * mCols + position));
776 
777  mCols -= 1;
778  }
779 
782  Matrix unique() const {
783  // include all data from the inner vector in a set
784  set<T> s;
785  unsigned long size = mData.size();
786 
787  for (unsigned i = 0; i < size; ++i)
788  s.insert(mData[i]);
789 
790  // include all the data from the set back into a vector
791  vector<T> auxVec;
792  auxVec.assign(s.begin(), s.end());
793 
794  // return a column matrix with the unique elements
795  return Matrix(auxVec.size(), 1, auxVec);
796  }
797 
799  void sort() {
800  // just sort the inner vector
801  std::sort(mData.begin(), mData.end());
802  }
803 
807  static Matrix sort(Matrix m) {
808  // copy the inner vector of the matrix passed as argument
809  // and return a new matrix with the sorted inner vector
810  vector<T> data = m.mData;
811  std::sort(data.begin(), data.end());
812  return Matrix(m.mRows, m.mCols, data);
813  }
814 
819  Matrix result = unique();
820  result.sort();
821 
822  result.addColumn(zeros(result.mRows, 1), 1);
823 
824  for (size_t i = 0; i < mRows; i++)
825  for (size_t j = 0; j < mCols; j++)
826  for (size_t g = 0; g < result.mRows; g++)
827  if (operator()(i, j) == result(g, 0)) {
828  result(g, 1)++;
829  break;
830  }
831 
832  return result;
833  }
834 
840  Matrix mean(Matrix groups) {
841  if (mRows != groups.mRows)
842  throw invalid_argument("Not enough groups for every element in the matrix");
843 
844  Matrix groupCount = groups.count();
845  Matrix result = zeros(groupCount.mRows, mCols);
846 
847  for (size_t i = 0; i < mRows; i++) {
848  for (size_t g = 0; g < groupCount.mRows; g++) {
849  if (groups(i, 0) == groupCount(g, 0)) {
850  for (size_t j = 0; j < mCols; j++) {
851  result(g, j) += operator()(i, j);
852  }
853  break;
854  }
855  }
856  }
857 
858  for (size_t i = 0; i < result.mRows; i++)
859  for (size_t j = 0; j < result.mCols; j++)
860  result(i, j) /= groupCount(i, 1);
861 
862  return result;
863  }
864 
868  Matrix result = zeros(mCols, 1);
869 
870  for (size_t i = 0; i < mRows; i++) {
871  for (size_t j = 0; j < mCols; j++) {
872  result(j, 0) += operator()(i, j);
873  }
874  }
875 
876  result /= mRows;
877 
878  return result;
879  }
880 
884  Matrix means = mean();
885  Matrix result(mCols, mCols);
886 
887  for (size_t i = 0; i < mRows; i++) {
888  Matrix rowDiff = getRow(i) - means;
889  result += rowDiff * rowDiff.transpose();
890  }
891 
892  return result;
893  }
894 
898  return scatter() / (mRows - 1);
899  }
900 
904  Matrix means = mean();
905  Matrix result = zeros(mCols, 1);
906 
907  for (size_t i = 0; i < mCols; i++) {
908  for (size_t ii = 0; ii < mRows; ii++)
909  result(i, 0) += pow((operator()(ii, i) - means(i, 0)), 2);
910 
911  result(i, 0) /= (mRows - 1);
912  }
913 
914  return result;
915  }
916 
920  Matrix result = var();
921 
922  #pragma omp parallel for
923  for (size_t i = 0; i < mCols; i++)
924  result(i, 0) = sqrt(result(i, 0));
925 
926  return result;
927  }
928 
932  void reshape(size_t rows, size_t cols) {
933  if (mData.size() != rows * cols)
934  throw invalid_argument(
935  "Invalid shape (" + to_string(rows) + "x" +
936  to_string(cols) + " = " + to_string(rows * cols) +
937  ") for a matrix with" + to_string(mData.size()) + " elements");
938 
939  mRows = rows;
940  mCols = cols;
941  }
942 
946  Matrix getColumn(size_t index) {
947  if (index >= mCols)
948  throw invalid_argument("Column index out of bounds");
949 
950  Matrix result(mRows, 1);
951  #pragma omp parallel for
952  for (size_t i = 0; i < mRows; i++)
953  result(i, 0) = operator()(i, index);
954 
955  return result;
956  }
957 
961  Matrix getRow(size_t index) {
962  if (index >= mRows)
963  throw invalid_argument("Row index out of bounds");
964 
965  Matrix result(mCols, 1);
966  #pragma omp parallel for
967  for (size_t i = 0; i < mCols; i++)
968  result(i, 0) = operator()(index, i);
969 
970  return result;
971  }
972 
977  friend ostream &operator<<(ostream &os, const Matrix &matrix) {
978  const int numWidth = 13;
979  char fill = ' ';
980 
981  for (int i = 0; i < matrix.mRows; i++) {
982  for (int j = 0; j < matrix.mCols; j++) {
983  // the trick to print a table-like structure was stolen from here
984  // https://stackoverflow.com/a/14796892
985  os << left << setw(numWidth) << setfill(fill) << to_string(matrix(i, j));
986  }
987  os << endl;
988  }
989 
990  return os;
991  }
992 
996  static Matrix fromCSV(const string &path) {
997  vector<vector<double>> outer = CSVReader::csvToNumericVecVec(path, true);
998 
999  Matrix result(outer.size(), outer[0].size());
1000 
1001  for (size_t i = 0; i < result.mRows; i++)
1002  for (size_t j = 0; j < result.mCols; j++)
1003  result(i, j) = outer[i][j];
1004 
1005  return result;
1006  }
1007 
1011  if (mRows != 1 and mCols != 1)
1012  throw runtime_error("Can't diagonalize, not a vector");
1013 
1014  size_t dimension = mCols > 1 ? mCols : mRows;
1015 
1016  Matrix result = zeros(dimension, dimension);
1017 
1018  #pragma omp parallel for
1019  for (size_t i = 0; i < dimension; i++) {
1020  result(i, i) = mCols > 1 ? operator()(0, i) : operator()(i, 0);
1021  }
1022  return result;
1023  }
1024 
1028  Matrix result(mRows, mCols);
1029  result.mData = mData;
1030  return result;
1031  }
1032 
1037  return standardize(mean(), stdev());
1038  }
1039 
1046  if (!means.isColumn())
1047  throw invalid_argument("Argument \"mean\" must have exactly one column");
1048  if (!stds.isColumn())
1049  throw invalid_argument("Argument \"stds\" must have exactly one column");
1050  if (means.mRows != mCols)
1051  throw invalid_argument("Number of mean values is different than number of features");
1052  if (stds.mRows != mCols)
1053  throw invalid_argument("Number of std. dev. values is different than number of features");
1054 
1055  Matrix result(mRows, mCols);
1056 
1057  #pragma omp parallel for collapse(2)
1058  for (size_t i = 0; i < mRows; i++) {
1059  for (size_t j = 0; j < mCols; j++) {
1060  result(i, j) = (operator()(i, j) - means(j, 0)) / stds(j, 0);
1061  }
1062  }
1063 
1064  return result;
1065  }
1066 
1068  Matrix result(mRows, mCols), means = mean();
1069 
1070  #pragma omp parallel for collapse(2)
1071  for (size_t i = 0; i < mRows; i++) {
1072  for (size_t j = 0; j < mCols; j++) {
1073  result(i, j) = operator()(i, j) - means(j, 0);
1074  }
1075  }
1076 
1077  return result;
1078  }
1079 
1083  bool contains(T value) {
1084  return std::find(mData.begin(), mData.end(), value) != mData.end();
1085  }
1086 
1089  bool isEmpty() {
1090  return mCols == 0 and mRows == 0;
1091  }
1092 
1098  Matrix filter(const Matrix<int> bin, bool columns = false) {
1099  size_t dimension = columns ? mCols : mRows;
1100 
1101  if (bin.nCols() != 1)
1102  throw invalid_argument("Binary filter must have only one column");
1103  if (bin.nRows() != dimension)
1104  throw invalid_argument("Binary filter has the wrong number of row entries");
1105 
1106  Matrix result;
1107 
1108  for (size_t i = 0; i < bin.nRows(); i++) {
1109  if (bin(i, 0)) {
1110  if (columns)
1111  result.addColumn(getColumn(i));
1112  else
1113  result.addRow(getRow(i));
1114  }
1115  }
1116 
1117  return result;
1118  }
1119 
1124  return filter(bin);
1125  }
1126 
1131  return filter(bin, true);
1132  }
1133 
1136  bool isSymmetric() {
1137  return *this == transpose();
1138  }
1139 
1144  Matrix result(mRows, mCols, mData);
1145 
1146  // Calculate length of the column vector
1147  for (size_t j = 0; j < mCols; j++) {
1148  T length = 0;
1149  #pragma omp parallel for reduction(+:length)
1150  for (size_t i = 0; i < mRows; i++) {
1151  length += pow(result(i, j), 2);
1152  }
1153  length = sqrt(length);
1154 
1155  // divide each element of the column by its length
1156  for (size_t i = 0; i < mRows; i++) {
1157  result(i, j) /= length;
1158  }
1159  }
1160 
1161  return result;
1162  }
1163 
1168  pair<Matrix, Matrix> eigen() {
1169  return isSymmetric() ? eigenSymmetric() : eigenNonSymmetric();
1170  };
1171 
1175  pair<Matrix, Matrix> eigenSymmetric() {
1176  // Jacobi eigenvalue algorithm as explained
1177  // by profs Marina Andretta and Franklina Toledo
1178 
1179  // copy the current matrix
1180  Matrix A = copy();
1181  // initialize the eigenvector matrix
1182  Matrix V = identity(A.mCols);
1183 
1184  // get the tolerance
1185  double eps = numeric_limits<double>::epsilon();
1186  unsigned iterations = 0;
1187 
1188  // initiate the loop for numerical approximation of the eigenvalues
1189  while (true) {
1190  // find the element in the matrix with the largest modulo
1191  size_t p, q;
1192  T largest = 0;
1193  for (size_t i = 0; i < A.mRows; i++) {
1194  for (size_t j = 0; j < A.mCols; j++) {
1195  // it can't be in the diagonal
1196  if (i != j and abs(A(i, j)) > largest) {
1197  largest = abs(A(i, j));
1198  p = i;
1199  q = j;
1200  }
1201  }
1202  }
1203 
1204  // if the largest non-diagonal element of A is zero +/- eps,
1205  // it means A is almost diagonalized and the eigenvalues are
1206  // in the diagonal of A
1207  if (largest < 2 * eps or iterations >= 1000) {
1208  //eigenvalues are returned in a column matrix for convenience
1209  return eigsort(A.diagonal(), V);
1210  }
1211 
1212  iterations++;
1213 
1214  // else, perform a Jacobi rotation using this angle phi as reference
1215  double phi = (A(q, q) - A(p, p)) / (2 * A(p, q));
1216  double sign = (phi > 0) - (phi < 0);
1217  double t = phi == 0 ? 1 : 1 / (phi + sign * sqrt(pow(phi, 2) + 1));
1218  double cos = 1 / (sqrt(1 + pow(t, 2)));
1219  double sin = t / (sqrt(1 + pow(t, 2)));
1220 
1221  // the matrix that will apply the rotation is basically an identity matrix...
1222  Matrix U = identity(A.mRows);
1223 
1224  // ... with the exception of these values
1225  U(p, p) = U(q, q) = cos;
1226  U(p, q) = sin;
1227  U(q, p) = -sin;
1228 
1229  // apply the rotation
1230  A = U.transpose() * A * U;
1231  // update the corresponding eigenvectors
1232  V = V * U;
1233  }
1234  }
1235 
1242  pair<Matrix, Matrix> eigenNonSymmetric(bool hes = true) {
1243 
1244  MatDoub unholyConvertion(static_cast<int>(mRows), static_cast<int>(mCols));
1245 
1246  #pragma omp parallel for collapse(2)
1247  for (size_t i = 0; i < mRows; i++) {
1248  for (size_t j = 0; j < mCols; j++) {
1249  unholyConvertion[i][j] = operator()(i, j);
1250  }
1251  }
1252 
1253  Unsymmeig nr3Miracle(unholyConvertion, true, hes);
1254 
1255  Matrix eigenvalues(1, static_cast<size_t>(nr3Miracle.wri.size()));
1256 
1257  for (size_t i = 0; i < nr3Miracle.wri.size(); i++) {
1258  eigenvalues(0, i) = nr3Miracle.wri[i].real();
1259  }
1260 
1261  Matrix eigenvectors(static_cast<size_t>(nr3Miracle.zz.nrows()),
1262  static_cast<size_t>(nr3Miracle.zz.ncols()));
1263 
1264  for (size_t i = 0; i < nr3Miracle.zz.nrows(); i++)
1265  for (size_t j = 0; j < nr3Miracle.zz.ncols(); j++)
1266  eigenvectors(i, j) = nr3Miracle.zz[i][j];
1267 
1268  return make_pair(eigenvalues, eigenvectors);
1269  }
1270 
1272  Matrix Sw = zeros(mCols, mCols);
1273  Matrix uniqueClasses = y.unique();
1274 
1275  for (size_t i = 0; i < uniqueClasses.nRows(); i++) {
1276  Matrix classElements = getRows(y == i); // get class elements
1277 
1278  Matrix scatterMatrix = classElements.scatter();
1279  Sw += scatterMatrix;
1280  }
1281 
1282  return Sw;
1283  }
1284 
1286  Matrix innerMean = mean(y); // means for each class
1287  Matrix grandMean = mean(); // mean of the entire data set
1288  Matrix Sb = zeros(mCols, mCols);
1289  Matrix uniqueClasses = y.unique();
1290 
1291  for (size_t i = 0; i < uniqueClasses.nRows(); i++) {
1292  Matrix classElements = getRows(y == i); // get class elements
1293  Matrix meanDiff = innerMean.getRow(i) - grandMean;
1294  Sb += classElements.nRows() * meanDiff * meanDiff.transpose();
1295  }
1296 
1297  return Sb;
1298  }
1299 
1300  T sum() const {
1301  T sum_of_elems = 0;
1302  for (T n : mData)
1303  sum_of_elems += n;
1304 
1305  return sum_of_elems;
1306  }
1307 
1308  bool isColumn() const {
1309  return mCols == 1;
1310  }
1311 
1312  bool isRow() const {
1313  return mRows == 1;
1314  }
1315 
1316  T min() const {
1317  return *std::min_element(std::begin(mData), std::end(mData));
1318  }
1319 
1320  T max() const {
1321  return *std::max_element(std::begin(mData), std::end(mData));
1322  }
1323 
1324  Matrix<T> apply(function<T(T)> f) {
1325  Matrix<T> result(mRows, mCols, vector<T>(mRows * mCols, 0));
1326  std::transform(mData.begin(), mData.end(), result.mData.begin(), f);
1327  return result;
1328  }
1329 
1331  Matrix uniqueValues = unique();
1332  Matrix oneHotUnique = identity(uniqueValues.mRows);
1333  Matrix result(mRows, oneHotUnique.mCols);
1334 
1335  for (size_t i = 0; i < mRows; i++) {
1336  for (size_t ii = 0; ii < uniqueValues.mRows; ++ii) {
1337  if (getRow(i) == uniqueValues.getRow(ii)) {
1338  result.setRow(i, oneHotUnique.getRow(ii).transpose());
1339  }
1340  }
1341  }
1342 
1343  return result;
1344  }
1345 
1346  void setRow(size_t index, Matrix<T> row) {
1347  if (mRows < index)
1348  throw invalid_argument("Invalid row index, matrix is not that large");
1349  if (mCols != row.mCols)
1350  throw invalid_argument("Incompatible number of columns");
1351  if (row.mRows > 1)
1352  throw invalid_argument("Row matrix contains more than one row");
1353 
1354  for (size_t col = 0; col < mCols; col++)
1355  operator()(index, col) = row(0, col);
1356  }
1357 
1358  void setColumn(size_t index, Matrix<T> column) {
1359  if (mCols < index)
1360  throw invalid_argument("Invalid row column, matrix is not that large");
1361  if (mRows != column.mRows)
1362  throw invalid_argument("Incompatible number of rows");
1363  if (column.mCols > 1)
1364  throw invalid_argument("Column matrix contains more than one column");
1365 
1366  for (size_t row = 0; row < mCols; row++)
1367  operator()(row, index) = column(row, 0);
1368  }
1369 
1370  bool isBinary() const {
1371  Matrix<T> uniqueBin = unique();
1372  return uniqueBin.mRows <= 2 && uniqueBin.contains(1) or uniqueBin.contains(0);
1373  }
1374 };
1375 
1378 
1379 #endif //MACHINE_LEARNING_MATRIX_HPP
bool isBinary() const
Definition: Matrix.hpp:1370
size_t nRows() const
Definition: Matrix.hpp:89
Matrix hadamard(const Matrix &b)
Executes the Hadamard, or entrywise multiplication between two matrices.
Definition: Matrix.hpp:534
Matrix diagonal()
Definition: Matrix.hpp:494
static Matrix diagonal(size_t size, double value)
Creates a square matrix with a fixed value on the diagonal.
Definition: Matrix.hpp:481
bool isEmpty()
Checks if the matrix is empty or uninitialized.
Definition: Matrix.hpp:1089
bool isColumn() const
Definition: Matrix.hpp:1308
Matrix getRows(const Matrix< int > bin)
Selects a subset of rows of the matrix.
Definition: Matrix.hpp:1123
Matrix inverse() const
Calculates the inverse of the current matrix.
Definition: Matrix.hpp:641
Matrix(size_t rows, size_t cols, const vector< T > &data)
Initializes a matrix with a predetermined number of rows and columns and populates it with data...
Definition: Matrix.hpp:117
Matrix normalize()
Normalizes the column vectors of the matrix.
Definition: Matrix.hpp:1143
void validateIndexes(size_t row, size_t col) const
Validates if indices are contained inside the matrix.
Definition: Matrix.hpp:40
pair< Matrix, Matrix > eigenSymmetric()
Calculates the eigenvalues and eigenvectors of a symmetric matrix using the Jacobi eigenvalue algorit...
Definition: Matrix.hpp:1175
Matrix var()
Calculates the variance of the columns of the matrix.
Definition: Matrix.hpp:903
size_t mCols
Definition: Matrix.hpp:33
size_t nCols() const
Definition: Matrix.hpp:87
static Matrix fromCSV(const string &path)
Loads CSV data into a matrix.
Definition: Matrix.hpp:996
void addRow(Matrix values, size_t position)
Adds a row to the matrix at the given position.
Definition: Matrix.hpp:740
void addColumn(Matrix values, size_t position)
Adds a column to the matrix at the given position.
Definition: Matrix.hpp:706
T sum() const
Definition: Matrix.hpp:1300
T & operator()(size_t i, size_t j)
Functor used to access elements in the matrix.
Definition: Matrix.hpp:451
bool operator!=(const Matrix &other)
Definition: Matrix.hpp:423
Matrix operator*=(double value)
Definition: Matrix.hpp:253
Matrix operator-=(double value)
Definition: Matrix.hpp:246
bool contains(T value)
Checks if the matrix contains a value.
Definition: Matrix.hpp:1083
pair< Matrix, Matrix > eigenNonSymmetric(bool hes=true)
Calculates the eigenvalues and eigenvectors of a on-symmetric matrix.
Definition: Matrix.hpp:1242
Matrix implementation, with a series of linear algebra functions.
void setColumn(size_t index, Matrix< T > column)
Definition: Matrix.hpp:1358
Matrix mean(Matrix groups)
Calculates means of a matrix, grouped by classes.
Definition: Matrix.hpp:840
static Matrix sort(Matrix m)
Sorts the elements of a matrix.
Definition: Matrix.hpp:807
bool isRow() const
Definition: Matrix.hpp:1312
void setRow(size_t index, Matrix< T > row)
Definition: Matrix.hpp:1346
static Matrix identity(size_t size)
Returns the identity matrix.
Definition: Matrix.hpp:511
Matrix stdev()
Calculates the standard deviation of the columns of the matrix.
Definition: Matrix.hpp:919
friend Matrix operator-(double value, const Matrix &m)
Scalar subtraction.
Definition: Matrix.hpp:175
size_t mRows
Definition: Matrix.hpp:32
Matrix copy()
Returns a copy of the matrix.
Definition: Matrix.hpp:1027
Matrix unique() const
Returns only unique values from the matrix.
Definition: Matrix.hpp:782
friend Matrix operator*(double value, const Matrix &m)
Scalar multiplication.
Definition: Matrix.hpp:200
Matrix cofactorMatrix() const
Calculates the cofactor matrix.
Definition: Matrix.hpp:620
Matrix & operator*=(const Matrix &other)
Definition: Matrix.hpp:365
Matrix minusMean()
Definition: Matrix.hpp:1067
bool isSquare() const
Definition: Matrix.hpp:489
friend Matrix operator-(const Matrix &m, double value)
Scalar subtraction.
Definition: Matrix.hpp:167
Matrix WithinClassScatter(Matrix y)
Definition: Matrix.hpp:1271
T max() const
Definition: Matrix.hpp:1320
void removeColumn(int position)
Removes a column from the matrix.
Definition: Matrix.hpp:772
Matrix operator/=(double value)
Definition: Matrix.hpp:260
Matrix operator+(const Matrix &b)
Matrix addition operation.
Definition: Matrix.hpp:273
void addColumn(Matrix values)
Adds a column at the end of the matrix.
Definition: Matrix.hpp:692
std::vector< T > mData
Definition: Matrix.hpp:34
Matrix operator+=(double value)
Definition: Matrix.hpp:239
Matrix operator!=(const double &value)
Definition: Matrix.hpp:417
Matrix getColumn(size_t index)
Gets a column from the matrix.
Definition: Matrix.hpp:946
static Matrix ones(size_t rows, size_t cols)
Returns a matrix filled with ones.
Definition: Matrix.hpp:519
friend Matrix operator+(const Matrix &m, double value)
Scalar addition.
Definition: Matrix.hpp:142
Matrix operator-()
Matrix negative operation.
Definition: Matrix.hpp:432
bool isSymmetric()
Checks if the matrix is symmetric.
Definition: Matrix.hpp:1136
Matrix count()
Counts occurrences of elements in a matrix.
Definition: Matrix.hpp:818
Matrix standardize(Matrix means, Matrix stds)
Standardizes the columns of the matrix, subtracting each element of a column by the mean argument and...
Definition: Matrix.hpp:1045
Matrix BetweenClassScatter(Matrix y)
Definition: Matrix.hpp:1285
T min() const
Definition: Matrix.hpp:1316
Matrix implementation, with a series of linear algebra functions.
Definition: Matrix.hpp:30
Matrix & operator-=(const Matrix &other)
Definition: Matrix.hpp:349
Matrix< T > apply(function< T(T)> f)
Definition: Matrix.hpp:1324
Matrix< double > MatrixD
Definition: Matrix.hpp:1376
friend Matrix operator/(const Matrix &m, double value)
Scalar division.
Definition: Matrix.hpp:208
Matrix cov()
Calculates the covariance matrix of the current matrix.
Definition: Matrix.hpp:897
Matrix asDiagonal()
Creates a diagonal matrix from a row or column vector.
Definition: Matrix.hpp:1010
Matrix mean()
Calculates the mean of the columns of the matrix.
Definition: Matrix.hpp:867
Matrix standardize()
Standardizes the columns of the matrix, subtracting each element of a column by the column mean and d...
Definition: Matrix.hpp:1036
Matrix< int > MatrixI
Definition: Matrix.hpp:1377
double determinant() const
Calculates the determinant of the matrix.
Definition: Matrix.hpp:656
void sort()
Sorts elements of the matrix inplace.
Definition: Matrix.hpp:799
Matrix(size_t rows, size_t cols, T(&data)[N])
Definition: Matrix.hpp:126
Matrix(size_t dimension)
Initializes a square matrix.
Definition: Matrix.hpp:100
Matrix(size_t rows, size_t cols)
Initializes a matrix with a predetermined number of rows and columns.
Definition: Matrix.hpp:107
Matrix adjugate() const
Returns the adjugate of the current matrix, which is the transpose of its cofactor matrix...
Definition: Matrix.hpp:634
friend Matrix operator/(double value, const Matrix &m)
Scalar division.
Definition: Matrix.hpp:225
bool operator==(const Matrix &other)
Definition: Matrix.hpp:406
void reshape(size_t rows, size_t cols)
Reshapes the current matrix.
Definition: Matrix.hpp:932
Matrix operator*(const Matrix &b) const
Matrix multiplication operation.
Definition: Matrix.hpp:314
static Matrix fill(size_t rows, size_t cols, double value)
Returns a matrix filled with a single value.
Definition: Matrix.hpp:472
Matrix filter(const Matrix< int > bin, bool columns=false)
Selects a subset of either columns or rows of the matrix.
Definition: Matrix.hpp:1098
pair< Matrix, Matrix > eigen()
Calculates the eigenvalues and eigenvectors of a matrix.
Definition: Matrix.hpp:1168
Matrix submatrix(size_t row, size_t column) const
Returns a submatrix of the current matrix, removing one row and column of the original matrix...
Definition: Matrix.hpp:556
Matrix< int > operator==(const T &value)
Definition: Matrix.hpp:393
friend ostream & operator<<(ostream &os, const Matrix &matrix)
Prints a matrix.
Definition: Matrix.hpp:977
Matrix getRow(size_t index)
Gets a row from the matrix.
Definition: Matrix.hpp:961
double getMinor(size_t row, size_t column) const
Returns the minor of a matrix, which is the determinant of a submatrix where a single row and column ...
Definition: Matrix.hpp:581
Matrix & operator+=(const Matrix &other)
Definition: Matrix.hpp:335
Matrix()
Initializes an empty matrix.
Definition: Matrix.hpp:94
static pair< Matrix, Matrix > eigsort(Matrix eigenvalues, Matrix eigenvectors)
Sorts eigenvalues by magnitude, sorting their corresponding eigenvectors in te same order...
Definition: Matrix.hpp:54
Matrix getColumns(const Matrix< int > bin)
Selects a subset of columns of the matrix.
Definition: Matrix.hpp:1130
void addRow(Matrix values)
Adds a row at the end of the matrix.
Definition: Matrix.hpp:698
double cofactor(size_t row, size_t column) const
Calculates the cofactor of a matrix at a given point.
Definition: Matrix.hpp:600
Matrix operator-(const Matrix &b)
Matrix subtraction operation.
Definition: Matrix.hpp:293
static Matrix zeros(size_t rows, size_t cols)
Returns a matrix filled with zeros.
Definition: Matrix.hpp:527
friend Matrix operator+(double value, const Matrix &m)
Scalar addition.
Definition: Matrix.hpp:159
Matrix transpose() const
Returns the transpose of a matrix.
Definition: Matrix.hpp:677
Matrix oneHot()
Definition: Matrix.hpp:1330
friend Matrix operator*(const Matrix &m, double value)
Scalar multiplication.
Definition: Matrix.hpp:183
T operator()(size_t i, size_t j) const
Functor used to access elements in the matrix.
Definition: Matrix.hpp:460
Matrix scatter()
Calculates the scatter matrix.
Definition: Matrix.hpp:883