Skip to content

Commit

Permalink
Merge pull request #1 from compute-io/develop
Browse files Browse the repository at this point in the history
Fmt, accessor support, and null return.
  • Loading branch information
Planeshifter committed Feb 28, 2015
2 parents 1e3d127 + d662bbb commit 6cc5d71
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 69 deletions.
64 changes: 51 additions & 13 deletions README.md
Expand Up @@ -20,34 +20,69 @@ For use in the browser, use [browserify](https://github.com/substack/node-browse
var nanmedian = require( 'compute-nanmedian' );
```

#### nanmedian( arr[, sorted] )
#### nanmedian( arr[, options] )

Computes the median of a numeric `array`. If the input `array` is already sorted in __ascending__ order, set the `sorted` flag to `true`.
Computes the median of an `array` ignoring non-numeric values. For unsorted primitive `arrays`,

``` javascript
var unsorted = [ 5, null, 3, 2, 4, null ],
sorted = [ null, 2, 3, 4, null, 5 ];
var unsorted = [ 5, null, 3, 2, 4, null ];

var m1 = nanmedian( unsorted );
// returns 3.5
```

The function accepts two `options`:
* `sorted`: `boolean` flag indicating if the input `array` is sorted in __ascending__ order. Default: `false`.
* `accessor`: accessor `function` for accessing values in object `arrays`.

If the input `array` is already sorted in __ascending__ order, set the `sorted` option to `true`.

var m2 = nanmedian( sorted, true );
``` javascript
var sorted = [ null, 2, 3, 4, null, 5 ];

var m2 = nanmedian( sorted, { 'sorted': true });
// returns 3.5
```

For object `arrays`, provide an accessor `function` for accessing `array` values

``` javascript
var data = [
[1,5],
[2,null],
[3,3],
[4,2],
[5,4],
[6,null]
];

function getValue( d ) {
return d[ 1 ];
}

var m3 = nanmedian( data, {
'sorted': false,
'accessor': getValue
});
// returns 3.5
```

__Note__: if provided an `array` which does not contain any numeric values, the function returns `null`.


## Examples

``` javascript
var data = new Array( 1001 );
var nanmedian = require( 'compute-nanmedian' );

var data = new Array( 1001 );
for ( var i = 0; i < data.length; i++ ) {
if( i % 2 === 0 ){
data[ i ] = Math.round( Math.random() * 100 );
} else {
data[ i ] = null;
}
if ( i % 2 === 0 ) {
data[ i ] = null;
} else {
data[ i ] = Math.round( Math.random() * 100 );
}
}

console.log( nanmedian( data ) );
```

Expand All @@ -57,9 +92,12 @@ To run the example code from the top-level application directory,
$ node ./examples/index.js
```


## Notes

If provided an unsorted input `array`, the function is `O( N log(N) )`, where `N` is the `array` length. If the `array` is already sorted in __ascending__ order, the function is `O(N)` as the function still has to perform a linear search to remove all non-numeric elements of the input array.
If provided an unsorted input `array`, the function is `O( N log(N) )`, where `N` is the `array` length. If the `array` is already sorted in __ascending__ order, the function is `O(N)` as the function needs to make a single linear pass to remove non-numeric values from the `array`.



## Tests

Expand Down
12 changes: 5 additions & 7 deletions examples/index.js
Expand Up @@ -3,13 +3,11 @@
var nanmedian = require( './../lib' );

var data = new Array( 1001 );

for ( var i = 0; i < data.length; i++ ) {
if( i % 2 === 0 ){
data[ i ] = Math.round( Math.random() * 100 );
} else {
data[ i ] = null;
}
if ( i % 2 === 0 ) {
data[ i ] = null;
} else {
data[ i ] = Math.round( Math.random() * 100 );
}
}

console.log( nanmedian( data ) );
85 changes: 55 additions & 30 deletions lib/index.js
Expand Up @@ -30,9 +30,11 @@

// MODULES //

var isArray = require( 'validate.io-array' );
var isBoolean = require( 'validate.io-boolean' );
var isNumber = require( 'validate.io-number' );
var isArray = require( 'validate.io-array' ),
isObject = require( 'validate.io-object' ),
isBoolean = require( 'validate.io-boolean' ),
isNumber = require( 'validate.io-number' );


// FUNCTIONS //

Expand All @@ -49,46 +51,69 @@ function ascending( a, b ) {
return a - b;
} // end FUNCTION ascending()


// MEDIAN //

/**
* FUNCTION: nanmedian( arr[, sorted] )
* Computes the median of a numeric array ignoring non-numeric values.
* FUNCTION: nanmedian( arr[, options] )
* Computes the median of an array ignoring non-numeric values.
*
* @param {Array} arr - numeric array
* @param {Boolean} [sorted] - boolean flag indicating if the array is sorted in ascending order
* @returns {Number} median value
* @param {Array} arr - input array
* @param {Object} [options] - function options
* @param {Boolean} [options.sorted] - boolean flag indicating if the array is sorted in ascending order
* @param {Function} [options.accessor] - accessor function for accessing array values
* @returns {Number|null} median value or null
*/
function nanmedian( arr, sorted ) {
if ( !isArray( arr ) ) {
throw new TypeError( 'nanmedian()::invalid input argument. Must provide an array.' );
}
if ( arguments.length > 1 && !isBoolean(sorted) ) {
throw new TypeError( 'nanmedian()::invalid input argument. Second argument must be a boolean.' );
function nanmedian( arr, options ) {
var sorted,
clbk,
len,
id,
d,
x;
if ( !isArray( arr ) ) {
throw new TypeError( 'nanmedian()::invalid input argument. Must provide an array. Value: `' + arr + '`.' );
}
if ( arguments.length > 1 ) {
if ( !isObject( options ) ) {
throw new TypeError( 'nanmedian()::invalid input argument. Options must be an object. Value: `' + options + '`.' );
}
if ( options.hasOwnProperty( 'sorted' ) ) {
sorted = options.sorted;
if ( !isBoolean( sorted ) ) {
throw new TypeError( 'nanmedian()::invalid option. Sorted flag must be a boolean. Option: `' + sorted + '`.' );
}
}
if ( options.hasOwnProperty( 'accessor' ) ) {
clbk = options.accessor;
if ( typeof clbk !== 'function' ) {
throw new TypeError( 'nanmedian()::invalid option. Accessor must be a function. Option: `' + clbk + '`.' );
}
}
}
d = [];
for ( var i = 0; i < arr.length; i++ ) {
x = ( clbk ) ? clbk( arr[ i ] ) : arr[ i ];
if ( isNumber( x ) ) {
d.push( x );
}
}
len = d.length;
if ( !len ) {
return null;
}

var red = [], len, id;
for(var i = 0; i < arr.length; i++){
if( isNumber(arr[i]) === true ){
red.push( arr[i] );
}
}

len = red.length;
if ( !sorted ) {
red.sort( ascending );
d.sort( ascending );
}

// Get the middle index:
// Get the middle index:
id = Math.floor( len / 2 );

if ( len % 2 ) {
// The number of elements is not evenly divisible by two, hence we have a middle index:
return red[ id ];
return d[ id ];
}
// Even number of elements, so must take the mean of the two middle values:
return ( red[ id-1 ] + red[ id ] ) / 2.0;

// Even number of elements, so must take the mean of the two middle values:
return ( d[ id-1 ] + d[ id ] ) / 2.0;
} // end FUNCTION nanmedian()


Expand Down
22 changes: 18 additions & 4 deletions package.json
Expand Up @@ -10,6 +10,10 @@
{
"name": "Philipp Burckhardt",
"email": "pburckhardt@outlook.com"
},
{
"name": "Athan Reines",
"email": "kgryte@gmail.com"
}
],
"scripts": {
Expand All @@ -25,19 +29,29 @@
"keywords": [
"compute.io",
"compute",
"computation"
"computation",
"statistics",
"stats",
"mathematics",
"math",
"central tendency",
"median",
"array",
"avg",
"average"
],
"bugs": {
"url": "https://github.com/compute-io/nanmedian/issues"
},
"dependencies": {
"validate.io-array": "^1.0.3",
"validate.io-boolean": "^1.0.4",
"validate.io-number": "^1.0.3"
"validate.io-number": "^1.0.3",
"validate.io-object": "^1.0.3"
},
"devDependencies": {
"chai": "1.x.x",
"mocha": "1.x.x",
"chai": "2.x.x",
"mocha": "2.x.x",
"coveralls": "^2.11.1",
"istanbul": "^0.3.0",
"jshint": "2.x.x",
Expand Down

0 comments on commit 6cc5d71

Please sign in to comment.