Skip to content

Commit

Permalink
Version 2.0
Browse files Browse the repository at this point in the history
This version contains some breaking changes:

  * Multi-modal datasets are returned for `mode`, as ES6 Sets
  * With the use of ES6 Sets and const/let, this requires Node v4.0.0+

New Features:

  * Support for multi-modal datasets (Fixes #1)
  * Cleanup and ES6-ification
  * Dependency update (tape)
  • Loading branch information
brycebaril committed Sep 9, 2015
1 parent fd9ae07 commit 49721c1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 50 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
examples
test
.npmignore
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ standard deviation: 2.430499811424252

```

**Compatibility Notice**: Version 2.0.0+ of this library use features that require Node.js v4.0.0 and above

API
===

Expand All @@ -65,17 +67,19 @@ numbers(["cat", 1, "22.9", 9])
`mean(vals)`
---

Calculate the [mean](http://en.wikipedia.org/wiki/Mean) average value of vals.
Calculate the [mean](http://en.wikipedia.org/wiki/Mean) average value of `vals`.

`median(vals)`
---

Calculate the [median](http://en.wikipedia.org/wiki/Median) average value of vals.
Calculate the [median](http://en.wikipedia.org/wiki/Median) average value of `vals`.

`mode(vals)`
---

Calculate the [mode](http://en.wikipedia.org/wiki/Mode_statistics) average value of vals.
Calculate the [mode](http://en.wikipedia.org/wiki/Mode_statistics) average value of `vals`.

If `vals` is multi-modal (contains multiple modes), `mode(vals)` will return a ES6 Set of the modes.

`variance(vals)`
---
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "A light statistical package that operates on numeric Arrays.",
"main": "stats.js",
"directories": {
"example": "examples",
"test": "test"
},
"scripts": {
Expand Down Expand Up @@ -32,10 +33,14 @@
"bugs": {
"url": "https://github.com/brycebaril/node-stats-lite/issues"
},
"engines": {
"node": ">=2.0.0"
},
"dependencies": {
"isnumber": "~1.0.0"
},
"devDependencies": {
"tape": "~2.10.2"
}
"tape": "~4.2.0"
},
"homepage": "https://github.com/brycebaril/node-stats-lite"
}
68 changes: 44 additions & 24 deletions stats.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use strict";

module.exports.numbers = numbers
module.exports.sum = sum
module.exports.mean = mean
Expand All @@ -7,28 +9,28 @@ module.exports.variance = variance
module.exports.stdev = stdev
module.exports.percentile = percentile

var isNumber = require("isnumber")
const isNumber = require("isnumber")

function numbers(vals) {
var nums = []
let nums = []
if (vals == null)
return nums

for (var i = 0; i < vals.length; i++) {
for (let i = 0; i < vals.length; i++) {
if (isNumber(vals[i]))
nums.push(+vals[i])
}
return nums
}

function nsort(vals) {
return vals.sort(function (a, b) { return a - b })
return vals.sort(function numericSort(a, b) { return a - b })
}

function sum(vals) {
vals = numbers(vals)
var total = 0
for (var i = 0; i < vals.length; i++) {
let total = 0
for (let i = 0; i < vals.length; i++) {
total += vals[i]
}
return total
Expand All @@ -44,7 +46,7 @@ function median(vals) {
vals = numbers(vals)
if (vals.length === 0) return NaN

var half = (vals.length / 2) | 0
let half = (vals.length / 2) | 0

vals = nsort(vals)
if (vals.length % 2) {
Expand All @@ -57,32 +59,50 @@ function median(vals) {
}
}

// Returns the mode of a unimodal dataset -- NaN for multi-modal or empty datasets.
// Returns the mode of a unimodal dataset
// If the dataset is multi-modal, returns a Set containing the modes
function mode(vals) {
vals = numbers(vals)
if (vals.length === 0) return NaN
var mode = NaN
var dist = {}
vals.forEach(function (n) {
var me = dist[n] || 0
let mode = NaN
let dist = {}

for (let i = 0; i < vals.length; i++) {
let value = vals[i]
let me = dist[value] || 0
me++
dist[n] = me
})
var rank = numbers(Object.keys(dist).sort(function (a, b) { return dist[b] - dist[a] }))
dist[value] = me
}

let rank = numbers(Object.keys(dist).sort(function sortMembers(a, b) { return dist[b] - dist[a] }))
mode = rank[0]
if (dist[rank[1]] == dist[mode]) {
// Multiple modes found, abort
return NaN
// multi-modal
if (rank.length == vals.length) {
// all values are modes
return vals
}
let modes = new Set([mode])
let modeCount = dist[mode]
for (let i = 1; i < rank.length; i++) {
if (dist[rank[i]] == modeCount) {
modes.add(rank[i])
}
else {
break
}
}
return modes
}
return mode
}

// Variance = average squared deviation from mean
function variance(vals) {
vals = numbers(vals)
var avg = mean(vals)
var diffs = []
for (var i = 0; i < vals.length; i++) {
let avg = mean(vals)
let diffs = []
for (let i = 0; i < vals.length; i++) {
diffs.push(Math.pow((vals[i] - avg), 2))
}
return mean(diffs)
Expand All @@ -100,10 +120,10 @@ function percentile(vals, ptile) {
// Fudge anything over 100 to 1.0
if (ptile > 1) ptile = 1
vals = nsort(vals)
var i = (vals.length * ptile) - 0.5
let i = (vals.length * ptile) - 0.5
if ((i | 0) === i) return vals[i]
// interpolated percentile -- using Estimation method
var int_part = i | 0
var fract = i - int_part
let int_part = i | 0
let fract = i - int_part
return (1 - fract) * vals[int_part] + fract * vals[int_part + 1]
}
}
46 changes: 25 additions & 21 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
var test = require("tape").test
"use strict";

var stats = require("../stats")
const test = require("tape").test

const stats = require("../stats")

test("numbers", function (t) {
var numbers = stats.numbers
let numbers = stats.numbers
t.equals(typeof numbers, "function", "numbers is a function")

t.deepEquals(numbers(), [], "undefined returns empty array")
Expand All @@ -18,7 +20,7 @@ test("numbers", function (t) {
})

test("sum", function (t) {
var sum = stats.sum
let sum = stats.sum

t.equals(typeof sum, "function", "sum is a function")

Expand All @@ -34,13 +36,13 @@ test("sum", function (t) {
})

test("mean", function (t) {
var mean = stats.mean
let mean = stats.mean

t.equals(typeof mean, "function", "mean is a function")

t.ok(isNaN(mean()), "mean of nothing is NaN")

t.ok(isNaN(mean([])), NaN, "mean of nothing is NaN")
t.ok(isNaN(mean([])), "mean of nothing is NaN")

t.deepEquals(mean([1, 2, 3]), 2, "mean works")

Expand All @@ -50,13 +52,13 @@ test("mean", function (t) {
})

test("median", function (t) {
var median = stats.median
let median = stats.median

t.equals(typeof median, "function", "median is a function")

t.ok(isNaN(median()), "median of nothing is NaN")

t.ok(isNaN(median([])), NaN, "median of nothing is NaN")
t.ok(isNaN(median([])), "median of nothing is NaN")

t.deepEquals(median([1, 2, 2, 2, 3, 14]), 2, "median works")

Expand All @@ -68,31 +70,33 @@ test("median", function (t) {
})

test("mode", function (t) {
var mode = stats.mode
let mode = stats.mode

t.equals(typeof mode, "function", "mode is a function")

t.ok(isNaN(mode()), "mode of nothing is NaN")

t.ok(isNaN(mode([])), NaN, "mode of nothing is NaN")
t.ok(isNaN(mode([])), "mode of nothing is NaN")

t.deepEquals(mode([1, 2, 2, 2, 3, 14]), 2, "mode works")

t.ok(isNaN(mode([1, 2, 7, 8, 5])), "gives up for multiple modes")
t.deepEquals(mode([1, 1, 7, 5, 5, 8, 7]), new Set([1, 5, 7]), "multi-modal works")

t.deepEquals(mode([1, 1, 7, 5, 5, 8, 7]), new Set([1, 7, 5]), "multi-modal works and order doesn't matter (yay Set)")

t.deepEquals(mode([1, "6", 2, 8, 7, 2]), 2, "mode works (even) number")
t.deepEquals(mode([1, "6", 2, 8, 7, 2]), 2, "mode works with stringification")

t.end()
})

test("variance", function (t) {
var variance = stats.variance
let variance = stats.variance

t.equals(typeof variance, "function", "variance is a function")

t.ok(isNaN(variance()), "variance of nothing is NaN")

t.ok(isNaN(variance([])), NaN, "variance of nothing is NaN")
t.ok(isNaN(variance([])), "variance of nothing is NaN")

t.deepEquals(variance([2, 4, 4, 4, 5, 5, 7, 9]), 4, "variance works")

Expand All @@ -102,13 +106,13 @@ test("variance", function (t) {
})

test("stdev", function (t) {
var stdev = stats.stdev
let stdev = stats.stdev

t.equals(typeof stdev, "function", "stdev is a function")

t.ok(isNaN(stdev()), "stdev of nothing is NaN")

t.ok(isNaN(stdev([])), NaN, "stdev of nothing is NaN")
t.ok(isNaN(stdev([])), "stdev of nothing is NaN")

t.deepEquals(stdev([2, 4, 4, 4, 5, 5, 7, 9]), 2, "stdev works")

Expand All @@ -118,17 +122,17 @@ test("stdev", function (t) {
})

test("percentile", function (t) {
var percentile = stats.percentile
let percentile = stats.percentile

t.equals(typeof percentile, "function", "percentile is a function")

t.ok(isNaN(percentile()), "percentile of nothing is NaN")

t.ok(isNaN(percentile([])), NaN, "percentile of nothing is NaN")
t.ok(isNaN(percentile([])), "percentile of nothing is NaN")

var scores = [4,4,5,5,5,5,6,6,6,7,7,7,8,8,9,9,9,10,10,10]
let scores = [4,4,5,5,5,5,6,6,6,7,7,7,8,8,9,9,9,10,10,10]

t.ok(isNaN(percentile(scores)), NaN, "percentile requires a target percentile")
t.ok(isNaN(percentile(scores)), "percentile requires a target percentile")


t.deepEquals(percentile(scores, 0.50), stats.median(scores), "50th percentile equals median")
Expand All @@ -142,4 +146,4 @@ test("percentile", function (t) {
t.deepEquals(percentile([15, 20, 35, 40, 50], 0.4), 27.5, "perentile works")

t.end()
})
})

0 comments on commit 49721c1

Please sign in to comment.