From dcf7451e964fb5bda2e47922220563c4bea5c8ec Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Fri, 5 Jun 2015 15:00:40 +0200 Subject: [PATCH 01/16] [UPDATE] Makefile, example, deps --- LICENSE | 4 +-- Makefile | 11 ++++++-- examples/index.js | 67 ++++++++++++++++++++++++++++++++++++++++++++--- package.json | 18 ++++++------- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/LICENSE b/LICENSE index 2fe3939..9f32cbf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Athan Reines. +Copyright (c) 2015 The Compute.io Authors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/Makefile b/Makefile index 8f4ec98..3234fbb 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,14 @@ # Set the node.js environment to test: NODE_ENV ?= test +# Kernel name: +KERNEL ?= $(shell uname -s) + +ifeq ($(KERNEL), Darwin) + OPEN ?= open +else + OPEN ?= xdg-open +endif # NOTES # @@ -98,8 +106,7 @@ test-istanbul-mocha: node_modules view-cov: view-istanbul-report view-istanbul-report: - open $(ISTANBUL_HTML_REPORT_PATH) - + $(OPEN) $(ISTANBUL_HTML_REPORT_PATH) # LINT # diff --git a/examples/index.js b/examples/index.js index 4262a94..75f57a1 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,9 +1,68 @@ 'use strict'; -var variance = require( './../lib' ); +var matrix = require( 'dstructs-matrix' ), + variance = require( './../lib' ); -var data = new Array( 1000 ); +var data, + mat, + s2, + i; + +// ---- +// Plain arrays... +var data = new Array( 100 ); for ( var i = 0; i < data.length; i++ ) { - data[ i ] = Math.random() * 100; + data[ i ] = Math.round( Math.random() * 10 + 1 ); +} +s2 = variance( data ); +console.log( 'Arrays: %d\n', s2 ); + + +// ---- +// Object arrays (accessors)... +function getValue( d ) { + return d.x; +} +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': data[ i ] + }; +} +s2 = variance( data, { + 'accessor': getValue +}); +console.log( 'Accessors: %d\n', s2 ); + + +// ---- +// Typed arrays... +data = new Int32Array( 100 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = Math.round( Math.random() * 10 + 1 ); } -console.log( variance( data ) ); +s2 = variance( data ); + + +// ---- +// Matrices (along rows)... +mat = matrix( data, [10,10], 'int32' ); +s2 = variance( mat, { + 'dim': 1 +}); +console.log( 'Matrix (rows): %s\n', s2.toString() ); + + +// ---- +// Matrices (along columns)... +s2 = variance( mat, { + 'dim': 2 +}); +console.log( 'Matrix (columns): %s\n', s2.toString() ); + + +// ---- +// Matrices (custom output data type)... +s2 = variance( mat, { + 'dtype': 'uint8' +}); +console.log( 'Matrix (%s): %s\n', s2.dtype, s2.toString() ); diff --git a/package.json b/package.json index 3677b9b..3135dda 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,15 @@ "url": "https://github.com/compute-io/variance/issues" }, "dependencies": { - "validate.io-array": "^1.0.3", - "validate.io-boolean-primitive": "^1.0.0", + "compute-array-constructors": "^1.0.0", + "compute-dtype": "^1.0.0", + "dstructs-matrix": "^1.0.0", + "validate.io-array-like": "^1.0.0", "validate.io-function": "^1.0.2", - "validate.io-object": "^1.0.3" + "validate.io-matrix-like": "^1.0.0", + "validate.io-object": "^1.0.4", + "validate.io-positive-integer": "^1.0.0", + "validate.io-string-primitive": "^1.0.0" }, "devDependencies": { "chai": "2.x.x", @@ -59,10 +64,5 @@ "jshint": "2.x.x", "jshint-stylish": "^1.0.0" }, - "licenses": [ - { - "type": "MIT", - "url": "http://www.opensource.org/licenses/MIT" - } - ] + "license": "MIT" } From cfa99ff7bb2fd5fd77cfd6b398528f4fde303eb6 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Fri, 5 Jun 2015 15:39:35 +0200 Subject: [PATCH 02/16] [UPDATE] implementation --- lib/accessor.js | 43 +++++++++++++ lib/array.js | 42 +++++++++++++ lib/index.js | 159 +++++++++++++++++++++--------------------------- lib/matrix.js | 60 ++++++++++++++++++ lib/validate.js | 58 ++++++++++++++++++ 5 files changed, 274 insertions(+), 88 deletions(-) create mode 100644 lib/accessor.js create mode 100644 lib/array.js create mode 100644 lib/matrix.js create mode 100644 lib/validate.js diff --git a/lib/accessor.js b/lib/accessor.js new file mode 100644 index 0000000..ac18c02 --- /dev/null +++ b/lib/accessor.js @@ -0,0 +1,43 @@ +'use strict'; + +/** +* FUNCTION: variance( arr, clbk[, bias] ) +* Computes the variance of an array using an accessor. +* +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array +* @param {Function} accessor - accessor function for accessing array values +* @param {Boolean} [bias=false] - boolean indicating whether to calculate a biased or unbiased estimate of the variance +* @returns {Number|null} variance or null +*/ +function variance( arr, clbk, bias ) { + var len = arr.length, + delta = 0, + mean = 0, + M2 = 0, + x, i; + + if ( !len ) { + return null; + } + if ( len < 2 ) { + return 0; + } + + for ( i = 0; i < len; i++ ) { + x = clbk( arr[ i ], i ); + delta = x - mean; + mean += delta / (i+1); + M2 += delta * ( x - mean ); + } + + if ( bias ) { + return M2 / ( i ); + } else { + return M2 / ( i - 1 ); + } +} // end FUNCTION variance() + + +// EXPORTS // + +module.exports = variance; diff --git a/lib/array.js b/lib/array.js new file mode 100644 index 0000000..d53d116 --- /dev/null +++ b/lib/array.js @@ -0,0 +1,42 @@ +'use strict'; + +/** +* FUNCTION: variance( arr[, bias] ) +* Computes the variance of an array. +* +* @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array +* @param {Boolean} [bias=false] - boolean indicating whether to calculate a biased or unbiased estimate of the variance +* @returns {Number|null} variance or null +*/ +function variance( arr, bias ) { + var len = arr.length, + delta = 0, + mean = 0, + M2 = 0, + x, i; + + if ( !len ) { + return null; + } + if ( len < 2 ) { + return 0; + } + + for ( i = 0; i < len; i++ ) { + x = arr[ i ]; + delta = x - mean; + mean += delta / (i+1); + M2 += delta * ( x - mean ); + } + + if ( bias ) { + return M2 / ( i ); + } else { + return M2 / ( i - 1 ); + } +} // end FUNCTION variance() + + +// EXPORTS // + +module.exports = variance; diff --git a/lib/index.js b/lib/index.js index 8be8581..dd57ea3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,108 +1,91 @@ -/** -* -* COMPUTE: variance -* -* -* DESCRIPTION: -* - Computes the variance of an array. -* -* -* NOTES: -* [1] -* -* -* TODO: -* [1] -* -* -* LICENSE: -* MIT -* -* Copyright (c) 2014. Athan Reines. -* -* -* AUTHOR: -* Athan Reines. kgryte@gmail.com. 2014. -* -*/ - 'use strict'; // MODULES // -var isArray = require( 'validate.io-array' ), - isBoolean = require( 'validate.io-boolean-primitive' ), - isObject = require( 'validate.io-object' ), - isFunction = require( 'validate.io-function' ); +var isArrayLike = require( 'validate.io-array-like' ), + isMatrixLike = require( 'validate.io-matrix-like' ), + ctors = require( 'compute-array-constructors' ), + matrix = require( 'dstructs-matrix' ).raw, + validate = require( './validate.js' ); + +// FUNCTIONS // + +var variance1 = require( './array.js' ), + variance2 = require( './accessor.js' ), + variance3 = require( './matrix.js' ); // VARIANCE // -/** -* FUNCTION: variance( arr[, options] ) -* Computes the variance of an array. +/* +* FUNCTION: variance( x[, options] ) +* Computes the variance of elements in x. * -* @param {Array} arr - input array -* @param {Object} [options] - function options -* @param {Boolean} [options.bias=false] - boolean indicating whether to calculate a biased or unbiased estimate of the variance -* @param {Function} [options.accessor] - accessor function for accessing array values -* @returns {Number|null} variance or null +* @param {Number[]|Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|Matrix} x - input value +* @param {Object} [opts] - function options +* @param {Boolean} [opts.bias=false] - - boolean indicating whether to calculate a biased or unbiased estimate of the variance +* @param {Function} [opts.accessor] - accessor function for accessing array values +* @param {Number} [opts.dim=2] - dimension along which to compute the variance +* @param {String} [opts.dtype="float64"] - output data type +* @returns {Number|Matrix|Null} variance value(s) or null */ -function variance( arr, opts ) { - var bias = false, - clbk; - if ( !isArray( arr ) ) { - throw new TypeError( 'variance()::invalid input argument. Must provide an array. Value: `' + arr + '`.' ); - } - if ( arguments.length > 1 ) { - if ( !isObject( opts ) ) { - throw new TypeError( 'variance()::invalid input argument. Options must be an object. Value: `' + opts + '`.' ); - } - if ( opts.hasOwnProperty( 'bias' ) ) { - bias = opts.bias; - if ( !isBoolean( bias ) ) { - throw new TypeError( 'variance()::invalid option. Bias option must be a boolean primitive. Value: `' + bias + '`.' ); - } - } - if ( opts.hasOwnProperty( 'accessor' ) ) { - clbk = opts.accessor; - if ( !isFunction( clbk ) ) { - throw new TypeError( 'variance()::invalid option. Accessor must be a function. Value: `' + clbk + '`.' ); - } +function variance( x, options ) { + /* jshint newcap:false */ + var opts = {}, + bias, + shape, + ctor, + err, + len, + dim, + dt, + d, + m; + + if ( arguments.length > 1 ) { + err = validate( opts, options ); + if ( err ) { + throw err; } } - var len = arr.length, - delta = 0, - mean = 0, - M2 = 0, - x, i; - if ( !len ) { - return null; - } - if ( len < 2 ) { - return 0; - } - if ( clbk ) { - for ( i = 0; i < len; i++ ) { - x = clbk( arr[ i ] ); - delta = x - mean; - mean += delta / (i+1); - M2 += delta * ( x - mean ); + bias = opts.bias || false; + + if ( isMatrixLike( x ) ) { + dt = opts.dtype || 'float64'; + dim = opts.dim; + + // Determine if provided a vector... + if ( x.shape[ 0 ] === 1 || x.shape[ 1 ] === 1 ) { + // Treat as an array-like object: + return variance1( x.data ); } - } else { - for ( i = 0; i < len; i++ ) { - x = arr[ i ]; - delta = x - mean; - mean += delta / (i+1); - M2 += delta * ( x - mean ); + if ( dim > 2 ) { + throw new RangeError( 'variance()::invalid option. Dimension option exceeds number of matrix dimensions. Option: `' + dim + '`.' ); } + if ( dim === void 0 || dim === 2 ) { + len = x.shape[ 0 ]; + shape = [ len, 1 ]; + } else { + len = x.shape[ 1 ]; + shape = [ 1, len ]; + } + ctor = ctors( dt ); + if ( ctor === null ) { + throw new Error( 'variance()::invalid option. Data type option does not have a corresponding array constructor. Option: `' + dt + '`.' ); + } + // Create an output matrix and calculate the variance(s): + d = new ctor( len ); + m = matrix( d, shape, dt ); + return variance3( m, x, bias, dim ); } - if ( bias ) { - return M2 / ( i ); - } else { - return M2 / ( i - 1 ); + if ( isArrayLike( x ) ) { + if ( opts.accessor ) { + return variance2( x, opts.accessor, bias ); + } + return variance1( x, bias ); } + throw new TypeError( 'variance()::invalid input argument. First argument must be either an array or a matrix. Value: `' + x + '`.' ); } // end FUNCTION variance() diff --git a/lib/matrix.js b/lib/matrix.js new file mode 100644 index 0000000..2a0b933 --- /dev/null +++ b/lib/matrix.js @@ -0,0 +1,60 @@ +'use strict'; + +/** +* FUNCTION: variance( out, mat,[bias , dim] ) +* Computes the variance along a matrix dimension +* +* @param {Matrix} out - output matrix +* @param {Matrix} mat - input matrix +* @param {Boolean} [bias=false] - boolean indicating whether to calculate a biased or unbiased estimate of the variance +* @param {Number} [dim=2] - matrix dimension along which to compute the maximum. If `dim=1`, compute along matrix rows. If `dim=2`, compute along matrix columns. +* @returns {Matrix|Null} variance or null +*/ +function variance( out, mat, bias, dim ) { + var mu, delta, x, M2, + M, N, + s0, s1, + i, j, k; + + if ( dim === 1 ) { + // Compute along the rows... + M = mat.shape[ 1 ]; + N = mat.shape[ 0 ]; + s0 = mat.strides[ 1 ]; + s1 = mat.strides[ 0 ]; + } else { + // Compute along the columns... + M = mat.shape[ 0 ]; + N = mat.shape[ 1 ]; + s0 = mat.strides[ 0 ]; + s1 = mat.strides[ 1 ]; + } + if ( M === 0 || N === 0 ) { + return null; + } + + for ( i = 0; i < M; i++ ) { + k = i * s0; + mu = 0; + delta = 0; + M2 = 0; + for ( j = 0; j < N; j++ ) { + x = mat.data[ k + j*s1 ]; + delta = x - mu; + mu += delta / (j+1); + M2 += delta * ( x - mu ); + } + if ( bias ) { + out.data[ i ] = M2 / ( N ); + } else { + out.data[ i ] = M2 / ( N - 1 ); + } + } + + return out; +} // end FUNCTION variance() + + +// EXPORTS // + +module.exports = variance; diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..6b78a1a --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,58 @@ +'use strict'; + +// MODULES // + +var isObject = require( 'validate.io-object' ), + isFunction = require( 'validate.io-function' ), + isString = require( 'validate.io-string-primitive' ), + isBoolean = require( 'validate.io-boolean-primitive'), + isPositiveInteger = require( 'validate.io-positive-integer' ); + + +// VALIDATE // + +/** +* FUNCTION: validate( opts, options ) +* Validates function options. +* +* @param {Object} opts - destination for validated options +* @param {Object} options - function options +* @param {Function} [options.accessor] - accessor function for accessing array values +* @param {Number} [options.dim] - dimension +* @returns {Null|Error} null or an error +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( 'variance()::invalid input argument. Options argument must be an object. Value: `' + options + '`.' ); + } + if ( options.hasOwnProperty( 'accessor' ) ) { + opts.accessor = options.accessor; + if ( !isFunction( opts.accessor ) ) { + return new TypeError( 'variance()::invalid option. Accessor must be a function. Option: `' + opts.accessor + '`.' ); + } + } + if ( options.hasOwnProperty( 'dim' ) ) { + opts.dim = options.dim; + if ( !isPositiveInteger( opts.dim ) ) { + return new TypeError( 'variance()::invalid option. Dimension option must be a positive integer. Option: `' + opts.dim + '`.' ); + } + } + if ( options.hasOwnProperty( 'dtype' ) ) { + opts.dtype = options.dtype; + if ( !isString( opts.dtype ) ) { + return new TypeError( 'variance()::invalid option. Data type option must be a string primitive. Option: `' + opts.dtype + '`.' ); + } + } + if ( options.hasOwnProperty( 'bias' ) ) { + opts.bias = options.bias; + if ( !isBoolean( opts.bias ) ) { + return new TypeError( 'variance()::invalid option. Bias option must be a Boolean primitive. Option: `' + opts.dim + '`.' ); + } + } + return null; +} // end FUNCTION validate() + + +// EXPORTS // + +module.exports = validate; From 571b91ec579fa35c007af62c895cc951fd9323d0 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Fri, 5 Jun 2015 15:39:45 +0200 Subject: [PATCH 03/16] [UPDATE] tests --- test/test.accessor.js | 88 +++++++++++++++++++++++++ test/test.array.js | 58 +++++++++++++++++ test/test.js | 99 +++++++++++++++++----------- test/test.matrix.js | 94 +++++++++++++++++++++++++++ test/test.validate.js | 147 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 450 insertions(+), 36 deletions(-) create mode 100644 test/test.accessor.js create mode 100644 test/test.array.js create mode 100644 test/test.matrix.js create mode 100644 test/test.validate.js diff --git a/test/test.accessor.js b/test/test.accessor.js new file mode 100644 index 0000000..adcaf93 --- /dev/null +++ b/test/test.accessor.js @@ -0,0 +1,88 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + variance = require( './../lib/accessor.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'accessor product', function tests() { + + it( 'should export a function', function test() { + expect( variance ).to.be.a( 'function' ); + }); + + it( 'should compute the variance using an accessor', function test() { + var data, expected; + + data = [ + {'x':2}, + {'x':4}, + {'x':5}, + {'x':3}, + {'x':8}, + {'x':2} + ]; + expected = 5.2; + + assert.strictEqual( variance( data, getValue ), expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should compute the (biased) variance using an accessor', function test() { + var data, expected; + + data = [ + {'x':2}, + {'x':4}, + {'x':5}, + {'x':3}, + {'x':8}, + {'x':2} + ]; + expected = 4.333333333333333; + + assert.strictEqual( variance( data, getValue, true ), expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should return 0 for a single element array', function test() { + var data, expected; + + data = [ {'x':2} ]; + expected = 0; + + assert.strictEqual( variance( data, getValue ), expected ); + + function getValue( d ) { + return d.x; + } + }); + + it( 'should return null if provided an empty array', function test() { + assert.isNull( variance( [], getValue ) ); + + function getValue( d ) { + return d.x; + } + }); + +}); diff --git a/test/test.array.js b/test/test.array.js new file mode 100644 index 0000000..be0fc9a --- /dev/null +++ b/test/test.array.js @@ -0,0 +1,58 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + variance = require( './../lib/array.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'array variance', function tests() { + + it( 'should export a function', function test() { + expect( variance ).to.be.a( 'function' ); + }); + + it( 'should compute the variance', function test() { + var data, expected; + + data = [ 2, 4, 5, 3, 8, 2 ]; + expected = 5.2; + + assert.strictEqual( variance( data ), expected ); + }); + + it( 'should compute the (biased) variance', function test() { + var data, expected; + + data = [ 2, 4, 5, 3, 8, 2 ]; + expected = 4.333333333333333; + + assert.strictEqual( variance( data, true ), expected ); + }); + + it( 'should return 0 for a single element array', function test() { + var data, expected; + + data = [ 2 ]; + expected = 0; + + assert.strictEqual( variance( data ), expected ); + }); + + it( 'should return null if provided an empty array', function test() { + assert.isNull( variance( [] ) ); + }); + +}); diff --git a/test/test.js b/test/test.js index a4af5a8..4aaf5a8 100644 --- a/test/test.js +++ b/test/test.js @@ -3,6 +3,8 @@ // MODULES // +var matrix = require( 'dstructs-matrix' ); + var // Expectation library: chai = require( 'chai' ), @@ -24,9 +26,9 @@ describe( 'compute-variance', function tests() { expect( variance ).to.be.a( 'function' ); }); - it( 'should throw an error if provided a non-array', function test() { + it( 'should throw an error if the first argument is neither array-like or matrix-like', function test() { var values = [ - '5', + // '5', // valid as is array-like (length) 5, true, undefined, @@ -46,72 +48,64 @@ describe( 'compute-variance', function tests() { } }); - it( 'should throw an error if provided an options argument which is not an object', function test() { + it( 'should throw an error if provided an unrecognized/unsupported data type option', function test() { var values = [ - '5', - 5, - true, - undefined, - null, - NaN, - function(){}, - [] + 'beep', + 'boop' ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( values[i] ) ).to.throw( TypeError ); + expect( badValue( values[i] ) ).to.throw( Error ); } function badValue( value ) { return function() { - variance( [1,2,3], value ); + variance( matrix( [2,2] ), { + 'dtype': value + }); }; } }); - it( 'should throw an error if provided an accessor which is not a function', function test() { + it( 'should throw an error if provided a dim option which is not a positive integer', function test() { + var data = matrix( new Int32Array([1,2,3,4]), [2,2] ); var values = [ '5', - 5, - [], + -5, + 2.2, + true, undefined, null, NaN, - true, + [], {} ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( values[i] ) ).to.throw( TypeError ); + expect( badValue( values[ i ] ) ).to.throw( Error ); } function badValue( value ) { return function() { - variance( [ 1, 2, 3 ], { 'accessor': value } ); + variance( data, {'dim': value} ); }; } }); - it( 'should throw an error if provided a bias option which is not a boolean primitive', function test() { + it( 'should throw an error if provided a dim option which exceeds matrix dimensions ( = 2 )', function test() { + var data = matrix( new Int32Array([1,2,3,4]), [2,2] ); var values = [ - '5', - 5, - [], - new Boolean( false ), - undefined, - null, - NaN, - function(){}, - {} + 3, + 4, + 5 ]; for ( var i = 0; i < values.length; i++ ) { - expect( badValue( values[i] ) ).to.throw( TypeError ); + expect( badValue( values[ i ] ) ).to.throw( RangeError ); } + function badValue( value ) { return function() { - variance( [1,2,3], { - 'bias': value - }); + variance( data, {'dim': value} ); }; } }); @@ -125,6 +119,15 @@ describe( 'compute-variance', function tests() { assert.strictEqual( variance( data ), expected ); }); + it( 'should compute the sample variance of a typed array', function test() { + var data, expected; + + data = new Int8Array( [ 2, 4, 5, 3, 8, 2 ] ); + expected = 5.2; + + assert.strictEqual( variance( data ), expected ); + }); + it( 'should compute the (biased) sample variance', function test() { var data, expected, actual; @@ -170,11 +173,35 @@ describe( 'compute-variance', function tests() { assert.strictEqual( variance( data ), expected ); }); - it( 'should return 0 for a single element array', function test() { + it( 'should calculate the column products of a matrix', function test() { + var data, expected, results; + + data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); + expected = matrix( new Float32Array( [ 1, 1, 1 ] ), [3,1] ); + + results = variance( data, {'dtype': 'float32'} ); + + assert.strictEqual( results.data.length, expected.data.length ); + assert.deepEqual( results.data, expected.data ); + }); + + it( 'should calculate the row products of a matrix', function test() { + var data, expected, results; + + data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); + expected = matrix( new Float32Array( [ 9, 9, 9 ] ), [1, 3] ); + + results = variance( data, {'dim': 1, 'dtype': 'float32'} ); + + assert.strictEqual( results.data.length, expected.data.length ); + assert.deepEqual( results.data, expected.data ); + }); + + it( 'should compute the product for a vector (matrix with one column or row)', function test() { var data, expected; - data = [ 2 ]; - expected = 0; + expected = 5.2; + data = matrix( new Int32Array( [ 2, 4, 5, 3, 8, 2 ] ), [6,1] ); assert.strictEqual( variance( data ), expected ); }); diff --git a/test/test.matrix.js b/test/test.matrix.js new file mode 100644 index 0000000..1744ffd --- /dev/null +++ b/test/test.matrix.js @@ -0,0 +1,94 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Matrix data structure: + matrix = require( 'dstructs-matrix' ), + + // Module to be tested: + variance = require( './../lib/matrix.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + +// TESTS // + +describe( 'matrix variance', function tests() { + + var data, + mat, + i; + + data = new Int32Array( 25 ); + for ( i = 0; i < data.length; i++ ) { + data[ i ] = i + 1; + } + mat = matrix( data, [5,5], 'int8' ); + + + it( 'should export a function', function test() { + expect( variance ).to.be.a( 'function' ); + }); + + it( 'should compute the variance along matrix columns', function test() { + var out, p, expected; + + out = matrix( [5,1], 'float64' ); + + p = variance( out, mat ); + expected = '2.5;2.5;2.5;2.5;2.5'; + + assert.strictEqual( p.toString(), expected ); + + p = variance( out, mat, false, 2 ); + expected = '2.5;2.5;2.5;2.5;2.5'; + + assert.strictEqual( p.toString(), expected ); + }); + + it( 'should compute the variance along matrix rows', function test() { + var out, p, expected; + + out = matrix( [1,5], 'float64' ); + + p = variance( out, mat, false, 1 ); + expected = '62.5,62.5,62.5,62.5,62.5'; + + assert.strictEqual( p.toString(), expected ); + }); + + it( 'should compute the (biased) variance along matrix rows', function test() { + var out, p, expected; + + out = matrix( [1,5], 'float64' ); + + p = variance( out, mat, true, 1 ); + expected = '50,50,50,50,50'; + + assert.strictEqual( p.toString(), expected ); + }); + + it( 'should return null if provided a matrix having one or more zero dimensions', function test() { + var out, mat; + + out = matrix( [0,0] ); + + mat = matrix( [0,10] ); + assert.isNull( variance( out, mat ) ); + + mat = matrix( [10,0] ); + assert.isNull( variance( out, mat ) ); + + mat = matrix( [0,0] ); + assert.isNull( variance( out, mat ) ); + }); + +}); diff --git a/test/test.validate.js b/test/test.validate.js new file mode 100644 index 0000000..b991561 --- /dev/null +++ b/test/test.validate.js @@ -0,0 +1,147 @@ +/* global describe, it, require */ +'use strict'; + +// MODULES // + +var // Expectation library: + chai = require( 'chai' ), + + // Module to be tested: + validate = require( './../lib/validate.js' ); + + +// VARIABLES // + +var expect = chai.expect, + assert = chai.assert; + + // TESTS // + + describe( 'validate', function tests() { + + it( 'should export a function', function test() { + expect( validate ).to.be.a( 'function' ); + }); + + it( 'should return an error if provided an options argument which is not an object', function test() { + var values = [ + '5', + 5, + true, + undefined, + null, + NaN, + function(){}, + [] + ]; + + for ( var i = 0; i < values.length; i++ ) { + assert.isTrue( validate( {}, values[ i ] ) instanceof TypeError ); + } + }); + + it( 'should return an error if provided an accessor which is not a function', function test() { + var values, err; + + values = [ + '5', + 5, + true, + undefined, + null, + NaN, + [], + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'accessor': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return an error if provided a dim option which is not a positive integer', function test() { + var values, err; + + values = [ + '5', + Math.PI, + -1, + 0, + true, + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'dim': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return an error if provided a dtype option which is not a string primitive', function test() { + var values, err; + + values = [ + 5, + true, + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'dtype': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should throw an error if provided a bias option which is not a boolean primitive', function test() { + var values, err; + + values = [ + '5', + 5, + [], + new Boolean( false ), + undefined, + null, + NaN, + function(){}, + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'bias': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + + it( 'should return null if all options are valid', function test() { + var err; + + err = validate( {}, { + 'accessor': function getValue(){}, + 'dim': 2, + 'dtype': 'int32' + }); + + assert.isNull( err ); + }); + + }); From 6b22bf0b42ae08fa4d0c50709005b8fbef82a0e7 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Fri, 5 Jun 2015 15:55:30 +0200 Subject: [PATCH 04/16] [UPDATE] add eqn svg --- docs/img/eqn.svg | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/img/eqn.svg diff --git a/docs/img/eqn.svg b/docs/img/eqn.svg new file mode 100644 index 0000000..2ed798a --- /dev/null +++ b/docs/img/eqn.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bdaaeaeb1718476b61f462cf3e5252d7b4c0c585 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Fri, 5 Jun 2015 15:59:50 +0200 Subject: [PATCH 05/16] [UPDATE] README --- README.md | 197 ++++++++++++++++++++++++++++++++++++++++++---- docs/img/eqn2.svg | 47 +++++++++++ 2 files changed, 230 insertions(+), 14 deletions(-) create mode 100644 docs/img/eqn2.svg diff --git a/README.md b/README.md index cdd68a6..2faa0cd 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ Variance > Computes the [variance](http://en.wikipedia.org/wiki/Variance) of an array. +The unbiased [sample variance](http://en.wikipedia.org/wiki/Variance) is defined by + +
+ Equation for the sample variance. +
+
+ +where `x_0, x_1,...,x_{N-1}` are individual data values and `N` is the total number of values in the data set. ## Installation @@ -20,21 +28,22 @@ For use in the browser, use [browserify](https://github.com/substack/node-browse var variance = require( 'compute-variance' ); ``` -### variance( arr[, opts] ) +### variance( x[, opts] ) -Computes the [variance](http://en.wikipedia.org/wiki/Variance) of an `array`. For numeric `arrays`, +Computes the [variance](http://en.wikipedia.org/wiki/Variance). `x` may be either an [`array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays), or [`matrix`](https://github.com/dstructs/matrix). + +For numeric `arrays`, ``` javascript var data = [ 2, 4, 5, 3, 4, 3, 1, 5, 6, 9 ]; var s2 = variance( data ); // returns 5.067 -``` -The function accepts two `options`: - -* __accessor__: accessor `function` for accessing `array` values -* __bias__: `boolean` indicating whether to compute the population variance (biased sample variance) or the (unbiased) sample variance. Default: `false`; i.e., the unbiased sample variance. +data = new Int8Array( data ); +s2 = variance( data ); +// returns 5.067 +``` For non-numeric `arrays`, provide an accessor `function` for accessing numeric `array` values @@ -68,26 +77,186 @@ By default, the function calculates the *unbiased* sample variance. To calculate var data = [ 2, 4, 5, 3, 4, 3, 1, 5, 6, 9 ]; var value = variance( data, { - 'bias': true + 'bias': true }); // returns 4.56 ``` -__Note__: if provided an empty `array`, the function returns `null`. +The biased variance is calculated as follows: + +
+ Equation for the biased sample variance. +
+
+If provided a [`matrix`](https://github.com/dstructs/matrix), the function accepts the following additional `options`: +* __dim__: dimension along which to compute the [sample variance](http://en.wikipedia.org/wiki/Variance). Default: `2` (along the columns). +* __dtype__: output [`matrix`](https://github.com/dstructs/matrix) data type. Default: `float64`. + +By default, the function computes the [variance](http://en.wikipedia.org/wiki/Variance) along the columns (`dim=2`). + +``` javascript +var matrix = require( 'dstructs-matrix' ), + data, + mat, + s2, + i; + +data = new Int8Array( 25 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = i; +} +mat = matrix( data, [5,5], 'int8' ); +/* + [ 0 1 2 3 4 + 5 6 7 8 9 + 10 11 12 13 14 + 15 16 17 18 19 + 20 21 22 23 24 ] +*/ + +s2 = variance( mat ); +/* + [ 2.5 + 2.5 + 2.5 + 2.5 + 2.5 ] +*/ +``` + +To compute the [variance](http://en.wikipedia.org/wiki/Variance) along the rows, set the `dim` option to `1`. + +``` javascript +s2 = variance( mat, { + 'dim': 1 +}); +/* + [ 62.5, 62.5, 62.5, 62.5, 62.5 ] +*/ +``` + +By default, the output [`matrix`](https://github.com/dstructs/matrix) data type is `float64`. To specify a different output data type, set the `dtype` option. + +``` javascript +s2 = variance( mat, { + 'dim': 1, + 'dtype': 'uint8' +}); +/* + [ 10, 11, 12, 13, 14 ] +*/ + +var dtype = s2.dtype; +// returns 'uint8' +``` + +If provided a [`matrix`](https://github.com/dstructs/matrix) having either dimension equal to `1`, the function treats the [`matrix`](https://github.com/dstructs/matrix) as a [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) and returns a `numeric` value. + +``` javascript +data = [ 2, 4, 5, 3, 4, 3, 1, 5, 6, 9 ]; + +// Row vector: +mat = matrix( new Int8Array( data ), [1,10], 'int8' ); +s2 = variance( mat ); +// returns 5.067 + +// Column vector: +mat = matrix( new Int8Array( data ), [10,1], 'int8' ); +s2 = variance( mat ); +// returns 5.067 +``` + +If provided an empty [`array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays), or [`matrix`](https://github.com/dstructs/matrix), the function returns `null`. + +``` javascript +s2 = variance( [] ); +// returns null + +s2 = variance( new Int8Array( [] ) ); +// returns null + +s2 = variance( matrix( [0,0] ) ); +// returns null + +s2 = variance( matrix( [0,10] ) ); +// returns null + +s2 = variance( matrix( [10,0] ) ); +// returns null +``` ## Examples ``` javascript -var variance = require( 'compute-variance' ); +var matrix = require( 'dstructs-matrix' ), + variance = require( 'compute-matrix' ); -var data = new Array( 1000 ); +var data, + mat, + s2, + i; + +// ---- +// Plain arrays... +var data = new Array( 100 ); for ( var i = 0; i < data.length; i++ ) { - data[ i ] = Math.random() * 100; + data[ i ] = Math.round( Math.random() * 10 + 1 ); +} +s2 = variance( data ); +console.log( 'Arrays: %d\n', s2 ); + + +// ---- +// Object arrays (accessors)... +function getValue( d ) { + return d.x; } +for ( i = 0; i < data.length; i++ ) { + data[ i ] = { + 'x': data[ i ] + }; +} +s2 = variance( data, { + 'accessor': getValue +}); +console.log( 'Accessors: %d\n', s2 ); + + +// ---- +// Typed arrays... +data = new Int32Array( 100 ); +for ( i = 0; i < data.length; i++ ) { + data[ i ] = Math.round( Math.random() * 10 + 1 ); +} +s2 = variance( data ); + + +// ---- +// Matrices (along rows)... +mat = matrix( data, [10,10], 'int32' ); +s2 = variance( mat, { + 'dim': 1 +}); +console.log( 'Matrix (rows): %s\n', s2.toString() ); + + +// ---- +// Matrices (along columns)... +s2 = variance( mat, { + 'dim': 2 +}); +console.log( 'Matrix (columns): %s\n', s2.toString() ); + + +// ---- +// Matrices (custom output data type)... +s2 = variance( mat, { + 'dtype': 'uint8' +}); +console.log( 'Matrix (%s): %s\n', s2.dtype, s2.toString() ); -console.log( variance( data ) ); ``` To run the example code from the top-level application directory, @@ -131,7 +300,7 @@ $ make view-cov ## Copyright -Copyright © 2014. Athan Reines. +Copyright © 2014-2015. The Compute.io Authors. [npm-image]: http://img.shields.io/npm/v/compute-variance.svg diff --git a/docs/img/eqn2.svg b/docs/img/eqn2.svg new file mode 100644 index 0000000..ceb6875 --- /dev/null +++ b/docs/img/eqn2.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bc151ec5824e2a669cf51ad40380b55d3f12b138 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Fri, 5 Jun 2015 16:05:33 +0200 Subject: [PATCH 06/16] [UPDATE] add eqn images --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2faa0cd..a55910f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Variance The unbiased [sample variance](http://en.wikipedia.org/wiki/Variance) is defined by
- Equation for the sample variance. + Equation for the sample variance.
@@ -85,7 +85,7 @@ var value = variance( data, { The biased variance is calculated as follows:
- Equation for the biased sample variance. + Equation for the biased sample variance.
From 746d9d9b128e86e7b74ad6a89bb55644c3031905 Mon Sep 17 00:00:00 2001 From: Planeshifter Date: Fri, 5 Jun 2015 16:23:25 +0200 Subject: [PATCH 07/16] [FIX] add missing dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3135dda..c05aa2a 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "compute-dtype": "^1.0.0", "dstructs-matrix": "^1.0.0", "validate.io-array-like": "^1.0.0", + "validate.io-boolean-primitive": "^1.0.0", "validate.io-function": "^1.0.2", "validate.io-matrix-like": "^1.0.0", "validate.io-object": "^1.0.4", From a69aee377d41f6746cd3d0c7f10c2735c0153e46 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Fri, 5 Jun 2015 22:37:31 +0200 Subject: [PATCH 08/16] [FIX] copy-paste err --- test/test.accessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.accessor.js b/test/test.accessor.js index adcaf93..80f3713 100644 --- a/test/test.accessor.js +++ b/test/test.accessor.js @@ -18,7 +18,7 @@ var expect = chai.expect, // TESTS // -describe( 'accessor product', function tests() { +describe( 'accessor variance', function tests() { it( 'should export a function', function test() { expect( variance ).to.be.a( 'function' ); From ae74c189b21fc56f75f3aeaf0bd9f2bb5b35b815 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Fri, 5 Jun 2015 22:43:17 +0200 Subject: [PATCH 09/16] [FIX] validate parameters --- lib/validate.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/validate.js b/lib/validate.js index 6b78a1a..e8b1120 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -19,6 +19,8 @@ var isObject = require( 'validate.io-object' ), * @param {Object} options - function options * @param {Function} [options.accessor] - accessor function for accessing array values * @param {Number} [options.dim] - dimension +* @param {String} [options.dtype] - output data type +* @param {Boolean} [opts.bias=false] - Boolean indicating whether to calculate a biased or unbiased estimate of the variance * @returns {Null|Error} null or an error */ function validate( opts, options ) { From bf06c3f1256ced82a445913d099fde84812b4336 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sat, 6 Jun 2015 00:36:40 +0200 Subject: [PATCH 10/16] [FIX] copy-paste err: prod -> variance --- test/test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test.js b/test/test.js index 4aaf5a8..9fca082 100644 --- a/test/test.js +++ b/test/test.js @@ -173,7 +173,7 @@ describe( 'compute-variance', function tests() { assert.strictEqual( variance( data ), expected ); }); - it( 'should calculate the column products of a matrix', function test() { + it( 'should calculate the column variances of a matrix', function test() { var data, expected, results; data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); @@ -185,7 +185,7 @@ describe( 'compute-variance', function tests() { assert.deepEqual( results.data, expected.data ); }); - it( 'should calculate the row products of a matrix', function test() { + it( 'should calculate the row variances of a matrix', function test() { var data, expected, results; data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); @@ -197,7 +197,7 @@ describe( 'compute-variance', function tests() { assert.deepEqual( results.data, expected.data ); }); - it( 'should compute the product for a vector (matrix with one column or row)', function test() { + it( 'should compute the variance for a vector (matrix with one column or row)', function test() { var data, expected; expected = 5.2; From f410f9e67dcac62bcf3dcba66e7e64227c56fe6c Mon Sep 17 00:00:00 2001 From: kgryte Date: Fri, 5 Jun 2015 21:04:08 -0700 Subject: [PATCH 11/16] [UPDATE] README to be consistent with compute-mean. --- README.md | 63 ++++++++++++++++++----------------------------- examples/index.js | 1 + package.json | 4 ++- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index a55910f..19f722d 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,25 @@ Variance === [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Dependencies][dependencies-image]][dependencies-url] -> Computes the [variance](http://en.wikipedia.org/wiki/Variance) of an array. +> Computes the [variance](http://en.wikipedia.org/wiki/Variance). -The unbiased [sample variance](http://en.wikipedia.org/wiki/Variance) is defined by +The population [variance](http://en.wikipedia.org/wiki/Variance) (biased sample variance) is defined as -
- Equation for the sample variance. +
+ Equation for the population (biased sample) variance. +
+
+ +and the unbiased [sample variance](http://en.wikipedia.org/wiki/Variance) is defined as + +
+ Equation for the unbiased sample variance.
where `x_0, x_1,...,x_{N-1}` are individual data values and `N` is the total number of values in the data set. + ## Installation ``` bash @@ -32,12 +40,11 @@ var variance = require( 'compute-variance' ); Computes the [variance](http://en.wikipedia.org/wiki/Variance). `x` may be either an [`array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), [`typed array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays), or [`matrix`](https://github.com/dstructs/matrix). -For numeric `arrays`, - ``` javascript -var data = [ 2, 4, 5, 3, 4, 3, 1, 5, 6, 9 ]; +var data, s2; -var s2 = variance( data ); +data = [ 2, 4, 5, 3, 4, 3, 1, 5, 6, 9 ]; +s2 = variance( data ); // returns 5.067 data = new Int8Array( data ); @@ -45,7 +52,7 @@ s2 = variance( data ); // returns 5.067 ``` -For non-numeric `arrays`, provide an accessor `function` for accessing numeric `array` values +For non-numeric `arrays`, provide an accessor `function` for accessing numeric `array` values. ``` javascript var data = [ @@ -71,27 +78,20 @@ var s2 = variance( data, { // returns 5.067 ``` -By default, the function calculates the *unbiased* sample variance. To calculate the population variance (or a *biased* sample variance), set the `bias` option to `true`. +By default, the function calculates the *unbiased* sample [variance](http://en.wikipedia.org/wiki/Variance). To calculate the population variance (or a *biased* sample variance), set the `bias` option to `true`. ``` javascript var data = [ 2, 4, 5, 3, 4, 3, 1, 5, 6, 9 ]; -var value = variance( data, { +var sigma2 = variance( data, { 'bias': true }); // returns 4.56 ``` -The biased variance is calculated as follows: - -
- Equation for the biased sample variance. -
-
- If provided a [`matrix`](https://github.com/dstructs/matrix), the function accepts the following additional `options`: -* __dim__: dimension along which to compute the [sample variance](http://en.wikipedia.org/wiki/Variance). Default: `2` (along the columns). +* __dim__: dimension along which to compute the [variance](http://en.wikipedia.org/wiki/Variance). Default: `2` (along the columns). * __dtype__: output [`matrix`](https://github.com/dstructs/matrix) data type. Default: `float64`. By default, the function computes the [variance](http://en.wikipedia.org/wiki/Variance) along the columns (`dim=2`). @@ -145,7 +145,7 @@ s2 = variance( mat, { 'dtype': 'uint8' }); /* - [ 10, 11, 12, 13, 14 ] + [ 62.5, 62.5, 62.5, 62.5, 62.5 ] */ var dtype = s2.dtype; @@ -187,28 +187,26 @@ s2 = variance( matrix( [10,0] ) ); // returns null ``` + + ## Examples ``` javascript var matrix = require( 'dstructs-matrix' ), - variance = require( 'compute-matrix' ); + variance = require( 'compute-variance' ); var data, mat, s2, i; -// ---- // Plain arrays... var data = new Array( 100 ); for ( var i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random() * 10 + 1 ); } s2 = variance( data ); -console.log( 'Arrays: %d\n', s2 ); - -// ---- // Object arrays (accessors)... function getValue( d ) { return d.x; @@ -221,10 +219,7 @@ for ( i = 0; i < data.length; i++ ) { s2 = variance( data, { 'accessor': getValue }); -console.log( 'Accessors: %d\n', s2 ); - -// ---- // Typed arrays... data = new Int32Array( 100 ); for ( i = 0; i < data.length; i++ ) { @@ -232,31 +227,21 @@ for ( i = 0; i < data.length; i++ ) { } s2 = variance( data ); - -// ---- // Matrices (along rows)... mat = matrix( data, [10,10], 'int32' ); s2 = variance( mat, { 'dim': 1 }); -console.log( 'Matrix (rows): %s\n', s2.toString() ); - -// ---- // Matrices (along columns)... s2 = variance( mat, { 'dim': 2 }); -console.log( 'Matrix (columns): %s\n', s2.toString() ); - -// ---- // Matrices (custom output data type)... s2 = variance( mat, { 'dtype': 'uint8' }); -console.log( 'Matrix (%s): %s\n', s2.dtype, s2.toString() ); - ``` To run the example code from the top-level application directory, @@ -300,7 +285,7 @@ $ make view-cov ## Copyright -Copyright © 2014-2015. The Compute.io Authors. +Copyright © 2014-2015. The [Compute.io](https://github.com/compute-io) Authors. [npm-image]: http://img.shields.io/npm/v/compute-variance.svg diff --git a/examples/index.js b/examples/index.js index 75f57a1..1f90adc 100644 --- a/examples/index.js +++ b/examples/index.js @@ -41,6 +41,7 @@ for ( i = 0; i < data.length; i++ ) { data[ i ] = Math.round( Math.random() * 10 + 1 ); } s2 = variance( data ); +console.log( 'Typed arrays: %d\n', s2 ); // ---- diff --git a/package.json b/package.json index c05aa2a..1446c73 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "spread", "array", "numeric array", - "population" + "population", + "matrix", + "accessor" ], "bugs": { "url": "https://github.com/compute-io/variance/issues" From 2b812a7ecb17a847cc39ed5730f13114636553d4 Mon Sep 17 00:00:00 2001 From: kgryte Date: Fri, 5 Jun 2015 21:11:53 -0700 Subject: [PATCH 12/16] [UPDATE] population variance eqn. --- README.md | 4 ++-- docs/img/eqn2.svg | 48 +++++++++++++++++++++++------------------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 19f722d..8662eae 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Variance > Computes the [variance](http://en.wikipedia.org/wiki/Variance). -The population [variance](http://en.wikipedia.org/wiki/Variance) (biased sample variance) is defined as +The [population variance](http://en.wikipedia.org/wiki/Variance) (biased sample variance) is defined as -
+
Equation for the population (biased sample) variance.
diff --git a/docs/img/eqn2.svg b/docs/img/eqn2.svg index ceb6875..96c5bf7 100644 --- a/docs/img/eqn2.svg +++ b/docs/img/eqn2.svg @@ -1,11 +1,11 @@ - - + + + - @@ -22,26 +22,26 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + From abbb61f95974cf640eaa1851bb3df76e96c9d777 Mon Sep 17 00:00:00 2001 From: kgryte Date: Fri, 5 Jun 2015 21:13:32 -0700 Subject: [PATCH 13/16] [UPDATE] eqn link. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8662eae..b4d2c0e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Variance The [population variance](http://en.wikipedia.org/wiki/Variance) (biased sample variance) is defined as
- Equation for the population (biased sample) variance. + Equation for the population (biased sample) variance.
From e5c5c02aa39f3943c98d24b6c2e1d98402592a9f Mon Sep 17 00:00:00 2001 From: kgryte Date: Fri, 5 Jun 2015 21:30:44 -0700 Subject: [PATCH 14/16] [UPDATE] tweaks. --- TODO.md | 1 - lib/accessor.js | 7 +- lib/array.js | 7 +- lib/matrix.js | 13 +-- lib/validate.js | 4 +- test/test.js | 47 ++++----- test/test.validate.js | 235 +++++++++++++++++++++--------------------- 7 files changed, 155 insertions(+), 159 deletions(-) diff --git a/TODO.md b/TODO.md index d82dbbb..74e5cc0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,2 @@ TODO ==== - diff --git a/lib/accessor.js b/lib/accessor.js index ac18c02..1df69f6 100644 --- a/lib/accessor.js +++ b/lib/accessor.js @@ -7,7 +7,7 @@ * @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array * @param {Function} accessor - accessor function for accessing array values * @param {Boolean} [bias=false] - boolean indicating whether to calculate a biased or unbiased estimate of the variance -* @returns {Number|null} variance or null +* @returns {Number|Null} variance or null */ function variance( arr, clbk, bias ) { var len = arr.length, @@ -22,19 +22,16 @@ function variance( arr, clbk, bias ) { if ( len < 2 ) { return 0; } - for ( i = 0; i < len; i++ ) { x = clbk( arr[ i ], i ); delta = x - mean; mean += delta / (i+1); M2 += delta * ( x - mean ); } - if ( bias ) { return M2 / ( i ); - } else { - return M2 / ( i - 1 ); } + return M2 / ( i - 1 ); } // end FUNCTION variance() diff --git a/lib/array.js b/lib/array.js index d53d116..cc8d343 100644 --- a/lib/array.js +++ b/lib/array.js @@ -6,7 +6,7 @@ * * @param {Number[]|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} arr - input array * @param {Boolean} [bias=false] - boolean indicating whether to calculate a biased or unbiased estimate of the variance -* @returns {Number|null} variance or null +* @returns {Number|Null} variance or null */ function variance( arr, bias ) { var len = arr.length, @@ -21,19 +21,16 @@ function variance( arr, bias ) { if ( len < 2 ) { return 0; } - for ( i = 0; i < len; i++ ) { x = arr[ i ]; delta = x - mean; mean += delta / (i+1); M2 += delta * ( x - mean ); } - if ( bias ) { return M2 / ( i ); - } else { - return M2 / ( i - 1 ); } + return M2 / ( i - 1 ); } // end FUNCTION variance() diff --git a/lib/matrix.js b/lib/matrix.js index 2a0b933..8cddcf3 100644 --- a/lib/matrix.js +++ b/lib/matrix.js @@ -1,8 +1,8 @@ 'use strict'; /** -* FUNCTION: variance( out, mat,[bias , dim] ) -* Computes the variance along a matrix dimension +* FUNCTION: variance( out, mat[, bias[, dim] ] ) +* Computes the variance along a matrix dimension. * * @param {Matrix} out - output matrix * @param {Matrix} mat - input matrix @@ -11,7 +11,10 @@ * @returns {Matrix|Null} variance or null */ function variance( out, mat, bias, dim ) { - var mu, delta, x, M2, + var delta, + M2, + mu, + x, M, N, s0, s1, i, j, k; @@ -32,12 +35,11 @@ function variance( out, mat, bias, dim ) { if ( M === 0 || N === 0 ) { return null; } - for ( i = 0; i < M; i++ ) { k = i * s0; + M2 = 0; mu = 0; delta = 0; - M2 = 0; for ( j = 0; j < N; j++ ) { x = mat.data[ k + j*s1 ]; delta = x - mu; @@ -50,7 +52,6 @@ function variance( out, mat, bias, dim ) { out.data[ i ] = M2 / ( N - 1 ); } } - return out; } // end FUNCTION variance() diff --git a/lib/validate.js b/lib/validate.js index e8b1120..08586e2 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -20,7 +20,7 @@ var isObject = require( 'validate.io-object' ), * @param {Function} [options.accessor] - accessor function for accessing array values * @param {Number} [options.dim] - dimension * @param {String} [options.dtype] - output data type -* @param {Boolean} [opts.bias=false] - Boolean indicating whether to calculate a biased or unbiased estimate of the variance +* @param {Boolean} [options.bias=false] - boolean indicating whether to calculate a biased or unbiased estimate of the variance * @returns {Null|Error} null or an error */ function validate( opts, options ) { @@ -48,7 +48,7 @@ function validate( opts, options ) { if ( options.hasOwnProperty( 'bias' ) ) { opts.bias = options.bias; if ( !isBoolean( opts.bias ) ) { - return new TypeError( 'variance()::invalid option. Bias option must be a Boolean primitive. Option: `' + opts.dim + '`.' ); + return new TypeError( 'variance()::invalid option. Bias option must be a boolean primitive. Option: `' + opts.dim + '`.' ); } } return null; diff --git a/test/test.js b/test/test.js index 9fca082..bae3242 100644 --- a/test/test.js +++ b/test/test.js @@ -3,11 +3,12 @@ // MODULES // -var matrix = require( 'dstructs-matrix' ); - var // Expectation library: chai = require( 'chai' ), + // Matrix data structure: + matrix = require( 'dstructs-matrix' ), + // Module to be tested: variance = require( './../lib' ); @@ -83,29 +84,34 @@ describe( 'compute-variance', function tests() { for ( var i = 0; i < values.length; i++ ) { expect( badValue( values[ i ] ) ).to.throw( Error ); } - function badValue( value ) { return function() { - variance( data, {'dim': value} ); + variance( data, { + 'dim': value + }); }; } }); - it( 'should throw an error if provided a dim option which exceeds matrix dimensions ( = 2 )', function test() { - var data = matrix( new Int32Array([1,2,3,4]), [2,2] ); - var values = [ + it( 'should throw an error if provided a dim option which exceeds the number of matrix dimensions (2)', function test() { + var data, values; + + values = [ 3, 4, 5 ]; + data = matrix( new Int32Array([1,2,3,4]), [2,2] ); + for ( var i = 0; i < values.length; i++ ) { expect( badValue( values[ i ] ) ).to.throw( RangeError ); } - function badValue( value ) { return function() { - variance( data, {'dim': value} ); + variance( data, { + 'dim': value + }); }; } }); @@ -165,36 +171,31 @@ describe( 'compute-variance', function tests() { }); it( 'should return `null` when provided an empty array', function test() { - var data, expected; - - data = []; - expected = null; - - assert.strictEqual( variance( data ), expected ); + assert.isNull( variance( [] ) ); }); it( 'should calculate the column variances of a matrix', function test() { - var data, expected, results; + var data, expected, s2; data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); expected = matrix( new Float32Array( [ 1, 1, 1 ] ), [3,1] ); - results = variance( data, {'dtype': 'float32'} ); + s2 = variance( data, {'dtype': 'float32'} ); - assert.strictEqual( results.data.length, expected.data.length ); - assert.deepEqual( results.data, expected.data ); + assert.strictEqual( s2.data.length, expected.data.length ); + assert.deepEqual( s2.data, expected.data ); }); it( 'should calculate the row variances of a matrix', function test() { - var data, expected, results; + var data, expected, s2; data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); expected = matrix( new Float32Array( [ 9, 9, 9 ] ), [1, 3] ); - results = variance( data, {'dim': 1, 'dtype': 'float32'} ); + s2 = variance( data, {'dim': 1, 'dtype': 'float32'} ); - assert.strictEqual( results.data.length, expected.data.length ); - assert.deepEqual( results.data, expected.data ); + assert.strictEqual( s2.data.length, expected.data.length ); + assert.deepEqual( s2.data, expected.data ); }); it( 'should compute the variance for a vector (matrix with one column or row)', function test() { diff --git a/test/test.validate.js b/test/test.validate.js index b991561..2d90c02 100644 --- a/test/test.validate.js +++ b/test/test.validate.js @@ -15,133 +15,134 @@ var // Expectation library: var expect = chai.expect, assert = chai.assert; - // TESTS // +// TESTS // - describe( 'validate', function tests() { +describe( 'validate', function tests() { - it( 'should export a function', function test() { - expect( validate ).to.be.a( 'function' ); - }); - - it( 'should return an error if provided an options argument which is not an object', function test() { - var values = [ - '5', - 5, - true, - undefined, - null, - NaN, - function(){}, - [] - ]; - - for ( var i = 0; i < values.length; i++ ) { - assert.isTrue( validate( {}, values[ i ] ) instanceof TypeError ); - } - }); - - it( 'should return an error if provided an accessor which is not a function', function test() { - var values, err; - - values = [ - '5', - 5, - true, - undefined, - null, - NaN, - [], - {} - ]; - - for ( var i = 0; i < values.length; i++ ) { - err = validate( {}, { - 'accessor': values[ i ] - }); - assert.isTrue( err instanceof TypeError ); - } - }); + it( 'should export a function', function test() { + expect( validate ).to.be.a( 'function' ); + }); - it( 'should return an error if provided a dim option which is not a positive integer', function test() { - var values, err; - - values = [ - '5', - Math.PI, - -1, - 0, - true, - undefined, - null, - NaN, - [], - {}, - function(){} - ]; - - for ( var i = 0; i < values.length; i++ ) { - err = validate( {}, { - 'dim': values[ i ] - }); - assert.isTrue( err instanceof TypeError ); - } - }); + it( 'should return an error if provided an options argument which is not an object', function test() { + var values = [ + '5', + 5, + true, + undefined, + null, + NaN, + function(){}, + [] + ]; + + for ( var i = 0; i < values.length; i++ ) { + assert.isTrue( validate( {}, values[ i ] ) instanceof TypeError ); + } + }); - it( 'should return an error if provided a dtype option which is not a string primitive', function test() { - var values, err; - - values = [ - 5, - true, - undefined, - null, - NaN, - [], - {}, - function(){} - ]; - - for ( var i = 0; i < values.length; i++ ) { - err = validate( {}, { - 'dtype': values[ i ] - }); - assert.isTrue( err instanceof TypeError ); - } - }); + it( 'should return an error if provided an accessor which is not a function', function test() { + var values, err; + + values = [ + '5', + 5, + true, + undefined, + null, + NaN, + [], + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'accessor': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); - it( 'should throw an error if provided a bias option which is not a boolean primitive', function test() { - var values, err; - - values = [ - '5', - 5, - [], - new Boolean( false ), - undefined, - null, - NaN, - function(){}, - {} - ]; - - for ( var i = 0; i < values.length; i++ ) { - err = validate( {}, { - 'bias': values[ i ] - }); - assert.isTrue( err instanceof TypeError ); - } - }); + it( 'should return an error if provided a dim option which is not a positive integer', function test() { + var values, err; + + values = [ + '5', + Math.PI, + -1, + 0, + true, + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'dim': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); - it( 'should return null if all options are valid', function test() { - var err; + it( 'should return an error if provided a dtype option which is not a string primitive', function test() { + var values, err; + + values = [ + 5, + true, + undefined, + null, + NaN, + [], + {}, + function(){} + ]; + + for ( var i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'dtype': values[ i ] + }); + assert.isTrue( err instanceof TypeError ); + } + }); + it( 'should return an error if provided a bias option which is not a boolean primitive', function test() { + var values, err; + + values = [ + '5', + 5, + [], + new Boolean( false ), + undefined, + null, + NaN, + function(){}, + {} + ]; + + for ( var i = 0; i < values.length; i++ ) { err = validate( {}, { - 'accessor': function getValue(){}, - 'dim': 2, - 'dtype': 'int32' + 'bias': values[ i ] }); + assert.isTrue( err instanceof TypeError ); + } + }); - assert.isNull( err ); + it( 'should return null if all options are valid', function test() { + var err; + + err = validate( {}, { + 'accessor': function getValue(){}, + 'dim': 2, + 'dtype': 'int32', + 'bias': true }); + assert.isNull( err ); }); + +}); From f9a3519e96eaaf94c7627c0fb72e52eb8ac72035 Mon Sep 17 00:00:00 2001 From: kgryte Date: Fri, 5 Jun 2015 23:26:08 -0700 Subject: [PATCH 15/16] [TESTS] separate test for output matrix data type. --- lib/index.js | 11 ++++------- test/test.js | 43 +++++++++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/lib/index.js b/lib/index.js index dd57ea3..663fc95 100644 --- a/lib/index.js +++ b/lib/index.js @@ -8,6 +8,7 @@ var isArrayLike = require( 'validate.io-array-like' ), matrix = require( 'dstructs-matrix' ).raw, validate = require( './validate.js' ); + // FUNCTIONS // var variance1 = require( './array.js' ), @@ -32,7 +33,6 @@ var variance1 = require( './array.js' ), function variance( x, options ) { /* jshint newcap:false */ var opts = {}, - bias, shape, ctor, err, @@ -48,9 +48,6 @@ function variance( x, options ) { throw err; } } - - bias = opts.bias || false; - if ( isMatrixLike( x ) ) { dt = opts.dtype || 'float64'; dim = opts.dim; @@ -77,13 +74,13 @@ function variance( x, options ) { // Create an output matrix and calculate the variance(s): d = new ctor( len ); m = matrix( d, shape, dt ); - return variance3( m, x, bias, dim ); + return variance3( m, x, opts.bias, dim ); } if ( isArrayLike( x ) ) { if ( opts.accessor ) { - return variance2( x, opts.accessor, bias ); + return variance2( x, opts.accessor, opts.bias ); } - return variance1( x, bias ); + return variance1( x, opts.bias ); } throw new TypeError( 'variance()::invalid input argument. First argument must be either an array or a matrix. Value: `' + x + '`.' ); } // end FUNCTION variance() diff --git a/test/test.js b/test/test.js index bae3242..cfb9318 100644 --- a/test/test.js +++ b/test/test.js @@ -29,7 +29,7 @@ describe( 'compute-variance', function tests() { it( 'should throw an error if the first argument is neither array-like or matrix-like', function test() { var values = [ - // '5', // valid as is array-like (length) + // '5', // valid as is array-like (length) 5, true, undefined, @@ -68,8 +68,9 @@ describe( 'compute-variance', function tests() { }); it( 'should throw an error if provided a dim option which is not a positive integer', function test() { - var data = matrix( new Int32Array([1,2,3,4]), [2,2] ); - var values = [ + var data, values; + + values = [ '5', -5, 2.2, @@ -81,6 +82,8 @@ describe( 'compute-variance', function tests() { {} ]; + data = matrix( new Int32Array([1,2,3,4]), [2,2] ); + for ( var i = 0; i < values.length; i++ ) { expect( badValue( values[ i ] ) ).to.throw( Error ); } @@ -158,16 +161,16 @@ describe( 'compute-variance', function tests() { {'x':8}, {'x':2} ]; - expected = 5.2; actual = variance( data, { 'accessor': getValue }); + expected = 5.2; + + assert.strictEqual( actual, expected ); function getValue( d ) { return d.x; } - - assert.strictEqual( actual, expected ); }); it( 'should return `null` when provided an empty array', function test() { @@ -178,11 +181,12 @@ describe( 'compute-variance', function tests() { var data, expected, s2; data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); - expected = matrix( new Float32Array( [ 1, 1, 1 ] ), [3,1] ); + expected = matrix( new Float64Array( [ 1, 1, 1 ] ), [3,1] ); - s2 = variance( data, {'dtype': 'float32'} ); + s2 = variance( data, { + 'dim': 2 + }); - assert.strictEqual( s2.data.length, expected.data.length ); assert.deepEqual( s2.data, expected.data ); }); @@ -190,11 +194,26 @@ describe( 'compute-variance', function tests() { var data, expected, s2; data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); - expected = matrix( new Float32Array( [ 9, 9, 9 ] ), [1, 3] ); + expected = matrix( new Float64Array( [ 9, 9, 9 ] ), [1, 3] ); + + s2 = variance( data, { + 'dim': 1 + }); + + assert.deepEqual( s2.data, expected.data ); + }); - s2 = variance( data, {'dim': 1, 'dtype': 'float32'} ); + it( 'should calculate the variances of a matrix and output a matrix having a specified data type', function test() { + var data, expected, s2; + + data = matrix( new Int32Array( [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ), [3,3] ); + expected = matrix( new Int32Array( [ 1, 1, 1 ] ), [3,1] ); + + s2 = variance( data, { + 'dtype': 'int32' + }); - assert.strictEqual( s2.data.length, expected.data.length ); + assert.strictEqual( s2.dtype, 'int32' ); assert.deepEqual( s2.data, expected.data ); }); From 914d81f88d4c94dc964f699fc08c9fcf26f7b312 Mon Sep 17 00:00:00 2001 From: kgryte Date: Fri, 5 Jun 2015 23:30:37 -0700 Subject: [PATCH 16/16] [UPDATE] README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4d2c0e..19bcf8e 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ var s2 = variance( data, { // returns 5.067 ``` -By default, the function calculates the *unbiased* sample [variance](http://en.wikipedia.org/wiki/Variance). To calculate the population variance (or a *biased* sample variance), set the `bias` option to `true`. +By default, the function calculates the *unbiased* sample [variance](http://en.wikipedia.org/wiki/Variance). To calculate the population [variance](http://en.wikipedia.org/wiki/Variance) (or a *biased* sample [variance](http://en.wikipedia.org/wiki/Variance)), set the `bias` option to `true`. ``` javascript var data = [ 2, 4, 5, 3, 4, 3, 1, 5, 6, 9 ];