var jStat = (function(Math, undefined) { // For quick reference. var concat = Array.prototype.concat; var slice = Array.prototype.slice; var toString = Object.prototype.toString; // Calculate correction for IEEE error // TODO: This calculation can be improved. function calcRdx(n, m) { var val = n > m ? n : m; return Math.pow(10, 17 - ~~(Math.log(((val > 0) ? val : -val)) * Math.LOG10E)); } var isArray = Array.isArray || function isArray(arg) { return toString.call(arg) === '[object Array]'; }; function isFunction(arg) { return toString.call(arg) === '[object Function]'; } function isNumber(num) { return (typeof num === 'number') ? num - num === 0 : false; } // Converts the jStat matrix to vector. function toVector(arr) { return concat.apply([], arr); } // The one and only jStat constructor. function jStat() { return new jStat._init(arguments); } // TODO: Remove after all references in src files have been removed. jStat.fn = jStat.prototype; // By separating the initializer from the constructor it's easier to handle // always returning a new instance whether "new" was used or not. jStat._init = function _init(args) { // If first argument is an array, must be vector or matrix. if (isArray(args[0])) { // Check if matrix. if (isArray(args[0][0])) { // See if a mapping function was also passed. if (isFunction(args[1])) args[0] = jStat.map(args[0], args[1]); // Iterate over each is faster than this.push.apply(this, args[0]. for (var i = 0; i < args[0].length; i++) this[i] = args[0][i]; this.length = args[0].length; // Otherwise must be a vector. } else { this[0] = isFunction(args[1]) ? jStat.map(args[0], args[1]) : args[0]; this.length = 1; } // If first argument is number, assume creation of sequence. } else if (isNumber(args[0])) { this[0] = jStat.seq.apply(null, args); this.length = 1; // Handle case when jStat object is passed to jStat. } else if (args[0] instanceof jStat) { // Duplicate the object and pass it back. return jStat(args[0].toArray()); // Unexpected argument value, return empty jStat object. // TODO: This is strange behavior. Shouldn't this throw or some such to let // the user know they had bad arguments? } else { this[0] = []; this.length = 1; } return this; }; jStat._init.prototype = jStat.prototype; jStat._init.constructor = jStat; // Utility functions. // TODO: for internal use only? jStat.utils = { calcRdx: calcRdx, isArray: isArray, isFunction: isFunction, isNumber: isNumber, toVector: toVector }; jStat._random_fn = Math.random; jStat.setRandom = function setRandom(fn) { if (typeof fn !== 'function') throw new TypeError('fn is not a function'); jStat._random_fn = fn; }; // Easily extend the jStat object. // TODO: is this seriously necessary? jStat.extend = function extend(obj) { var i, j; if (arguments.length === 1) { for (j in obj) jStat[j] = obj[j]; return this; } for (i = 1; i < arguments.length; i++) { for (j in arguments[i]) obj[j] = arguments[i][j]; } return obj; }; // Returns the number of rows in the matrix. jStat.rows = function rows(arr) { return arr.length || 1; }; // Returns the number of columns in the matrix. jStat.cols = function cols(arr) { return arr[0].length || 1; }; // Returns the dimensions of the object { rows: i, cols: j } jStat.dimensions = function dimensions(arr) { return { rows: jStat.rows(arr), cols: jStat.cols(arr) }; }; // Returns a specified row as a vector or return a sub matrix by pick some rows jStat.row = function row(arr, index) { if (isArray(index)) { return index.map(function(i) { return jStat.row(arr, i); }) } return arr[index]; }; // return row as array // rowa([[1,2],[3,4]],0) -> [1,2] jStat.rowa = function rowa(arr, i) { return jStat.row(arr, i); }; // Returns the specified column as a vector or return a sub matrix by pick some // columns jStat.col = function col(arr, index) { if (isArray(index)) { var submat = jStat.arange(arr.length).map(function() { return new Array(index.length); }); index.forEach(function(ind, i){ jStat.arange(arr.length).forEach(function(j) { submat[j][i] = arr[j][ind]; }); }); return submat; } var column = new Array(arr.length); for (var i = 0; i < arr.length; i++) column[i] = [arr[i][index]]; return column; }; // return column as array // cola([[1,2],[3,4]],0) -> [1,3] jStat.cola = function cola(arr, i) { return jStat.col(arr, i).map(function(a){ return a[0] }); }; // Returns the diagonal of the matrix jStat.diag = function diag(arr) { var nrow = jStat.rows(arr); var res = new Array(nrow); for (var row = 0; row < nrow; row++) res[row] = [arr[row][row]]; return res; }; // Returns the anti-diagonal of the matrix jStat.antidiag = function antidiag(arr) { var nrow = jStat.rows(arr) - 1; var res = new Array(nrow); for (var i = 0; nrow >= 0; nrow--, i++) res[i] = [arr[i][nrow]]; return res; }; // Transpose a matrix or array. jStat.transpose = function transpose(arr) { var obj = []; var objArr, rows, cols, j, i; // Make sure arr is in matrix format. if (!isArray(arr[0])) arr = [arr]; rows = arr.length; cols = arr[0].length; for (i = 0; i < cols; i++) { objArr = new Array(rows); for (j = 0; j < rows; j++) objArr[j] = arr[j][i]; obj.push(objArr); } // If obj is vector, return only single array. return obj.length === 1 ? obj[0] : obj; }; // Map a function to an array or array of arrays. // "toAlter" is an internal variable. jStat.map = function map(arr, func, toAlter) { var row, nrow, ncol, res, col; if (!isArray(arr[0])) arr = [arr]; nrow = arr.length; ncol = arr[0].length; res = toAlter ? arr : new Array(nrow); for (row = 0; row < nrow; row++) { // if the row doesn't exist, create it if (!res[row]) res[row] = new Array(ncol); for (col = 0; col < ncol; col++) res[row][col] = func(arr[row][col], row, col); } return res.length === 1 ? res[0] : res; }; // Cumulatively combine the elements of an array or array of arrays using a function. jStat.cumreduce = function cumreduce(arr, func, toAlter) { var row, nrow, ncol, res, col; if (!isArray(arr[0])) arr = [arr]; nrow = arr.length; ncol = arr[0].length; res = toAlter ? arr : new Array(nrow); for (row = 0; row < nrow; row++) { // if the row doesn't exist, create it if (!res[row]) res[row] = new Array(ncol); if (ncol > 0) res[row][0] = arr[row][0]; for (col = 1; col < ncol; col++) res[row][col] = func(res[row][col-1], arr[row][col]); } return res.length === 1 ? res[0] : res; }; // Destructively alter an array. jStat.alter = function alter(arr, func) { return jStat.map(arr, func, true); }; // Generate a rows x cols matrix according to the supplied function. jStat.create = function create(rows, cols, func) { var res = new Array(rows); var i, j; if (isFunction(cols)) { func = cols; cols = rows; } for (i = 0; i < rows; i++) { res[i] = new Array(cols); for (j = 0; j < cols; j++) res[i][j] = func(i, j); } return res; }; function retZero() { return 0; } // Generate a rows x cols matrix of zeros. jStat.zeros = function zeros(rows, cols) { if (!isNumber(cols)) cols = rows; return jStat.create(rows, cols, retZero); }; function retOne() { return 1; } // Generate a rows x cols matrix of ones. jStat.ones = function ones(rows, cols) { if (!isNumber(cols)) cols = rows; return jStat.create(rows, cols, retOne); }; // Generate a rows x cols matrix of uniformly random numbers. jStat.rand = function rand(rows, cols) { if (!isNumber(cols)) cols = rows; return jStat.create(rows, cols, jStat._random_fn); }; function retIdent(i, j) { return i === j ? 1 : 0; } // Generate an identity matrix of size row x cols. jStat.identity = function identity(rows, cols) { if (!isNumber(cols)) cols = rows; return jStat.create(rows, cols, retIdent); }; // Tests whether a matrix is symmetric jStat.symmetric = function symmetric(arr) { var size = arr.length; var row, col; if (arr.length !== arr[0].length) return false; for (row = 0; row < size; row++) { for (col = 0; col < size; col++) if (arr[col][row] !== arr[row][col]) return false; } return true; }; // Set all values to zero. jStat.clear = function clear(arr) { return jStat.alter(arr, retZero); }; // Generate sequence. jStat.seq = function seq(min, max, length, func) { if (!isFunction(func)) func = false; var arr = []; var hival = calcRdx(min, max); var step = (max * hival - min * hival) / ((length - 1) * hival); var current = min; var cnt; // Current is assigned using a technique to compensate for IEEE error. // TODO: Needs better implementation. for (cnt = 0; current <= max && cnt < length; cnt++, current = (min * hival + step * hival * cnt) / hival) { arr.push((func ? func(current, cnt) : current)); } return arr; }; // arange(5) -> [0,1,2,3,4] // arange(1,5) -> [1,2,3,4] // arange(5,1,-1) -> [5,4,3,2] jStat.arange = function arange(start, end, step) { var rl = []; var i; step = step || 1; if (end === undefined) { end = start; start = 0; } if (start === end || step === 0) { return []; } if (start < end && step < 0) { return []; } if (start > end && step > 0) { return []; } if (step > 0) { for (i = start; i < end; i += step) { rl.push(i); } } else { for (i = start; i > end; i += step) { rl.push(i); } } return rl; }; // A=[[1,2,3],[4,5,6],[7,8,9]] // slice(A,{row:{end:2},col:{start:1}}) -> [[2,3],[5,6]] // slice(A,1,{start:1}) -> [5,6] // as numpy code A[:2,1:] jStat.slice = (function(){ function _slice(list, start, end, step) { // note it's not equal to range.map mode it's a bug var i; var rl = []; var length = list.length; if (start === undefined && end === undefined && step === undefined) { return jStat.copy(list); } start = start || 0; end = end || list.length; start = start >= 0 ? start : length + start; end = end >= 0 ? end : length + end; step = step || 1; if (start === end || step === 0) { return []; } if (start < end && step < 0) { return []; } if (start > end && step > 0) { return []; } if (step > 0) { for (i = start; i < end; i += step) { rl.push(list[i]); } } else { for (i = start; i > end;i += step) { rl.push(list[i]); } } return rl; } function slice(list, rcSlice) { var colSlice, rowSlice; rcSlice = rcSlice || {}; if (isNumber(rcSlice.row)) { if (isNumber(rcSlice.col)) return list[rcSlice.row][rcSlice.col]; var row = jStat.rowa(list, rcSlice.row); colSlice = rcSlice.col || {}; return _slice(row, colSlice.start, colSlice.end, colSlice.step); } if (isNumber(rcSlice.col)) { var col = jStat.cola(list, rcSlice.col); rowSlice = rcSlice.row || {}; return _slice(col, rowSlice.start, rowSlice.end, rowSlice.step); } rowSlice = rcSlice.row || {}; colSlice = rcSlice.col || {}; var rows = _slice(list, rowSlice.start, rowSlice.end, rowSlice.step); return rows.map(function(row) { return _slice(row, colSlice.start, colSlice.end, colSlice.step); }); } return slice; }()); // A=[[1,2,3],[4,5,6],[7,8,9]] // sliceAssign(A,{row:{start:1},col:{start:1}},[[0,0],[0,0]]) // A=[[1,2,3],[4,0,0],[7,0,0]] jStat.sliceAssign = function sliceAssign(A, rcSlice, B) { var nl, ml; if (isNumber(rcSlice.row)) { if (isNumber(rcSlice.col)) return A[rcSlice.row][rcSlice.col] = B; rcSlice.col = rcSlice.col || {}; rcSlice.col.start = rcSlice.col.start || 0; rcSlice.col.end = rcSlice.col.end || A[0].length; rcSlice.col.step = rcSlice.col.step || 1; nl = jStat.arange(rcSlice.col.start, Math.min(A.length, rcSlice.col.end), rcSlice.col.step); var m = rcSlice.row; nl.forEach(function(n, i) { A[m][n] = B[i]; }); return A; } if (isNumber(rcSlice.col)) { rcSlice.row = rcSlice.row || {}; rcSlice.row.start = rcSlice.row.start || 0; rcSlice.row.end = rcSlice.row.end || A.length; rcSlice.row.step = rcSlice.row.step || 1; ml = jStat.arange(rcSlice.row.start, Math.min(A[0].length, rcSlice.row.end), rcSlice.row.step); var n = rcSlice.col; ml.forEach(function(m, j) { A[m][n] = B[j]; }); return A; } if (B[0].length === undefined) { B = [B]; } rcSlice.row.start = rcSlice.row.start || 0; rcSlice.row.end = rcSlice.row.end || A.length; rcSlice.row.step = rcSlice.row.step || 1; rcSlice.col.start = rcSlice.col.start || 0; rcSlice.col.end = rcSlice.col.end || A[0].length; rcSlice.col.step = rcSlice.col.step || 1; ml = jStat.arange(rcSlice.row.start, Math.min(A.length, rcSlice.row.end), rcSlice.row.step); nl = jStat.arange(rcSlice.col.start, Math.min(A[0].length, rcSlice.col.end), rcSlice.col.step); ml.forEach(function(m, i) { nl.forEach(function(n, j) { A[m][n] = B[i][j]; }); }); return A; }; // [1,2,3] -> // [[1,0,0],[0,2,0],[0,0,3]] jStat.diagonal = function diagonal(diagArray) { var mat = jStat.zeros(diagArray.length, diagArray.length); diagArray.forEach(function(t, i) { mat[i][i] = t; }); return mat; }; // return copy of A jStat.copy = function copy(A) { return A.map(function(row) { if (isNumber(row)) return row; return row.map(function(t) { return t; }); }); }; // TODO: Go over this entire implementation. Seems a tragic waste of resources // doing all this work. Instead, and while ugly, use new Function() to generate // a custom function for each static method. // Quick reference. var jProto = jStat.prototype; // Default length. jProto.length = 0; // For internal use only. // TODO: Check if they're actually used, and if they are then rename them // to _* jProto.push = Array.prototype.push; jProto.sort = Array.prototype.sort; jProto.splice = Array.prototype.splice; jProto.slice = Array.prototype.slice; // Return a clean array. jProto.toArray = function toArray() { return this.length > 1 ? slice.call(this) : slice.call(this)[0]; }; // Map a function to a matrix or vector. jProto.map = function map(func, toAlter) { return jStat(jStat.map(this, func, toAlter)); }; // Cumulatively combine the elements of a matrix or vector using a function. jProto.cumreduce = function cumreduce(func, toAlter) { return jStat(jStat.cumreduce(this, func, toAlter)); }; // Destructively alter an array. jProto.alter = function alter(func) { jStat.alter(this, func); return this; }; // Extend prototype with methods that have no argument. (function(funcs) { for (var i = 0; i < funcs.length; i++) (function(passfunc) { jProto[passfunc] = function(func) { var self = this, results; // Check for callback. if (func) { setTimeout(function() { func.call(self, jProto[passfunc].call(self)); }); return this; } results = jStat[passfunc](this); return isArray(results) ? jStat(results) : results; }; })(funcs[i]); })('transpose clear symmetric rows cols dimensions diag antidiag'.split(' ')); // Extend prototype with methods that have one argument. (function(funcs) { for (var i = 0; i < funcs.length; i++) (function(passfunc) { jProto[passfunc] = function(index, func) { var self = this; // check for callback if (func) { setTimeout(function() { func.call(self, jProto[passfunc].call(self, index)); }); return this; } return jStat(jStat[passfunc](this, index)); }; })(funcs[i]); })('row col'.split(' ')); // Extend prototype with simple shortcut methods. (function(funcs) { for (var i = 0; i < funcs.length; i++) (function(passfunc) { jProto[passfunc] = function() { return jStat(jStat[passfunc].apply(null, arguments)); }; })(funcs[i]); })('create zeros ones rand identity'.split(' ')); // Exposing jStat. return jStat; }(Math));