mljs/matrix

Apply function row/column-wise

Opened this issue · 3 comments

Hello,

thanks for the great library, I use it quite often in my work. Something I didn't find in the API and thus copy around my projects is an apply function? Like R's apply or numpy's apply_along_axis.

It seems like a simple function, my implementation is basically

function apply(M, fun, axis = 1) {
  // axis = 0: apply and collapse along cols (= output length is # rows)
  // axis = 1: apply and collapse along rows (= output length is # cols)
  const collapseRows = axis === 1;
  const n = collapseRows ? M.columns : M.rows;
  const result = [];
  for (let i = 0; i < n; i++) {
    const x = collapseRows ? M.getColumn(i) : M.getRow(i);
    result.push(fun(x));
  }
  return Matrix.rowVector(result); // NOTE could also be columnVector depending on `axis`, I just never wanted one...
}

Would this be something that you would consider adding to your library?

This seems like a useful feature. Pull request welcome (please add test cases and update matrix.d.ts)!

We suggest the following signature:

class Matrix {
  applyAlongAxis(
    callback: (vector: number[], index: number) => number,
    by: MatrixDimension
  ): Matrix
}
class MatrixExt extends Matrix {
	mapColumns(fun: (column: number[], rowIndex: number, matrix: this) => number | number[]): Matrix {
		const n = this.columns;
		const result = Array();
		for (let i = 0; i < n; i++) {
			const x = this.getColumn(i)
			result.push(fun(x, i, this));
		}
		if (Array.isArray(result[0])) {
			return new Matrix(result).transpose()
		} else {
			return Matrix.rowVector(result);
		}
	}
}

let m = new MatrixExt([
	[1, 2, 3],
	[4, 5, 6],
	[7, 8, 9]
])

console.log(
	m,
	/**
	 * Matrix {
	 *   [
	 *     147      258      369     
	 *   ]
	 */
	m.mapColumns(
		(row, i, a) => row.reduce((r, x) => r * 10 + x)
	),
	/**
	 * Matrix {
	 *  [
	 *    1        2        3       
	 *    11       22       33      
	 *    4        5        6
	 *    44       55       66
	 *    7        8        9
	 *    77       88       99
	 *  ]
	 */
	m.mapColumns(
		(row, i, a) => row.flatMap(x => [x, x * 11])
	),
)

Here's implementation of an extended variant

What part of it is technically usable?

Hi, sorry for the late reply, I have actually no idea where the notifications go, if they go somewhere at all.

@Dimava: I like your approach because it's more flexible regarding the resulting matrix dimensions.

@targos: What do you think about it? Should it rather match what exists in other libraries / languages?