UNPKG

@ai-on-browser/data-analysis-models

Version:

Data analysis model package without any dependencies

1,803 lines (1,735 loc) 133 kB
import Complex from './complex.js' import Tensor from './tensor.js' const normal_random = (m, s) => { const std = Math.sqrt(s) const x = Math.random() const y = Math.random() const X = Math.sqrt(-2 * Math.log(x)) * Math.cos(2 * Math.PI * y) const Y = Math.sqrt(-2 * Math.log(x)) * Math.sin(2 * Math.PI * y) return [X * std + m, Y * std + m] } /** * Exception for matrix class */ export class MatrixException extends Error { /** * @param {string} message Error message * @param {*} value Some value */ constructor(message, value) { super(message) this.value = value this.name = 'MatrixException' } } /** * Matrix class * @template {*} [T=number] - Element type */ export default class Matrix { /** * @overload * @param {number} rows Number of rows * @param {number} cols Number of columns * @param {T | Array<T> | Array<Array<T>>} [values] Initial values */ /** * @overload * @param {[number, number]} size Sizes for each dimension * @param {T | Array<T> | Array<Array<T>>} [values] Initial values */ /** * @param {number | [number, number]} rows Number of rows * @param {T | Array<T> | Array<Array<T>>} cols Number of columns or initial values * @param {T | Array<T> | Array<Array<T>>} [values] Initial values */ constructor(rows, cols, values) { if (Array.isArray(rows)) { values = cols ;[rows, cols] = rows } if (!values) { /** @private */ this._value = Array(rows * cols).fill(0) } else if (!Array.isArray(values)) { this._value = Array(rows * cols).fill(values) } else if (Array.isArray(values[0])) { this._value = values.flat() } else { this._value = values } /** @private */ this._size = [rows, cols] } /** * Returns a matrix filled with 0. * @overload * @param {number} rows Number of rows * @param {number} cols Number of columns * @returns {Matrix<number>} Matrix filled with 0 */ /** * Returns a matrix filled with 0. * @overload * @param {[number, number]} size Sizes for each dimension * @returns {Matrix<number>} Matrix filled with 0 */ /** * @param {number | [number, number]} rows Number of rows or sizes for each dimension * @param {number} [cols] Number of columns * @returns {Matrix<number>} Matrix filled with 0 */ static zeros(rows, cols) { if (Array.isArray(rows)) { ;[rows, cols] = rows } return new Matrix(rows, cols, Array(rows * cols).fill(0)) } /** * Returns a matrix filled with 1. * @overload * @param {number} rows Number of rows * @param {number} cols Number of columns * @returns {Matrix<number>} Matrix filled with 1 */ /** * Returns a matrix filled with 1. * @overload * @param {[number, number]} size Sizes for each dimension * @returns {Matrix<number>} Matrix filled with 1 */ /** * @param {number | [number, number]} rows Number of rows or sizes for each dimension * @param {number} [cols] Number of columns * @returns {Matrix<number>} Matrix filled with 1 */ static ones(rows, cols) { if (Array.isArray(rows)) { ;[rows, cols] = rows } return new Matrix(rows, cols, Array(rows * cols).fill(1)) } /** * Returns a identity matrix. * @overload * @param {number} rows Number of rows * @param {number} cols Number of columns * @param {number} [init] Diagonal values * @returns {Matrix<number>} Identity matrix */ /** * Returns a identity matrix. * @overload * @param {[number, number]} size Sizes for each dimension * @param {number} [init] Diagonal values * @returns {Matrix<number>} Identity matrix */ /** * @param {number | [number, number]} rows Number of rows or sizes for each dimension * @param {number} [cols] Number of columns * @param {number} [init] Diagonal values * @returns {Matrix<number>} Identity matrix */ static eye(rows, cols, init = 1) { if (Array.isArray(rows)) { init = cols ?? 1 ;[rows, cols] = rows } const mat = new Matrix(rows, cols) const rank = Math.min(rows, cols) for (let i = 0; i < rank; i++) { mat._value[i * cols + i] = init } return mat } /** * Returns a matrix initialized uniform random values. * @overload * @param {number} rows Number of rows * @param {number} cols Number of columns * @param {number} [min] Minimum value of the Matrix (include) * @param {number} [max] Maximum value of the Matrix (exclude) * @returns {Matrix<number>} Matrix initialized uniform random values */ /** * Returns a matrix initialized uniform random values. * @overload * @param {[number, number]} size Sizes for each dimension * @param {number} [min] Minimum value of the Matrix (include) * @param {number} [max] Maximum value of the Matrix (exclude) * @returns {Matrix<number>} Matrix initialized uniform random values */ /** * @param {number | [number, number]} rows Number of rows or sizes for each dimension * @param {number} [cols] Number of columns * @param {number} [min] Minimum value of the Matrix (include) * @param {number} [max] Maximum value of the Matrix (exclude) * @returns {Matrix<number>} Matrix initialized uniform random values */ static random(rows, cols, min, max) { if (Array.isArray(rows)) { max = min min = cols ;[rows, cols] = rows } min ??= 0 max ??= 1 const mat = new Matrix(rows, cols) for (let i = 0; i < mat.length; i++) { mat._value[i] = Math.random() * (max - min) + min } return mat } /** * Returns a matrix initialized uniform random integer values. * @overload * @param {number} rows Number of rows * @param {number} cols Number of columns * @param {number} [min] Minimum value of the Matrix (include) * @param {number} [max] Maximum value of the Matrix (include) * @returns {Matrix<number>} Matrix initialized uniform random values */ /** * Returns a matrix initialized uniform random integer values. * @overload * @param {[number, number]} size Sizes for each dimension * @param {number} [min] Minimum value of the Matrix (include) * @param {number} [max] Maximum value of the Matrix (include) * @returns {Matrix<number>} Matrix initialized uniform random values */ /** * @param {number | [number, number]} rows Number of rows or sizes for each dimension * @param {number} [cols] Number of columns * @param {number} [min] Minimum value of the Matrix (include) * @param {number} [max] Maximum value of the Matrix (include) * @returns {Matrix<number>} Matrix initialized uniform random values */ static randint(rows, cols, min, max) { if (Array.isArray(rows)) { max = min min = cols ;[rows, cols] = rows } min ??= 0 max ??= 1 const mat = new Matrix(rows, cols) for (let i = 0; i < mat.length; i++) { mat._value[i] = Math.floor(Math.random() * (max - min + 1) + min) } return mat } /** * Returns a matrix initialized normal random values. * @overload * @param {number} rows Number of rows * @param {number} cols Number of columns * @param {number | number[]} [myu] Mean value(s) of each columns * @param {number | Array<Array<number>>} [sigma] Variance value or covariance matrix of each columns * @returns {Matrix<number>} Matrix initialized normal random values */ /** * Returns a matrix initialized normal random values. * @overload * @param {[number, number]} rows Sizes for each dimension * @param {number | number[]} [myu] Mean value(s) of each columns * @param {number | Array<Array<number>>} [sigma] Variance value or covariance matrix of each columns * @returns {Matrix<number>} Matrix initialized normal random values */ /** * @param {number} rows Number of rows or sizes for each dimension * @param {number | number[]} [cols] Number of columns * @param {number | number[] | Array<Array<number>>} [myu] Mean value(s) of each columns * @param {number | Array<Array<number>>} [sigma] Variance value or covariance matrix of each columns * @returns {Matrix<number>} Matrix initialized normal random values */ static randn(rows, cols, myu, sigma) { if (Array.isArray(rows)) { sigma = myu myu = cols ;[rows, cols] = rows } myu ??= 0 sigma ??= 1 const mat = new Matrix(rows, cols) if (Array.isArray(myu)) { myu = new Matrix(1, myu.length, myu) } if (Array.isArray(sigma)) { sigma = Matrix.fromArray(sigma) } if (!(myu instanceof Matrix) && !(sigma instanceof Matrix)) { for (let i = 0; i < mat.length; i += 2) { const nr = normal_random(myu, sigma) mat._value[i] = nr[0] if (i + 1 < mat.length) { mat._value[i + 1] = nr[1] } } return mat } if (!(myu instanceof Matrix)) { myu = new Matrix(1, cols, myu) } else if (myu.rows === cols || myu.cols === 1) { myu = myu.t } if (myu.cols !== cols || myu.rows !== 1) { throw new MatrixException("'myu' cols must be same as 'cols' and rows must be 1.") } if (!(sigma instanceof Matrix)) { sigma = Matrix.eye(cols, cols, sigma) } else if (sigma.rows !== cols || sigma.cols !== cols) { throw new MatrixException("'sigma' cols and rows must be same as 'cols'.") } const L = sigma.cholesky() for (let i = 0; i < mat.length; i += 2) { const nr = normal_random(0, 1) mat._value[i] = nr[0] if (i + 1 < mat.length) { mat._value[i + 1] = nr[1] } } const smat = mat.dot(L.t) smat.add(myu) return smat } /** * Returns a diagonal matrix. * @template T * @param {(T | Matrix<T>)[]} d Diagonal values * @returns {Matrix<T>} Diagonal matrix */ static diag(d) { let n = 0 let m = 0 for (const v of d) { if (typeof v === 'number') { n++ m++ } else { n += v.rows m += v.cols } } const mat = new Matrix(n, m) for (let k = 0, i = 0, j = 0; i < n; k++) { const dk = d[k] if (typeof dk === 'number') { mat._value[i * m + j] = dk i++ j++ } else { mat.set(i, j, dk) i += dk.rows j += dk.cols } } return mat } /** * Returns a matrix from some value. * @template T * @param {Matrix<T> | Array<Array<T>> | Array<T> | T} arr Original values * @returns {Matrix<T>} Matrix from some value */ static fromArray(arr) { if (arr instanceof Matrix) { return arr } else if (!Array.isArray(arr)) { return new Matrix(1, 1, arr) } else if (arr.length === 0) { return new Matrix(0, 0) } else if (!Array.isArray(arr[0])) { return new Matrix(arr.length, 1, arr) } return new Matrix(arr.length, arr[0].length, arr) } /** * Dimension of the matrix. * @type {number} */ get dimension() { return this._size.length } /** * Sizes of the matrix. * @type {number[]} */ get sizes() { return this._size } /** * Number of all elements in the matrix. * @type {number} */ get length() { return this._size[0] * this._size[1] } /** * Number of rows of the matrix. * @type {number} */ get rows() { return this._size[0] } /** * Number of columns of the matrix. * @type {number} */ get cols() { return this._size[1] } /** * Elements in the matrix. * @type {T[]} */ get value() { return this._value } /** * Transpose matrix. * @type {Matrix<T>} */ get t() { return this.transpose() } /** * Iterate over the elements. * @yields {T} */ *[Symbol.iterator]() { yield* this._value } /** * Returns a nested array represented this matrix. * @returns {Array<Array<T>>} Nested array */ toArray() { const arr = [] const n = this.cols for (let i = 0; i < this.length; i += n) { arr.push(this._value.slice(i, i + n)) } return arr } /** * Returns the only element. * @returns {T} The only element */ toScaler() { if (this.rows !== 1 || this.cols !== 1) { throw new MatrixException('The matrix cannot convert to scaler.') } return this._value[0] } /** * Returns a string represented this matrix. * @returns {string} String represented this matrix */ toString() { let s = '[' for (let i = 0; i < this.rows; i++) { if (i > 0) s += ',\n ' s += '[' for (let j = 0; j < this.cols; j++) { if (j > 0) s += ', ' s += this._value[i * this.cols + j] } s += ']' } return `${s}]` } _to_position(...i) { let p = 0 for (let d = 0; d < this.dimension; d++) { if (i[d] < 0 || this._size[d] <= i[d]) { throw new MatrixException('Index out of bounds.') } p = p * this._size[d] + i[d] } return p } _to_index(p) { return [Math.floor(p / this._size[1]), p % this._size[1]] } /** * Returns a copy of this matrix. * @param {Matrix<T>} [dst] Destination matrix * @returns {Matrix<T>} Copied matrix */ copy(dst) { if (dst === this) { return this } else if (dst) { dst._size = [].concat(this._size) this._value.forEach((v, i) => { dst._value[i] = v }) return dst } return new Matrix(this.rows, this.cols, [].concat(this._value)) } /** * Returns this matrix is equals to the others. * @param {*} other Check tensor * @param {number} [tol] Tolerance to be recognized as the same * @returns {boolean} `true` if equal */ equals(other, tol = 0) { if (other instanceof Matrix) { if (this._size[0] !== other._size[0] || this._size[1] !== other._size[1]) { return false } for (let i = this.length - 1; i >= 0; i--) { if (Math.abs(this._value[i] - other._value[i]) > tol) { return false } } return true } return false } /** * Returns a value at the position. * @overload * @param {number} r Row index * @param {number} c Column index * @returns {T} Value at the position */ /** * Returns a value at the position. * @overload * @param {[number, number]} index Index values * @returns {T} Value at the position */ /** * @param {number | [number, number]} r Row index or index values * @param {number} [c] Column index * @returns {T} Value at the position */ at(r, c) { if (Array.isArray(r)) { ;[r, c] = r } if (r < 0 || this.rows <= r || c < 0 || this.cols <= c) throw new MatrixException('Index out of bounds.') return this._value[r * this.cols + c] } /** * Set a value at the position. * @overload * @param {number} r Row index * @param {number} c Column index * @param {T | Matrix<T>} value The value to be set * @returns {T=} Old value */ /** * Set a value at the position. * @overload * @param {[number, number]} r Index values * @param {T | Matrix<T>} value The value to be set * @returns {T=} Old value */ /** * @param {number | [number, number]} r Row index or index values. If this value is an array, the next argument should be the value to be set * @param {number | T | Matrix<T>} c Column index or the value to be set * @param {T | Matrix<T>} [value] The value to be set * @returns {T=} Old value */ set(r, c, value) { if (Array.isArray(r)) { value = c ;[r, c] = r } if (value instanceof Matrix) { if (r < 0 || this.rows <= r + value.rows - 1 || c < 0 || this.cols <= c + value.cols - 1) { throw new MatrixException('Index out of bounds.') } for (let i = 0; i < value.rows; i++) { for (let j = 0; j < value.cols; j++) { this._value[(i + r) * this.cols + j + c] = value._value[i * value.cols + j] } } return null } else { if (r < 0 || this.rows <= r || c < 0 || this.cols <= c) throw new MatrixException('Index out of bounds.') const old = this._value[r * this.cols + c] this._value[r * this.cols + c] = value return old } } /** * Returns a row matrix at r. * @param {number | number[] | boolean[]} r Indexes of rows, or an array of boolean values where the row to be selected is true. * @returns {Matrix<T>} Row selected matrix */ row(r) { if (Array.isArray(r)) { if (typeof r[0] === 'boolean') { if (r.length !== this.rows) { throw new MatrixException('Length is invalid.') } const rp = [] for (let i = 0; i < r.length; i++) { if (r[i]) { rp.push(i) } } r = rp } if (r.some(v => v < 0 || this.rows <= v)) { throw new MatrixException('Index out of bounds.') } const mat = new Matrix(r.length, this.cols) for (let i = 0; i < r.length; i++) { for (let j = 0; j < this.cols; j++) { mat._value[i * this.cols + j] = this._value[r[i] * this.cols + j] } } return mat } else { if (r < 0 || this.rows <= r) throw new MatrixException('Index out of bounds.') return new Matrix(1, this.cols, this._value.slice(r * this.cols, (r + 1) * this.cols)) } } /** * Returns a col matrix at c. * @param {number | number[] | boolean[]} c Indexes of columns, or an array of boolean values where the column to be selected is true. * @returns {Matrix<T>} Column selected matrix */ col(c) { if (Array.isArray(c)) { if (typeof c[0] === 'boolean') { if (c.length !== this.cols) { throw new MatrixException('Length is invalid.') } const cp = [] for (let i = 0; i < c.length; i++) { if (c[i]) { cp.push(i) } } c = cp } if (c.some(v => v < 0 || this.cols <= v)) { throw new MatrixException('Index out of bounds.') } const mat = new Matrix(this.rows, c.length) for (let i = 0; i < this.rows; i++) { for (let j = 0; j < c.length; j++) { mat._value[i * c.length + j] = this._value[i * this.cols + c[j]] } } return mat } else { if (c < 0 || this.cols <= c) throw new MatrixException('Index out of bounds.') const mat = new Matrix(this.rows, 1) for (let i = 0; i < this.rows; i++) { mat._value[i] = this._value[i * this.cols + c] } return mat } } /** * Returns sliced matrix. * @param {number} from Start index * @param {number} to End index * @param {number} [axis] Axis to be sliced * @returns {Matrix<T>} Sliced matrix */ slice(from, to, axis = 0) { if (typeof from !== 'number') { from = 0 } if (typeof to !== 'number') { to = this._size[axis] } if (from < 0 || this._size[axis] < from || to < 0 || this._size[axis] < to) { throw new MatrixException('Index out of bounds.') } else if (from > to) { throw new MatrixException("'to' must be greater than or equals to 'from'.") } if (axis === 0) { const mat = new Matrix(to - from, this.cols) mat._value = this._value.slice(from * this.cols, to * this.cols) return mat } else if (axis === 1) { const mat = new Matrix(this.rows, to - from) for (let i = 0; i < mat.rows; i++) { for (let j = 0; j < mat.cols; j++) { mat._value[i * mat.cols + j] = this._value[i * this.cols + j + from] } } return mat } else { throw new MatrixException('Invalid axis.') } } /** * Returns the sub-matrix corresponding to position. * @param {number} [rows_from] Start row index * @param {number} [cols_from] Start column index * @param {number} [rows_to] End row index(exclusive) * @param {number} [cols_to] End column index(exclusive) * @returns {Matrix<T>} Sub matrix */ block(rows_from, cols_from, rows_to, cols_to) { if (typeof rows_from !== 'number') { rows_from = 0 } if (typeof cols_from !== 'number') { cols_from = 0 } if (typeof rows_to !== 'number') { rows_to = this.rows } if (typeof cols_to !== 'number') { cols_to = this.cols } if ( rows_from < 0 || this.rows < rows_from || rows_to < 0 || this.rows < rows_to || cols_from < 0 || this.cols < cols_from || cols_to < 0 || this.cols < cols_to ) { throw new MatrixException('Index out of bounds.') } else if (rows_from > rows_to) { throw new MatrixException("'rows_to' must be greater than or equals to 'rows_from'.") } else if (cols_from > cols_to) { throw new MatrixException("'cols_to' must be greater than or equals to 'cols_from'.") } const mat = new Matrix(rows_to - rows_from, cols_to - cols_from) for (let i = 0; i < mat.rows; i++) { for (let j = 0; j < mat.cols; j++) { mat._value[i * mat.cols + j] = this._value[(i + rows_from) * this.cols + j + cols_from] } } return mat } /** * Remove specified indexes. * @param {number | number[]} idx Remove index * @param {number<T>} [axis] Axis to be removed */ remove(idx, axis = 0) { if (axis === 0) { if (Array.isArray(idx)) { if (idx.some(v => v < 0 || this.rows <= v)) { throw new MatrixException('Index out of bounds.') } idx = [...new Set(idx)] idx.sort((a, b) => b - a) for (let i = 0; i < idx.length; i++) { this._value.splice(idx[i] * this.cols, this.cols) } this._size[0] -= idx.length } else { if (idx < 0 || this.rows <= idx) throw new MatrixException('Index out of bounds.') this._value.splice(idx * this.cols, this.cols) this._size[0]-- } } else if (axis === 1) { if (Array.isArray(idx)) { if (idx.some(v => v < 0 || this.cols <= v)) { throw new MatrixException('Index out of bounds.') } idx = [...new Set(idx)] idx.sort((a, b) => a - b) let si = 0, di = 0 for (let i = 0; i < this.rows; i++) { for (let j = 0, p = 0; j < this.cols; j++, si++) { if (idx[p] === j) { p++ continue } this._value[di++] = this._value[si] } } this._size[1] -= idx.length this._value.length = this.length } else { if (idx < 0 || this.cols <= idx) throw new MatrixException('Index out of bounds.') let si = 0, di = 0 for (let i = 0; i < this.rows; i++) { for (let j = 0; j < this.cols; j++, si++) { if (idx === j) { continue } this._value[di++] = this._value[si] } } this._size[1]-- this._value.length = this.length } } else { throw new MatrixException('Invalid axis.') } } /** * Remove specified indexes. * @param {function (Matrix<T>): boolean} cond Remove condition function. Remove if it returns `true` * @param {number<T>} [axis] Axis to be removed */ removeIf(cond, axis = 0) { const idx = [] if (axis === 0) { for (let i = 0; i < this.rows; i++) { if (cond(this.row(i))) { idx.push(i) } } } else if (axis === 1) { for (let i = 0; i < this.cols; i++) { if (cond(this.col(i))) { idx.push(i) } } } else { throw new MatrixException('Invalid axis.') } this.remove(idx, axis) } /** * Returns a matrix that sampled along the axis. * @param {number} n Sampled size * @param {number} [axis] Axis to be sampled * @param {boolean} [duplicate] Allow duplicate index or not * @returns {[Matrix<T>, number[]]} Sampled matrix and its original indexes */ sample(n, axis = 0, duplicate = false) { const k = this.sizes[axis] const idx = [] if (duplicate) { for (let i = 0; i < n; i++) { idx.push(Math.floor(Math.random() * k)) } } else { if (n > k) { throw new MatrixException('Invalid sampled size.') } for (let i = 0; i < n; i++) { idx.push(Math.floor(Math.random() * (k - i))) } for (let i = n - 1; i >= 0; i--) { for (let j = n - 1; j > i; j--) { if (idx[i] <= idx[j]) { idx[j]++ } } } } if (axis === 0) { return [this.row(idx), idx] } else if (axis === 1) { return [this.col(idx), idx] } else { throw new MatrixException('Invalid axis.') } } /** * Fill in all the elements with the value. * @param {T} value Filled value */ fill(value) { this._value.fill(value) } /** * Iterate over all the elements and replace the value. * @param {function (T, number[], Matrix<T>): T} cb Mapping function */ map(cb) { for (let i = 0, p = 0; i < this._size[0]; i++) { for (let j = 0; j < this._size[1]; j++, p++) { this._value[p] = cb(this._value[p], [i, j], this) } } } /** * Returns a matrix that replace all the elements. * @template T,U * @param {Matrix<T>} mat Original matrix * @param {function (T, number[], Matrix<T>): U} cb Mapping function * @returns {Matrix<U>} Mapped matrix */ static map(mat, cb) { const map = new Matrix(mat.rows, mat.cols) for (let i = 0, p = 0; i < mat._size[0]; i++) { for (let j = 0; j < mat._size[1]; j++, p++) { map._value[p] = cb(mat._value[p], [i, j], mat) } } return map } /** * Iterate over all the elements. * @param {function (T, number[], Matrix<T>): *} cb Callback function */ forEach(cb) { for (let i = 0, p = 0; i < this._size[0]; i++) { for (let j = 0; j < this._size[1]; j++, p++) { cb(this._value[p], [i, j], this) } } } /** * Returns transpose matrix. * @returns {Matrix<T>} Transposed matrix */ transpose() { const mat = new Matrix(this.cols, this.rows) for (let i = 0; i < this.rows; i++) { for (let j = 0; j < this.cols; j++) { mat._value[j * this.rows + i] = this._value[i * this.cols + j] } } return mat } /** * Returns adjoint matrix. * @returns {Matrix<T>} Adjoint matrix */ adjoint() { return this.transpose() } /** * Flip values along the axis. * @param {number} [axis] Axis to be flipped */ flip(axis = 0) { if (axis === 0) { for (let i = 0; i < this.rows / 2; i++) { const t = (this.rows - i - 1) * this.cols for (let j = 0; j < this.cols; j++) { const tmp = this._value[i * this.cols + j] this._value[i * this.cols + j] = this._value[t + j] this._value[t + j] = tmp } } } else if (axis === 1) { for (let j = 0; j < this.cols / 2; j++) { const t = this.cols - j - 1 for (let i = 0; i < this.rows; i++) { const tmp = this._value[i * this.cols + j] this._value[i * this.cols + j] = this._value[i * this.cols + t] this._value[i * this.cols + t] = tmp } } } else { throw new MatrixException('Invalid axis.') } } /** * Swap the index a and b along the axis. * @param {number} a First index * @param {number} b Second index * @param {number} [axis] Axis to be swapped */ swap(a, b, axis = 0) { if (axis === 0) { if (a < 0 || b < 0 || this.rows <= a || this.rows <= b) throw new MatrixException('Index out of bounds.') const diff = (b - a) * this.cols for (let j = a * this.cols; j < (a + 1) * this.cols; j++) { ;[this._value[j], this._value[j + diff]] = [this._value[j + diff], this._value[j]] } } else if (axis === 1) { if (a < 0 || b < 0 || this.cols <= a || this.cols <= b) throw new MatrixException('Index out of bounds.') const diff = b - a for (let j = a; j < this.length; j += this.cols) { ;[this._value[j], this._value[j + diff]] = [this._value[j + diff], this._value[j]] } } else { throw new MatrixException('Invalid axis.') } } /** * Sort values along the axis. * @param {number} [axis] Axis to be sorted * @returns {number[]} Original index. */ sort(axis = 0) { if (axis === 0) { const p = Array.from({ length: this.rows }, (_, i) => i) p.sort((a, b) => { const ac = a * this.cols const bc = b * this.cols for (let i = 0; i < this.cols; i++) { const ai = this._value[ac + i] const bi = this._value[bc + i] const d = ai - bi if (d !== 0) return d } return 0 }) this._value = this.row(p)._value return p } else if (axis === 1) { const p = Array.from({ length: this.cols }, (_, i) => i) p.sort((a, b) => { for (let i = 0; i < this.rows; i++) { const ai = this._value[a + i * this.cols] const bi = this._value[b + i * this.cols] const d = ai - bi if (d !== 0) return d } return 0 }) this._value = this.col(p)._value return p } throw new MatrixException('Invalid axis.') } /** * Shuffle values along the axis. * @param {number} [axis] Axis to be shuffled * @returns {number[]} Original index. */ shuffle(axis = 0) { const idx = Array.from({ length: this._size[axis] }, (_, i) => i) for (let i = idx.length - 1; i > 0; i--) { const r = Math.floor(Math.random() * (i + 1)) ;[idx[i], idx[r]] = [idx[r], idx[i]] } if (axis === 0) { this._value = this.row(idx)._value } else if (axis === 1) { this._value = this.col(idx)._value } else { throw new MatrixException('Invalid axis.') } return idx } /** * Make it unique in the specified axis. * @param {number} [axis] Axis to be uniqued * @param {number} [tol] Tolerance to be recognized as the same * @returns {number[]} Selected indexes */ unique(axis = 0, tol = 0) { const idx = [] if (axis === 0) { let r = 0 for (let i = 0; i < this.rows; i++) { let same_k = -1 for (let k = 0; k < i; k++) { let issame = true for (let j = 0; issame && j < this.cols; j++) { if (Math.abs(this._value[i * this.cols + j] - this._value[k * this.cols + j]) > tol) { issame = false } } if (issame) { same_k = k break } } if (same_k < 0) { for (let j = 0; j < this.cols; j++) { this._value[r * this.cols + j] = this._value[i * this.cols + j] } idx.push(i) r++ } } this._size[0] = r this._value.length = this.length } else if (axis === 1) { for (let j = 0; j < this.cols; j++) { let hasSame = false for (let k = 0; !hasSame && k < idx.length; k++) { hasSame = true for (let i = 0; hasSame && i < this.rows; i++) { if (Math.abs(this._value[i * this.cols + j] - this._value[i * this.cols + idx[k]]) > tol) { hasSame = false } } } if (!hasSame) { idx.push(j) } } for (let i = 0, p = 0; i < this.rows; i++) { for (let j = 0; j < idx.length; j++, p++) { this._value[p] = this._value[i * this.cols + idx[j]] } } this._size[1] = idx.length this._value.length = this.length } else { throw new MatrixException('Invalid axis.') } return idx } /** * Resize this matrix. * @overload * @param {number} rows New row size * @param {number} cols New column size * @param {T} [init] Value of the extended region */ /** * Resize this matrix. * @overload * @param {[number, number]} size New sizes for each dimension * @param {T} [init] Value of the extended region */ /** * @param {number | [number, number]} rows New row size or sizes for each dimension * @param {number | T} [cols] New column size * @param {T} [init] Value of the extended region */ resize(rows, cols, init = 0) { if (Array.isArray(rows)) { init = cols ?? 0 ;[rows, cols] = rows } const newValue = Array(rows * cols).fill(init) const mr = Math.min(this.rows, rows) const mc = Math.min(this.cols, cols) for (let i = 0; i < mr; i++) { for (let j = 0; j < mc; j++) { newValue[i * cols + j] = this._value[i * this.cols + j] } } this._value = newValue this._size = [rows, cols] } /** * Return resized matrix. * @template T * @overload * @param {Matrix<T>} mat Original matrix * @param {number} rows New row size * @param {number} cols New column size * @param {T} [init] Value of the extended region * @returns {Matrix<T>} Resized matrix */ /** * Return resized matrix. * @template T * @overload * @param {Matrix<T>} mat Original matrix * @param {[number, number]} rows New sizes for each dimension * @param {T} [init] Value of the extended region * @returns {Matrix<T>} Resized matrix */ /** * @template T * @param {Matrix<T>} mat Original matrix * @param {number | [number, number]} rows New row size or sizes for each dimension * @param {number | T} [cols] New column size * @param {T} [init] Value of the extended region * @returns {Matrix<T>} Resized matrix */ static resize(mat, rows, cols, init = 0) { if (Array.isArray(rows)) { init = cols ?? 0 ;[rows, cols] = rows } const r = new Matrix(rows, cols) r.fill(init) const mr = Math.min(mat.rows, rows) const mc = Math.min(mat.cols, cols) for (let i = 0; i < mr; i++) { for (let j = 0; j < mc; j++) { r._value[i * cols + j] = mat._value[i * mat.cols + j] } } return r } /** * Reshape this. * @overload * @param {number} rows New row size * @param {number} cols New column size */ /** * Reshape this. * @overload * @param {[number, number]} sizes New sizes for each dimension */ /** * @param {number | [number, number]} rows New row size * @param {number} [cols] New column size */ reshape(rows, cols) { if (Array.isArray(rows)) { ;[rows, cols] = rows } if (rows === -1) { if (this.length % cols !== 0) { throw new MatrixException('Length is different.') } rows = this.length / cols } else if (cols === -1) { if (this.length % rows !== 0) { throw new MatrixException('Length is different.') } cols = this.length / rows } else if (this.length !== rows * cols) throw new MatrixException('Length is different.') this._size = [rows, cols] } /** * Repeat the elements n times along the axis this. * @overload * @param {number} n Repeated count * @param {number} [axis] Axis to be repeated * @returns {void} No return */ /** * Repeat the elements n times along the axis this. * @overload * @param {number[]} n Repeated counts for each axis * @returns {void} No return */ /** * @param {number | number[]} n Repeated count(s) * @param {number} [axis] Axis to be repeated */ repeat(n, axis = 0) { if (!Array.isArray(n)) { const an = Array(this._size.length).fill(1) an[axis] = n n = an } else if (n.length < this._size.length) { for (let i = n.length; i < this._size.length; i++) { n[i] = 1 } } const p = n.reduce((s, v) => s * v, 1) if (p === 1) { return } const new_value = Array(this.length * p) const new_size = this._size.map((s, i) => s * n[i]) for (let i = 0; i < new_size[0]; i++) { for (let j = 0; j < new_size[1]; j++) { new_value[i * new_size[1] + j] = this._value[(i % this.rows) * this.cols + (j % this.cols)] } } this._value = new_value this._size = new_size } /** * Returns a matrix that repeat the elements n times along the axis. * @template T * @overload * @param {Matrix<T>} mat Original matrix * @param {number} n Repeated count * @param {number} [axis] Axis to be repeated * @returns {Matrix<T>} Repeated matrix */ /** * Returns a matrix that repeat the elements n times along the axis. * @template T * @overload * @param {Matrix<T>} mat Original matrix * @param {number[]} n Repeated counts for each axis * @returns {Matrix<T>} Repeated matrix */ /** * @template T * @param {Matrix<T>} mat Original matrix * @param {number | number[]} n Repeated count(s) * @param {number} [axis] Axis to be repeated * @returns {Matrix<T>} Repeated matrix */ static repeat(mat, n, axis = 0) { const r = mat.copy() r.repeat(n, axis) return r } /** * Concatenate this and m. * @param {Matrix<T>} m Concatenate matrix * @param {number} [axis] Axis to be concatenated */ concat(m, axis = 0) { if (axis === 0) { if (this.cols !== m.cols) throw new MatrixException('Size is different.') this._value = [].concat(this._value, m._value) this._size[0] += m.rows } else if (axis === 1) { if (this.rows !== m.rows) throw new MatrixException('Size is different.') const orgCol = this.cols this.resize(this.rows, this.cols + m.cols) for (let i = 0; i < this.rows; i++) { for (let j = 0; j < m.cols; j++) { this._value[i * this.cols + j + orgCol] = m._value[i * m.cols + j] } } } else { throw new MatrixException('Invalid axis.') } } /** * Returns a matrix concatenated this and m. * @template T,U * @param {Matrix<T>} a Original matrix * @param {Matrix<U>} b Concatenate matrix * @param {number} [axis] Axis to be concatenated * @returns {Matrix<T | U>} Concatenated matrix */ static concat(a, b, axis = 0) { const r = a.copy() r.concat(b, axis) return r } /** * Returns a matrix reduced along all element with the callback function. * @overload * @param {function (T, T, number[], Matrix<T>): T} cb Reducing function * @param {undefined | null} [init] Initial value * @returns {T} Reduced value */ /** * Returns a matrix reduced along all element with the callback function. * @template U * @overload * @param {function (U, T, number[], Matrix<T>): U} cb Reducing function * @param {U} init Initial value * @returns {U} Reduced value */ /** * Returns a matrix reduced along the axis with the callback function. * @template {number | number[]} A * @template {boolean} F * @overload * @param {function (T, T, number[], Matrix<T>): T} cb Reducing function * @param {undefined | null} init Initial value * @param {A} axis Axis to be reduced * @param {F} [keepdims] Keep dimensions or not. If null, negative axis retuns number and other axis returns Matrix. * @returns {Matrix<T> | (A extends 0 | 1 ? never : F extends true ? never : T)} Reduced matrix */ /** * Returns a matrix reduced along the axis with the callback function. * @template U * @template {number | number[]} A * @template {boolean} F * @overload * @param {function (U, T, number[], Matrix<T>): U} cb Reducing function * @param {U} init Initial value * @param {A} axis Axis to be reduced * @param {F} [keepdims] Keep dimensions or not. If null, negative axis retuns number and other axis returns Matrix. * @returns {Matrix<U> | (A extends 0 | 1 ? never : F extends true ? never : U)} Reduced matrix */ /** * @template U * @param {function (U, T, number[], Matrix<T>): U} cb Reducing function * @param {U} [init] Initial value * @param {number | number[]} [axis] Axis to be reduced. If negative, reduce along all elements. * @param {boolean} [keepdims] Keep dimensions or not. If null, negative axis retuns number and other axis returns Matrix. * @returns {Matrix<U> | U} Reduced matrix or value */ reduce(cb, init, axis = -1, keepdims = null) { if (Array.isArray(axis)) { if (axis.includes(-1) || (axis.includes(0) && axis.includes(1))) { axis = -1 } else if (axis.includes(0)) { axis = 0 } else if (axis.includes(1)) { axis = 1 } else { throw new MatrixException('Invalid axis.') } } if (axis > 1) { throw new MatrixException('Invalid axis.') } if (axis < 0) { let v = init ?? this._value[0] for (let i = 0, p = 0; i < this._size[0]; i++) { for (let j = 0; j < this._size[1]; j++, p++) { if (p === 0 && init == null) { continue } v = cb(v, this._value[p], [i, j], this) } } if (keepdims === true) { return new Matrix(1, 1, v) } return v } if (keepdims === false) { throw new MatrixException('keepdims only accept true if axis >= 0.') } const v_step = axis === 0 ? 1 : this.cols const s_step = axis === 0 ? this.cols : 1 const new_size = [].concat(this._size) new_size[axis] = 1 const mat = Matrix.zeros(...new_size) for (let n = 0, nv = 0; n < mat.length; n++, nv += v_step) { let v = init ?? this._value[nv] for (let i = v === init ? 0 : 1; i < this._size[axis]; i++) { v = cb(v, this._value[i * s_step + nv], axis === 0 ? [i, n] : [n, i], this) } mat._value[n] = v } return mat } /** * Determines whether all the members of a matrix satisfy the specified test. * @overload * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @returns {boolean} Reduced value or matrix */ /** * Determines whether all the members of a matrix satisfy the specified test. * @overload * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<boolean>} Reduced value or matrix */ /** * Determines whether all the members of a matrix satisfy the specified test. * @overload * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @param {number} axis Axis to be reduced * @returns {boolean | Matrix<boolean>} Reduced value or matrix */ /** * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @param {number} [axis] Axis to be reduced * @returns {boolean | Matrix<boolean>} Reduced value or matrix */ every(cb, axis = -1) { return this.reduce((f, v, i, m) => f && cb(v, i, m), true, axis) } /** * Determines whether the specified callback function returns true for any element of a matrix. * @overload * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @returns {boolean} Reduced value or matrix */ /** * Determines whether the specified callback function returns true for any element of a matrix. * @overload * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<boolean>} Reduced value or matrix */ /** * Determines whether the specified callback function returns true for any element of a matrix. * @overload * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @param {number} axis Axis to be reduced * @returns {boolean | Matrix<boolean>} Reduced value or matrix */ /** * @param {function (T, number[], Matrix<T>): boolean} cb Check function * @param {number} [axis] Axis to be reduced * @returns {boolean | Matrix<boolean>} Reduced value or matrix */ some(cb, axis = -1) { return this.reduce((f, v, i, m) => f || cb(v, i, m), false, axis) } /** * Returns maximum value of all element. * @overload * @returns {number} Maximum value */ /** * Returns maximum values along the axis. * @overload * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<number>} Maximum values */ /** * Returns maximum values along the axis. * @overload * @param {number} axis Axis to be reduced. If negative, returns the maximum value of the all element. * @returns {Matrix<number> | number} Maximum values */ /** * @param {number} [axis] Axis to be reduced. If negative, returns the maximum value of the all element. * @returns {Matrix<number> | number} Maximum values */ max(axis = -1) { return this.reduce((m, v) => Math.max(m, v), -Infinity, axis) } /** * Returns minimum value of all element. * @overload * @returns {number} Minimum value */ /** * Returns minimum values along the axis. * @overload * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<number>} Minimum values */ /** * Returns minimum values along the axis. * @overload * @param {number} axis Axis to be reduced. If negative, returns the minimum value of the all element. * @returns {Matrix<number> | number} Minimum values */ /** * @param {number} [axis] Axis to be reduced. If negative, returns the minimum value of the all element. * @returns {Matrix<number> | number} Minimum values */ min(axis = -1) { return this.reduce((m, v) => Math.min(m, v), Infinity, axis) } /** * Returns median of all element. * @overload * @returns {number} Median value */ /** * Returns medians along the axis. * @overload * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<number>} Median values */ /** * Returns medians along the axis. * @overload * @param {number} axis Axis to be reduced. If negative, returns a median of the all element. * @returns {Matrix<number> | number} Median values */ /** * @param {number} [axis] Axis to be reduced. If negative, returns a median of the all element. * @returns {Matrix<number> | number} Median values */ median(axis = -1) { if (axis < 0) { const v = this._value.concat() v.sort((a, b) => a - b) if (v.length % 2 === 1) { return v[(v.length - 1) / 2] } else { return (v[v.length / 2] + v[v.length / 2 - 1]) / 2 } } const v_step = axis === 0 ? 1 : this.cols const s_step = axis === 0 ? this.cols : 1 const new_size = [].concat(this._size) new_size[axis] = 1 const mat = Matrix.zeros(...new_size) for (let n = 0, nv = 0; n < mat.length; n++, nv += v_step) { const v = [] for (let i = 0; i < this._size[axis]; i++) { v.push(this._value[i * s_step + nv]) } v.sort((a, b) => a - b) if (v.length % 2 === 1) { mat._value[n] = v[(v.length - 1) / 2] } else { mat._value[n] = (v[v.length / 2] + v[v.length / 2 - 1]) / 2 } } return mat } /** * Returns quantile value of all element. * @overload * @param {number} q Partition rate * @returns {number} Quantile value */ /** * Returns quantile values along the axis. * @overload * @param {number} q Partition rate * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<number>} Quantile values */ /** * Returns quantile values along the axis. * @overload * @param {number} q Partition rate * @param {number} axis Axis to be reduced. If negative, returns the quantile value of the all element. * @returns {Matrix<number> | number} Quantile values */ /** * @param {number} q Partition rate * @param {number} [axis] Axis to be reduced. If negative, returns the quantile value of the all element. * @returns {Matrix<number> | number} Quantile values */ quantile(q, axis = -1) { if (q === 0) { return this.min(axis) } else if (q === 1) { return this.max(axis) } const quantile = (value, q) => { value.sort((a, b) => a - b) if (value.length === 1) { return value[0] } const q1 = q * (value.length - 1) const q0 = Math.floor(q1) if (q1 === q0) { return value[q0] } return value[q0] * (1 - q1 + q0) + value[q0 + 1] * (q1 - q0) } if (axis < 0) { const value = this._value.concat() return quantile(value, q) } const v_step = axis === 0 ? 1 : this.cols const s_step = axis === 0 ? this.cols : 1 const mat = Matrix.zeros(...this._size.map((v, i) => (i === axis ? 1 : v))) for (let n = 0, nv = 0; n < mat.length; n++, nv += v_step) { const v = [] for (let i = 0; i < this._size[axis]; i++) { v.push(this._value[i * s_step + nv]) } mat._value[n] = quantile(v, q) } return mat } /** * Returns maximum indexes along the axis. * @param {number} axis Axis to be reduced * @returns {Matrix<number>} Argmax values */ argmax(axis) { const v_step = axis === 0 ? 1 : this.cols const s_step = axis === 0 ? this.cols : 1 const new_size = [].concat(this._size) new_size[axis] = 1 const mat = Matrix.zeros(...new_size) for (let n = 0, nv = 0; n < mat.length; n++, nv += v_step) { let v = this._value[nv] let idx = 0 for (let i = 1; i < this._size[axis]; i++) { const tmp = this._value[i * s_step + nv] if (tmp > v) { v = tmp idx = i } } mat._value[n] = idx } return mat } /** * Returns minimum indexes along the axis. * @param {number} axis Axis to be reduced * @returns {Matrix<number>} Argmin values */ argmin(axis) { const v_step = axis === 0 ? 1 : this.cols const s_step = axis === 0 ? this.cols : 1 const new_size = [].concat(this._size) new_size[axis] = 1 const mat = Matrix.zeros(...new_size) for (let n = 0, nv = 0; n < mat.length; n++, nv += v_step) { let v = this._value[nv] let idx = 0 for (let i = 1; i < this._size[axis]; i++) { const tmp = this._value[i * s_step + nv] if (tmp < v) { v = tmp idx = i } } mat._value[n] = idx } return mat } /** * Returns summation value of all element. * @overload * @returns {number} Summation value */ /** * Returns summation values along the axis. * @overload * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<number>} Summation values */ /** * Returns summation values along the axis. * @overload * @param {number} axis Axis to be reduced. If negative, returns a summation value of the all element. * @returns {Matrix<number> | number} Summation values */ /** * @param {number} [axis] Axis to be reduced. If negative, returns a summation value of the all element. * @returns {Matrix<number> | number} Summation values */ sum(axis = -1) { return this.reduce((s, v) => s + v, 0, axis) } /** * Returns mean of all element. * @overload * @returns {number} Mean value */ /** * Returns means along the axis. * @overload * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<number>} Mean values */ /** * Returns means along the axis. * @overload * @param {number} axis Axis to be reduced. If negative, returns a mean value of the all element. * @returns {Matrix<number> | number} Mean values */ /** * @param {number} [axis] Axis to be reduced. If negative, returns a mean value of the all element. * @returns {Matrix<number> | number} Mean values */ mean(axis = -1) { if (axis < 0) { return this.sum(axis) / this.length } const m = this.sum(axis) m.div(this._size[axis]) return m } /** * Returns producted value of all element. * @overload * @returns {number} Producted value */ /** * Returns producted values along the axis. * @overload * @param {0 | 1} axis Axis to be reduced * @returns {Matrix<number>} Producted values */ /** * Returns producted values along the axis. * @overload * @param {number} axis Axis to be reduced. If negative, returns a producted value of the all element. * @returns {Matrix<number> | number} Producted values */ /** * @param {number} [axis] Axis to be reduced. If negative, returns a producted value of the all element. * @returns {Matrix<number> | number} Producted values */ prod(axis = -1) { return this.reduce((s, v) => s * v, 1, axis) } /** * Returns variance of all element. * @overload * @returns {number} Variance value */ /** * Returns variances along the axis. * @overload * @param {0 | 1} axis Axis to be reduced. * @param {number} [ddof] Delta Degrees of Freedom * @returns {Matrix<number>} Variance values */ /** * Returns variances along the axis. * @overload * @param {number} axis Axis to be reduced. If negative, returns a variance of the all element. * @param {number} [ddof] Delta Degrees of Freedom * @returns {Matrix<number> | number} Variance values */ /** * @param {number} [axis] Axis to be reduced. If negative, returns a variance of the all element. * @param {number} [ddof] De