@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
1,803 lines (1,735 loc) • 133 kB
JavaScript
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