diff --git a/README.md b/README.md index 2b8d189..948209a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,23 @@ Calculate the [standard deviation](http://en.wikipedia.org/wiki/Standard_deviati Calculate the value representing the desired [percentile](http://en.wikipedia.org/wiki/Percentile) `(0 < ptile <= 1)`. Uses the Estimation method to interpolate non-member percentiles. +`quartiles(vals)` +--- + +Calculate the [quartiles](http://en.wikipedia.org/wiki/Quartile) as an array of three values. + +`interquartile(vals)` +--- + +Calculate the [interquartile](https://en.wikipedia.org/wiki/Interquartile_range) value - `Q3 - Q1`. + +`removeOutliers(vals)` +--- + +Remove the [outliers](https://en.wikipedia.org/wiki/Interquartile_range#Outliers), computed as values outside the interval `Q1 - 1,5 * IQR, Q3 + 1,5 IQR`. + + + `histogram(vals[, bins])` --- diff --git a/stats.js b/stats.js index 2165afe..844f622 100644 --- a/stats.js +++ b/stats.js @@ -1,185 +1,228 @@ -"use strict"; - -module.exports.numbers = numbers -module.exports.sum = sum -module.exports.mean = mean -module.exports.median = median -module.exports.mode = mode -module.exports.variance = variance -module.exports.stdev = stdev -module.exports.percentile = percentile -module.exports.histogram = histogram - -var isNumber = require("isnumber") +'use strict'; + +module.exports.numbers = numbers; +module.exports.sum = sum; +module.exports.mean = mean; +module.exports.median = median; +module.exports.mode = mode; +module.exports.variance = variance; +module.exports.stdev = stdev; +module.exports.percentile = percentile; +module.exports.quartiles = quartiles; +module.exports.interquartile = interquartile; +module.exports.outliersBounds = outliersBounds; +module.exports.removeOutliers = removeOutliers; +module.exports.outliers = outliers; +module.exports.histogram = histogram; + +var isNumber = require('isnumber'); function numbers(vals) { - var nums = [] - if (vals == null) - return nums + var nums = []; + if (vals == null) return nums; for (var i = 0; i < vals.length; i++) { - if (isNumber(vals[i])) - nums.push(+vals[i]) + if (isNumber(vals[i])) nums.push(+vals[i]); } - return nums + return nums; } function nsort(vals) { - return vals.sort(function numericSort(a, b) { return a - b }) + return vals.sort(function numericSort(a, b) { + return a - b; + }); } function sum(vals) { - vals = numbers(vals) - var total = 0 + vals = numbers(vals); + var total = 0; for (var i = 0; i < vals.length; i++) { - total += vals[i] + total += vals[i]; } - return total + return total; } function mean(vals) { - vals = numbers(vals) - if (vals.length === 0) return NaN - return (sum(vals) / vals.length) + vals = numbers(vals); + if (vals.length === 0) return NaN; + return sum(vals) / vals.length; } function median(vals) { - vals = numbers(vals) - if (vals.length === 0) return NaN + vals = numbers(vals); + if (vals.length === 0) return NaN; - var half = (vals.length / 2) | 0 + var half = (vals.length / 2) | 0; - vals = nsort(vals) + vals = nsort(vals); if (vals.length % 2) { // Odd length, true middle element - return vals[half] - } - else { + return vals[half]; + } else { // Even length, average middle two elements - return (vals[half-1] + vals[half]) / 2.0 + return (vals[half - 1] + vals[half]) / 2.0; } } // 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 = numbers(vals); + if (vals.length === 0) return NaN; + var mode = NaN; + var dist = {}; for (var i = 0; i < vals.length; i++) { - var value = vals[i] - var me = dist[value] || 0 - me++ - dist[value] = me + var value = vals[i]; + var me = dist[value] || 0; + me++; + dist[value] = me; } - var rank = numbers(Object.keys(dist).sort(function sortMembers(a, b) { return dist[b] - dist[a] })) - mode = rank[0] + var rank = numbers( + Object.keys(dist).sort(function sortMembers(a, b) { + return dist[b] - dist[a]; + }) + ); + mode = rank[0]; if (dist[rank[1]] == dist[mode]) { // multi-modal if (rank.length == vals.length) { // all values are modes - return vals + return vals; } - var modes = new Set([mode]) - var modeCount = dist[mode] + var modes = new Set([mode]); + var modeCount = dist[mode]; for (var i = 1; i < rank.length; i++) { if (dist[rank[i]] == modeCount) { - modes.add(rank[i]) - } - else { - break + modes.add(rank[i]); + } else { + break; } } - return modes + return modes; } - return mode + return mode; } // Variance = average squared deviation from mean function variance(vals) { - vals = numbers(vals) - var avg = mean(vals) - var diffs = [] + vals = numbers(vals); + var avg = mean(vals); + var diffs = []; for (var i = 0; i < vals.length; i++) { - diffs.push(Math.pow((vals[i] - avg), 2)) + diffs.push(Math.pow(vals[i] - avg, 2)); } - return mean(diffs) + return mean(diffs); } // Standard Deviation = sqrt of variance function stdev(vals) { - return Math.sqrt(variance(vals)) + return Math.sqrt(variance(vals)); +} + +function interquartile(vals) { + var qs = quartiles(vals); + return qs[2] - qs[0]; +} + +function outliersBounds(vals) { + var out = []; + var iqr = interquartile(vals); + var qs = quartiles(vals); + var min = qs[0] - 1.5 * iqr; + var max = qs[2] + 1.5 * iqr; + + return [min, max]; +} + +function removeOutliers(vals) { + var minmax = outliersBounds(vals); + return vals.filter(v => v >= minmax[0] && v <= minmax[1]); +} + +function outliers(vals) { + var minmax = outliersBounds(vals); + return vals.filter(v => v < minmax[0] || v > minmax[1]); +} + +function quartiles(vals) { + return [ + percentile(vals, 0.25), + percentile(vals, 0.5), + percentile(vals, 0.75) + ]; } function percentile(vals, ptile) { - vals = numbers(vals) - if (vals.length === 0 || ptile == null || ptile < 0) return NaN + vals = numbers(vals); + if (vals.length === 0 || ptile == null || ptile < 0) return NaN; // Fudge anything over 100 to 1.0 - if (ptile > 1) ptile = 1 - vals = nsort(vals) - var i = (vals.length * ptile) - 0.5 - if ((i | 0) === i) return vals[i] + if (ptile > 1) ptile = 1; + vals = nsort(vals); + var 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 - return (1 - fract) * vals[int_part] + fract * vals[Math.min(int_part + 1, vals.length - 1)] + var int_part = i | 0; + var fract = i - int_part; + return ( + (1 - fract) * vals[int_part] + + fract * vals[Math.min(int_part + 1, vals.length - 1)] + ); } -function histogram (vals, bins) { +function histogram(vals, bins) { if (vals == null) { - return null + return null; } - vals = nsort(numbers(vals)) + vals = nsort(numbers(vals)); if (vals.length === 0) { - return null + return null; } if (bins == null) { // pick bins by simple method: Math.sqrt(n) - bins = Math.sqrt(vals.length) + bins = Math.sqrt(vals.length); } - bins = Math.round(bins) + bins = Math.round(bins); if (bins < 1) { - bins = 1 + bins = 1; } - var min = vals[0] - var max = vals[vals.length - 1] + var min = vals[0]; + var max = vals[vals.length - 1]; if (min === max) { // fudge for non-variant data - min = min - 0.5 - max = max + 0.5 + min = min - 0.5; + max = max + 0.5; } - var range = (max - min) + var range = max - min; // make the bins slightly larger by expanding the range about 10% // this helps with dumb floating point stuff - var binWidth = (range + (range * 0.05)) / bins - var midpoint = (min + max) / 2 + var binWidth = (range + range * 0.05) / bins; + var midpoint = (min + max) / 2; // even bin count, midpoint makes an edge - var leftEdge = midpoint - (binWidth * Math.floor(bins / 2)) + var leftEdge = midpoint - binWidth * Math.floor(bins / 2); if (bins % 2 !== 0) { // odd bin count, center middle bin on midpoint - var leftEdge = (midpoint - (binWidth / 2)) - (binWidth * Math.floor(bins / 2)) + var leftEdge = midpoint - binWidth / 2 - binWidth * Math.floor(bins / 2); } var hist = { values: Array(bins).fill(0), bins: bins, binWidth: binWidth, - binLimits: [leftEdge, leftEdge + (binWidth * bins)] - } + binLimits: [leftEdge, leftEdge + binWidth * bins] + }; - var binIndex = 0 + var binIndex = 0; for (var i = 0; i < vals.length; i++) { - while (vals[i] > (((binIndex + 1) * binWidth) + leftEdge)) { - binIndex++ + while (vals[i] > (binIndex + 1) * binWidth + leftEdge) { + binIndex++; } - hist.values[binIndex]++ + hist.values[binIndex]++; } - return hist + return hist; } diff --git a/test/index.js b/test/index.js index d6229a8..6fcb666 100644 --- a/test/index.js +++ b/test/index.js @@ -1,213 +1,363 @@ -"use strict"; +('use strict'); -var test = require("tape").test +var test = require('tape').test; -var stats = require("../stats") -var dice = require('dice') +var stats = require('../stats'); +var dice = require('dice'); -test("numbers", function (t) { - var numbers = stats.numbers - t.equals(typeof numbers, "function", "numbers is a function") +test('numbers', function(t) { + var numbers = stats.numbers; + t.equals(typeof numbers, 'function', 'numbers is a function'); - t.deepEquals(numbers(), [], "undefined returns empty array") + t.deepEquals(numbers(), [], 'undefined returns empty array'); - t.deepEquals(numbers([]), [], "empty array") + t.deepEquals(numbers([]), [], 'empty array'); - t.deepEquals(numbers([1, 2, 3]), [1, 2, 3], "only numbers") + t.deepEquals(numbers([1, 2, 3]), [1, 2, 3], 'only numbers'); - t.deepEquals(numbers(["cat", 1, 23.9, "33.2"]), [1, 23.9, 33.2], "removes non-nums, converts str numbers to numers") + t.deepEquals( + numbers(['cat', 1, 23.9, '33.2']), + [1, 23.9, 33.2], + 'removes non-nums, converts str numbers to numers' + ); - t.end() -}) + t.end(); +}); -test("sum", function (t) { - var sum = stats.sum +test('sum', function(t) { + var sum = stats.sum; - t.equals(typeof sum, "function", "sum is a function") + t.equals(typeof sum, 'function', 'sum is a function'); - t.deepEquals(sum(), 0, "sum of nothing is 0") + t.deepEquals(sum(), 0, 'sum of nothing is 0'); - t.deepEquals(sum([]), 0, "sum of nothing is 0") + t.deepEquals(sum([]), 0, 'sum of nothing is 0'); - t.deepEquals(sum([1, 2, 3]), 6, "sum works") + t.deepEquals(sum([1, 2, 3]), 6, 'sum works'); - t.deepEquals(sum([1, 23.9, "33.2"]), 58.1, "works with type conversion") + t.deepEquals(sum([1, 23.9, '33.2']), 58.1, 'works with type conversion'); - t.end() -}) + t.end(); +}); -test("mean", function (t) { - var mean = stats.mean +test('mean', function(t) { + var mean = stats.mean; - t.equals(typeof mean, "function", "mean is a function") + t.equals(typeof mean, 'function', 'mean is a function'); - t.ok(isNaN(mean()), "mean of nothing is NaN") + t.ok(isNaN(mean()), 'mean of nothing is NaN'); - t.ok(isNaN(mean([])), "mean of nothing is NaN") + t.ok(isNaN(mean([])), 'mean of nothing is NaN'); - t.deepEquals(mean([1, 2, 3]), 2, "mean works") + t.deepEquals(mean([1, 2, 3]), 2, 'mean works'); - t.deepEquals(mean([1, 23.9, "33.3"]), 19.4, "works with type conversion") + t.deepEquals(mean([1, 23.9, '33.3']), 19.4, 'works with type conversion'); - t.end() -}) + t.end(); +}); -test("median", function (t) { - var median = stats.median +test('median', function(t) { + var median = stats.median; - t.equals(typeof median, "function", "median is a function") + t.equals(typeof median, 'function', 'median is a function'); - t.ok(isNaN(median()), "median of nothing is NaN") + t.ok(isNaN(median()), 'median of nothing is NaN'); - t.ok(isNaN(median([])), "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") + t.deepEquals(median([1, 2, 2, 2, 3, 14]), 2, 'median works'); - t.deepEquals(median([1, 2, 7, 8, 5]), 5, "median works") + t.deepEquals(median([1, 2, 7, 8, 5]), 5, 'median works'); - t.deepEquals(median([1, "6", 2, 8, 7, 2]), 4, "median works (even) number") + t.deepEquals(median([1, '6', 2, 8, 7, 2]), 4, 'median works (even) number'); - t.end() -}) + t.end(); +}); -test("mode", function (t) { - var mode = stats.mode +test('mode', function(t) { + var mode = stats.mode; - t.equals(typeof mode, "function", "mode is a function") + t.equals(typeof mode, 'function', 'mode is a function'); - t.ok(isNaN(mode()), "mode of nothing is NaN") + t.ok(isNaN(mode()), 'mode of nothing is NaN'); - t.ok(isNaN(mode([])), "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.deepEquals(mode([1, 2, 2, 2, 3, 14]), 2, 'mode works'); - 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, 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, 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 with stringification") + t.deepEquals( + mode([1, '6', 2, 8, 7, 2]), + 2, + 'mode works with stringification' + ); - t.end() -}) + t.end(); +}); -test("variance", function (t) { - var variance = stats.variance +test('variance', function(t) { + var variance = stats.variance; - t.equals(typeof variance, "function", "variance is a function") + t.equals(typeof variance, 'function', 'variance is a function'); - t.ok(isNaN(variance()), "variance of nothing is NaN") + t.ok(isNaN(variance()), 'variance of nothing is NaN'); - t.ok(isNaN(variance([])), "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") + t.deepEquals(variance([2, 4, 4, 4, 5, 5, 7, 9]), 4, 'variance works'); - t.deepEquals(variance([600, "470", 170, 430, 300]), 21704, "variance works") + t.deepEquals(variance([600, '470', 170, 430, 300]), 21704, 'variance works'); - t.end() -}) + t.end(); +}); -test("stdev", function (t) { - var stdev = stats.stdev +test('stdev', function(t) { + var stdev = stats.stdev; - t.equals(typeof stdev, "function", "stdev is a function") + t.equals(typeof stdev, 'function', 'stdev is a function'); - t.ok(isNaN(stdev()), "stdev of nothing is NaN") + t.ok(isNaN(stdev()), 'stdev of nothing is NaN'); - t.ok(isNaN(stdev([])), "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") + t.deepEquals(stdev([2, 4, 4, 4, 5, 5, 7, 9]), 2, 'stdev works'); - t.deepEquals(stdev([600, "470", 170, 430, 300]), 147.32277488562318, "stdev works") + t.deepEquals( + stdev([600, '470', 170, 430, 300]), + 147.32277488562318, + 'stdev works' + ); - t.end() -}) + t.end(); +}); -test("percentile", function (t) { - var percentile = stats.percentile +test('percentile', function(t) { + var percentile = stats.percentile; - t.equals(typeof percentile, "function", "percentile is a function") + t.equals(typeof percentile, 'function', 'percentile is a function'); - t.ok(isNaN(percentile()), "percentile of nothing is NaN") + t.ok(isNaN(percentile()), 'percentile of nothing is NaN'); - t.ok(isNaN(percentile([])), "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] + var 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)), "percentile requires a target percentile") + t.ok(isNaN(percentile(scores)), 'percentile requires a target percentile'); + t.deepEquals( + percentile(scores, 0.5), + stats.median(scores), + '50th percentile equals median' + ); - t.deepEquals(percentile(scores, 0.50), stats.median(scores), "50th percentile equals median") + t.deepEquals(percentile(scores, 0.25), 5, 'percentile works'); - t.deepEquals(percentile(scores, 0.25), 5, "percentile works") + t.deepEquals(percentile(scores, 0.85), 9.5, 'percentile works'); - t.deepEquals(percentile(scores, 0.85), 9.5, "percentile works") + t.deepEquals( + percentile([3, 5, 7, 8, 9, 11, 13, 15], 0.25), + 6, + 'percentile works' + ); - t.deepEquals(percentile([3, 5, 7, 8, 9, 11, 13, 15], 0.25), 6, "percentile works") + t.deepEquals(percentile([15, 20, 35, 40, 50], 0.4), 27.5, 'perentile works'); - t.deepEquals(percentile([15, 20, 35, 40, 50], 0.4), 27.5, "perentile works") + t.deepEquals(percentile([100, 200], 0.9), 200, 'percentiles above data work'); - t.deepEquals(percentile([100, 200], 0.9), 200, "percentiles above data work") + t.end(); +}); - t.end() -}) +test('quartiles', t => { + var quartiles = stats.quartiles; + var percentile = stats.percentile; -test('histogram', (t) => { - var histogram = stats.histogram + t.equals(typeof quartiles, 'function', 'percentile is a function'); + + var scores = [4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10]; + + t.deepEquals( + quartiles(scores)[1], + stats.median(scores), + 'Second quartile equals median' + ); + + t.deepEquals( + quartiles(scores)[0], + percentile(scores, 0.25), + 'first quartile works' + ); + + t.deepEquals(quartiles(scores)[0], 5, 'first quartile works'); + t.deepEquals(quartiles(scores)[2], 9, 'last quartile works'); - t.equals(typeof histogram, 'function', 'histogram is a function') + t.deepEquals( + quartiles(scores)[2], + percentile(scores, 0.75), + 'third quartiles works' + ); - t.notOk(histogram(), 'histogram of nothing is null') + t.end(); +}); - t.notOk(histogram([]), 'histogram of nothing is null') +test('interquartile', t => { + var scores = [4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10]; + var quartiles = stats.quartiles; + var interquartile = stats.interquartile; + + t.deepEquals(interquartile(scores), 4, 'Interquartile works'); + + t.end(); +}); + +test('outliersBounds', t => { + var scores = [4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10]; + var outliersBounds = stats.outliersBounds; + + t.deepEquals(outliersBounds(scores), [-1, 15], 'Boundaries ok'); + + t.end(); +}); + +test('withoutOutliers', t => { + var scores = [4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10]; + var scoresWith = [ + -2, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 7, + 7, + 7, + 8, + 8, + 9, + 9, + 9, + 10, + 25, + 26 + ]; + var removeOutliers = stats.removeOutliers; + + t.deepEquals(removeOutliers(scores), scores, 'No outliers to remove ok'); + t.deepEquals( + removeOutliers(scoresWith), + [4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 10], + 'Some outliers removed' + ); + + t.end(); +}); + +test('outliers', t => { + var scores = [4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10]; + var scoresWith = [ + -2, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 7, + 7, + 7, + 8, + 8, + 9, + 9, + 9, + 10, + 25, + 26 + ]; + var outliers = stats.outliers; + + t.deepEquals(outliers(scores), [], 'No outliers ok'); + t.deepEquals(outliers(scoresWith), [-2, 25, 26], 'Some outliers'); + + t.end(); +}); + +test('histogram', t => { + var histogram = stats.histogram; + + t.equals(typeof histogram, 'function', 'histogram is a function'); + + t.notOk(histogram(), 'histogram of nothing is null'); + + t.notOk(histogram([]), 'histogram of nothing is null'); // With preset bin count var expect = { - values: [ 1, 0, 0, 0, 1 ], + values: [1, 0, 0, 0, 1], bins: 5, binWidth: 2.94, - binLimits: [ 1.6500000000000004, 16.35 ] - } - t.deepEquals(histogram([2, 16], 5), expect) + binLimits: [1.6500000000000004, 16.35] + }; + t.deepEquals(histogram([2, 16], 5), expect); expect = { - values: [ 1, 0, 0, 0, 0, 1 ], + values: [1, 0, 0, 0, 0, 1], bins: 6, binWidth: 2.4499999999999997, - binLimits: [ 1.6500000000000004, 16.35 ] - } - t.deepEquals(histogram([2, 16], 6), expect) + binLimits: [1.6500000000000004, 16.35] + }; + t.deepEquals(histogram([2, 16], 6), expect); expect = { - values: [ 1 ], + values: [1], bins: 1, binWidth: 1.05, - binLimits: [ 99.475, 100.52499999999999 ] - } - t.deepEquals(histogram([100], 1), expect, "Single entry") + binLimits: [99.475, 100.52499999999999] + }; + t.deepEquals(histogram([100], 1), expect, 'Single entry'); expect = { - binLimits: [ -105, 105 ], + binLimits: [-105, 105], binWidth: 10.5, bins: 20, - values: [ 1, 0, 0, 0, 1, 0, 0, 1, 0, 2, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] - } - t.deepEquals(histogram([-100, -53, 10, 100, -22, 0, 0, 1, 3, 44], 20), expect, 'range goes from negative to positive') - - function diceTest (n, dieString) { - var rolls = [] + values: [1, 0, 0, 0, 1, 0, 0, 1, 0, 2, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1] + }; + t.deepEquals( + histogram([-100, -53, 10, 100, -22, 0, 0, 1, 3, 44], 20), + expect, + 'range goes from negative to positive' + ); + + function diceTest(n, dieString) { + var rolls = []; for (var i = 0; i < n; i++) { - rolls.push(dice.sum(dice.roll(dieString))) + rolls.push(dice.sum(dice.roll(dieString))); } - var hist = histogram(rolls) - t.equals(stats.sum(hist.values), n, 'right number of values') + var hist = histogram(rolls); + t.equals(stats.sum(hist.values), n, 'right number of values'); // t.ok(hist.values[0] > 0, 'first bin is full') // t.ok(hist.values[hist.values.length - 1] > 0, 'last bin is full') - t.ok(hist.bins > 1, 'more than one bin') + t.ok(hist.bins > 1, 'more than one bin'); } - diceTest(1000, '2d10') - diceTest(100, '3d100') - diceTest(2000, '30d5') - diceTest(2000, '1d1000') - diceTest(2000, '2d-5') - t.end() -}) + diceTest(1000, '2d10'); + diceTest(100, '3d100'); + diceTest(2000, '30d5'); + diceTest(2000, '1d1000'); + diceTest(2000, '2d-5'); + t.end(); +});