-
Notifications
You must be signed in to change notification settings - Fork 0
Matrix
A matrix is a structure of numbers in tabular format for which mathematical operations
that are analagous to arithmetic or that are geometrical in nature are available.
Below, the fluent-data library is loaded into the variable $$ and and three arrays are
created. These arrays are then pushed into three corresponding matricies. These arrays
and matricies are used by the examples in this page.
let $$ = require('./dist/fluent-data.server.js');
let community_array = [
{ marker: 'Applewood Park', x: 0, y: 0 },
{ marker: 'Orangewood School', x: 10, y: 0},
{ marker: 'Kiwitown Market', x: 1, y: 10 },
{ marker: `The Millers`, x: -5, y: 0 },
{ marker: 'The Romeros', x: 0, y: -5 },
{ marker: 'The Lees', x: 5, y: 5 },
{ marker: 'The Keitas', x: 5, y: 0 },
{ marker: 'The Lebedevs', x: 15, y: 5 }
];
let plotA_array = [
{ marker: 'possible park', x: 660, y: -660 },
{ marker: 'possible school', x: -1320, y: 1320 }
];
let plotB_array = [
{ marker: 'possible park', x: 112.1830, y: 33.5425 },
{ marker: 'possible school', x: 112.3693, y: 33.6295 }
];
let community = $$(community_array).matrix('x,y', 'marker');
let plotA = $$(plotA_array).matrix('x,y', 'marker');
let plotB = $$(plotB_array).matrix('x,y', 'marker');
For reference in the expandable examples below, the contents of the matricies are as follows:
community.log(null, 'community');
community.log(null, 'plotA');
community.log(null, 'plotB');
community
┌───────────────────┬────┬────┐
│ │ x │ y │
├───────────────────┼────┼────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 1 │ 10 │
│ The Millers │ -5 │ 0 │
│ The Romeros │ 0 │ -5 │
│ The Lees │ 5 │ 5 │
│ The Keitas │ 5 │ 0 │
│ The Lebedevs │ 15 │ 5 │
└───────────────────┴────┴────┘
plotA
┌───────────────────┬────┬────┐
│ │ x │ y │
├───────────────────┼────┼────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 1 │ 10 │
│ The Millers │ -5 │ 0 │
│ The Romeros │ 0 │ -5 │
│ The Lees │ 5 │ 5 │
│ The Keitas │ 5 │ 0 │
│ The Lebedevs │ 15 │ 5 │
└───────────────────┴────┴────┘
plotB
┌───────────────────┬────┬────┐
│ │ x │ y │
├───────────────────┼────┼────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 1 │ 10 │
│ The Millers │ -5 │ 0 │
│ The Romeros │ 0 │ -5 │
│ The Lees │ 5 │ 5 │
│ The Keitas │ 5 │ 0 │
│ The Lebedevs │ 15 │ 5 │
└───────────────────┴────┴────┘
Constructing a Matrix
Perhaps the quickest method to constructing a matrix is to use the csv syntax. Simply list the properties you wish to include in the matrix in a comma separated string. This will pick up the relevant properties for inclusion into the matrix and it will set the column names. You can add an optional second string parameter to identify the property having the row names.
let community = $$(community_array).matrix(
'x, y', // the properties to include in the matrix
'marker' // the property describing a given row's name
);
Sometimes it is necessary to construct a matrix in a way that doesn't simply just list the property names. For such situations, an alternate constructor syntax is available that accepts a lambda function returning an array. Its use is below, as is the use of methods that can set the row and column names outside of the constructor.
let community = $$(community_array)
.matrix(row => [row.x + 1, row.y / 2])
.setColNames('xPlusOne, yHalf')
.setRowNames(community_array.map(row => row.marker))
Sometimes you just want to make up a quick matrix straight from an array, and aren't so concerned with row or column names.
let community = new $$.matrix(community_array);
cols
An iterator that allows you to loop the columns of a matrix. Each iteration produces a matrix representing a column in the parent matrix.
for(let col of plotA.cols)
col.log();
┌─────────────────┬───────┐
│ │ x │
├─────────────────┼───────┤
│ possible park │ 660 │
│ possible school │ -1320 │
└─────────────────┴───────┘
┌─────────────────┬──────┐
│ │ y │
├─────────────────┼──────┤
│ possible park │ -660 │
│ possible school │ 1320 │
└─────────────────┴──────┘
colNames
The column names of a matrix.
console.log(community.colNames);
[ 'x', 'y' ]
data
The underlying nested array representing the core data of the matrix.
console.log(community.data);
[
[ 0, 0 ], [ 10, 0 ],
[ 1, 10 ], [ -5, 0 ],
[ 0, -5 ], [ 5, 5 ],
[ 5, 0 ], [ 15, 5 ]
]
nCell
Produces the count of cells in a matrix.
console.log(`commmunity has ${community.nCell} cells.`);
commmunity has 16 cells.
nCol
Produces the count of columns in a matrix.
console.log(`commmunity has ${community.nCol} columns.`);
commmunity has 2 columns.
nRow
Produces the count of rows in a matrix.
console.log(`commmunity has ${community.nRow} rows.`);
commmunity has 8 rows.
rows
An iterator that allows you to loop the rows of a matrix. Each iteration produces a matrix representing a row in the parent matrix.
for(let row of plotA.rows)
row.log();
┌───────────────┬─────┬──────┐
│ │ x │ y │
├───────────────┼─────┼──────┤
│ possible park │ 660 │ -660 │
└───────────────┴─────┴──────┘
┌─────────────────┬───────┬──────┐
│ │ x │ y │
├─────────────────┼───────┼──────┤
│ possible school │ -1320 │ 1320 │
└─────────────────┴───────┴──────┘
rowNames
The row names of a matrix.
console.log(community.rowNames);
[
'Applewood Park',
'Orangewood School',
'Kiwitown Market',
'The Millers',
'The Romeros',
'The Lees',
'The Keitas',
'The Lebedevs'
]
add
Add two matrices from each other: cell by cell.
plotA.add(plotB).log();
┌─────────────────┬────────────┬───────────┐
│ │ x │ y │
├─────────────────┼────────────┼───────────┤
│ possible park │ 772.183 │ -626.4575 │
│ possible school │ -1207.6307 │ 1353.6295 │
└─────────────────┴────────────┴───────────┘
appendCols
Append columns to a matrix.
plotA.appendCols(plotB).log();
┌─────────────────┬──────────┬─────────┐
│ │ x │ y │
├─────────────────┼──────────┼─────────┤
│ possible park │ 112.183 │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
appendRows
Append rows to a matrix.
plotA.appendRows(plotB).log();
┌─────────────────┬──────────┬─────────┐
│ │ x │ y │
├─────────────────┼──────────┼─────────┤
│ possible park │ 660 │ -660 │
│ possible school │ -1320 │ 1320 │
│ possible park │ 112.183 │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
apply
Apply a function to each cell in a matrix. Or, apply a function relating each cell in two matrices of the same size.
plotA
.apply(cell => Math.sign(cell))
.log(null, 'signs');
plotA
.apply(plotB, (a,b) => a > b ? 'a' : 'b')
.log(null, 'which is bigger?');
signs
┌─────────────────┬────┬────┐
│ │ x │ y │
├─────────────────┼────┼────┤
│ possible park │ 1 │ -1 │
│ possible school │ -1 │ 1 │
└─────────────────┴────┴────┘
which is bigger?
┌─────────────────┬───┬───┐
│ │ x │ y │
├─────────────────┼───┼───┤
│ possible park │ a │ b │
│ possible school │ b │ a │
└─────────────────┴───┴───┘
diagonal
Takes the diagonal entries of a matrix to the exclusion of the rest. An optional boolean parameter controls whether the output is as a matrix with zeroed out non-diagonal entries, or if it is a vector with the diagonal entries listed in a single column. The default is the former.
plotA.diagonal().log();
plotA.diagonal(true).log();
┌─────────────────┬─────┬──────┐
│ │ x │ y │
├─────────────────┼─────┼──────┤
│ possible park │ 660 │ 0 │
│ possible school │ 0 │ 1320 │
└─────────────────┴─────┴──────┘
┌────┬──────┐
│ │ c0 │
├────┼──────┤
│ r0 │ 660 │
│ r1 │ 1320 │
└────┴──────┘
filter
Subsets rows and columns of a matrix based on index attributes or cell value. Remember that javascript has 0-based indexing. So there is such a thing as row 0. It's the first row.
The example below grabs rows 0, 3, and 5 of the community matrix. It repeats the third row twice and it switches the order of the columns.
community.filter([0,3,3,5], [1,0]).log();
┌────────────────┬───┬────┐
│ │ y │ x │
├────────────────┼───┼────┤
│ Applewood Park │ 0 │ 0 │
│ The Millers │ 0 │ -5 │
│ The Millers │ 0 │ -5 │
│ The Lees │ 5 │ 5 │
└────────────────┴───┴────┘
If only one row or column is desired, an integer can be passed instead of an array. If all rows or all columns are desired, pass null or undefined to the respective parameter.
community
.filter(null, 1)
.log(null, 'second column');
community
.filter(5)
.log(null, 'sixth row');
community
.filter()
.log(null, 'all');
second column
┌───────────────────┬────┐
│ │ y │
├───────────────────┼────┤
│ Applewood Park │ 0 │
│ Orangewood School │ 0 │
│ Kiwitown Market │ 10 │
│ The Millers │ 0 │
│ The Romeros │ -5 │
│ The Lees │ 5 │
│ The Keitas │ 0 │
│ The Lebedevs │ 5 │
└───────────────────┴────┘
sixth row
┌──────────┬───┬───┐
│ │ x │ y │
├──────────┼───┼───┤
│ The Lees │ 5 │ 5 │
└──────────┴───┴───┘
all
┌───────────────────┬────┬────┐
│ │ x │ y │
├───────────────────┼────┼────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 1 │ 10 │
│ The Millers │ -5 │ 0 │
│ The Romeros │ 0 │ -5 │
│ The Lees │ 5 │ 5 │
│ The Keitas │ 5 │ 0 │
│ The Lebedevs │ 15 │ 5 │
└───────────────────┴────┴────┘
Here, all rows are fetched becaue the first parameter is null. All columns are returned except the first, because a negative in front of a positional indicates to exclude that column but include the rest. There is such a thing as a negative 0 in javascript, so it is not equivalent to replacing it with positive 0.
community.filter(null, [-0]).log();
┌───────────────────┬────┐
│ │ y │
├───────────────────┼────┤
│ Applewood Park │ 0 │
│ Orangewood School │ 0 │
│ Kiwitown Market │ 10 │
│ The Millers │ 0 │
│ The Romeros │ -5 │
│ The Lees │ 5 │
│ The Keitas │ 0 │
│ The Lebedevs │ 5 │
└───────────────────┴────┘
filter also has a functional syntax. The first parameter of the input function is
the row or column index. The second parameter is optional. If passed in it points
to the entire row or entire column.
The first example below fetches all rows with index of 3 or less (the first four rows). The second example fetches the first column for any row with a value in that column that is positive.
community
.filter(ix => ix <= 3, null)
.log(null, 'first four');
community
.filter((ix,row) => row[0] > 0, 0)
.log(null, 'column 1 positives');
first four
┌───────────────────┬────┬────┐
│ │ x │ y │
├───────────────────┼────┼────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 1 │ 10 │
│ The Millers │ -5 │ 0 │
└───────────────────┴────┴────┘
column 1 positives
┌───────────────────┬────┐
│ │ x │
├───────────────────┼────┤
│ Orangewood School │ 10 │
│ Kiwitown Market │ 1 │
│ The Lees │ 5 │
│ The Keitas │ 5 │
│ The Lebedevs │ 15 │
└───────────────────┴────┘
log
Output matrix data to the console in a friendly table form that utilizes the existing row and column names. After logging, the method returns the matrix that called it so that the fluent syntax chain can continue.
Parameters:
-
element: An html element to print to. Use the same selector syntax as you would use in
document.querySelector(). Be sure the element is one in which it makes sense to append adivto. If null (default), prints to the console. - caption: A string representing a title for the output. If null (default), does not print out a caption.
-
mapper: A function giving a final mapping of the rows before output. If null
(default),
x => xis ultimately passed. Alternatively, a number representing the multiple to which output should round numbers. - limit: An integer (default = 50). The maximum number of rows to be printed.
plotB
.log(null, 'PlotB, unrounded, before apply')
.apply(cell => cell + 1)
.log(null, 'PlotB + 1', 1e-2);
PlotB, unrounded, before apply
┌─────────────────┬──────────┬─────────┐
│ │ x │ y │
├─────────────────┼──────────┼─────────┤
│ possible park │ 112.183 │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
PlotB + 1
┌─────────────────┬────────┬───────┐
│ │ x │ y │
├─────────────────┼────────┼───────┤
│ possible park │ 113.18 │ 34.54 │
│ possible school │ 113.37 │ 34.63 │
└─────────────────┴────────┴───────┘
multiply
Multiply a matrix with a scalar value, a 'vector', or another matrix.
let vectorTens = new $$.matrix([[10],[10]]);
community
.multiply(plotA)
.log(null, 'multiplied by matrix');
community
.multiply(vectorTens)
.log(null, 'multiplied by vector');
community
.multiply(10)
.log(null, 'multiplied by scalar');
multiplied by matrix
┌───────────────────┬────────┬───────┐
│ │ x │ y │
├───────────────────┼────────┼───────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 6600 │ -6600 │
│ Kiwitown Market │ -12540 │ 12540 │
│ The Millers │ -3300 │ 3300 │
│ The Romeros │ 6600 │ -6600 │
│ The Lees │ -3300 │ 3300 │
│ The Keitas │ 3300 │ -3300 │
│ The Lebedevs │ 3300 │ -3300 │
└───────────────────┴────────┴───────┘
multiplied by vector
┌───────────────────┬─────┐
│ │ c0 │
├───────────────────┼─────┤
│ Applewood Park │ 0 │
│ Orangewood School │ 100 │
│ Kiwitown Market │ 110 │
│ The Millers │ -50 │
│ The Romeros │ -50 │
│ The Lees │ 100 │
│ The Keitas │ 50 │
│ The Lebedevs │ 200 │
└───────────────────┴─────┘
multiplied by scalar
┌───────────────────┬─────┬─────┐
│ │ x │ y │
├───────────────────┼─────┼─────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 100 │ 0 │
│ Kiwitown Market │ 10 │ 100 │
│ The Millers │ -50 │ 0 │
│ The Romeros │ 0 │ -50 │
│ The Lees │ 50 │ 50 │
│ The Keitas │ 50 │ 0 │
│ The Lebedevs │ 150 │ 50 │
└───────────────────┴─────┴─────┘
pseudoInverse
pseudoInverse produces a matrix such that when the new matrix is multiplied
by the original matrix (or the other way around), the identity matrix is produced.
This will produce an approximation, so it is advised that the end-result be
rounded before reporting out.
Two optional parameters are available. The first, 'errorThreshold', determines when to stop the iterative process. The second, 'maxIterations', sets a custom cap on the number of iterations that are permitted before a failure is returned.
In the example below, pseudoInverse is called with possible values for these
parameters commented out.
let inverted = plotB.pseudoInverse(/* 1e-16, 1000 */);
let multiplied = plotB.multiply(inverted);
inverted.log(null, 'inverted', 1e-8);
multiplied.log(null, 'multiplied', 1e-8);
plotB multiplied by its inverse in fact produces an identity matrix.
inverted
┌───┬───────────────┬─────────────────┐
│ │ possible park │ possible school │
├───┼───────────────┼─────────────────┤
│ x │ 9.5784528 │ -9.55367321 │
│ y │ -32.00535353 │ 31.95229102 │
└───┴───────────────┴─────────────────┘
multiplied
┌─────────────────┬───────────────┬─────────────────┐
│ │ possible park │ possible school │
├─────────────────┼───────────────┼─────────────────┤
│ possible park │ 1 │ 0 │
│ possible school │ 0 │ 1 │
└─────────────────┴───────────────┴─────────────────┘
Presently, there is an inverse method that produces the inverse. This uses the
solve method to find it. It is not advised to use it. PseudoInverse is typically
more accurate. It may dissapear in the future.
reduce
Aggregate (aka: summarize or fold) a matrix.
The first parameter indicates the direction of the aggregation. You can choose 'row', 'col', or 'all'. Alternatively, you can plug in 1, 2, or 0 to get the respective directions. Finally, you can write out 'column' entirely.
The second parameter requires a two-parameter function used to indicate how
the aggregation occurs. The first parameter of this function is the working
aggregation. The second is the new incoming cell value to be worked into the
first. It is similar in concept to javascript's Array.reduce().
The third parameter is optional. It allows the user to pass in a seed to get the aggregation started.
Below are simple additiona aggregations for each direction. The last one demonstrates the use of a seed of 100.
community
.reduce('row', (a,b) => a + b)
.log(null, 'row aggs');
community
.reduce('col', (a,b) => a + b)
.log(null, 'col aggs');
community
.reduce('all', (a,b) => a + b)
.log(null, 'full agg');
community
.reduce('all', (a,b) => a + b, 100)
.log(null, 'seeded full agg');
row aggs
┌───────────────────┬────┐
│ │ c0 │
├───────────────────┼────┤
│ Applewood Park │ 0 │
│ Orangewood School │ 10 │
│ Kiwitown Market │ 11 │
│ The Millers │ -5 │
│ The Romeros │ -5 │
│ The Lees │ 10 │
│ The Keitas │ 5 │
│ The Lebedevs │ 20 │
└───────────────────┴────┘
col aggs
┌────┬────┬────┐
│ │ x │ y │
├────┼────┼────┤
│ r0 │ 31 │ 15 │
└────┴────┴────┘
full agg
┌────┬────┐
│ │ c0 │
├────┼────┤
│ r0 │ 46 │
└────┴────┘
seeded full agg
┌────┬─────┐
│ │ c0 │
├────┼─────┤
│ r0 │ 146 │
└────┴─────┘
round
Round all cells in a matrix to a specified number of digits. In contrast
to logging with a rounding parameter, the round method actually changes
the state of the matrix.
plotB.round(1e-3).log();
┌─────────────────┬─────────┬────────┐
│ │ x │ y │
├─────────────────┼─────────┼────────┤
│ possible park │ 112.183 │ 33.542 │
│ possible school │ 112.369 │ 33.63 │
└─────────────────┴─────────┴────────┘
solve
Solves a system of linear equations. Assume a matrix A and a matrix or vector B. Given these, solve for x if A * x = B.
let repeated = $$.matrix.repeat(2,2);
let x = plotB.solve(repeated);
let test = plotB.multiply(x);
repeated.log(null, 'repeated', 1e-8);
x.log(null, 'x', 1e-8);
test.log(null, 'test', 1e-8);
In the example below, we plug plotB and repeated into solve and get
matrix x. And as can be seen in test below, plotB * x produces the
same values as repeated.
repeated
┌────┬────┬────┐
│ │ c0 │ c1 │
├────┼────┼────┤
│ r0 │ 2 │ 2 │
│ r1 │ 2 │ 2 │
└────┴────┴────┘
x
┌─────────────────┬─────────────┬─────────────┐
│ │ x │ y │
├─────────────────┼─────────────┼─────────────┤
│ possible park │ 0.04955919 │ 0.04955919 │
│ possible school │ -0.10612502 │ -0.10612502 │
└─────────────────┴─────────────┴─────────────┘
test
┌─────────────────┬───┬───┐
│ │ x │ y │
├─────────────────┼───┼───┤
│ possible park │ 2 │ 2 │
│ possible school │ 2 │ 2 │
└─────────────────┴───┴───┘
subtract
Subtract two matrices from each other, cell by cell.
plotA.subtract(plotB).log();
┌─────────────────┬────────────┬───────────┐
│ │ x │ y │
├─────────────────┼────────────┼───────────┤
│ possible park │ 547.817 │ -693.5425 │
│ possible school │ -1432.3693 │ 1286.3705 │
└─────────────────┴────────────┴───────────┘
transform
Applies a transformation to a matrix.
It's usually more convenient than multiplication to achieve the same task because it takes care of any transpositions for you, by default it easily works with a matrix where points are represented as rows as opposed to columns (more common in business concerns), it takes care of adding and later removing the extra dimension from the target matrix for affine transforms, and it preserves the row/column names after the transformation.
The following parameters are available:
- transformer: Required. The matrix defining the transformation. Must be a square matrix. For 'standard' transformations with pointsAreRows = true, it must have the same number of columns as the calling matrix (if pointsAreRows = false then transformation column length must equal calling matrix row length). For 'affine' transforations, add an extra dimension to the transformation matrix on both the rows and columns. Do not add a dimension to the calling matrix. That will be done for you.
- pointsAreRows: Optional. If true, then each row of the calling matrix is taken to be a point (a pair of 2D points is [{x0,y0}{x1,y1}]). If false, then each column of the calling matrix is taken to be a point (a pair of 2D points is [{x0,x1},{y0,y1}]). The default is true.
The 'transformer' below represents a shear transform that, if applied to the 'community', would precicely move Kiwitown market just enough to the right in order to make an equilateral triangle between the park, the school, and the market.
let transformer = new $$.matrix([
[1, 0.4],
[0, Math.pow(3,0.5) / 2 ]
]);
community.transform(transformer).log();
Below, (0,0), (10,0), and (5,8.660254) forms an equilateral triangle.
┌───────────────────┬────┬────────────────────┐
│ │ x │ y │
├───────────────────┼────┼────────────────────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 5 │ 8.660254037844386 │
│ The Millers │ -5 │ 0 │
│ The Romeros │ -2 │ -4.330127018922193 │
│ The Lees │ 7 │ 4.330127018922193 │
│ The Keitas │ 5 │ 0 │
│ The Lebedevs │ 17 │ 4.330127018922193 │
└───────────────────┴────┴────────────────────┘
Or imagine that the community was structured with x/y points represented
as one point per column. Then just set pointsAreRows to false.
let tCommunity = community.filter([
'Applewood Park',
'Orangewood School',
'Kiwitown Market'
]).transpose();
let transformed = tCommunity.transform(transformer, false);
tCommunity.log(null, 'tCommunity');
transformed.log(null, 'transformed');
tCommunity
┌───┬────────────────┬───────────────────┬─────────────────┐
│ │ Applewood Park │ Orangewood School │ Kiwitown Market │
├───┼────────────────┼───────────────────┼─────────────────┤
│ x │ 0 │ 10 │ 1 │
│ y │ 0 │ 0 │ 10 │
└───┴────────────────┴───────────────────┴─────────────────┘
transformed
┌───┬────────────────┬───────────────────┬───────────────────┐
│ │ Applewood Park │ Orangewood School │ Kiwitown Market │
├───┼────────────────┼───────────────────┼───────────────────┤
│ x │ 0 │ 10 │ 5 │
│ y │ 0 │ 0 │ 8.660254037844386 │
└───┴────────────────┴───────────────────┴───────────────────┘
As for affine transformations, here's an example that applies a translation such that Kiwitown market becomes the origin. Notice that an extra dimension exists for the row and column of the transforming matrix. No manipulations are needed for the community matrix to make this work:
let affineTranslator = new $$.matrix([
[ 1, 0, -1 ],
[ 0, 1, -10 ],
[ 0, 1, 1 ]
])
community.transform(affineTranslator).log();
┌───────────────────┬────┬─────┐
│ │ x │ y │
├───────────────────┼────┼─────┤
│ Applewood Park │ -1 │ -10 │
│ Orangewood School │ 9 │ -10 │
│ Kiwitown Market │ 0 │ 0 │
│ The Millers │ -6 │ -10 │
│ The Romeros │ -1 │ -15 │
│ The Lees │ 4 │ -5 │
│ The Keitas │ 4 │ -10 │
│ The Lebedevs │ 14 │ -5 │
└───────────────────┴────┴─────┘
transpose
Swaps out rows for columns.
plotA.transpose().log();
┌───┬───────────────┬─────────────────┐
│ │ possible park │ possible school │
├───┼───────────────┼─────────────────┤
│ x │ 660 │ -1320 │
│ y │ -660 │ 1320 │
└───┴───────────────┴─────────────────┘
validate
Throws an error if the matrix is not in valid form. Otherwise returns the matrix calling it.
let applyAndLog = (func) => {
try {
plotA.apply(func).validate().log();
}
catch (err) {
console.log(
`\nInput function produced a bad matrix: \n\t${err}`
);
}
}
applyAndLog(cell => cell / 10);
applyAndLog(cell => 'a' + cell);
┌─────────────────┬──────┬─────┐
│ │ x │ y │
├─────────────────┼──────┼─────┤
│ possible park │ 66 │ -66 │
│ possible school │ -132 │ 132 │
└─────────────────┴──────┴─────┘
Input function produced a bad matrix:
'x' in row 0 is not a finite number
decomposeQR
Returns an object with the following properties:
- A: The original matrix before decomposition
- Q: An orthogonal matrix (e.g. vectors are mutually perpendicular)
- R: An upper triangular matrix.
A is 'decomposed' into L and U. So multiplying L * U will produce A.
This can be seen by the decomposition of 'plotB' below:
let qr = plotB.decomposeQR();
qr.A.log(null, 'A', 1e-8);
qr.R.log(null, 'R', 1e-8);
qr.Q.log(null, 'Q', 1e-8);
let testMult = qr.Q.multiply(qr.R).equals(plotB, 1e-8);
let testOrth = qr.Q.isOrthonormal();
console.log(`\nIt is '${testMult}' that qr.Q * qr.R = plotB`);
console.log(`It is '${testOrth}' that qr.Q is orthonormal`);
A
┌─────────────────┬──────────┬─────────┐
│ │ x │ y │
├─────────────────┼──────────┼─────────┤
│ possible park │ 112.183 │ 33.5425 │
│ possible school │ 112.3693 │ 33.6295 │
└─────────────────┴──────────┴─────────┘
R
┌────┬──────────────┬─────────────┐
│ │ x │ y │
├────┼──────────────┼─────────────┤
│ r0 │ 158.78250871 │ 47.4978114 │
│ r1 │ 0 │ -0.02211171 │
└────┴──────────────┴─────────────┘
Q
┌────┬────────────┬─────────────┐
│ │ c0 │ c1 │
├────┼────────────┼─────────────┤
│ r0 │ 0.70651989 │ 0.70769319 │
│ r1 │ 0.70769319 │ -0.70651989 │
└────┴────────────┴─────────────┘
It is 'true' that qr.Q * qr.R = plotB
It is 'true' that qr.Q is orthonormal
decomposeSVDcomp
Computes the compact Singular Value Decomposition of a matrix. It is 'compact' because it only calculates the values and vectors for non-zero singular values.
The following parameters are available:
- errorThreshold: Determines when the converging iterations to calculate the values stops (default is 1e-8).
- maxIterations: Determines the maximum number of iterations to allow before it is determined that a value will not converge (default is 1000).
Returns an object with the following properties:
- A: The original matrix before decomposition
- L: A matrix with the 'left' singular vectors of A.
- D: A diagonal matrix with the 'singular values' of A on the diagonal.
- R: A matrix with the 'right' singular values of A.
- iterations: The number of iterations required to produce the calculation.
This can be seen by the decomposition of 'community' below:
let svd = community.decomposeSVDcomp();
// for better display
let iterations = svd.iterations;
svd.iterations = undefined;
svd.A.log(null, 'A', 1e-8);
svd.L.log(null, 'L', 1e-8);
svd.D.log(null, 'D', 1e-8);
svd.R.log(null, 'R', 1e-8);
console.log(`\niterations: ${iterations}`);
A
┌───────────────────┬────┬────┐
│ │ x │ y │
├───────────────────┼────┼────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 1 │ 10 │
│ The Millers │ -5 │ 0 │
│ The Romeros │ 0 │ -5 │
│ The Lees │ 5 │ 5 │
│ The Keitas │ 5 │ 0 │
│ The Lebedevs │ 15 │ 5 │
└───────────────────┴────┴────┘
L
┌────┬─────────────┬─────────────┐
│ │ c0 │ c1 │
├────┼─────────────┼─────────────┤
│ r0 │ 0 │ 0 │
│ r1 │ 0.4388266 │ 0.32979645 │
│ r2 │ 0.22220216 │ -0.77861668 │
│ r3 │ -0.2194133 │ -0.16489822 │
│ r4 │ -0.08915975 │ 0.40579816 │
│ r5 │ 0.30857305 │ -0.24089994 │
│ r6 │ 0.2194133 │ 0.16489822 │
│ r7 │ 0.74739965 │ 0.08889651 │
└────┴─────────────┴─────────────┘
D
┌────┬────────────┬─────────────┐
│ │ c0 │ c1 │
├────┼────────────┼─────────────┤
│ c0 │ 21.1115864 │ 0 │
│ c1 │ 0 │ 11.41494282 │
└────┴────────────┴─────────────┘
R
┌────┬────────────┬─────────────┐
│ │ c0 │ c1 │
├────┼────────────┼─────────────┤
│ r0 │ 0.92643256 │ 0.37646076 │
│ r1 │ 0.37646076 │ -0.92643256 │
└────┴────────────┴─────────────┘
iterations: 20
eigen
Calculates eigenvectors and eigenvalues for a matrix.
The method takes one parameter, but in reality it acts as multiple named parameters:
Pass an object with one or more of the following properties:
- valueThreshold: Determines when the converging iterations to calculate the eigenvalues stops (default is 1e-8).
- valueLoopMax: Determines the maximum permitted iterations for the calculation of eigenvalues (default is 1000),
- valueMerge: Determines the proximity of two eigenvalues required to interperet them as the same. If they are deemed the same, they are averaged out into the same value. The calcuation of eigenvectors then happens against this merged value (default is 1e-3).
- vectorThreshold: Determines when the convertiong iterations to calculate each eigenvector stops (default is 1e-8).
- vectorLoopMax: Determines the maximum permitted iterations for the calculation of each eigenvector (default is 1000).
- testThreshold: A test is taken to ensure that the matrix multiplied by each eigenvector equals the eigenvector multiplied by its eigenvalue. This threshold determines how close the equality must be in order to succeed (default is 1e-6).
Alternative parameter structures include:
- Pass threshold as a property in the object parameter to set value and vector thresholds simultaneously.
- Pass loopMax as a property in the object parameter to set value and vector loop max settings simultaneously.
- Pass an integer to have the same effect as passing an object with only the 'threshold' parameter set.
Returns an object with the following properties:
- A: The matrix calling the method.
- values: An array of the eigenvalues.
- vectors: A square matrix holding the eigenvectors.
-
rawValues: An array of the eigenvectors before special modifications were
applied to it. - iterations: An object showing the history of the iterations for different calculations. Properties are 'values', 'vector 1', 'vector 2', ..., 'vector n', with integers indicating numbers of itereations for each.
Example:
The 'transformer' below represents a shear transform that, if applied to the 'community', would precicely move Kiwitown market just enough to the right in order to make an equilateral triangle between the park, the school, and the market.
let transformer = new $$.matrix([
[1, 0.4],
[0, Math.pow(3,0.5) / 2 ]
]);
Using the eigen method, we can observe the eigenvectors and the eigenvalues.
let eigen = transformer.eigen({loopMax: 10000});
console.log('eigenvalues:', eigen.values, '\n');
console.log('eigenvectors: ')
eigen.vectors.log(null, null, 1e-8);
eigenvalues: [ 1, 0.8660254 ]
eigenvectors:
┌────┬────┬─────────────┐
│ │ c0 │ c1 │
├────┼────┼─────────────┤
│ r0 │ -1 │ -0.94822626 │
│ r1 │ 0 │ 0.31759558 │
└────┴────┴─────────────┘
Note: Stop here if you just needed to see the output for eigen. But if you need
a little more help on what the eigenvectors and values actually do, read on for
an applied example.
Application:
The more interesting eigen pair is vector (-0.94822626, 0.31759558) of length 1 (it will always be 1) corresponding to value 0.8660254. This means that any vector on the same line as this eigenvector will remain on the same line after transformation, but will scale by the eigenvalue.
Imagine someone desired to build a theatre. It would start out as a temporary
drive-in. But the community will be moving and will rebuild on a new plot of
land with the coordinates produced by the transformation. In the new
community, the thature will be brick-and-mortar. But the owner wants to build
a signpost at the park with arrows pointing to the school and to the theatre.
If it is to remain accurate after the transformation, he'd better put his
theatre along the one of the eigenvectors. The owner is not so worried about
the distance of the temporary drive-in. But after transformation it should be
5 units away from the park.
Since the eigenvector of interest is unit length 1, but will scale to become 0.8660254 units, the vector (5 / 0.8660254) * (-0.94822626, 0.31759558) should remain on the same line that is mapped out by the eigenvector and should scale out to become 5 units long.
let scaleFactor = 5 / eigen.values[1];
community_array.push({
marker: 'theatre',
x: eigen.vectors.getCell(0,1) * scaleFactor,
y: eigen.vectors.getCell(1,1) * scaleFactor
});
community = $$(community_array).matrix('x, y', 'marker');
Now let's build the community, transform it, and observe the new position of the theatre.
let transformed = community.transform(transformer);
community.filter('theatre').log(null, 'theatre before transform', 1e-8);
transformed.log(null, 'transformed', 1e-8);
theatre before transform
┌─────────┬─────────────┬────────────┐
│ │ x │ y │
├─────────┼─────────────┼────────────┤
│ theatre │ -5.47458692 │ 1.83363893 │
└─────────┴─────────────┴────────────┘
transformed
┌───────────────────┬─────────────┬─────────────┐
│ │ x │ y │
├───────────────────┼─────────────┼─────────────┤
│ Applewood Park │ 0 │ 0 │
│ Orangewood School │ 10 │ 0 │
│ Kiwitown Market │ 5 │ 8.66025404 │
│ The Millers │ -5 │ 0 │
│ The Romeros │ -2 │ -4.33012702 │
│ The Lees │ 7 │ 4.33012702 │
│ The Keitas │ 5 │ 0 │
│ The Lebedevs │ 17 │ 4.33012702 │
│ theatre │ -4.74113134 │ 1.58797789 │
└───────────────────┴─────────────┴─────────────┘
Eyeballing the theatre distance seems to show that it is close to the expected 5 units. And a quick check in code confirms that:
let theatreDist = transformed.filter('theatre', null).norm('euclidian');
console.log(`theatre distance from park: ${theatreDist}`);
theatre distance from park: 5.000000021849468
determinant
Returns the determinant of a matrix.
let d = plotB.determinant();
console.log(d);
3.5109532500009664
Note that the procedure technically has a parameter, 'data'. But this is used in a recursive calculation. It is not advised that it be used under circumstances outside it's internal usage.
equals
Determines whether the given matrix is equal to another matrix: cell-by-cell.
The following parameters are available:
- other: Another matrix to compare to.
- errorThreshold: How close in value should each cell be to determine equality (default is 0).
-
dataOnly: If false, row and column names, and potentially other properties will be included
in the comparison. If true, only the data is compared (default is true).
Returns true or false.
let shifted = plotA.apply(cell => cell + 0.01).setColNames(['run','amok']);
console.log({
strictEquality: plotA.equals(shifted),
looseEquality: plotA.equals(shifted, 1e-2),
nameEquality: plotA.equals(shifted, 1e-2, false)
});
{ strictEquality: false, looseEquality: true, nameEquality: false }
isDiagonal
Indicates whether the matrix is a diagonal or not.
The optional zeroThreshold parameter is available to allow tiny values off of the diagonal to be treated as zero in determining the result.
Returns true or false.
let d = new $$.matrix([
[ 1, 0, 0.01 ],
[ 0, 2, 0.001 ],
[ 0, 0, 3 ]
])
console.log({
dIsExactlyDiag: d.isDiagonal(),
dIsCloseToDiag: d.isDiagonal(1e-2)
})
{ dIsExactlyDiag: false, dIsCloseToDiag: true }
isLowerTriangular
Indicates whether the matrix is lower triangular or not. If so, it means that non-zero values are only found on the diagonal line or below it.
The optional zeroThreshold parameter is available to allow tiny values above the diagonal to be treated as zero in determining the result.
Returns true or false.
let mx = new $$.matrix([
[ 1, 0, 0.01 ],
[ 2, 3, 0 ],
[ 3, 4, 5 ]
]);
console.log({
mxIsLowerTriExactly: mx.isLowerTriangular(),
mxIsLowerTriApprox: mx.isLowerTriangular(1e-2)
})
{ mxIsLowerTriExactly: false, mxIsLowerTriApprox: true }
isOrthonormal
Indicates whether the matrix is orthonormal or not. If so, it means that the row and column vectors are all perpendicular to each other (ortho) and are of length one (normal).
The optional errorThreshold parameter allows some give in the calculations to test whether the matrix is orthonormal or not. The default is 1e-8.
Returns true or false.
let mx = new $$.matrix([
[ 0.70651989, 0.70769319 + 0.01 ],
[ 0.70769319, -0.70651989 ]
]);
console.log({
mxIsVeryCloseToOrthonormal: mx.isOrthonormal(),
mxIsSomewhatCloseToOrthonormal: mx.isOrthonormal(0.1)
});
{
mxIsVeryCloseToOrthonormal: false,
mxIsSomewhatCloseToOrthonormal: true
}
isSquare
Indicates whether the matrix is square or not.
Returns true or false.
console.log({
communityIsSquare: community.isSquare(),
plotAisSquare: plotA.isSquare()
})
{ communityIsSquare: false, plotAisSquare: true }
isUpperTriangular
Indicates whether the matrix is upper triangular or not. If so, it means that non-zero values are only found on the diagonal line or above it.
The optional zeroThreshold parameter is available to allow tiny values below the diagonal to be treated as zero in determining the result.
Returns true or false.
let mx = new $$.matrix([
[ 1, 2, 3 ],
[ 0, 4, 5 ],
[ 0.001, 0, 6 ]
]);
console.log({
mxIsUpperTriExactly: mx.isUpperTriangular(),
mxIsUpperTriApprox: mx.isUpperTriangular(1e-2)
})
{ mxIsUpperTriExactly: false, mxIsUpperTriApprox: true }
norm
Produces the norm of a matrix.
The type parameter determines what kind of norm is produced. The default is 'frobenious', which can be aliased using 'eulcidian', 'f', or 'e'. Also available is '1' for a 1-norm, and 'infinity' or 'i' for an infinity-norm.
console.log({
euclidianNorm: community.norm(),
oneNorm: community.norm(1),
infinityNorm: community.norm('i')
})
{ euclidianNorm: 24, oneNorm: 41, infinityNorm: 20 }
identity
Creates an identity matrix.
The following parameters are available:
- numRows: The number of rows to put in the matrix.
- numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
$$.matrix.identity(3).log(null, 'square identity');
$$.matrix.identity(3,4).log(null, 'rectangle identity');
square identity
┌────┬────┬────┬────┐
│ │ c0 │ c1 │ c2 │
├────┼────┼────┼────┤
│ r0 │ 1 │ 0 │ 0 │
│ r1 │ 0 │ 1 │ 0 │
│ r2 │ 0 │ 0 │ 1 │
└────┴────┴────┴────┘
rectangle identity
┌────┬────┬────┬────┬────┐
│ │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 1 │ 0 │ 0 │ 0 │
│ r1 │ 0 │ 1 │ 0 │ 0 │
│ r2 │ 0 │ 0 │ 1 │ 0 │
└────┴────┴────┴────┴────┘
ones
Creates a matrix with all ones as cell values.
The following parameters are available:
- numRows: The number of rows to put in the matrix.
- numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
$$.matrix.ones(3,4).log();
┌────┬────┬────┬────┬────┐
│ │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 1 │ 1 │ 1 │ 1 │
│ r1 │ 1 │ 1 │ 1 │ 1 │
│ r2 │ 1 │ 1 │ 1 │ 1 │
└────┴────┴────┴────┴────┘
randomizer
A class that can craete a matrix with random values in it's entries.
Call the method setSize() and set the parameters numRows and numCols in order to set up the matrix to be a certain size.
Call the method setValues() and set the parameters lowVal and highVal to set bounds on the range randomized values can take when creating the matrix. Optionally set integers to ensure those values are only integers.
Call the method get() to create the matrix based on previous settings.
new $$.matrix.randomizer().setSize(2,3).setValues(-5,5).get().log();
new $$.matrix.randomizer().setSize(2,3).setValues(-5,5).get().log();
As can be seen below, the two identical lines above produce different matricies of random values.
┌────┬─────────────────────┬────────────────────┬──────────────────────┐
│ │ c0 │ c1 │ c2 │
├────┼─────────────────────┼────────────────────┼──────────────────────┤
│ r0 │ 3.5386930574153137 │ -3.367077144616626 │ -4.323092838476703 │
│ r1 │ -0.3755470412689821 │ 2.239453636042681 │ -0.05016840292257019 │
└────┴─────────────────────┴────────────────────┴──────────────────────┘
┌────┬─────────────────────┬───────────────────┬─────────────────────┐
│ │ c0 │ c1 │ c2 │
├────┼─────────────────────┼───────────────────┼─────────────────────┤
│ r0 │ -1.3725213928020707 │ -2.76675330820785 │ -0.9948801834067966 │
│ r1 │ -2.4988244489280653 │ 4.834078388536142 │ 3.378161702938378 │
└────┴─────────────────────┴───────────────────┴─────────────────────┘
Below produces one with similar bounds, but will only output integers:
new $$.matrix.randomizer().setSize(2,3).setValues(-5,5,true).get().log();
┌────┬────┬────┬────┐
│ │ c0 │ c1 │ c2 │
├────┼────┼────┼────┤
│ r0 │ -4 │ 2 │ -5 │
│ r1 │ -5 │ 5 │ 3 │
└────┴────┴────┴────┘
repeat
Creates a matrix with all cell having the same value.
The following parameters are available:
- repeater: The value to be repeated accross all cells.
- numRows: The number of rows to put in the matrix.
- numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
- diagOnly: Optional, boolean. If true, creates a diagonal matrix with the entries on the diagonal repeated.
$$.matrix.repeat(9,2,4).log();
┌────┬────┬────┬────┬────┐
│ │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 9 │ 9 │ 9 │ 9 │
│ r1 │ 9 │ 9 │ 9 │ 9 │
└────┴────┴────┴────┴────┘
zeroes
Creates a matrix with all zeroes as cell values.
The following parameters are available:
- numRows: The number of rows to put in the matrix.
- numCols: Optional. The number of columns to put in the matrix. If omitted, then numCols = numRows.
$$.matrix.zeroes(3,4).log();
┌────┬────┬────┬────┬────┐
│ │ c0 │ c1 │ c2 │ c3 │
├────┼────┼────┼────┼────┤
│ r0 │ 0 │ 0 │ 0 │ 0 │
│ r1 │ 0 │ 0 │ 0 │ 0 │
│ r2 │ 0 │ 0 │ 0 │ 0 │
└────┴────┴────┴────┴────┘