mljs / matrix

Matrix manipulation and computation library
https://mljs.github.io/matrix/
MIT License
353 stars 54 forks source link

Apply function row/column-wise #154

Open npiccolotto opened 2 years ago

npiccolotto commented 2 years ago

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?

targos commented 2 years ago

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
}
Dimava commented 1 year ago
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?

npiccolotto commented 1 year ago

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?