├── .gitignore ├── build ├── index.html ├── scripts │ ├── bayes.js │ └── bayes.min.js └── style.css ├── gruntfile.js ├── node_modules └── beta-js │ ├── beta.js │ └── package.json ├── package.json ├── readme.md └── src ├── index.html ├── scripts ├── bayes.js └── libs │ └── jstat.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bayesian A/B Test Calculator 6 | 7 | 8 | 9 | 10 |
11 |

Bayesian A/B Test Calculator

12 |

The Beta-Bernoulli model in the context of A/B testing.

13 |
14 | 15 |
16 | 17 | 30 | 31 |
32 |

TL:DR; Instructions

33 | 34 |
    35 |
  1. 1Specify the prior alpha and beta parameters.
  2. 36 |
  3. 2Plot the priors and revise parameters as necessary.
  4. 37 |
  5. 3Enter data on the number of successes and failures in the test and control groups.
  6. 38 |
  7. 4Plot to see posterior distributions.
  8. 39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 | 97 | 98 |
99 | 100 |
101 |

Test and Control Probability Density Functions

102 |

The success probability distributions in test and control groups.

103 |
104 |
105 | 106 |
107 |

Histogram of Test - Control Probability

108 |

Distribution of differences in success probability between test and control groups.

109 |
110 |
111 | 112 |
113 |

Quantiles of the differences distribution.

114 |

Posterior probability that the difference lies below the value x.

115 |
116 |

117 | The average difference between test and control is: 0. The probability that test performs better: 0.5 118 |

119 |
120 | 121 |
122 | 123 |
124 | 125 | 126 |
127 |
128 | 129 |
130 |
131 | 132 |

Explanation

133 | 134 |
135 |
136 |

This simple calculator uses the Beta-Bernoulli model (a binary outcome model, where the prior for the success probability is a Beta distribution) applied in the A/B testing context, where the goal of inference is understanding the probability that the test group performs better than the control group.

137 |

Bayesian inference consists in first specifying a prior belief about what effects are likely, and then updating the prior with incoming data.

138 |

For example, if our conversion rate is 5%, we may say that it's reasonably likely that a change we want to test could improve that by 5 percentage points—but that it is most likely that the change will have no effect, and that it is entirely unlikely that the conversion rate will shoot up to 30% (after all, we are only making a small change).

139 |

As the data start coming in, we start updating our beliefs. If the incoming data points point to an improvement in the conversion rate, we start moving our estimate of the effect from the prior upwards; the more data we collect, the more confident we are in it and the further we can move away from our prior. The end result is what is called the posterior—a probability distribution describing the likely effect from our treatment.

140 |
141 |
142 |
    143 |
  1. 1 Specify the prior through the alpha and beta parameters of the Beta distribution. The parameter values govern two things: the prior success probability (our belief about the average conversion rate, for example) as well as the variance of the prior distribution (small alpha and beta will lead to a prior distribution where success probabilities can vary quite a lot around their mean; large values will lead to a distribution with a small variance). For example, setting alpha to 10 and beta to 10 will give us a prior distribution where the expected success probability is 0.5, but there is a fair amount of uncertainty around that value. Setting them to 100 and 100 will give us the same expected probability of 0.5, but the variance around that value will be much smaller. 144 |
  2. 2 Have a look at the histogram of success probability differences between the test and control. It expresses prior beliefs about the likely difference of success probabilities between the test and control groups. Because we specified a symmetric prior, the belief is centered around a difference of zero (a priori, A/B tests are just as likely to do worse as they are to do better than the control). If our priors have a low variance, the histogram will put put a low weight on large differences (it is unlikely that a test will do much better or much worse than the control); if the priors have a high variance, large differences will be much more likely. 145 |
  3. 3 Gather data! 146 |
  4. 4 Input the number of successes (conversions, clicks and so on) and failures in both the test and control groups. This triggers updating the priors with the data. 147 |
  5. 5 The prior plots shift to express posterior (prior updated with data) distributions. The density plots will (may) diverge, showing the posterior distributions of the success probability in test and control groups. Similarly, the difference histogram will shift. The part of the distribution lying to the right of zero expresses the confidence that the test performs better; the part to the left that it performs worse. 148 |
149 |
150 |
151 |
152 |
153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /build/style.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-size:14px;font-family:'Helvetica Neue',sans-serif;background:#F1F1F1;color:#000}h1,h2,h3,h4{margin:0}h1{font-size:22px;font-weight:500;text-align:center;margin:20px 0}h3{font-size:15px;font-weight:500;margin-bottom:10px}h2{font-size:18px;font-weight:500}h4{font-size:12px;font-weight:500}p{margin:15px 0;line-height:22px}ol{line-height:22px;list-style:none;margin:0;padding:0}ol li{margin:15px 0;padding:0 0 0 35px;position:relative}ol .number{display:block;position:absolute;left:0;top:2px;margin-right:10px;width:20px;height:20px;font-size:12px;line-height:19px;text-align:center;border-radius:15px;background:#E9E9E9;font-weight:500;color:#333}.form-group{margin:0 0 30px}.form-title{margin:20px}.form-group-title{padding:10px 20px;color:#212121;border-bottom:1px solid #E9E9E9}.form-buttons{padding:15px;text-align:center}fieldset{margin:0;padding:0 10px 0 20px;border:none;border-bottom:1px solid #E9E9E9;overflow:hidden;cursor:pointer}fieldset:hover{background:#F7F7F7}label{display:block;float:left;width:82px;line-height:40px;font-size:14px;cursor:pointer}input{font-family:'Helvetica Neue',sans-serif}input[type=number]{padding:5px 0 5px 5px;margin:0;width:95px;float:left;font-size:15px;background:0 0;box-sizing:border-box;text-align:right;border:none;line-height:30px;cursor:pointer}input[type=reset],input[type=submit]{border-radius:3px;padding:10px 15px;background:#F7F7F7;border:1px solid #E9E9E9;font-weight:500;cursor:pointer;color:#000;font-size:13px;display:inline-block}input[type=reset]:hover,input[type=submit]:hover{color:#555}input[type=reset]:active,input[type=submit]:active{position:relative;top:1px}input[type=reset]{background:#FFF;color:#444;margin-left:5px}input[type=reset]:hover{color:#E84A4A}input:focus{outline:0}.wrapper{margin:30px auto;width:940px;overflow:hidden;position:relative}.content,.sidebar,.wrapper{box-sizing:border-box}.header{padding:35px 0 25px}.logo{margin-bottom:0}.tagline{color:#999;font-size:15px;text-align:center;margin-top:8px}.note-wrapper{padding:20px}.note{font-size:14px;line-height:22px;margin-bottom:15px}.sidebar{width:210px;position:absolute;left:0;top:0;background:#FFF;height:100%;border-radius:3px 0 0 3px;border-right:3px solid #F7F7F7}.form-group-title.control:before,.form-group-title.test:before{content:"";display:inline-block;width:6px;height:6px;border-radius:100px;margin-right:5px;position:relative;top:-1px}.form-group-title.control:before{background:#007AFF}.form-group-title.test:before{background:#E93A5E}.content{width:730px;padding:20px;margin-left:210px;background:#FCFCFC;border-radius:0 3px 3px 0}.content.full-width{margin-left:0;width:940px;border-radius:3px;background:#FFF;padding:20px 20px 40px 40px}.chart{margin:10px 0 0}.chart+.chart{margin-top:80px}.chart-title{text-align:center;margin-bottom:5px}.chart-description{font-size:13px;text-align:center;margin:0 0 20px;color:#999}.chart text{font-size:12px}.tick line{stroke:#C9C9C9}.axis path{stroke:#27344B;stroke-width:1px;shape-rendering:crispEdges;fill:none}.columns{overflow:hidden}.column{box-sizing:border-box;padding-right:30px;width:50%;float:left}#pdfplot #controlLine,#pdfplot #testLine{opacity:.95}#pdfplot #testLine{fill:#E93A5E}#pdfplot #controlLine{fill:#007AFF}#histogram .bar rect{fill:#007AFF;shape-rendering:crispEdges}#histogram .bar:nth-child(2n){opacity:.95}#quantileTable{border:none;border-collapse:collapse}#quantileTable td{cellspacing:0;border-right:1px solid #000;padding:10px}.table-row-title{font-weight:500}#quantileTable td:last-child{border-right:none}#quantileTable tr:nth-child(2n){border-top:1px solid #000} -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Load tasks. 4 | grunt.loadNpmTasks('grunt-browserify'); 5 | grunt.loadNpmTasks('grunt-contrib-uglify'); 6 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 7 | grunt.loadNpmTasks('grunt-contrib-copy'); 8 | grunt.loadNpmTasks('grunt-contrib-watch'); 9 | 10 | grunt.initConfig({ 11 | 12 | browserify: { 13 | build : { 14 | files : { 15 | 'build/scripts/bayes.js' : ['src/scripts/bayes.js'], 16 | }, 17 | } 18 | }, 19 | 20 | uglify : { 21 | build : { 22 | files : { 23 | 'build/scripts/bayes.min.js' : ['build/scripts/bayes.js'] 24 | } 25 | } 26 | }, 27 | 28 | cssmin : { 29 | build: { 30 | files : { 31 | 'build/style.css' : ['src/style.css'] 32 | } 33 | } 34 | }, 35 | 36 | copy : { 37 | build: { 38 | files : { 39 | 'build/index.html' : ['src/index.html'] 40 | } 41 | } 42 | }, 43 | 44 | watch : { 45 | css : { 46 | files : ['src/style.css'], 47 | tasks : ['cssmin:build'] 48 | }, 49 | js : { 50 | files : ['src/bayes.js'], 51 | tasks : [ 52 | 'browserify:build', 53 | 'uglify:build' 54 | ] 55 | }, 56 | html : { 57 | files : ['src/index.html'], 58 | tasks : ['copy:build'] 59 | } 60 | } 61 | }); 62 | 63 | grunt.registerTask('build', [ 64 | 'browserify:build', 65 | 'uglify:build', 66 | 'cssmin:build', 67 | 'copy:build' 68 | ]); 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /node_modules/beta-js/beta.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A snippet that builds upon jStat to provide 3 | * the probability density function and the log 4 | * probability density function for the Beta distribution. 5 | */ 6 | 7 | var jStat = require('jStat'); 8 | var gammaln = jStat.jStat.gammaln; 9 | 10 | var BetaDistribution = function (alpha, beta) { 11 | 12 | this.alpha = alpha; 13 | this.beta = beta; 14 | 15 | this.betaInverse = (gammaln(this.alpha + this.beta) 16 | - gammaln(this.alpha) 17 | - gammaln(this.beta)); 18 | }; 19 | 20 | BetaDistribution.prototype.lpdf = function(x) { 21 | if (x < 0 || x > 1) { 22 | return Number.NEGATIVE_INFINITY; 23 | }; 24 | return (this.betaInverse 25 | + (this.alpha - 1) * Math.log(x) 26 | + (this.beta - 1) * Math.log(1 - x)); 27 | }; 28 | 29 | BetaDistribution.prototype.pdf = function(x) { 30 | if (x < 0 || x > 1) { 31 | return 0; 32 | }; 33 | if (this.alpha == 1 && this.beta == 1) { 34 | return 1; 35 | }; 36 | return Math.exp(this.lpdf(x)); 37 | }; 38 | 39 | BetaDistribution.prototype.rv = function () { 40 | return jStat.jStat.beta.sample(this.alpha, this.beta); 41 | }; 42 | 43 | BetaDistribution.prototype.rvs = function(n) { 44 | var rvs = []; 45 | 46 | for (var i=0; i < n; i++) { 47 | rvs.push(this.rv()); 48 | }; 49 | 50 | return rvs; 51 | }; 52 | 53 | 54 | var beta = function(alpha, beta) { 55 | return new BetaDistribution(alpha, beta); 56 | }; 57 | 58 | 59 | module.exports.lngamma = gammaln; 60 | module.exports.beta = beta; -------------------------------------------------------------------------------- /node_modules/beta-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beta-js" 3 | , "version": "0.0.1" 4 | , "description": "Beta probability density function for JS" 5 | , "main": "beta" 6 | , "engines": { "node": ">= 0.4.x < 0.7.0" } 7 | , "scripts": { 8 | "test": "make test" 9 | } 10 | , "dependencies": { 11 | "jStat": "0.0.2" 12 | } 13 | , "devDependencies": { 14 | "mocha": "0.3.x" 15 | , "browserify": "3.44.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bayesian-ab-testing-js", 3 | "version": "0.0.1", 4 | "description": "Bayesian A/B test inference", 5 | "dependencies": { 6 | "jStat": "0.0.2", 7 | "d3": "3.4.6", 8 | "beta-js": "0.0.1" 9 | }, 10 | "devDependencies": { 11 | "grunt": "^0.4.5", 12 | "grunt-contrib-uglify": "^0.4.0", 13 | "browserify": "^4.1.5", 14 | "grunt-browserify": "^2.1.0", 15 | "grunt-contrib-cssmin": "^0.9.0", 16 | "grunt-contrib-copy": "^0.5.0", 17 | "grunt-contrib-watch": "^0.6.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # BayesianTestJS 2 | Rudimentary plotting of prior and posterior distributions of a Beta-Bernoulli model, to aid inference in binary A/B tests. 3 | 4 | Made at [Lyst](http://www.lyst.com) and available [here](http://developers.lyst.com/bayesian-calculator/). 5 | 6 | # Usage 7 | Clone the repo and open `build/index.html`. 8 | 9 | If you make changes, use `grunt build` to build. You will need grunt. 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bayesian A/B Test Calculator 6 | 7 | 8 | 9 | 10 |
11 |

Bayesian A/B Test Calculator

12 |

The Beta-Bernoulli model in the context of A/B testing.

13 |
14 | 15 |
16 | 17 | 30 | 31 |
32 |

TL:DR; Instructions

33 | 34 |
    35 |
  1. 1Specify the prior alpha and beta parameters.
  2. 36 |
  3. 2Plot the priors and revise parameters as necessary.
  4. 37 |
  5. 3Enter data on the number of successes and failures in the test and control groups.
  6. 38 |
  7. 4Plot to see posterior distributions.
  8. 39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 | 97 | 98 |
99 | 100 |
101 |

Test and Control Probability Density Functions

102 |

The success probability distributions in test and control groups.

103 |
104 |
105 | 106 |
107 |

Histogram of Test - Control Probability

108 |

Distribution of differences in success probability between test and control groups.

109 |
110 |
111 | 112 |
113 |

Quantiles of the differences distribution.

114 |

Posterior probability that the difference lies below the value x.

115 |
116 |

117 | The average difference between test and control is: 0. The probability that test performs better: 0.5 118 |

119 |
120 | 121 |
122 | 123 |
124 | 125 | 126 |
127 |
128 | 129 |
130 |
131 | 132 |

Explanation

133 | 134 |
135 |
136 |

This simple calculator uses the Beta-Bernoulli model (a binary outcome model, where the prior for the success probability is a Beta distribution) applied in the A/B testing context, where the goal of inference is understanding the probability that the test group performs better than the control group.

137 |

Bayesian inference consists in first specifying a prior belief about what effects are likely, and then updating the prior with incoming data.

138 |

For example, if our conversion rate is 5%, we may say that it's reasonably likely that a change we want to test could improve that by 5 percentage points—but that it is most likely that the change will have no effect, and that it is entirely unlikely that the conversion rate will shoot up to 30% (after all, we are only making a small change).

139 |

As the data start coming in, we start updating our beliefs. If the incoming data points point to an improvement in the conversion rate, we start moving our estimate of the effect from the prior upwards; the more data we collect, the more confident we are in it and the further we can move away from our prior. The end result is what is called the posterior—a probability distribution describing the likely effect from our treatment.

140 |
141 |
142 |
    143 |
  1. 1 Specify the prior through the alpha and beta parameters of the Beta distribution. The parameter values govern two things: the prior success probability (our belief about the average conversion rate, for example) as well as the variance of the prior distribution (small alpha and beta will lead to a prior distribution where success probabilities can vary quite a lot around their mean; large values will lead to a distribution with a small variance). For example, setting alpha to 10 and beta to 10 will give us a prior distribution where the expected success probability is 0.5, but there is a fair amount of uncertainty around that value. Setting them to 100 and 100 will give us the same expected probability of 0.5, but the variance around that value will be much smaller. 144 |
  2. 2 Have a look at the histogram of success probability differences between the test and control. It expresses prior beliefs about the likely difference of success probabilities between the test and control groups. Because we specified a symmetric prior, the belief is centered around a difference of zero (a priori, A/B tests are just as likely to do worse as they are to do better than the control). If our priors have a low variance, the histogram will put put a low weight on large differences (it is unlikely that a test will do much better or much worse than the control); if the priors have a high variance, large differences will be much more likely. 145 |
  3. 3 Gather data! 146 |
  4. 4 Input the number of successes (conversions, clicks and so on) and failures in both the test and control groups. This triggers updating the priors with the data. 147 |
  5. 5 The prior plots shift to express posterior (prior updated with data) distributions. The density plots will (may) diverge, showing the posterior distributions of the success probability in test and control groups. Similarly, the difference histogram will shift. The part of the distribution lying to the right of zero expresses the confidence that the test performs better; the part to the left that it performs worse. 148 |
149 |
150 |
151 |
152 |
153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/scripts/bayes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var beta = require("beta-js"); 4 | var d3 = require("d3"); 5 | var jStat = require("./libs/jstat.js"); 6 | 7 | 8 | var BetaModel = function (alpha, beta) { 9 | 10 | this.alpha = alpha; 11 | this.beta = beta; 12 | }; 13 | 14 | BetaModel.prototype.distribution = function () { 15 | return beta.beta(this.alpha, this.beta); 16 | }; 17 | 18 | BetaModel.prototype.getPDF = function (noPoints) { 19 | var pdf = []; 20 | var distribution = this.distribution(); 21 | for (var i=0; i < noPoints; i++) { 22 | var val = distribution.pdf(i/noPoints); 23 | // Get rid of density singularities for plotting 24 | if (val == Number.POSITIVE_INFINITY) { 25 | val = 0; 26 | }; 27 | pdf.push({'x': i/noPoints, 'y': val}); 28 | }; 29 | return pdf; 30 | }; 31 | 32 | BetaModel.prototype.getRvs = function (noSamples) { 33 | return this.distribution().rvs(noSamples); 34 | }; 35 | 36 | BetaModel.prototype.update = function (successes, failures) { 37 | this.alpha = this.alpha + successes; 38 | this.beta = this.beta + failures; 39 | }; 40 | 41 | BetaModel.prototype.percentileOfScore = function(arr, score, kind) { 42 | var counter = 0; 43 | var len = arr.length; 44 | var strict = false; 45 | var value, i; 46 | 47 | if (kind === 'strict') strict = true; 48 | 49 | for (i = 0; i < len; i++) { 50 | value = arr[i]; 51 | if ((strict && value < score) || 52 | (!strict && value <= score)) { 53 | counter++; 54 | } 55 | } 56 | 57 | return counter / len; 58 | }; 59 | 60 | BetaModel.prototype.mean = function(arr) { 61 | var i = 0; 62 | var counter = 0; 63 | 64 | for (i = 0; i < arr.length; i++) { 65 | counter = counter + arr[i]; 66 | } 67 | 68 | return counter/i; 69 | }; 70 | 71 | // ----------------------------------------------- 72 | 73 | var Plots = function(alpha, beta) { 74 | this.controlBeta = new BetaModel(alpha, beta); 75 | this.testBeta = new BetaModel(alpha, beta); 76 | }; 77 | 78 | Plots.prototype.getHistogramElements = function () { 79 | 80 | var noSamples = 5000; 81 | var noBins = 200; 82 | 83 | var controlData = this.controlBeta.getRvs(noSamples); 84 | var testData = this.testBeta.getRvs(noSamples); 85 | var differenceData = []; 86 | 87 | for (var i=0; i < controlData.length; i++) { 88 | differenceData.push(testData[i] - controlData[i]); 89 | }; 90 | 91 | var margin = {top: 20, right: 20, bottom: 30, left: 50}; 92 | var width = 690 - margin.left - margin.right; 93 | var height = 350 - margin.top - margin.bottom; 94 | 95 | var x = d3.scale.linear() 96 | .domain([-1, 1]) 97 | .range([0, width]); 98 | 99 | var histogram = d3.layout.histogram() 100 | .bins(x.ticks(noBins))(differenceData); 101 | 102 | var y = d3.scale.linear() 103 | .domain([0, d3.max(histogram, function(d) { return d.y; })]) 104 | .range([height, 0]); 105 | 106 | var xAxis = d3.svg.axis() 107 | .scale(x) 108 | .orient("bottom"); 109 | 110 | var yAxis = d3.svg.axis() 111 | .scale(y) 112 | .orient("left"); 113 | 114 | return { 115 | "margin": margin, 116 | "width": width, 117 | "height": height, 118 | "xAxis": xAxis, 119 | "yAxis": yAxis, 120 | "x": x, 121 | "y": y, 122 | "differenceData": differenceData, 123 | "histogram": histogram 124 | }; 125 | }; 126 | 127 | Plots.prototype.getPDFElements = function () { 128 | 129 | var controlData = this.controlBeta.getPDF(1000); 130 | var testData = this.testBeta.getPDF(1000); 131 | var allData = controlData.concat(testData); 132 | var interpolationMode = 'cardinal'; 133 | 134 | var margin = {top: 20, right: 20, bottom: 30, left: 50}; 135 | var width = 690 - margin.left - margin.right; 136 | var height = 350 - margin.top - margin.bottom; 137 | 138 | var x = d3.scale.linear() 139 | .domain(d3.extent(allData, function(d) { return d.x; })) 140 | .range([0, width]); 141 | 142 | var y = d3.scale.linear() 143 | .domain([0, d3.max(allData, function(d) { return d.y; })+1]) 144 | .range([height, 0]); 145 | 146 | var xAxis = d3.svg.axis() 147 | .scale(x) 148 | .orient("bottom"); 149 | 150 | var yAxis = d3.svg.axis() 151 | .scale(y) 152 | .orient("left"); 153 | 154 | var controlLine = d3.svg.area() 155 | .x(function(d) { return x(d.x); }) 156 | .y1(height) 157 | .y0(function(d) { return y(d.y); }) 158 | .interpolate(interpolationMode); 159 | 160 | var testLine = d3.svg.area() 161 | .x(function(d) { return x(d.x); }) 162 | .y1(height) 163 | .y0(function(d) { return y(d.y); }) 164 | .interpolate(interpolationMode); 165 | 166 | return { 167 | "margin": margin, 168 | "width": width, 169 | "height": height, 170 | "xAxis": xAxis, 171 | "yAxis": yAxis, 172 | "testLine": testLine, 173 | "controlLine": controlLine, 174 | "testData": testData, 175 | "controlData": controlData 176 | }; 177 | }; 178 | 179 | Plots.prototype.drawHistogram = function () { 180 | var el = this.getHistogramElements(); 181 | 182 | var svg = d3.select("#histogram").append("svg") 183 | .attr("width", el.width + el.margin.left + el.margin.right) 184 | .attr("height", el.height + el.margin.top + el.margin.bottom) 185 | .append("g") 186 | .attr("transform", "translate(" + el.margin.left + "," + el.margin.top + ")"); 187 | 188 | svg.append("g") 189 | .attr("class", "x axis") 190 | .attr("transform", "translate(0," + el.height + ")") 191 | .call(el.xAxis); 192 | 193 | svg.append("g") 194 | .attr("class", "y axis") 195 | .call(el.yAxis) 196 | .append("text") 197 | .attr("transform", "rotate(-90)") 198 | .attr("y", 6) 199 | .attr("dy", ".71em") 200 | .style("text-anchor", "end") 201 | .text("Samples"); 202 | 203 | var bar = svg.selectAll(".bar") 204 | .data(el.histogram) 205 | .enter().append("g") 206 | .attr("class", "bar") 207 | .attr("transform", function(d) { return "translate(" + el.x(d.x) + ",0)"; }); 208 | 209 | bar.append("rect") 210 | .attr("x", 1) 211 | .attr("y", function(d) { return el.y(d.y);}) 212 | .attr("width", el.histogram[0].dx/2 * el.width) 213 | .attr("height", function(d) { return el.height - el.y(d.y); }); 214 | 215 | this.histogramSVG = svg; 216 | 217 | this.drawSummaryStatistics(el); 218 | 219 | }; 220 | 221 | 222 | Plots.prototype.drawPDF = function () { 223 | var d = this.getPDFElements(); 224 | 225 | var svg = d3.select("#pdfplot").append("svg") 226 | .attr("width", d.width + d.margin.left + d.margin.right) 227 | .attr("height", d.height + d.margin.top + d.margin.bottom) 228 | .append("g") 229 | .attr("transform", "translate(" + d.margin.left + "," + d.margin.top + ")"); 230 | 231 | svg.append("g") 232 | .attr("class", "x axis") 233 | .attr("transform", "translate(0," + d.height + ")") 234 | .call(d.xAxis); 235 | 236 | svg.append("g") 237 | .attr("class", "y axis") 238 | .call(d.yAxis) 239 | .append("text") 240 | .attr("transform", "rotate(-90)") 241 | .attr("y", 6) 242 | .attr("dy", ".71em") 243 | .style("text-anchor", "end") 244 | .text("Density"); 245 | 246 | svg.append("path") 247 | .datum(d.testData) 248 | .attr("class", "line") 249 | .attr("d", d.testLine) 250 | .attr("id", "testLine"); 251 | 252 | svg.append("path") 253 | .datum(d.controlData) 254 | .attr("class", "area") 255 | .attr("d", d.controlLine) 256 | .attr("id", "controlLine"); 257 | 258 | this.pdfSVG = svg; 259 | }; 260 | 261 | 262 | Plots.prototype.drawTable = function (arr1, arr2) { 263 | var tb = ''; 264 | 265 | tb += ""; 266 | tb += "Percentiles"; 267 | for (var i=0; i < arr1.length; i++) { 268 | tb += ("" + arr1[i] * 100 + "%"); 269 | } 270 | 271 | tb += ""; 272 | tb += "Value"; 273 | for (var i=0; i < arr1.length; i++) { 274 | tb += ("" + (Math.round(100 * arr2[i]) / 100) + ""); 275 | } 276 | 277 | tb += "" 278 | 279 | return tb; 280 | }; 281 | 282 | Plots.prototype.drawSummaryStatistics = function (el) { 283 | 284 | var quantiles = [0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.975, 0.99] 285 | var differenceQuantiles = jStat.jStat.quantiles(el.differenceData, quantiles); 286 | var tableElement = document.getElementById('quantileTable'); 287 | tableElement.innerHTML = this.drawTable(quantiles, differenceQuantiles); 288 | 289 | var percentileOfZero = BetaModel.prototype.percentileOfScore(el.differenceData, 0); 290 | var testSuccessProbability = document.getElementById('testSuccessProbability'); 291 | testSuccessProbability.innerHTML = Math.round((1.0 - percentileOfZero) * 100) / 100; 292 | 293 | var differenceMeanHTML = document.getElementById('differenceMean'); 294 | var differenceMean = BetaModel.prototype.mean(el.differenceData); 295 | differenceMeanHTML.innerHTML = Math.round(100 * differenceMean) / 100; 296 | }; 297 | 298 | 299 | Plots.prototype.redrawHistogram = function () { 300 | var el = this.getHistogramElements(); 301 | 302 | var svg = this.histogramSVG; 303 | 304 | svg.selectAll("rect") 305 | .data(el.histogram) 306 | .transition() 307 | .duration(1000) 308 | .attr("y", function(d) { return el.y(d.y);}) 309 | .attr("height", function(d) { return el.height - el.y(d.y); }); 310 | 311 | this.drawSummaryStatistics(el); 312 | }; 313 | 314 | Plots.prototype.redrawPDF = function () { 315 | 316 | var d = this.getPDFElements(); 317 | 318 | var svg = this.pdfSVG; 319 | 320 | svg.select('#testLine') 321 | .datum(d.testData) 322 | .transition() 323 | .duration(1000) 324 | .attr("d", d.testLine); 325 | 326 | svg.select('#controlLine') 327 | .datum(d.controlData) 328 | .transition() 329 | .duration(1000) 330 | .attr("d", d.controlLine); 331 | 332 | svg.select('.y.axis') 333 | .transition() 334 | .duration(1000) 335 | .call(d.yAxis); 336 | 337 | svg.select('.x.axis') 338 | .transition() 339 | .call(d.xAxis); 340 | }; 341 | 342 | Plots.prototype.updatePrior = function (alpha, beta) { 343 | this.controlBeta = new BetaModel(alpha, beta); 344 | this.testBeta = new BetaModel(alpha, beta); 345 | }; 346 | 347 | Plots.prototype.updatePosterior = function (testSuccesses, testFailures, controlSuccesses, controlFailures) { 348 | this.testBeta.update(testSuccesses, testFailures); 349 | this.controlBeta.update(controlSuccesses, controlFailures); 350 | }; 351 | 352 | 353 | var getNumber = function (x, def) { 354 | return Number(x); 355 | }; 356 | 357 | var getInputs = function () { 358 | 359 | var priorAlpha = getNumber(document.getElementById("priorAlpha").value, 10); 360 | var priorBeta = getNumber(document.getElementById("priorBeta").value, 10); 361 | var controlSuccesses = getNumber(document.getElementById("controlSuccesses").value, 10); 362 | var controlFailures = getNumber(document.getElementById("controlFailures").value, 10); 363 | var testSuccesses = getNumber(document.getElementById("testSuccesses").value, 10); 364 | var testFailures = getNumber(document.getElementById("testFailures").value, 10); 365 | 366 | return { 367 | "priorAlpha": priorAlpha, 368 | "priorBeta": priorBeta, 369 | "controlSuccesses": controlSuccesses, 370 | "controlFailures": controlFailures, 371 | "testSuccesses": testSuccesses, 372 | "testFailures": testFailures 373 | }; 374 | }; 375 | 376 | var initializePlots = function() { 377 | 378 | var inputs = getInputs(); 379 | var plots = new Plots(inputs.priorAlpha, inputs.priorBeta); 380 | plots.drawPDF(); 381 | plots.drawHistogram(); 382 | window.plots = plots; 383 | }; 384 | 385 | initializePlots(); 386 | 387 | var updatePlots = function() { 388 | 389 | var inputs = getInputs(); 390 | var plots = window.plots; 391 | plots.updatePrior(inputs.priorAlpha, inputs.priorBeta); 392 | plots.updatePosterior(inputs.testSuccesses, 393 | inputs.testFailures, 394 | inputs.controlSuccesses, 395 | inputs.controlFailures); 396 | plots.redrawPDF(); 397 | plots.redrawHistogram(); 398 | 399 | }; 400 | 401 | var bindInputs = function() { 402 | document.getElementById("form").onsubmit = function(event) { 403 | event.preventDefault(); 404 | updatePlots(); 405 | }; 406 | }; 407 | 408 | 409 | bindInputs(); 410 | -------------------------------------------------------------------------------- /src/scripts/libs/jstat.js: -------------------------------------------------------------------------------- 1 | this.j$ = this.jStat = (function(Math, undefined) { 2 | 3 | // For quick reference. 4 | var concat = Array.prototype.concat; 5 | var slice = Array.prototype.slice; 6 | var toString = Object.prototype.toString; 7 | 8 | // Calculate correction for IEEE error 9 | // TODO: This calculation can be improved. 10 | function calcRdx(n, m) { 11 | var val = n > m ? n : m; 12 | return Math.pow(10, 13 | 17 - ~~(Math.log(((val > 0) ? val : -val)) * Math.LOG10E)); 14 | } 15 | 16 | 17 | var isArray = Array.isArray || function isArray(arg) { 18 | return toString.call(arg) === '[object Array]'; 19 | }; 20 | 21 | 22 | function isFunction(arg) { 23 | return toString.call(arg) === '[object Function]'; 24 | } 25 | 26 | 27 | function isNumber(arg) { 28 | return typeof arg === 'number' && arg === arg; 29 | } 30 | 31 | 32 | // Converts the jStat matrix to vector. 33 | function toVector(arr) { 34 | return concat.apply([], arr); 35 | } 36 | 37 | 38 | // The one and only jStat constructor. 39 | function jStat() { 40 | return new jStat._init(arguments); 41 | } 42 | 43 | 44 | // TODO: Remove after all references in src files have been removed. 45 | jStat.fn = jStat.prototype; 46 | 47 | 48 | // By separating the initializer from the constructor it's easier to handle 49 | // always returning a new instance whether "new" was used or not. 50 | jStat._init = function _init(args) { 51 | var i; 52 | 53 | // If first argument is an array, must be vector or matrix. 54 | if (isArray(args[0])) { 55 | // Check if matrix. 56 | if (isArray(args[0][0])) { 57 | // See if a mapping function was also passed. 58 | if (isFunction(args[1])) 59 | args[0] = jStat.map(args[0], args[1]); 60 | // Iterate over each is faster than this.push.apply(this, args[0]. 61 | for (i = 0; i < args[0].length; i++) 62 | this[i] = args[0][i]; 63 | this.length = args[0].length; 64 | 65 | // Otherwise must be a vector. 66 | } else { 67 | this[0] = isFunction(args[1]) ? jStat.map(args[0], args[1]) : args[0]; 68 | this.length = 1; 69 | } 70 | 71 | // If first argument is number, assume creation of sequence. 72 | } else if (isNumber(args[0])) { 73 | this[0] = jStat.seq.apply(null, args); 74 | this.length = 1; 75 | 76 | // Handle case when jStat object is passed to jStat. 77 | } else if (args[0] instanceof jStat) { 78 | // Duplicate the object and pass it back. 79 | return jStat(args[0].toArray()); 80 | 81 | // Unexpected argument value, return empty jStat object. 82 | // TODO: This is strange behavior. Shouldn't this throw or some such to let 83 | // the user know they had bad arguments? 84 | } else { 85 | this[0] = []; 86 | this.length = 1; 87 | } 88 | 89 | return this; 90 | }; 91 | jStat._init.prototype = jStat.prototype; 92 | jStat._init.constructor = jStat; 93 | 94 | 95 | // Utility functions. 96 | // TODO: for internal use only? 97 | jStat.utils = { 98 | calcRdx: calcRdx, 99 | isArray: isArray, 100 | isFunction: isFunction, 101 | isNumber: isNumber, 102 | toVector: toVector 103 | }; 104 | 105 | 106 | // Easily extend the jStat object. 107 | // TODO: is this seriously necessary? 108 | jStat.extend = function extend(obj) { 109 | var i, j; 110 | 111 | if (arguments.length === 1) { 112 | for (j in obj) 113 | jStat[j] = obj[j]; 114 | return this; 115 | } 116 | 117 | for (i = 1; i < arguments.length; i++) { 118 | for (j in arguments[i]) 119 | obj[j] = arguments[i][j]; 120 | } 121 | 122 | return obj; 123 | }; 124 | 125 | 126 | // Returns the number of rows in the matrix. 127 | jStat.rows = function rows(arr) { 128 | return arr.length || 1; 129 | }; 130 | 131 | 132 | // Returns the number of columns in the matrix. 133 | jStat.cols = function cols(arr) { 134 | return arr[0].length || 1; 135 | }; 136 | 137 | 138 | // Returns the dimensions of the object { rows: i, cols: j } 139 | jStat.dimensions = function dimensions(arr) { 140 | return { 141 | rows: jStat.rows(arr), 142 | cols: jStat.cols(arr) 143 | }; 144 | }; 145 | 146 | 147 | // Returns a specified row as a vector 148 | jStat.row = function row(arr, index) { 149 | return arr[index]; 150 | }; 151 | 152 | 153 | // Returns the specified column as a vector 154 | jStat.col = function cols(arr, index) { 155 | var column = new Array(arr.length); 156 | for (var i = 0; i < arr.length; i++) 157 | column[i] = [arr[i][index]]; 158 | return column; 159 | }; 160 | 161 | 162 | // Returns the diagonal of the matrix 163 | jStat.diag = function diag(arr) { 164 | var nrow = jStat.rows(arr); 165 | var res = new Array(nrow); 166 | for (var row = 0; row < nrow; row++) 167 | res[row] = [arr[row][row]]; 168 | return res; 169 | }; 170 | 171 | 172 | // Returns the anti-diagonal of the matrix 173 | jStat.antidiag = function antidiag(arr) { 174 | var nrow = jStat.rows(arr) - 1; 175 | var res = new Array(nrow); 176 | for (var i = 0; nrow >= 0; nrow--, i++) 177 | res[i] = [arr[i][nrow]]; 178 | return res; 179 | }; 180 | 181 | // Transpose a matrix or array. 182 | jStat.transpose = function transpose(arr) { 183 | var obj = []; 184 | var objArr, rows, cols, j, i; 185 | 186 | // Make sure arr is in matrix format. 187 | if (!isArray(arr[0])) 188 | arr = [arr]; 189 | 190 | rows = arr.length; 191 | cols = arr[0].length; 192 | 193 | for (i = 0; i < cols; i++) { 194 | objArr = new Array(rows); 195 | for (j = 0; j < rows; j++) 196 | objArr[j] = arr[j][i]; 197 | obj.push(objArr); 198 | } 199 | 200 | // If obj is vector, return only single array. 201 | return obj.length === 1 ? obj[0] : obj; 202 | }; 203 | 204 | 205 | // Map a function to an array or array of arrays. 206 | // "toAlter" is an internal variable. 207 | jStat.map = function map(arr, func, toAlter) { 208 | var row, nrow, ncol, res, col; 209 | 210 | if (!isArray(arr[0])) 211 | arr = [arr]; 212 | 213 | nrow = arr.length; 214 | ncol = arr[0].length; 215 | res = toAlter ? arr : new Array(nrow); 216 | 217 | for (row = 0; row < nrow; row++) { 218 | // if the row doesn't exist, create it 219 | if (!res[row]) 220 | res[row] = new Array(ncol); 221 | for (col = 0; col < ncol; col++) 222 | res[row][col] = func(arr[row][col], row, col); 223 | } 224 | 225 | return res.length === 1 ? res[0] : res; 226 | }; 227 | 228 | 229 | // Destructively alter an array. 230 | jStat.alter = function alter(arr, func) { 231 | return jStat.map(arr, func, true); 232 | }; 233 | 234 | 235 | // Generate a rows x cols matrix according to the supplied function. 236 | jStat.create = function create(rows, cols, func) { 237 | var res = new Array(rows); 238 | var i, j; 239 | 240 | if (isFunction(cols)) { 241 | func = cols; 242 | cols = rows; 243 | } 244 | 245 | for (i = 0; i < rows; i++) { 246 | res[i] = new Array(cols); 247 | for (j = 0; j < cols; j++) 248 | res[i][j] = func(i, j); 249 | } 250 | 251 | return res; 252 | }; 253 | 254 | 255 | function retZero() { return 0; } 256 | 257 | 258 | // Generate a rows x cols matrix of zeros. 259 | jStat.zeros = function zeros(rows, cols) { 260 | if (!isNumber(cols)) 261 | cols = rows; 262 | return jStat.create(rows, cols, retZero); 263 | }; 264 | 265 | 266 | function retOne() { return 1; } 267 | 268 | 269 | // Generate a rows x cols matrix of ones. 270 | jStat.ones = function ones(rows, cols) { 271 | if (!isNumber(cols)) 272 | cols = rows; 273 | return jStat.create(rows, cols, retOne); 274 | }; 275 | 276 | 277 | // Generate a rows x cols matrix of uniformly random numbers. 278 | jStat.rand = function rand(rows, cols) { 279 | if (!isNumber(cols)) 280 | cols = rows; 281 | return jStat.create(rows, cols, Math.random); 282 | }; 283 | 284 | 285 | function retIdent(i, j) { return i === j ? 1 : 0; } 286 | 287 | 288 | // Generate an identity matrix of size row x cols. 289 | jStat.identity = function identity(rows, cols) { 290 | if (!isNumber(cols)) 291 | cols = rows; 292 | return jStat.create(rows, cols, retIdent); 293 | }; 294 | 295 | 296 | // Tests whether a matrix is symmetric 297 | jStat.symmetric = function symmetric(arr) { 298 | var issymmetric = true; 299 | var size = arr.length; 300 | var row, col; 301 | 302 | if (arr.length !== arr[0].length) 303 | return false; 304 | 305 | for (row = 0; row < size; row++) { 306 | for (col = 0; col < size; col++) 307 | if (arr[col][row] !== arr[row][col]) 308 | return false; 309 | } 310 | 311 | return true; 312 | }; 313 | 314 | 315 | // Set all values to zero. 316 | jStat.clear = function clear(arr) { 317 | return jStat.alter(arr, retZero); 318 | }; 319 | 320 | 321 | // Generate sequence. 322 | jStat.seq = function seq(min, max, length, func) { 323 | if (!isFunction(func)) 324 | func = false; 325 | 326 | var arr = []; 327 | var hival = calcRdx(min, max); 328 | var step = (max * hival - min * hival) / ((length - 1) * hival); 329 | var current = min; 330 | var cnt; 331 | 332 | // Current is assigned using a technique to compensate for IEEE error. 333 | // TODO: Needs better implementation. 334 | for (cnt = 0; 335 | current <= max; 336 | cnt++, current = (min * hival + step * hival * cnt) / hival) { 337 | arr.push((func ? func(current, cnt) : current)); 338 | } 339 | 340 | return arr; 341 | }; 342 | 343 | 344 | // TODO: Go over this entire implementation. Seems a tragic waste of resources 345 | // doing all this work. Instead, and while ugly, use new Function() to generate 346 | // a custom function for each static method. 347 | 348 | // Quick reference. 349 | var jProto = jStat.prototype; 350 | 351 | // Default length. 352 | jProto.length = 0; 353 | 354 | // For internal use only. 355 | // TODO: Check if they're actually used, and if they are then rename them 356 | // to _* 357 | jProto.push = Array.prototype.push; 358 | jProto.sort = Array.prototype.sort; 359 | jProto.splice = Array.prototype.splice; 360 | jProto.slice = Array.prototype.slice; 361 | 362 | 363 | // Return a clean array. 364 | jProto.toArray = function toArray() { 365 | return this.length > 1 ? slice.call(this) : slice.call(this)[0]; 366 | }; 367 | 368 | 369 | // Map a function to a matrix or vector. 370 | jProto.map = function map(func, toAlter) { 371 | return jStat(jStat.map(this, func, toAlter)); 372 | }; 373 | 374 | 375 | // Destructively alter an array. 376 | jProto.alter = function alter(func) { 377 | jStat.alter(this, func); 378 | return this; 379 | }; 380 | 381 | 382 | // Extend prototype with methods that have no argument. 383 | (function(funcs) { 384 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 385 | jProto[passfunc] = function(func) { 386 | var self = this, 387 | results; 388 | // Check for callback. 389 | if (func) { 390 | setTimeout(function() { 391 | func.call(self, jProto[passfunc].call(self)); 392 | }); 393 | return this; 394 | } 395 | results = jStat[passfunc](this); 396 | return isArray(results) ? jStat(results) : results; 397 | }; 398 | })(funcs[i]); 399 | })('transpose clear symmetric rows cols dimensions diag antidiag'.split(' ')); 400 | 401 | 402 | // Extend prototype with methods that have one argument. 403 | (function(funcs) { 404 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 405 | jProto[passfunc] = function(index, func) { 406 | var self = this; 407 | // check for callback 408 | if (func) { 409 | setTimeout(function() { 410 | func.call(self, jProto[passfunc].call(self, index)); 411 | }); 412 | return this; 413 | } 414 | return jStat(jStat[passfunc](this, index)); 415 | }; 416 | })(funcs[i]); 417 | })('row col'.split(' ')); 418 | 419 | 420 | // Extend prototype with simple shortcut methods. 421 | (function(funcs) { 422 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 423 | jProto[passfunc] = new Function( 424 | 'return jStat(jStat.' + passfunc + '.apply(null, arguments));'); 425 | })(funcs[i]); 426 | })('create zeros ones rand identity'.split(' ')); 427 | 428 | 429 | // Exposing jStat. 430 | return jStat; 431 | 432 | }(Math)); 433 | (function(jStat, Math) { 434 | 435 | var isFunction = jStat.utils.isFunction; 436 | 437 | // Ascending functions for sort 438 | function ascNum(a, b) { return a - b; } 439 | 440 | function clip(arg, min, max) { 441 | return Math.max(min, Math.min(arg, max)); 442 | } 443 | 444 | 445 | // sum of an array 446 | jStat.sum = function sum(arr) { 447 | var sum = 0; 448 | var i = arr.length; 449 | var tmp; 450 | while (--i >= 0) 451 | sum += arr[i]; 452 | return sum; 453 | }; 454 | 455 | 456 | // sum squared 457 | jStat.sumsqrd = function sumsqrd(arr) { 458 | var sum = 0; 459 | var i = arr.length; 460 | while (--i >= 0) 461 | sum += arr[i] * arr[i]; 462 | return sum; 463 | }; 464 | 465 | 466 | // sum of squared errors of prediction (SSE) 467 | jStat.sumsqerr = function sumsqerr(arr) { 468 | var mean = jStat.mean(arr); 469 | var sum = 0; 470 | var i = arr.length; 471 | var tmp; 472 | while (--i >= 0) { 473 | tmp = arr[i] - mean; 474 | sum += tmp * tmp; 475 | } 476 | return sum; 477 | }; 478 | 479 | 480 | // product of an array 481 | jStat.product = function product(arr) { 482 | var prod = 1; 483 | var i = arr.length; 484 | while (--i >= 0) 485 | prod *= arr[i]; 486 | return prod; 487 | }; 488 | 489 | 490 | // minimum value of an array 491 | jStat.min = function min(arr) { 492 | var low = arr[0]; 493 | var i = 0; 494 | while (++i < arr.length) 495 | if (arr[i] < low) 496 | low = arr[i]; 497 | return low; 498 | }; 499 | 500 | 501 | // maximum value of an array 502 | jStat.max = function max(arr) { 503 | var high = arr[0]; 504 | var i = 0; 505 | while (++i < arr.length) 506 | if (arr[i] > high) 507 | high = arr[i]; 508 | return high; 509 | }; 510 | 511 | 512 | // mean value of an array 513 | jStat.mean = function mean(arr) { 514 | return jStat.sum(arr) / arr.length; 515 | }; 516 | 517 | 518 | // mean squared error (MSE) 519 | jStat.meansqerr = function meansqerr(arr) { 520 | return jStat.sumsqerr(arr) / arr.length; 521 | }; 522 | 523 | 524 | // geometric mean of an array 525 | jStat.geomean = function geomean(arr) { 526 | return Math.pow(jStat.product(arr), 1 / arr.length); 527 | }; 528 | 529 | 530 | // median of an array 531 | jStat.median = function median(arr) { 532 | var arrlen = arr.length; 533 | var _arr = arr.slice().sort(ascNum); 534 | // check if array is even or odd, then return the appropriate 535 | return !(arrlen & 1) 536 | ? (_arr[(arrlen / 2) - 1 ] + _arr[(arrlen / 2)]) / 2 537 | : _arr[(arrlen / 2) | 0 ]; 538 | }; 539 | 540 | 541 | // cumulative sum of an array 542 | jStat.cumsum = function cumsum(arr) { 543 | var len = arr.length; 544 | var sums = new Array(len); 545 | var i; 546 | sums[0] = arr[0]; 547 | for (i = 1; i < len; i++) 548 | sums[i] = sums[i - 1] + arr[i]; 549 | return sums; 550 | }; 551 | 552 | 553 | // successive differences of a sequence 554 | jStat.diff = function diff(arr) { 555 | var diffs = []; 556 | var arrLen = arr.length; 557 | var i; 558 | for (i = 1; i < arrLen; i++) 559 | diffs.push(arr[i] - arr[i - 1]); 560 | return diffs; 561 | }; 562 | 563 | 564 | // mode of an array 565 | // if there are multiple modes of an array, return all of them 566 | // is this the appropriate way of handling it? 567 | jStat.mode = function mode(arr) { 568 | var arrLen = arr.length; 569 | var _arr = arr.slice().sort(ascNum); 570 | var count = 1; 571 | var maxCount = 0; 572 | var numMaxCount = 0; 573 | var mode_arr = []; 574 | var i; 575 | 576 | for (i = 0; i < arrLen; i++) { 577 | if (_arr[i] === _arr[i + 1]) { 578 | count++; 579 | } else { 580 | if (count > maxCount) { 581 | mode_arr = [_arr[i]]; 582 | maxCount = count; 583 | numMaxCount = 0; 584 | } 585 | // are there multiple max counts 586 | else if (count === maxCount) { 587 | mode_arr.push(_arr[i]); 588 | numMaxCount++; 589 | } 590 | // resetting count for new value in array 591 | count = 1; 592 | } 593 | } 594 | 595 | return numMaxCount === 0 ? mode_arr[0] : mode_arr; 596 | }; 597 | 598 | 599 | // range of an array 600 | jStat.range = function range(arr) { 601 | return jStat.max(arr) - jStat.min(arr); 602 | }; 603 | 604 | // variance of an array 605 | // flag indicates population vs sample 606 | jStat.variance = function variance(arr, flag) { 607 | return jStat.sumsqerr(arr) / (arr.length - (flag ? 1 : 0)); 608 | }; 609 | 610 | 611 | // standard deviation of an array 612 | // flag indicates population vs sample 613 | jStat.stdev = function stdev(arr, flag) { 614 | return Math.sqrt(jStat.variance(arr, flag)); 615 | }; 616 | 617 | 618 | // mean deviation (mean absolute deviation) of an array 619 | jStat.meandev = function meandev(arr) { 620 | var devSum = 0; 621 | var mean = jStat.mean(arr); 622 | var i; 623 | for (i = arr.length - 1; i >= 0; i--) 624 | devSum += Math.abs(arr[i] - mean); 625 | return devSum / arr.length; 626 | }; 627 | 628 | 629 | // median deviation (median absolute deviation) of an array 630 | jStat.meddev = function meddev(arr) { 631 | var devSum = 0; 632 | var median = jStat.median(arr); 633 | var i; 634 | for (i = arr.length - 1; i >= 0; i--) 635 | devSum += Math.abs(arr[i] - median); 636 | return devSum / arr.length; 637 | }; 638 | 639 | 640 | // coefficient of variation 641 | jStat.coeffvar = function coeffvar(arr) { 642 | return jStat.stdev(arr) / jStat.mean(arr); 643 | }; 644 | 645 | 646 | // quartiles of an array 647 | jStat.quartiles = function quartiles(arr) { 648 | var arrlen = arr.length; 649 | var _arr = arr.slice().sort(ascNum); 650 | return [ 651 | _arr[ Math.round((arrlen) / 4) - 1 ], 652 | _arr[ Math.round((arrlen) / 2) - 1 ], 653 | _arr[ Math.round((arrlen) * 3 / 4) - 1 ] 654 | ]; 655 | }; 656 | 657 | 658 | // Arbitary quantiles of an array. Direct port of the scipy.stats 659 | // implementation by Pierre GF Gerard-Marchant. 660 | jStat.quantiles = function quantiles(arr, quantilesArray, alphap, betap) { 661 | var sortedArray = arr.slice().sort(ascNum); 662 | var quantileVals = [quantilesArray.length]; 663 | var n = arr.length; 664 | var i, p, m, aleph, k, gamma; 665 | 666 | if (typeof alphap === 'undefined') 667 | alphap = 3 / 8; 668 | if (typeof betap === 'undefined') 669 | betap = 3 / 8; 670 | 671 | for (i = 0; i < quantilesArray.length; i++) { 672 | p = quantilesArray[i]; 673 | m = alphap + p * (1 - alphap - betap); 674 | aleph = n * p + m; 675 | k = Math.floor(clip(aleph, 1, n - 1)); 676 | gamma = clip(aleph - k, 0, 1); 677 | quantileVals[i] = (1 - gamma) * sortedArray[k - 1] + gamma * sortedArray[k]; 678 | } 679 | 680 | return quantileVals; 681 | }; 682 | 683 | // The percentile rank of score in a given array. Returns the percentage 684 | // of all values in the input array that are less than (kind='strict') or 685 | // less or equal than (kind='weak') score. Default is weak. 686 | jStat.percentileOfScore = function percentileOfScore(arr, score, kind) { 687 | var counter = 0; 688 | var len = arr.length; 689 | var strict = false; 690 | var value, i; 691 | 692 | if (kind === 'strict') 693 | strict = true; 694 | 695 | for (i = 0; i < len; i++) { 696 | value = arr[i]; 697 | if ((strict && value < score) || 698 | (!strict && value <= score)) { 699 | counter++; 700 | } 701 | } 702 | 703 | return counter / len; 704 | }; 705 | 706 | // covariance of two arrays 707 | jStat.covariance = function covariance(arr1, arr2) { 708 | var u = jStat.mean(arr1); 709 | var v = jStat.mean(arr2); 710 | var arr1Len = arr1.length; 711 | var sq_dev = new Array(arr1Len); 712 | var i; 713 | 714 | for (i = 0; i < arr1Len; i++) 715 | sq_dev[i] = (arr1[i] - u) * (arr2[i] - v); 716 | 717 | return jStat.sum(sq_dev) / (arr1Len - 1); 718 | }; 719 | 720 | 721 | // (pearson's) population correlation coefficient, rho 722 | jStat.corrcoeff = function corrcoeff(arr1, arr2) { 723 | return jStat.covariance(arr1, arr2) / 724 | jStat.stdev(arr1, 1) / 725 | jStat.stdev(arr2, 1); 726 | }; 727 | 728 | 729 | var jProto = jStat.prototype; 730 | 731 | 732 | // Extend jProto with method for calculating cumulative sums, as it does not 733 | // run again in case of true. 734 | // If a matrix is passed, automatically assume operation should be done on the 735 | // columns. 736 | jProto.cumsum = function(fullbool, func) { 737 | var arr = []; 738 | var i = 0; 739 | var tmpthis = this; 740 | 741 | // Assignment reassignation depending on how parameters were passed in. 742 | if (isFunction(fullbool)) { 743 | func = fullbool; 744 | fullbool = false; 745 | } 746 | 747 | // Check if a callback was passed with the function. 748 | if (func) { 749 | setTimeout(function() { 750 | func.call(tmpthis, jProto.cumsum.call(tmpthis, fullbool)); 751 | }); 752 | return this; 753 | } 754 | 755 | // Check if matrix and run calculations. 756 | if (this.length > 1) { 757 | tmpthis = fullbool === true ? this : this.transpose(); 758 | for (; i < tmpthis.length; i++) 759 | arr[i] = jStat.cumsum(tmpthis[i]); 760 | return arr; 761 | } 762 | 763 | return jStat.cumsum(this[0], fullbool); 764 | }; 765 | 766 | 767 | // Extend jProto with methods which don't require arguments and work on columns. 768 | (function(funcs) { 769 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 770 | // If a matrix is passed, automatically assume operation should be done on 771 | // the columns. 772 | jProto[passfunc] = function(fullbool, func) { 773 | var arr = []; 774 | var i = 0; 775 | var tmpthis = this; 776 | // Assignment reassignation depending on how parameters were passed in. 777 | if (isFunction(fullbool)) { 778 | func = fullbool; 779 | fullbool = false; 780 | } 781 | // Check if a callback was passed with the function. 782 | if (func) { 783 | setTimeout(function() { 784 | func.call(tmpthis, jProto[passfunc].call(tmpthis, fullbool)); 785 | }); 786 | return this; 787 | } 788 | // Check if matrix and run calculations. 789 | if (this.length > 1) { 790 | tmpthis = fullbool === true ? this : this.transpose(); 791 | for (; i < tmpthis.length; i++) 792 | arr[i] = jStat[passfunc](tmpthis[i]); 793 | return fullbool === true 794 | ? jStat[passfunc](jStat.utils.toVector(arr)) 795 | : arr; 796 | } 797 | // Pass fullbool if only vector, not a matrix. for variance and stdev. 798 | return jStat[passfunc](this[0], fullbool); 799 | }; 800 | })(funcs[i]); 801 | })(('sum sumsqrd sumsqerr product min max mean meansqerr geomean median diff ' + 802 | 'mode range variance stdev meandev meddev coeffvar quartiles').split(' ')); 803 | 804 | 805 | // Extend jProto with functions that take arguments. Operations on matrices are 806 | // done on columns. 807 | (function(funcs) { 808 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 809 | jProto[passfunc] = function() { 810 | var arr = []; 811 | var i = 0; 812 | var tmpthis = this; 813 | var args = Array.prototype.slice.call(arguments); 814 | 815 | // If the last argument is a function, we assume it's a callback; we 816 | // strip the callback out and call the function again. 817 | if (isFunction(args[args.length - 1])) { 818 | var callbackFunction = args[args.length - 1]; 819 | var argsToPass = args.slice(0, args.length - 1); 820 | 821 | setTimeout(function() { 822 | callbackFunction.call(tmpthis, 823 | jProto[passfunc].apply(tmpthis, argsToPass)); 824 | }); 825 | return this; 826 | 827 | // Otherwise we curry the function args and call normally. 828 | } else { 829 | var callbackFunction = undefined; 830 | var curriedFunction = function curriedFunction(vector) { 831 | return jStat[passfunc].apply(tmpthis, [vector].concat(args)); 832 | } 833 | } 834 | 835 | // If this is a matrix, run column-by-column. 836 | if (this.length > 1) { 837 | tmpthis = tmpthis.transpose(); 838 | for (; i < tmpthis.length; i++) 839 | arr[i] = curriedFunction(tmpthis[i]); 840 | return arr; 841 | } 842 | 843 | // Otherwise run on the vector. 844 | return curriedFunction(this[0]); 845 | }; 846 | })(funcs[i]); 847 | })('quantiles percentileOfScore'.split(' ')); 848 | 849 | }(this.jStat, Math)); 850 | // Special functions // 851 | (function(jStat, Math) { 852 | 853 | // Log-gamma function 854 | jStat.gammaln = function gammaln(x) { 855 | var j = 0; 856 | var cof = [ 857 | 76.18009172947146, -86.50532032941677, 24.01409824083091, 858 | -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5 859 | ]; 860 | var ser = 1.000000000190015; 861 | var xx, y, tmp; 862 | tmp = (y = xx = x) + 5.5; 863 | tmp -= (xx + 0.5) * Math.log(tmp); 864 | for (; j < 6; j++) 865 | ser += cof[j] / ++y; 866 | return Math.log(2.5066282746310005 * ser / xx) - tmp; 867 | }; 868 | 869 | 870 | // gamma of x 871 | jStat.gammafn = function gammafn(x) { 872 | var p = [-1.716185138865495, 24.76565080557592, -379.80425647094563, 873 | 629.3311553128184, 866.9662027904133, -31451.272968848367, 874 | -36144.413418691176, 66456.14382024054 875 | ]; 876 | var q = [-30.8402300119739, 315.35062697960416, -1015.1563674902192, 877 | -3107.771671572311, 22538.118420980151, 4755.8462775278811, 878 | -134659.9598649693, -115132.2596755535]; 879 | var fact = false; 880 | var n = 0; 881 | var xden = 0; 882 | var xnum = 0; 883 | var y = x; 884 | var i, z, yi, res, sum, ysq; 885 | if (y <= 0) { 886 | res = y % 1 + 3.6e-16; 887 | if (res) { 888 | fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res); 889 | y = 1 - y; 890 | } else { 891 | return Infinity; 892 | } 893 | } 894 | yi = y; 895 | if (y < 1) { 896 | z = y++; 897 | } else { 898 | z = (y -= n = (y | 0) - 1) - 1; 899 | } 900 | for (i = 0; i < 8; ++i) { 901 | xnum = (xnum + p[i]) * z; 902 | xden = xden * z + q[i]; 903 | } 904 | res = xnum / xden + 1; 905 | if (yi < y) { 906 | res /= yi; 907 | } else if (yi > y) { 908 | for (i = 0; i < n; ++i) { 909 | res *= y; 910 | y++; 911 | } 912 | } 913 | if (fact) { 914 | res = fact / res; 915 | } 916 | return res; 917 | }; 918 | 919 | 920 | // lower incomplete gamma function P(a,x) 921 | jStat.gammap = function gammap(a, x) { 922 | var aln = jStat.gammaln(a); 923 | var ap = a; 924 | var sum = 1 / a; 925 | var del = sum; 926 | var b = x + 1 - a; 927 | var c = 1 / 1.0e-30; 928 | var d = 1 / b; 929 | var h = d; 930 | var i = 1; 931 | // calculate maximum number of itterations required for a 932 | var ITMAX = -~(Math.log((a >= 1) ? a : 1 / a) * 8.5 + a * 0.4 + 17); 933 | var an, endval; 934 | 935 | if (x < 0 || a <= 0) { 936 | return NaN; 937 | } else if (x < a + 1) { 938 | for (; i <= ITMAX; i++) { 939 | sum += del *= x / ++ap; 940 | } 941 | return sum * Math.exp(-x + a * Math.log(x) - (aln)); 942 | } 943 | 944 | for (; i <= ITMAX; i++) { 945 | an = -i * (i - a); 946 | b += 2; 947 | d = an * d + b; 948 | c = b + an / c; 949 | d = 1 / d; 950 | h *= d * c; 951 | } 952 | 953 | return 1 - h * Math.exp(-x + a * Math.log(x) - (aln)); 954 | }; 955 | 956 | 957 | // natural log factorial of n 958 | jStat.factorialln = function factorialln(n) { 959 | return n < 0 ? NaN : jStat.gammaln(n + 1); 960 | }; 961 | 962 | // factorial of n 963 | jStat.factorial = function factorial(n) { 964 | return n < 0 ? NaN : jStat.gammafn(n + 1); 965 | }; 966 | 967 | // combinations of n, m 968 | jStat.combination = function combination(n, m) { 969 | // make sure n or m don't exceed the upper limit of usable values 970 | return (n > 170 || m > 170) 971 | ? Math.exp(jStat.combinationln(n, m)) 972 | : (jStat.factorial(n) / jStat.factorial(m)) / jStat.factorial(n - m); 973 | }; 974 | 975 | 976 | jStat.combinationln = function combinationln(n, m){ 977 | return jStat.factorialln(n) - jStat.factorialln(m) - jStat.factorialln(n - m); 978 | }; 979 | 980 | 981 | // permutations of n, m 982 | jStat.permutation = function permutation(n, m) { 983 | return jStat.factorial(n) / jStat.factorial(n - m); 984 | }; 985 | 986 | 987 | // beta function 988 | jStat.betafn = function betafn(x, y) { 989 | // ensure arguments are positive 990 | if (x <= 0 || y <= 0) 991 | return undefined; 992 | // make sure x + y doesn't exceed the upper limit of usable values 993 | return (x + y > 170) 994 | ? Math.exp(jStat.betaln(x, y)) 995 | : jStat.gammafn(x) * jStat.gammafn(y) / jStat.gammafn(x + y); 996 | }; 997 | 998 | 999 | // natural logarithm of beta function 1000 | jStat.betaln = function betaln(x, y) { 1001 | return jStat.gammaln(x) + jStat.gammaln(y) - jStat.gammaln(x + y); 1002 | }; 1003 | 1004 | 1005 | // Evaluates the continued fraction for incomplete beta function by modified 1006 | // Lentz's method. 1007 | jStat.betacf = function betacf(x, a, b) { 1008 | var fpmin = 1e-30; 1009 | var m = 1; 1010 | var qab = a + b; 1011 | var qap = a + 1; 1012 | var qam = a - 1; 1013 | var c = 1; 1014 | var d = 1 - qab * x / qap; 1015 | var m2, aa, del, h; 1016 | 1017 | // These q's will be used in factors that occur in the coefficients 1018 | if (Math.abs(d) < fpmin) 1019 | d = fpmin; 1020 | d = 1 / d; 1021 | h = d; 1022 | 1023 | for (; m <= 100; m++) { 1024 | m2 = 2 * m; 1025 | aa = m * (b - m) * x / ((qam + m2) * (a + m2)); 1026 | // One step (the even one) of the recurrence 1027 | d = 1 + aa * d; 1028 | if (Math.abs(d) < fpmin) 1029 | d = fpmin; 1030 | c = 1 + aa / c; 1031 | if (Math.abs(c) < fpmin) 1032 | c = fpmin; 1033 | d = 1 / d; 1034 | h *= d * c; 1035 | aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2)); 1036 | // Next step of the recurrence (the odd one) 1037 | d = 1 + aa * d; 1038 | if (Math.abs(d) < fpmin) 1039 | d = fpmin; 1040 | c = 1 + aa / c; 1041 | if (Math.abs(c) < fpmin) 1042 | c = fpmin; 1043 | d = 1 / d; 1044 | del = d * c; 1045 | h *= del; 1046 | if (Math.abs(del - 1.0) < 3e-7) 1047 | break; 1048 | } 1049 | 1050 | return h; 1051 | }; 1052 | 1053 | 1054 | // Returns the inverse incomplte gamma function 1055 | jStat.gammapinv = function gammapinv(p, a) { 1056 | var j = 0; 1057 | var a1 = a - 1; 1058 | var EPS = 1e-8; 1059 | var gln = jStat.gammaln(a); 1060 | var x, err, t, u, pp, lna1, afac; 1061 | 1062 | if (p >= 1) 1063 | return Math.max(100, a + 100 * Math.sqrt(a)); 1064 | if (p <= 0) 1065 | return 0; 1066 | if (a > 1) { 1067 | lna1 = Math.log(a1); 1068 | afac = Math.exp(a1 * (lna1 - 1) - gln); 1069 | pp = (p < 0.5) ? p : 1 - p; 1070 | t = Math.sqrt(-2 * Math.log(pp)); 1071 | x = (2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t; 1072 | if (p < 0.5) 1073 | x = -x; 1074 | x = Math.max(1e-3, 1075 | a * Math.pow(1 - 1 / (9 * a) - x / (3 * Math.sqrt(a)), 3)); 1076 | } else { 1077 | t = 1 - a * (0.253 + a * 0.12); 1078 | if (p < t) 1079 | x = Math.pow(p / t, 1 / a); 1080 | else 1081 | x = 1 - Math.log(1 - (p - t) / (1 - t)); 1082 | } 1083 | 1084 | for(; j < 12; j++) { 1085 | if (x <= 0) 1086 | return 0; 1087 | err = jStat.gammap(a, x) - p; 1088 | if (a > 1) 1089 | t = afac * Math.exp(-(x - a1) + a1 * (Math.log(x) - lna1)); 1090 | else 1091 | t = Math.exp(-x + a1 * Math.log(x) - gln); 1092 | u = err / t; 1093 | x -= (t = u / (1 - 0.5 * Math.min(1, u * ((a - 1) / x - 1)))); 1094 | if (x <= 0) 1095 | x = 0.5 * (x + t); 1096 | if (Math.abs(t) < EPS * x) 1097 | break; 1098 | } 1099 | 1100 | return x; 1101 | }; 1102 | 1103 | 1104 | // Returns the error function erf(x) 1105 | jStat.erf = function erf(x) { 1106 | var cof = [-1.3026537197817094, 6.4196979235649026e-1, 1.9476473204185836e-2, 1107 | -9.561514786808631e-3, -9.46595344482036e-4, 3.66839497852761e-4, 1108 | 4.2523324806907e-5, -2.0278578112534e-5, -1.624290004647e-6, 1109 | 1.303655835580e-6, 1.5626441722e-8, -8.5238095915e-8, 1110 | 6.529054439e-9, 5.059343495e-9, -9.91364156e-10, 1111 | -2.27365122e-10, 9.6467911e-11, 2.394038e-12, 1112 | -6.886027e-12, 8.94487e-13, 3.13092e-13, 1113 | -1.12708e-13, 3.81e-16, 7.106e-15, 1114 | -1.523e-15, -9.4e-17, 1.21e-16, 1115 | -2.8e-17]; 1116 | var j = cof.length - 1; 1117 | var isneg = false; 1118 | var d = 0; 1119 | var dd = 0; 1120 | var t, ty, tmp, res; 1121 | 1122 | if (x < 0) { 1123 | x = -x; 1124 | isneg = true; 1125 | } 1126 | 1127 | t = 2 / (2 + x); 1128 | ty = 4 * t - 2; 1129 | 1130 | for(; j > 0; j--) { 1131 | tmp = d; 1132 | d = ty * d - dd + cof[j]; 1133 | dd = tmp; 1134 | } 1135 | 1136 | res = t * Math.exp(-x * x + 0.5 * (cof[0] + ty * d) - dd); 1137 | return isneg ? res - 1 : 1 - res; 1138 | }; 1139 | 1140 | 1141 | // Returns the complmentary error function erfc(x) 1142 | jStat.erfc = function erfc(x) { 1143 | return 1 - jStat.erf(x); 1144 | }; 1145 | 1146 | 1147 | // Returns the inverse of the complementary error function 1148 | jStat.erfcinv = function erfcinv(p) { 1149 | var j = 0; 1150 | var x, err, t, pp; 1151 | if (p >= 2) 1152 | return -100; 1153 | if (p <= 0) 1154 | return 100; 1155 | pp = (p < 1) ? p : 2 - p; 1156 | t = Math.sqrt(-2 * Math.log(pp / 2)); 1157 | x = -0.70711 * ((2.30753 + t * 0.27061) / 1158 | (1 + t * (0.99229 + t * 0.04481)) - t); 1159 | for (; j < 2; j++) { 1160 | err = jStat.erfc(x) - pp; 1161 | x += err / (1.12837916709551257 * Math.exp(-x * x) - x * err); 1162 | } 1163 | return (p < 1) ? x : -x; 1164 | }; 1165 | 1166 | 1167 | // Returns the inverse of the incomplete beta function 1168 | jStat.ibetainv = function ibetainv(p, a, b) { 1169 | var EPS = 1e-8; 1170 | var a1 = a - 1; 1171 | var b1 = b - 1; 1172 | var j = 0; 1173 | var lna, lnb, pp, t, u, err, x, al, h, w, afac; 1174 | if (p <= 0) 1175 | return 0; 1176 | if (p >= 1) 1177 | return 1; 1178 | if (a >= 1 && b >= 1) { 1179 | pp = (p < 0.5) ? p : 1 - p; 1180 | t = Math.sqrt(-2 * Math.log(pp)); 1181 | x = (2.30753 + t * 0.27061) / (1 + t* (0.99229 + t * 0.04481)) - t; 1182 | if (p < 0.5) 1183 | x = -x; 1184 | al = (x * x - 3) / 6; 1185 | h = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1)); 1186 | w = (x * Math.sqrt(al + h) / h) - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * 1187 | (al + 5 / 6 - 2 / (3 * h)); 1188 | x = a / (a + b * Math.exp(2 * w)); 1189 | } else { 1190 | lna = Math.log(a / (a + b)); 1191 | lnb = Math.log(b / (a + b)); 1192 | t = Math.exp(a * lna) / a; 1193 | u = Math.exp(b * lnb) / b; 1194 | w = t + u; 1195 | if (p < t / w) 1196 | x = Math.pow(a * w * p, 1 / a); 1197 | else 1198 | x = 1 - Math.pow(b * w * (1 - p), 1 / b); 1199 | } 1200 | afac = -jStat.gammaln(a) - jStat.gammaln(b) + jStat.gammaln(a + b); 1201 | for(; j < 10; j++) { 1202 | if (x === 0 || x === 1) 1203 | return x; 1204 | err = jStat.ibeta(x, a, b) - p; 1205 | t = Math.exp(a1 * Math.log(x) + b1 * Math.log(1 - x) + afac); 1206 | u = err / t; 1207 | x -= (t = u / (1 - 0.5 * Math.min(1, u * (a1 / x - b1 / (1 - x))))); 1208 | if (x <= 0) 1209 | x = 0.5 * (x + t); 1210 | if (x >= 1) 1211 | x = 0.5 * (x + t + 1); 1212 | if (Math.abs(t) < EPS * x && j > 0) 1213 | break; 1214 | } 1215 | return x; 1216 | }; 1217 | 1218 | 1219 | // Returns the incomplete beta function I_x(a,b) 1220 | jStat.ibeta = function ibeta(x, a, b) { 1221 | // Factors in front of the continued fraction. 1222 | var bt = (x === 0 || x === 1) ? 0 : 1223 | Math.exp(jStat.gammaln(a + b) - jStat.gammaln(a) - 1224 | jStat.gammaln(b) + a * Math.log(x) + b * 1225 | Math.log(1 - x)); 1226 | if (x < 0 || x > 1) 1227 | return false; 1228 | if (x < (a + 1) / (a + b + 2)) 1229 | // Use continued fraction directly. 1230 | return bt * jStat.betacf(x, a, b) / a; 1231 | // else use continued fraction after making the symmetry transformation. 1232 | return 1 - bt * jStat.betacf(1 - x, b, a) / b; 1233 | }; 1234 | 1235 | 1236 | // Returns a normal deviate (mu=0, sigma=1). 1237 | // If n and m are specified it returns a object of normal deviates. 1238 | jStat.randn = function randn(n, m) { 1239 | var u, v, x, y, q, mat; 1240 | if (!m) 1241 | m = n; 1242 | if (n) 1243 | return jStat.create(n, m, function() { return jStat.randn(); }); 1244 | do { 1245 | u = Math.random(); 1246 | v = 1.7156 * (Math.random() - 0.5); 1247 | x = u - 0.449871; 1248 | y = Math.abs(v) + 0.386595; 1249 | q = x * x + y * (0.19600 * y - 0.25472 * x); 1250 | } while (q > 0.27597 && (q > 0.27846 || v * v > -4 * Math.log(u) * u * u)); 1251 | return v / u; 1252 | }; 1253 | 1254 | 1255 | // Returns a gamma deviate by the method of Marsaglia and Tsang. 1256 | jStat.randg = function randg(shape, n, m) { 1257 | var oalph = shape; 1258 | var a1, a2, u, v, x, mat; 1259 | if (!m) 1260 | m = n; 1261 | if (!shape) 1262 | shape = 1; 1263 | if (n) { 1264 | mat = jStat.zeros(n,m); 1265 | mat.alter(function() { return jStat.randg(shape); }); 1266 | return mat; 1267 | } 1268 | if (shape < 1) 1269 | shape += 1; 1270 | a1 = shape - 1 / 3; 1271 | a2 = 1 / Math.sqrt(9 * a1); 1272 | do { 1273 | do { 1274 | x = jStat.randn(); 1275 | v = 1 + a2 * x; 1276 | } while(v <= 0); 1277 | v = v * v * v; 1278 | u = Math.random(); 1279 | } while(u > 1 - 0.331 * Math.pow(x, 4) && 1280 | Math.log(u) > 0.5 * x*x + a1 * (1 - v + Math.log(v))); 1281 | // alpha > 1 1282 | if (shape == oalph) 1283 | return a1 * v; 1284 | // alpha < 1 1285 | do { 1286 | u = Math.random(); 1287 | } while(u === 0); 1288 | return Math.pow(u, 1 / oalph) * a1 * v; 1289 | }; 1290 | 1291 | 1292 | // making use of static methods on the instance 1293 | (function(funcs) { 1294 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 1295 | jStat.fn[passfunc] = function() { 1296 | return jStat( 1297 | jStat.map(this, function(value) { return jStat[passfunc](value); })); 1298 | } 1299 | })(funcs[i]); 1300 | })('gammaln gammafn factorial factorialln'.split(' ')); 1301 | 1302 | 1303 | (function(funcs) { 1304 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 1305 | jStat.fn[passfunc] = function() { 1306 | return jStat(jStat[passfunc].apply(null, arguments)); 1307 | }; 1308 | })(funcs[i]); 1309 | })('randn'.split(' ')); 1310 | 1311 | }(this.jStat, Math)); 1312 | (function(jStat, Math) { 1313 | 1314 | // generate all distribution instance methods 1315 | (function(list) { 1316 | for (var i = 0; i < list.length; i++) (function(func) { 1317 | // distribution instance method 1318 | jStat[ func ] = function(a, b, c) { 1319 | if (!(this instanceof arguments.callee)) return new arguments.callee(a, b, c); 1320 | this._a = a; 1321 | this._b = b; 1322 | this._c = c; 1323 | return this; 1324 | }; 1325 | // distribution method to be used on a jStat instance 1326 | jStat.fn[ func ] = function(a, b, c) { 1327 | var newthis = jStat[ func ](a, b, c); 1328 | newthis.data = this; 1329 | return newthis; 1330 | }; 1331 | // sample instance method 1332 | jStat[ func ].prototype.sample = function(arr) { 1333 | var a = this._a, 1334 | b = this._b, 1335 | c = this._c; 1336 | if (arr) 1337 | return jStat.alter(arr, function() { 1338 | return jStat[ func ].sample(a, b, c); 1339 | }); 1340 | else 1341 | return jStat[ func ].sample(a, b, c); 1342 | }; 1343 | // generate the pdf, cdf and inv instance methods 1344 | (function(vals) { 1345 | for (var i = 0; i < vals.length; i++) (function(fnfunc) { 1346 | jStat[ func ].prototype[ fnfunc ] = function(x) { 1347 | var a = this._a, 1348 | b = this._b, 1349 | c = this._c; 1350 | if (!x) x = this.data; 1351 | if (typeof x !== 'number') { 1352 | return jStat.fn.map.call(x, function(x) { 1353 | return jStat[ func ][ fnfunc ](x, a, b, c); 1354 | }); 1355 | } 1356 | return jStat[ func ][ fnfunc ](x, a, b, c); 1357 | }; 1358 | })(vals[ i ]); 1359 | })('pdf cdf inv'.split(' ')); 1360 | // generate the mean, median, mode and variance instance methods 1361 | (function(vals) { 1362 | for (var i = 0; i < vals.length; i++) (function(fnfunc) { 1363 | jStat[ func ].prototype[ fnfunc ] = function() { 1364 | return jStat[ func ][ fnfunc ](this._a, this._b, this._c); 1365 | }; 1366 | })(vals[ i ]); 1367 | })('mean median mode variance'.split(' ')); 1368 | })(list[ i ]); 1369 | })(( 1370 | 'beta centralF cauchy chisquare exponential gamma invgamma kumaraswamy lognormal normal ' + 1371 | 'pareto studentt weibull uniform binomial negbin hypgeom poisson triangular' 1372 | ).split(' ')); 1373 | 1374 | 1375 | 1376 | // extend beta function with static methods 1377 | jStat.extend(jStat.beta, { 1378 | 1379 | pdf : function(x, alpha, beta) { 1380 | // PDF is zero outside the support 1381 | if (x > 1 || x < 0) { 1382 | return 0; 1383 | }; 1384 | // PDF is one for the uniform case 1385 | if (alpha == 1 && beta == 1) { 1386 | return 1; 1387 | }; 1388 | return Math.exp((alpha - 1) * Math.log(x) 1389 | + (beta - 1) * Math.log(1-x) 1390 | - jStat.betaln(alpha, beta)); 1391 | }, 1392 | 1393 | cdf : function(x, alpha, beta) { 1394 | return (x > 1 || x < 0) ? (x > 1) * 1 : jStat.ibeta(x, alpha, beta); 1395 | }, 1396 | 1397 | inv : function(x, alpha, beta) { 1398 | return jStat.ibetainv(x, alpha, beta); 1399 | }, 1400 | 1401 | mean : function(alpha, beta) { 1402 | return alpha / (alpha + beta); 1403 | }, 1404 | 1405 | median : function(alpha, beta) { 1406 | // TODO: implement beta median 1407 | }, 1408 | 1409 | mode : function(alpha, beta) { 1410 | return (alpha * beta) / (Math.pow(alpha + beta, 2) * (alpha + beta + 1)); 1411 | }, 1412 | 1413 | // return a random sample 1414 | sample : function(alpha, beta) { 1415 | var u = jStat.randg(alpha); 1416 | return u / (u + jStat.randg(beta)); 1417 | }, 1418 | 1419 | variance : function(alpha, beta) { 1420 | return (alpha * beta) / (Math.pow(alpha + beta, 2) * (alpha + beta + 1)); 1421 | } 1422 | }); 1423 | 1424 | // extend F function with static methods 1425 | jStat.extend(jStat.centralF, { 1426 | pdf : function(x, df1, df2) { 1427 | return (x >= 0) ? 1428 | Math.sqrt((Math.pow(df1 * x, df1) * Math.pow(df2, df2)) / (Math.pow(df1 * x + df2, df1 + df2))) / (x * jStat.betafn(df1/2, df2/2)) : undefined; 1429 | 1430 | }, 1431 | 1432 | cdf : function(x, df1, df2) { 1433 | return jStat.ibeta((df1 * x) / (df1 * x + df2), df1 / 2, df2 / 2); 1434 | }, 1435 | 1436 | inv : function(x, df1, df2) { 1437 | return df2 / (df1 * (1 / jStat.ibetainv(x, df1 / 2, df2 / 2) - 1)); 1438 | }, 1439 | 1440 | mean : function(df1, df2) { 1441 | return (df2 > 2) ? df2 / (df2 - 2) : undefined; 1442 | }, 1443 | 1444 | mode : function(df1, df2) { 1445 | return (df1 > 2) ? (df2 * (df1 - 2)) / (df1 * (df2 + 2)) : undefined; 1446 | }, 1447 | 1448 | // return a random sample 1449 | sample : function(df1, df2) { 1450 | var x1 = jStat.randg(df1 / 2) * 2; 1451 | var x2 = jStat.randg(df2 / 2) * 2; 1452 | return (x1 / df1) / (x2 / df2); 1453 | }, 1454 | 1455 | variance : function(df1, df2) { 1456 | return (df2 > 4) ? 2 * df2 * df2 * (df1 + df2 - 2) / (df1 * (df2 - 2) * (df2 - 2) * (df2 - 4)): undefined; 1457 | } 1458 | }); 1459 | 1460 | 1461 | // extend cauchy function with static methods 1462 | jStat.extend(jStat.cauchy, { 1463 | pdf : function(x, local, scale) { 1464 | return (scale / (Math.pow(x - local, 2) + Math.pow(scale, 2))) / Math.PI; 1465 | }, 1466 | 1467 | cdf : function(x, local, scale) { 1468 | return Math.atan((x - local) / scale) / Math.PI + 0.5; 1469 | }, 1470 | 1471 | inv : function(p, local, scale) { 1472 | return local + scale * Math.tan(Math.PI * (p - 0.5)); 1473 | }, 1474 | 1475 | median: function(local, scale) { 1476 | return local; 1477 | }, 1478 | 1479 | mode : function(local, scale) { 1480 | return local; 1481 | }, 1482 | 1483 | sample : function(local, scale) { 1484 | return jStat.randn() * Math.sqrt(1 / (2 * jStat.randg(0.5))) * scale + local; 1485 | } 1486 | }); 1487 | 1488 | 1489 | 1490 | // extend chisquare function with static methods 1491 | jStat.extend(jStat.chisquare, { 1492 | pdf : function(x, dof) { 1493 | return Math.exp((dof / 2 - 1) * Math.log(x) - x / 2 - (dof / 2) * Math.log(2) - jStat.gammaln(dof / 2)); 1494 | }, 1495 | 1496 | cdf : function(x, dof) { 1497 | return jStat.gammap(dof / 2, x / 2); 1498 | }, 1499 | 1500 | inv : function(p, dof) { 1501 | return 2 * jStat.gammapinv(p, 0.5 * dof); 1502 | }, 1503 | 1504 | mean : function(dof) { 1505 | return dof; 1506 | }, 1507 | 1508 | //TODO: this is an approximation (is there a better way?) 1509 | median : function(dof) { 1510 | return dof * Math.pow(1 - (2 / (9 * dof)), 3); 1511 | }, 1512 | 1513 | mode : function(dof) { 1514 | return (dof - 2 > 0) ? dof - 2 : 0; 1515 | }, 1516 | 1517 | sample : function(dof) { 1518 | return jStat.randg(dof / 2) * 2; 1519 | }, 1520 | 1521 | variance: function(dof) { 1522 | return 2 * dof; 1523 | } 1524 | }); 1525 | 1526 | 1527 | 1528 | // extend exponential function with static methods 1529 | jStat.extend(jStat.exponential, { 1530 | pdf : function(x, rate) { 1531 | return x < 0 ? 0 : rate * Math.exp(-rate * x); 1532 | }, 1533 | 1534 | cdf : function(x, rate) { 1535 | return x < 0 ? 0 : 1 - Math.exp(-rate * x); 1536 | }, 1537 | 1538 | inv : function(p, rate) { 1539 | return -Math.log(1 - p) / rate; 1540 | }, 1541 | 1542 | mean : function(rate) { 1543 | return 1 / rate; 1544 | }, 1545 | 1546 | median : function (rate) { 1547 | return (1 / rate) * Math.log(2); 1548 | }, 1549 | 1550 | mode : function(rate) { 1551 | return 0; 1552 | }, 1553 | 1554 | sample : function(rate) { 1555 | return -1 / rate * Math.log(Math.random()); 1556 | }, 1557 | 1558 | variance : function(rate) { 1559 | return Math.pow(rate, -2); 1560 | } 1561 | }); 1562 | 1563 | 1564 | 1565 | // extend gamma function with static methods 1566 | jStat.extend(jStat.gamma, { 1567 | pdf : function(x, shape, scale) { 1568 | return Math.exp((shape - 1) * Math.log(x) - x / scale - jStat.gammaln(shape) - shape * Math.log(scale)); 1569 | }, 1570 | 1571 | cdf : function(x, shape, scale) { 1572 | return jStat.gammap(shape, x / scale); 1573 | }, 1574 | 1575 | inv : function(p, shape, scale) { 1576 | return jStat.gammapinv(p, shape) * scale; 1577 | }, 1578 | 1579 | mean : function(shape, scale) { 1580 | return shape * scale; 1581 | }, 1582 | 1583 | mode : function(shape, scale) { 1584 | if(shape > 1) return (shape - 1) * scale; 1585 | return undefined; 1586 | }, 1587 | 1588 | sample : function(shape, scale) { 1589 | return jStat.randg(shape) * scale; 1590 | }, 1591 | 1592 | variance: function(shape, scale) { 1593 | return shape * scale * scale; 1594 | } 1595 | }); 1596 | 1597 | // extend inverse gamma function with static methods 1598 | jStat.extend(jStat.invgamma, { 1599 | pdf : function(x, shape, scale) { 1600 | return Math.exp(-(shape + 1) * Math.log(x) - scale/x - jStat.gammaln(shape) + shape * Math.log(scale)); 1601 | }, 1602 | 1603 | cdf : function(x, shape, scale) { 1604 | return 1 - jStat.gammap(shape, scale / x); 1605 | }, 1606 | 1607 | inv : function(p, shape, scale) { 1608 | return scale / jStat.gammapinv(1 - p, shape); 1609 | }, 1610 | 1611 | mean : function(shape, scale) { 1612 | return (shape > 1) ? scale / (shape - 1) : undefined; 1613 | }, 1614 | 1615 | mode : function(shape, scale) { 1616 | return scale / (shape + 1); 1617 | }, 1618 | 1619 | sample : function(shape, scale) { 1620 | return scale / jStat.randg(shape); 1621 | }, 1622 | 1623 | variance: function(shape, scale) { 1624 | return (shape > 2) ? scale * scale / ((shape - 1) * (shape - 1) * (shape - 2)): undefined; 1625 | } 1626 | }); 1627 | 1628 | 1629 | // extend kumaraswamy function with static methods 1630 | jStat.extend(jStat.kumaraswamy, { 1631 | pdf : function(x, alpha, beta) { 1632 | return Math.exp(Math.log(alpha) + Math.log(beta) + (alpha - 1) * Math.log(x) + (beta - 1) * Math.log(1 - Math.pow(x, alpha))); 1633 | }, 1634 | 1635 | cdf : function(x, alpha, beta) { 1636 | return (1 - Math.pow(1 - Math.pow(x, alpha), beta)); 1637 | }, 1638 | 1639 | mean : function(alpha, beta) { 1640 | return (beta * jStat.gammafn(1 + 1 / alpha) * jStat.gammafn(beta)) / (jStat.gammafn(1 + 1 / alpha + beta)); 1641 | }, 1642 | 1643 | median : function(alpha, beta) { 1644 | return Math.pow(1 - Math.pow(2, -1 / beta), 1 / alpha); 1645 | }, 1646 | 1647 | mode : function(alpha, beta) { 1648 | return (alpha >= 1 && beta >= 1 && (alpha !== 1 && beta !== 1)) ? Math.pow((alpha - 1) / (alpha * beta - 1), 1 / alpha) : undefined; 1649 | }, 1650 | 1651 | variance: function(alpha, beta) { 1652 | // TODO: complete this 1653 | } 1654 | }); 1655 | 1656 | 1657 | 1658 | // extend lognormal function with static methods 1659 | jStat.extend(jStat.lognormal, { 1660 | pdf : function(x, mu, sigma) { 1661 | return Math.exp(-Math.log(x) - 0.5 * Math.log(2 * Math.PI) - Math.log(sigma) - Math.pow(Math.log(x) - mu, 2) / (2 * sigma * sigma)); 1662 | }, 1663 | 1664 | cdf : function(x, mu, sigma) { 1665 | return 0.5 + (0.5 * jStat.erf((Math.log(x) - mu) / Math.sqrt(2 * sigma * sigma))); 1666 | }, 1667 | 1668 | inv : function(p, mu, sigma) { 1669 | return Math.exp(-1.41421356237309505 * sigma * jStat.erfcinv(2 * p) + mu); 1670 | }, 1671 | 1672 | mean : function(mu, sigma) { 1673 | return Math.exp(mu + sigma * sigma / 2); 1674 | }, 1675 | 1676 | median : function(mu, sigma) { 1677 | return Math.exp(mu); 1678 | }, 1679 | 1680 | mode : function(mu, sigma) { 1681 | return Math.exp(mu - sigma * sigma); 1682 | }, 1683 | 1684 | sample : function(mu, sigma) { 1685 | return Math.exp(jStat.randn() * sigma + mu); 1686 | }, 1687 | 1688 | variance : function(mu, sigma) { 1689 | return (Math.exp(sigma * sigma) - 1) * Math.exp(2 * mu + sigma * sigma); 1690 | } 1691 | }); 1692 | 1693 | 1694 | 1695 | // extend normal function with static methods 1696 | jStat.extend(jStat.normal, { 1697 | pdf : function(x, mean, std) { 1698 | return Math.exp(-0.5 * Math.log(2 * Math.PI) - Math.log(std) - Math.pow(x - mean, 2) / (2 * std * std)); 1699 | }, 1700 | 1701 | cdf : function(x, mean, std) { 1702 | return 0.5 * (1 + jStat.erf((x - mean) / Math.sqrt(2 * std * std))); 1703 | }, 1704 | 1705 | inv : function(p, mean, std) { 1706 | return -1.41421356237309505 * std * jStat.erfcinv(2 * p) + mean; 1707 | }, 1708 | 1709 | mean : function(mean, std) { 1710 | return mean; 1711 | }, 1712 | 1713 | median : function(mean, std) { 1714 | return mean; 1715 | }, 1716 | 1717 | mode : function (mean, std) { 1718 | return mean; 1719 | }, 1720 | 1721 | sample : function(mean, std) { 1722 | return jStat.randn() * std + mean; 1723 | }, 1724 | 1725 | variance : function(mean, std) { 1726 | return std * std; 1727 | } 1728 | }); 1729 | 1730 | 1731 | 1732 | // extend pareto function with static methods 1733 | jStat.extend(jStat.pareto, { 1734 | pdf : function(x, scale, shape) { 1735 | return (x > scale) ? (shape * Math.pow(scale, shape)) / Math.pow(x, shape + 1) : undefined; 1736 | }, 1737 | 1738 | cdf : function(x, scale, shape) { 1739 | return 1 - Math.pow(scale / x, shape); 1740 | }, 1741 | 1742 | mean : function(scale, shape) { 1743 | return (shape > 1) ? (shape * Math.pow(scale, shape)) / (shape - 1) : undefined; 1744 | }, 1745 | 1746 | median : function(scale, shape) { 1747 | return scale * (shape * Math.SQRT2); 1748 | }, 1749 | 1750 | mode : function(scale, shape) { 1751 | return scale; 1752 | }, 1753 | 1754 | variance : function(scale, shape) { 1755 | return (shape > 2) ? (scale*scale * shape) / (Math.pow(shape - 1, 2) * (shape - 2)) : undefined; 1756 | } 1757 | }); 1758 | 1759 | 1760 | 1761 | // extend studentt function with static methods 1762 | jStat.extend(jStat.studentt, { 1763 | pdf : function(x, dof) { 1764 | return (jStat.gammafn((dof + 1) / 2) / (Math.sqrt(dof * Math.PI) * jStat.gammafn(dof / 2))) * Math.pow(1 + ((x*x) / dof), -((dof + 1) / 2)); 1765 | }, 1766 | 1767 | cdf : function(x, dof) { 1768 | var dof2 = dof / 2; 1769 | return jStat.ibeta((x + Math.sqrt(x * x + dof)) / (2 * Math.sqrt(x * x + dof)), dof2, dof2); 1770 | }, 1771 | 1772 | inv : function(p, dof) { 1773 | var x = jStat.ibetainv(2 * Math.min(p, 1 - p), 0.5 * dof, 0.5); 1774 | x = Math.sqrt(dof * (1 - x) / x); 1775 | return (p > 0) ? x : -x; 1776 | }, 1777 | 1778 | mean : function(dof) { 1779 | return (dof > 1) ? 0 : undefined; 1780 | }, 1781 | 1782 | median : function (dof) { 1783 | return 0; 1784 | }, 1785 | 1786 | mode : function(dof) { 1787 | return 0; 1788 | }, 1789 | 1790 | sample : function(dof) { 1791 | return jStat.randn() * Math.sqrt(dof / (2 * jStat.randg(dof / 2))); 1792 | }, 1793 | 1794 | variance : function(dof) { 1795 | return (dof > 2) ? dof / (dof - 2) : (dof > 1) ? Infinity : undefined; 1796 | } 1797 | }); 1798 | 1799 | 1800 | 1801 | // extend weibull function with static methods 1802 | jStat.extend(jStat.weibull, { 1803 | pdf : function(x, scale, shape) { 1804 | return x < 0 ? 0 : (shape / scale) * Math.pow((x / scale),(shape - 1)) * Math.exp(-(Math.pow((x / scale), shape))); 1805 | }, 1806 | 1807 | cdf : function(x, scale, shape) { 1808 | return x < 0 ? 0 : 1 - Math.exp(-Math.pow((x / scale), shape)); 1809 | }, 1810 | 1811 | inv : function(p, scale, shape) { 1812 | return scale * Math.pow(-Math.log(1 - p), 1 / shape); 1813 | }, 1814 | 1815 | mean : function(scale, shape) { 1816 | return scale * jStat.gammafn(1 + 1 / shape); 1817 | }, 1818 | 1819 | median : function(scale, shape) { 1820 | return scale * Math.pow(Math.log(2), 1 / shape); 1821 | }, 1822 | 1823 | mode : function(scale, shape) { 1824 | return (shape > 1) ? scale * Math.pow((shape - 1) / shape, 1 / shape) : undefined; 1825 | }, 1826 | 1827 | sample : function(scale, shape) { 1828 | return scale * Math.pow(-Math.log(Math.random()), 1 / shape); 1829 | }, 1830 | 1831 | variance : function(scale, shape) { 1832 | return scale * scale * jStat.gammafn(1 + 2 / shape) - Math.pow(this.mean(scale, shape), 2); 1833 | } 1834 | }); 1835 | 1836 | 1837 | 1838 | // extend uniform function with static methods 1839 | jStat.extend(jStat.uniform, { 1840 | pdf : function(x, a, b) { 1841 | return (x < a || x > b) ? 0 : 1 / (b - a); 1842 | }, 1843 | 1844 | cdf : function(x, a, b) { 1845 | if (x < a) { 1846 | return 0; 1847 | } else if (x < b) { 1848 | return (x - a) / (b - a); 1849 | } 1850 | return 1; 1851 | }, 1852 | 1853 | mean : function(a, b) { 1854 | return 0.5 * (a + b); 1855 | }, 1856 | 1857 | median : function(a, b) { 1858 | return jStat.mean(a, b); 1859 | }, 1860 | 1861 | mode : function(a, b) { 1862 | // TODO: complete this 1863 | }, 1864 | 1865 | sample : function(a, b) { 1866 | return (a / 2 + b / 2) + (b / 2 - a / 2) * (2 * Math.random() - 1); 1867 | }, 1868 | 1869 | variance : function(a, b) { 1870 | return Math.pow(b - a, 2) / 12; 1871 | } 1872 | }); 1873 | 1874 | 1875 | 1876 | // extend uniform function with static methods 1877 | jStat.extend(jStat.binomial, { 1878 | pdf : function(k, n, p) { 1879 | return (p === 0 || p === 1) ? 1880 | ((n * p) === k ? 1 : 0) : 1881 | jStat.combination(n, k) * Math.pow(p, k) * Math.pow(1 - p, n - k); 1882 | }, 1883 | 1884 | cdf : function(x, n, p) { 1885 | var binomarr = [], 1886 | k = 0; 1887 | if (x < 0) { 1888 | return 0; 1889 | } 1890 | if (x < n) { 1891 | for (; k <= x; k++) { 1892 | binomarr[ k ] = jStat.binomial.pdf(k, n, p); 1893 | } 1894 | return jStat.sum(binomarr); 1895 | } 1896 | return 1; 1897 | } 1898 | }); 1899 | 1900 | 1901 | 1902 | // extend uniform function with static methods 1903 | jStat.extend(jStat.negbin, { 1904 | pdf : function(k, r, p) { 1905 | return k !== k | 0 1906 | ? false 1907 | : k < 0 1908 | ? 0 1909 | : jStat.combination(k + r - 1, k) * Math.pow(1 - p, r) * Math.pow(p, k); 1910 | }, 1911 | 1912 | cdf : function(x, r, p) { 1913 | var sum = 0, 1914 | k = 0; 1915 | if (x < 0) return 0; 1916 | for (; k <= x; k++) { 1917 | sum += jStat.negbin.pdf(k, r, p); 1918 | } 1919 | return sum; 1920 | } 1921 | }); 1922 | 1923 | 1924 | 1925 | // extend uniform function with static methods 1926 | jStat.extend(jStat.hypgeom, { 1927 | pdf : function(k, N, m, n) { 1928 | return k !== k | 0 1929 | ? false 1930 | : (k < 0) 1931 | ? 0 1932 | : jStat.combination(m, k) * jStat.combination(N - m , n - k) / jStat.combination(N, n); 1933 | }, 1934 | 1935 | cdf : function(x, N, m, n) { 1936 | var sum = 0, 1937 | k = 0; 1938 | if (x < 0) return 0; 1939 | for (; k <= x; k++) { 1940 | sum += jStat.hypgeom.pdf(k, N, m, n); 1941 | } 1942 | return sum; 1943 | } 1944 | }); 1945 | 1946 | 1947 | 1948 | // extend uniform function with static methods 1949 | jStat.extend(jStat.poisson, { 1950 | pdf : function(k, l) { 1951 | return Math.pow(l, k) * Math.exp(-l) / jStat.factorial(k); 1952 | }, 1953 | 1954 | cdf : function(x, l) { 1955 | var sumarr = [], 1956 | k = 0; 1957 | if (x < 0) return 0; 1958 | for (; k <= x; k++) { 1959 | sumarr.push(jStat.poisson.pdf(k, l)); 1960 | } 1961 | return jStat.sum(sumarr); 1962 | }, 1963 | 1964 | mean : function(l) { 1965 | return l; 1966 | }, 1967 | 1968 | variance : function(l) { 1969 | return l; 1970 | }, 1971 | 1972 | sample : function(l) { 1973 | var p = 1, k = 0, L = Math.exp(-l); 1974 | do { 1975 | k++; 1976 | p *= Math.random(); 1977 | } while (p > L); 1978 | return k - 1; 1979 | } 1980 | }); 1981 | 1982 | // extend triangular function with static methods 1983 | jStat.extend(jStat.triangular, { 1984 | pdf : function(x, a, b, c) { 1985 | return (b <= a || c < a || c > b) 1986 | ? undefined 1987 | : (x < a || x > b) 1988 | ? 0 1989 | : (x <= c) 1990 | ? (2 * (x - a)) / ((b - a) * (c - a)) 1991 | : (2 * (b - x)) / ((b - a) * (b - c)); 1992 | }, 1993 | 1994 | cdf : function(x, a, b, c) { 1995 | if (b <= a || c < a || c > b) 1996 | return undefined; 1997 | if (x < a) { 1998 | return 0; 1999 | } else { 2000 | if (x <= c) 2001 | return Math.pow(x - a, 2) / ((b - a) * (c - a)); 2002 | return 1 - Math.pow(b - x, 2) / ((b - a) * (b - c)); 2003 | } 2004 | // never reach this 2005 | return 1; 2006 | }, 2007 | 2008 | mean : function(a, b, c) { 2009 | return (a + b + c) / 3; 2010 | }, 2011 | 2012 | median : function(a, b, c) { 2013 | if (c <= (a + b) / 2) { 2014 | return b - Math.sqrt((b - a) * (b - c)) / Math.sqrt(2); 2015 | } else if (c > (a + b) / 2) { 2016 | return a + Math.sqrt((b - a) * (c - a)) / Math.sqrt(2); 2017 | } 2018 | }, 2019 | 2020 | mode : function(a, b, c) { 2021 | return c; 2022 | }, 2023 | 2024 | sample : function(a, b, c) { 2025 | var u = Math.random(); 2026 | return u < ((c - a) / (b - a)) ? 2027 | a + Math.sqrt(u * (b - a) * (c - a)) : b - Math.sqrt((1 - u) * (b - a) * (b - c)); 2028 | }, 2029 | 2030 | variance : function(a, b, c) { 2031 | return (a * a + b * b + c * c - a * b - a * c - b * c) / 18; 2032 | } 2033 | }); 2034 | 2035 | }(this.jStat, Math)); 2036 | /* Provides functions for the solution of linear system of equations, integration, extrapolation, 2037 | * interpolation, eigenvalue problems, differential equations and PCA analysis. */ 2038 | 2039 | (function(jStat, Math) { 2040 | 2041 | var push = Array.prototype.push, 2042 | isArray = jStat.utils.isArray; 2043 | 2044 | jStat.extend({ 2045 | 2046 | // add a vector/matrix to a vector/matrix or scalar 2047 | add : function(arr, arg) { 2048 | // check if arg is a vector or scalar 2049 | if (isArray(arg)) { 2050 | if (!isArray(arg[0])) arg = [ arg ]; 2051 | return jStat.map(arr, function(value, row, col) { return value + arg[row][col]; }); 2052 | } 2053 | return jStat.map(arr, function(value) { return value + arg; }); 2054 | }, 2055 | 2056 | // subtract a vector or scalar from the vector 2057 | subtract : function(arr, arg) { 2058 | // check if arg is a vector or scalar 2059 | if (isArray(arg)) { 2060 | if (!isArray(arg[0])) arg = [ arg ]; 2061 | return jStat.map(arr, function(value, row, col) { return value - arg[row][col] || 0; }); 2062 | } 2063 | return jStat.map(arr, function(value) { return value - arg; }); 2064 | }, 2065 | 2066 | // matrix division 2067 | divide : function(arr, arg) { 2068 | if (isArray(arg)) { 2069 | if (!isArray(arg[0])) arg = [ arg ]; 2070 | return jStat.multiply(arr, jStat.inv(arg)); 2071 | } 2072 | return jStat.map(arr, function(value) { return value / arg; }); 2073 | }, 2074 | 2075 | // matrix multiplication 2076 | multiply : function(arr, arg) { 2077 | var row, col, nrescols, sum, 2078 | nrow = arr.length, 2079 | ncol = arr[0].length, 2080 | res = jStat.zeros(nrow, nrescols = (isArray(arg)) ? arg[0].length : ncol), 2081 | rescols = 0; 2082 | if (isArray(arg)) { 2083 | for (; rescols < nrescols; rescols++) { 2084 | for (row = 0; row < nrow; row++) { 2085 | sum = 0; 2086 | for (col = 0; col < ncol; col++) 2087 | sum += arr[row][col] * arg[col][rescols]; 2088 | res[row][rescols] = sum; 2089 | } 2090 | } 2091 | return (nrow === 1 && rescols === 1) ? res[0][0] : res; 2092 | } 2093 | return jStat.map(arr, function(value) { return value * arg; }); 2094 | }, 2095 | 2096 | // Returns the dot product of two matricies 2097 | dot : function(arr, arg) { 2098 | if (!isArray(arr[0])) arr = [ arr ]; 2099 | if (!isArray(arg[0])) arg = [ arg ]; 2100 | // convert column to row vector 2101 | var left = (arr[0].length === 1 && arr.length !== 1) ? jStat.transpose(arr) : arr, 2102 | right = (arg[0].length === 1 && arg.length !== 1) ? jStat.transpose(arg) : arg, 2103 | res = [], 2104 | row = 0, 2105 | nrow = left.length, 2106 | ncol = left[0].length, 2107 | sum, col; 2108 | for (; row < nrow; row++) { 2109 | res[row] = []; 2110 | sum = 0; 2111 | for (col = 0; col < ncol; col++) 2112 | sum += left[row][col] * right[row][col]; 2113 | res[row] = sum; 2114 | } 2115 | return (res.length === 1) ? res[0] : res; 2116 | }, 2117 | 2118 | // raise every element by a scalar 2119 | pow : function(arr, arg) { 2120 | return jStat.map(arr, function(value) { return Math.pow(value, arg); }); 2121 | }, 2122 | 2123 | // generate the absolute values of the vector 2124 | abs : function(arr) { 2125 | return jStat.map(arr, function(value) { return Math.abs(value); }); 2126 | }, 2127 | 2128 | // TODO: make compatible with matrices 2129 | // computes the p-norm of the vector 2130 | norm : function(arr, p) { 2131 | var nnorm = 0, 2132 | i = 0; 2133 | // check the p-value of the norm, and set for most common case 2134 | if (isNaN(p)) p = 2; 2135 | // check if multi-dimensional array, and make vector correction 2136 | if (isArray(arr[0])) arr = arr[0]; 2137 | // vector norm 2138 | for (; i < arr.length; i++) { 2139 | nnorm += Math.pow(Math.abs(arr[i]), p); 2140 | } 2141 | return Math.pow(nnorm, 1 / p); 2142 | }, 2143 | 2144 | // TODO: make compatible with matrices 2145 | // computes the angle between two vectors in rads 2146 | angle : function(arr, arg) { 2147 | return Math.acos(jStat.dot(arr, arg) / (jStat.norm(arr) * jStat.norm(arg))); 2148 | }, 2149 | 2150 | // augment one matrix by another 2151 | aug : function(a, b) { 2152 | var newarr = a.slice(), 2153 | i = 0; 2154 | for (; i < newarr.length; i++) { 2155 | push.apply(newarr[i], b[i]); 2156 | } 2157 | return newarr; 2158 | }, 2159 | 2160 | inv : function(a) { 2161 | var rows = a.length, 2162 | cols = a[0].length, 2163 | b = jStat.identity(rows, cols), 2164 | c = jStat.gauss_jordan(a, b), 2165 | obj = [], 2166 | i = 0, 2167 | j; 2168 | for (; i < rows; i++) { 2169 | obj[i] = []; 2170 | for (j = cols - 1; j < c[0].length; j++) 2171 | obj[i][j - cols] = c[i][j]; 2172 | } 2173 | return obj; 2174 | }, 2175 | 2176 | // calculate the determinant of a matrix 2177 | det : function(a) { 2178 | var alen = a.length, 2179 | alend = alen * 2, 2180 | vals = new Array(alend), 2181 | rowshift = alen - 1, 2182 | colshift = alend - 1, 2183 | mrow = rowshift - alen + 1, 2184 | mcol = colshift, 2185 | i = 0, 2186 | result = 0, 2187 | j; 2188 | // check for special 2x2 case 2189 | if (alen === 2) { 2190 | return a[0][0] * a[1][1] - a[0][1] * a[1][0]; 2191 | } 2192 | for (; i < alend; i++) { 2193 | vals[i] = 1; 2194 | } 2195 | for (i = 0; i < alen; i++) { 2196 | for (j = 0; j < alen; j++) { 2197 | vals[(mrow < 0) ? mrow + alen : mrow ] *= a[i][j]; 2198 | vals[(mcol < alen) ? mcol + alen : mcol ] *= a[i][j]; 2199 | mrow++; 2200 | mcol--; 2201 | } 2202 | mrow = --rowshift - alen + 1; 2203 | mcol = --colshift; 2204 | } 2205 | for (i = 0; i < alen; i++) { 2206 | result += vals[i]; 2207 | } 2208 | for (; i < alend; i++) { 2209 | result -= vals[i]; 2210 | } 2211 | return result; 2212 | }, 2213 | 2214 | gauss_elimination : function(a, b) { 2215 | var i = 0, 2216 | j = 0, 2217 | n = a.length, 2218 | m = a[0].length, 2219 | factor = 1, 2220 | sum = 0, 2221 | x = [], 2222 | maug, pivot, temp, k; 2223 | a = jStat.aug(a, b); 2224 | maug = a[0].length; 2225 | for(; i < n; i++) { 2226 | pivot = a[i][i]; 2227 | j = i; 2228 | for (k = i + 1; k < m; k++) { 2229 | if (pivot < Math.abs(a[k][i])) { 2230 | pivot = a[k][i]; 2231 | j = k; 2232 | } 2233 | } 2234 | if (j != i) { 2235 | for(k = 0; k < maug; k++) { 2236 | temp = a[i][k]; 2237 | a[i][k] = a[j][k]; 2238 | a[j][k] = temp; 2239 | } 2240 | } 2241 | for (j = i + 1; j < n; j++) { 2242 | factor = a[j][i] / a[i][i]; 2243 | for(k = i; k < maug; k++) { 2244 | a[j][k] = a[j][k] - factor * a[i][k]; 2245 | } 2246 | } 2247 | } 2248 | for (i = n - 1; i >= 0; i--) { 2249 | sum = 0; 2250 | for (j = i + 1; j<= n - 1; j++) { 2251 | sum = x[j] * a[i][j]; 2252 | } 2253 | x[i] =(a[i][maug - 1] - sum) / a[i][i]; 2254 | } 2255 | return x; 2256 | }, 2257 | 2258 | gauss_jordan : function(a, b) { 2259 | var m = jStat.aug(a, b), 2260 | h = m.length, 2261 | w = m[0].length; 2262 | // find max pivot 2263 | for (var y = 0; y < h; y++) { 2264 | var maxrow = y; 2265 | for (var y2 = y+1; y2 < h; y2++) { 2266 | if (Math.abs(m[y2][y]) > Math.abs(m[maxrow][y])) 2267 | maxrow = y2; 2268 | } 2269 | var tmp = m[y]; 2270 | m[y] = m[maxrow]; 2271 | m[maxrow] = tmp 2272 | for (var y2 = y+1; y2 < h; y2++) { 2273 | c = m[y2][y] / m[y][y]; 2274 | for (var x = y; x < w; x++) { 2275 | m[y2][x] -= m[y][x] * c; 2276 | } 2277 | } 2278 | } 2279 | // backsubstitute 2280 | for (var y = h-1; y >= 0; y--) { 2281 | c = m[y][y]; 2282 | for (var y2 = 0; y2 < y; y2++) { 2283 | for (var x = w-1; x > y-1; x--) { 2284 | m[y2][x] -= m[y][x] * m[y2][y] / c; 2285 | } 2286 | } 2287 | m[y][y] /= c; 2288 | for (var x = h; x < w; x++) { 2289 | m[y][x] /= c; 2290 | } 2291 | } 2292 | return m; 2293 | }, 2294 | 2295 | lu : function(a, b) { 2296 | //TODO 2297 | }, 2298 | 2299 | cholesky : function(a, b) { 2300 | //TODO 2301 | }, 2302 | 2303 | gauss_jacobi : function(a, b, x, r) { 2304 | var i = 0, 2305 | j = 0, 2306 | n = a.length, 2307 | l = [], 2308 | u = [], 2309 | d = [], 2310 | xv, c, h, xk; 2311 | for (; i < n; i++) { 2312 | l[i] = []; 2313 | u[i] = []; 2314 | d[i] = []; 2315 | for (j = 0; j < n; j++) { 2316 | if (i > j) { 2317 | l[i][j] = a[i][j]; 2318 | u[i][j] = d[i][j] = 0; 2319 | } else if (i < j) { 2320 | u[i][j] = a[i][j]; 2321 | l[i][j] = d[i][j] = 0; 2322 | } else { 2323 | d[i][j] = a[i][j]; 2324 | l[i][j] = u[i][j] = 0; 2325 | } 2326 | } 2327 | } 2328 | h = jStat.multiply(jStat.multiply(jStat.inv(d), jStat.add(l, u)), -1); 2329 | c = jStat.multiply(jStat.inv(d), b); 2330 | xv = x; 2331 | xk = jStat.add(jStat.multiply(h, x), c); 2332 | i = 2; 2333 | while (Math.abs(jStat.norm(jStat.subtract(xk,xv))) > r) { 2334 | xv = xk; 2335 | xk = jStat.add(jStat.multiply(h, xv), c); 2336 | i++; 2337 | } 2338 | return xk; 2339 | }, 2340 | 2341 | gauss_seidel : function(a, b, x, r) { 2342 | var i = 0, 2343 | n = a.length, 2344 | l = [], 2345 | u = [], 2346 | d = [], 2347 | j, xv, c, h, xk; 2348 | for (; i < n; i++) { 2349 | l[i] = []; 2350 | u[i] = []; 2351 | d[i] = []; 2352 | for (j = 0; j < n; j++) { 2353 | if (i > j) { 2354 | l[i][j] = a[i][j]; 2355 | u[i][j] = d[i][j] = 0; 2356 | } else if (i < j) { 2357 | u[i][j] = a[i][j]; 2358 | l[i][j] = d[i][j] = 0; 2359 | } else { 2360 | d[i][j] = a[i][j]; 2361 | l[i][j] = u[i][j] = 0; 2362 | } 2363 | } 2364 | } 2365 | h = jStat.multiply(jStat.multiply(jStat.inv(jStat.add(d, l)), u), -1); 2366 | c = jStat.multiply(jStat.inv(jStat.add(d, l)), b); 2367 | xv = x; 2368 | xk = jStat.add(jStat.multiply(h, x), c); 2369 | i = 2; 2370 | while (Math.abs(jStat.norm(jStat.subtract(xk, xv))) > r) { 2371 | xv = xk; 2372 | xk = jStat.add(jStat.multiply(h, xv), c); 2373 | i = i + 1; 2374 | } 2375 | return xk; 2376 | }, 2377 | 2378 | SOR : function(a, b, x, r, w) { 2379 | var i = 0, 2380 | n = a.length, 2381 | l = [], 2382 | u = [], 2383 | d = [], 2384 | j, xv, c, h, xk; 2385 | for (; i < n; i++) { 2386 | l[i] = []; 2387 | u[i] = []; 2388 | d[i] = []; 2389 | for (j = 0; j < n; j++) { 2390 | if (i > j) { 2391 | l[i][j] = a[i][j]; 2392 | u[i][j] = d[i][j] = 0; 2393 | } else if (i < j) { 2394 | u[i][j] = a[i][j]; 2395 | l[i][j] = d[i][j] = 0; 2396 | } else { 2397 | d[i][j] = a[i][j]; 2398 | l[i][j] = u[i][j] = 0; 2399 | } 2400 | } 2401 | } 2402 | h = jStat.multiply(jStat.inv(jStat.add(d, jStat.multiply(l, w))), jStat.subtract(jStat.multiply(d, 1 - w), jStat.multiply(u, w))); 2403 | c = jStat.multiply(jStat.multiply(jStat.inv(jStat.add(d, jStat.multiply(l, w))), b), w); 2404 | xv = x; 2405 | xk = jStat.add(jStat.multiply(h, x), c); 2406 | i = 2; 2407 | while (Math.abs(jStat.norm(jStat.subtract(xk, xv))) > r) { 2408 | xv = xk; 2409 | xk = jStat.add(jStat.multiply(h, xv), c); 2410 | i++; 2411 | } 2412 | return xk; 2413 | }, 2414 | 2415 | householder : function(a) { 2416 | var m = a.length, 2417 | n = a[0].length, 2418 | i = 0, 2419 | w = [], 2420 | p = [], 2421 | alpha, r, k, j, factor; 2422 | for (; i < m - 1; i++) { 2423 | alpha = 0; 2424 | for (j = i + 1; j < n; j++) 2425 | alpha += (a[j][i] * a[j][i]); 2426 | factor = (a[i + 1][i] > 0) ? -1 : 1; 2427 | alpha = factor * Math.sqrt(alpha); 2428 | r = Math.sqrt((((alpha * alpha) - a[i + 1][i] * alpha) / 2)); 2429 | w = jStat.zeros(m, 1); 2430 | w[i + 1][0] = (a[i + 1][i] - alpha) / (2 * r); 2431 | for (k = i + 2; k < m; k++) w[k][0] = a[k][i] / (2 * r); 2432 | p = jStat.subtract(jStat.identity(m, n), jStat.multiply(jStat.multiply(w, jStat.transpose(w)), 2)); 2433 | a = jStat.multiply(p, jStat.multiply(a, p)); 2434 | } 2435 | return a; 2436 | }, 2437 | 2438 | // TODO: not working properly. 2439 | QR : function(a, b) { 2440 | var m = a.length, 2441 | n = a[0].length, 2442 | i = 0, 2443 | w = [], 2444 | p = [], 2445 | x = [], 2446 | j, alpha, r, k, factor,sum; 2447 | for (; i < m - 1; i++) { 2448 | alpha = 0; 2449 | for (j = i + 1; j < n; j++) 2450 | alpha += (a[j][i] * a[j][i]); 2451 | factor = (a[i + 1][i] > 0) ? -1 : 1; 2452 | alpha = factor * Math.sqrt(alpha); 2453 | r = Math.sqrt((((alpha * alpha) - a[i + 1][i] * alpha) / 2)); 2454 | w = jStat.zeros(m, 1); 2455 | w[i + 1][0] = (a[i + 1][i] - alpha) / (2 * r); 2456 | for (k = i + 2; k < m; k++) w[k][0] = a[k][i] / (2 * r); 2457 | p = jStat.subtract(jStat.identity(m, n), jStat.multiply(jStat.multiply(w, jStat.transpose(w)), 2)); 2458 | a = jStat.multiply(p, a); 2459 | b = jStat.multiply(p, b); 2460 | } 2461 | for (i = m - 1; i >= 0; i--) { 2462 | sum = 0; 2463 | for (j = i + 1; j <= n - 1; j++) 2464 | sum = x[j] * a[i][j]; 2465 | x[i] = b[i][0] / a[i][i]; 2466 | } 2467 | return x; 2468 | }, 2469 | 2470 | jacobi : function(a) { 2471 | var condition = 1, 2472 | count = 0, 2473 | n = a.length, 2474 | e = jStat.identity(n, n), 2475 | ev = [], 2476 | b, i, j, p, q, maxim, theta, s; 2477 | // condition === 1 only if tolerance is not reached 2478 | while (condition === 1) { 2479 | count++; 2480 | maxim = a[0][1]; 2481 | p = 0; 2482 | q = 1; 2483 | for (i = 0; i < n; i++) { 2484 | for (j = 0; j < n; j++) { 2485 | if (i != j) { 2486 | if (maxim < Math.abs(a[i][j])) { 2487 | maxim = Math.abs(a[i][j]); 2488 | p = i; 2489 | q = j; 2490 | } 2491 | } 2492 | } 2493 | } 2494 | if (a[p][p] === a[q][q]) 2495 | theta = (a[p][q] > 0) ? Math.PI / 4 : -Math.PI / 4; 2496 | else 2497 | theta = Math.atan(2 * a[p][q] / (a[p][p] - a[q][q])) / 2; 2498 | s = jStat.identity(n, n); 2499 | s[p][p] = Math.cos(theta); 2500 | s[p][q] = -Math.sin(theta); 2501 | s[q][p] = Math.sin(theta); 2502 | s[q][q] = Math.cos(theta); 2503 | // eigen vector matrix 2504 | e = jStat.multiply(e, s); 2505 | b = jStat.multiply(jStat.multiply(jStat.inv(s), a), s); 2506 | a = b; 2507 | condition = 0; 2508 | for (i = 1; i < n; i++) { 2509 | for (j = 1; j < n; j++) { 2510 | if (i != j && Math.abs(a[i][j]) > 0.001) { 2511 | condition = 1; 2512 | } 2513 | } 2514 | } 2515 | } 2516 | for(i = 0; i < n; i++) ev.push(a[i][i]); 2517 | //returns both the eigenvalue and eigenmatrix 2518 | return [e, ev]; 2519 | }, 2520 | 2521 | rungekutta : function(f, h, p, t_j, u_j, order) { 2522 | var k1, k2, u_j1, k3, k4; 2523 | if (order === 2) { 2524 | while (t_j <= p) { 2525 | k1 = h * f(t_j, u_j); 2526 | k2 = h * f(t_j + h, u_j + k1); 2527 | u_j1 = u_j + (k1 + k2) / 2; 2528 | u_j = u_j1; 2529 | t_j = t_j + h; 2530 | } 2531 | } 2532 | if (order === 4) { 2533 | while (t_j <= p) { 2534 | k1 = h * f(t_j, u_j); 2535 | k2 = h * f(t_j + h / 2, u_j + k1 / 2); 2536 | k3 = h * f(t_j + h / 2, u_j + k2 / 2); 2537 | k4 = h * f(t_j +h, u_j + k3); 2538 | u_j1 = u_j + (k1 + 2 * k2 + 2 * k3 + k4) / 6; 2539 | u_j = u_j1; 2540 | t_j = t_j + h; 2541 | } 2542 | } 2543 | return u_j; 2544 | }, 2545 | 2546 | romberg : function(f, a, b, order) { 2547 | var i = 0, 2548 | h = (b - a) / 2, 2549 | x = [], 2550 | h1 = [], 2551 | g = [], 2552 | m, a1, j, k, I, d; 2553 | while (i < order / 2) { 2554 | I = f(a); 2555 | for (j = a, k = 0; j <= b; j = j + h, k++) x[k] = j; 2556 | m = x.length; 2557 | for (j = 1; j < m - 1; j++) { 2558 | I += (((j % 2) !== 0) ? 4 : 2) * f(x[j]); 2559 | } 2560 | I = (h / 3) * (I + f(b)); 2561 | g[i] = I; 2562 | h /= 2; 2563 | i++; 2564 | } 2565 | a1 = g.length; 2566 | m = 1; 2567 | while (a1 !== 1) { 2568 | for (j = 0; j < a1 - 1; j++) 2569 | h1[j] = ((Math.pow(4, m)) * g[j + 1] - g[j]) / (Math.pow(4, m) - 1); 2570 | a1 = h1.length; 2571 | g = h1; 2572 | h1 = []; 2573 | m++; 2574 | } 2575 | return g; 2576 | }, 2577 | 2578 | richardson : function(X, f, x, h) { 2579 | function pos(X, x) { 2580 | var i = 0, 2581 | n = X.length, 2582 | p; 2583 | for (; i < n; i++) 2584 | if (X[i] === x) p = i; 2585 | return p; 2586 | } 2587 | var n = X.length, 2588 | h_min = Math.abs(x - X[pos(X, x) + 1]), 2589 | i = 0, 2590 | g = [], 2591 | h1 = [], 2592 | y1, y2, m, a, j; 2593 | while (h >= h_min) { 2594 | y1 = pos(X, x + h); 2595 | y2 = pos(X, x); 2596 | g[i] = (f[y1] - 2 * f[y2] + f[2 * y2 - y1]) / (h * h); 2597 | h /= 2; 2598 | i++; 2599 | } 2600 | a = g.length; 2601 | m = 1; 2602 | while (a != 1) { 2603 | for (j = 0; j < a - 1; j++) 2604 | h1[j] = ((Math.pow(4, m)) * g[j + 1] - g[j]) / (Math.pow(4, m) - 1); 2605 | a = h1.length; 2606 | g = h1; 2607 | h1 = []; 2608 | m++; 2609 | } 2610 | return g; 2611 | }, 2612 | 2613 | simpson : function(f, a, b, n) { 2614 | var h = (b - a) / n, 2615 | I = f(a), 2616 | x = [], 2617 | j = a, 2618 | k = 0, 2619 | i = 1, 2620 | m; 2621 | for (; j <= b; j = j + h, k++) 2622 | x[k] = j; 2623 | m = x.length; 2624 | for (; i < m - 1; i++) { 2625 | I += ((i % 2 !== 0) ? 4 : 2) * f(x[i]); 2626 | } 2627 | return (h / 3) * (I + f(b)); 2628 | }, 2629 | 2630 | hermite : function(X, F, dF, value) { 2631 | var n = X.length, 2632 | p = 0, 2633 | i = 0, 2634 | l = [], 2635 | dl = [], 2636 | A = [], 2637 | B = [], 2638 | j; 2639 | for (; i < n; i++) { 2640 | l[i] = 1; 2641 | for (j = 0; j < n; j++) { 2642 | if (i != j) l[i] *= (value - X[j]) / (X[i] - X[j]); 2643 | } 2644 | dl[i] = 0; 2645 | for (j = 0; j < n; j++) { 2646 | if (i != j) dl[i] += 1 / (X [i] - X[j]); 2647 | } 2648 | A[i] = (1 - 2 * (value - X[i]) * dl[i]) * (l[i] * l[i]); 2649 | B[i] = (value - X[i]) * (l[i] * l[i]); 2650 | p += (A[i] * F[i] + B[i] * dF[i]); 2651 | } 2652 | return p; 2653 | }, 2654 | 2655 | lagrange : function(X, F, value) { 2656 | var p = 0, 2657 | i = 0, 2658 | j, l, 2659 | n = X.length; 2660 | for (; i < n; i++) { 2661 | l = F[i]; 2662 | for (j = 0; j < n; j++) { 2663 | // calculating the lagrange polynomial L_i 2664 | if (i != j) l *= (value - X[j]) / (X[i] - X[j]); 2665 | } 2666 | // adding the lagrange polynomials found above 2667 | p += l; 2668 | } 2669 | return p; 2670 | }, 2671 | 2672 | cubic_spline : function(X, F, value) { 2673 | var n = X.length, 2674 | i = 0, j, 2675 | A = [], 2676 | B = [], 2677 | alpha = [], 2678 | c = [], 2679 | h = [], 2680 | b = [], 2681 | d = []; 2682 | for (; i < n - 1; i++) 2683 | h[i] = X[i + 1] - X[i]; 2684 | alpha[0] = 0; 2685 | for (i = 1; i < n - 1; i++) 2686 | alpha[i] = (3 / h[i]) * (F[i + 1] - F[i]) - (3 / h[i-1]) * (F[i] - F[i-1]); 2687 | for (i = 1; i < n - 1; i++) { 2688 | A[i] = []; 2689 | B[i] = []; 2690 | A[i][i-1] = h[i-1]; 2691 | A[i][i] = 2 * (h[i - 1] + h[i]); 2692 | A[i][i+1] = h[i]; 2693 | B[i][0] = alpha[i]; 2694 | } 2695 | c = jStat.multiply(jStat.inv(A), B); 2696 | for (j = 0; j < n - 1; j++) { 2697 | b[j] = (F[j + 1] - F[j]) / h[j] - h[j] * (c[j + 1][0] + 2 * c[j][0]) / 3; 2698 | d[j] = (c[j + 1][0] - c[j][0]) / (3 * h[j]); 2699 | } 2700 | for (j = 0; j < n; j++) { 2701 | if (X[j] > value) break; 2702 | } 2703 | j -= 1; 2704 | return F[j] + (value - X[j]) * b[j] + jStat.sq(value-X[j]) * c[j] + (value - X[j]) * jStat.sq(value - X[j]) * d[j]; 2705 | }, 2706 | 2707 | gauss_quadrature : function() { 2708 | //TODO 2709 | }, 2710 | 2711 | PCA : function(X) { 2712 | var m = X.length, 2713 | n = X[0].length, 2714 | flag = false, 2715 | i = 0, 2716 | j, temp1, 2717 | u = [], 2718 | D = [], 2719 | result = [], 2720 | temp2 = [], 2721 | Y = [], 2722 | Bt = [], 2723 | B = [], 2724 | C = [], 2725 | V = [], 2726 | Vt = []; 2727 | for (i = 0; i < m; i++) { 2728 | u[i] = jStat.sum(X[i]) / n; 2729 | } 2730 | for (i = 0; i < n; i++) { 2731 | B[i] = []; 2732 | for(j = 0; j < m; j++) { 2733 | B[i][j] = X[j][i] - u[j]; 2734 | } 2735 | } 2736 | B = jStat.transpose(B); 2737 | for (i = 0; i < m; i++) { 2738 | C[i] = []; 2739 | for (j = 0; j < m; j++) { 2740 | C[i][j] = (jStat.dot([B[i]], [B[j]])) / (n - 1); 2741 | } 2742 | } 2743 | result = jStat.jacobi(C); 2744 | V = result[0]; 2745 | D = result[1]; 2746 | Vt = jStat.transpose(V); 2747 | for (i = 0; i < D.length; i++) { 2748 | for (j = i; j < D.length; j++) { 2749 | if(D[i] < D[j]) { 2750 | temp1 = D[i]; 2751 | D[i] = D[j]; 2752 | D[j] = temp1; 2753 | temp2 = Vt[i]; 2754 | Vt[i] = Vt[j]; 2755 | Vt[j] = temp2; 2756 | } 2757 | } 2758 | } 2759 | Bt = jStat.transpose(B); 2760 | for (i = 0; i < m; i++) { 2761 | Y[i] = []; 2762 | for (j = 0; j < Bt.length; j++) { 2763 | Y[i][j] = jStat.dot([Vt[i]], [Bt[j]]); 2764 | } 2765 | } 2766 | return [X, D, Vt, Y]; 2767 | } 2768 | }); 2769 | 2770 | // extend jStat.fn with methods that require one argument 2771 | (function(funcs) { 2772 | for (var i = 0; i < funcs.length; i++) (function(passfunc) { 2773 | jStat.fn[ passfunc ] = function(arg, func) { 2774 | var tmpthis = this; 2775 | // check for callback 2776 | if (func) { 2777 | setTimeout(function() { 2778 | func.call(tmpthis, jStat.fn[ passfunc ].call(tmpthis, arg)); 2779 | }, 15); 2780 | return this; 2781 | } 2782 | return jStat(jStat[ passfunc ](this, arg)); 2783 | }; 2784 | }(funcs[i])); 2785 | }('add divide multiply subtract dot pow abs norm angle'.split(' '))); 2786 | 2787 | }(this.jStat, Math)); 2788 | (function(jStat, Math) { 2789 | 2790 | var slice = [].slice, 2791 | isNumber = jStat.utils.isNumber; 2792 | 2793 | // flag==true denotes use of sample standard deviation 2794 | // Z Statistics 2795 | jStat.extend({ 2796 | // 2 different parameter lists: 2797 | // (value, mean, sd) 2798 | // (value, array, flag) 2799 | zscore : function() { 2800 | var args = slice.call(arguments); 2801 | if (isNumber(args[1])) { 2802 | return (args[0] - args[1]) / args[2]; 2803 | } 2804 | return (args[0] - jStat.mean(args[1])) / jStat.stdev(args[1], args[2]); 2805 | }, 2806 | 2807 | // 3 different paramter lists: 2808 | // (value, mean, sd, sides) 2809 | // (zscore, sides) 2810 | // (value, array, sides, flag) 2811 | ztest : function() { 2812 | var args = slice.call(arguments); 2813 | if (args.length === 4) { 2814 | if(isNumber(args[1])) { 2815 | var z = jStat.zscore(args[0],args[1],args[2]) 2816 | return (args[3] === 1) ? 2817 | (jStat.normal.cdf(-Math.abs(z),0,1)) : 2818 | (jStat.normal.cdf(-Math.abs(z),0,1)* 2); 2819 | } 2820 | var z = args[0] 2821 | return (args[2] === 1) ? 2822 | (jStat.normal.cdf(-Math.abs(z),0,1)) : 2823 | (jStat.normal.cdf(-Math.abs(z),0,1)*2); 2824 | } 2825 | var z = jStat.zscore(args[0],args[1],args[3]) 2826 | return (args[1] === 1) ? 2827 | (jStat.normal.cdf(-Math.abs(z), 0, 1)) : 2828 | (jStat.normal.cdf(-Math.abs(z), 0, 1)*2); 2829 | } 2830 | }); 2831 | 2832 | jStat.extend(jStat.fn, { 2833 | zscore : function(value, flag) { 2834 | return (value - this.mean()) / this.stdev(flag); 2835 | }, 2836 | 2837 | ztest : function(value, sides, flag) { 2838 | var zscore = Math.abs(this.zscore(value, flag)); 2839 | return (sides === 1) ? 2840 | (jStat.normal.cdf(-zscore, 0, 1)) : 2841 | (jStat.normal.cdf(-zscore, 0, 1) * 2); 2842 | } 2843 | }); 2844 | 2845 | // T Statistics 2846 | jStat.extend({ 2847 | // 2 parameter lists 2848 | // (value, mean, sd, n) 2849 | // (value, array) 2850 | tscore : function() { 2851 | var args = slice.call(arguments); 2852 | return (args.length === 4) ? 2853 | ((args[0] - args[1]) / (args[2] / Math.sqrt(args[3]))) : 2854 | ((args[0] - jStat.mean(args[1])) / (jStat.stdev(args[1], true) / Math.sqrt(args[1].length))); 2855 | }, 2856 | 2857 | // 3 different paramter lists: 2858 | // (value, mean, sd, n, sides) 2859 | // (tscore, n, sides) 2860 | // (value, array, sides) 2861 | ttest : function() { 2862 | var args = slice.call(arguments); 2863 | var tscore; 2864 | if (args.length === 5) { 2865 | tscore = Math.abs(jStat.tscore(args[0], args[1], args[2], args[3])); 2866 | return (args[4] === 1) ? 2867 | (jStat.studentt.cdf(-tscore, args[3]-1)) : 2868 | (jStat.studentt.cdf(-tscore, args[3]-1)*2); 2869 | } 2870 | if (isNumber(args[1])) { 2871 | tscore = Math.abs(args[0]) 2872 | return (args[2] == 1) ? 2873 | (jStat.studentt.cdf(-tscore, args[1]-1)) : 2874 | (jStat.studentt.cdf(-tscore, args[1]-1) * 2); 2875 | } 2876 | tscore = Math.abs(jStat.tscore(args[0], args[1])) 2877 | return (args[2] == 1) ? 2878 | (jStat.studentt.cdf(-tscore, args[1].length-1)) : 2879 | (jStat.studentt.cdf(-tscore, args[1].length-1) * 2); 2880 | } 2881 | }); 2882 | 2883 | jStat.extend(jStat.fn, { 2884 | tscore : function(value) { 2885 | return (value - this.mean()) / (this.stdev(true) / Math.sqrt(this.cols())); 2886 | }, 2887 | 2888 | ttest : function(value, sides) { 2889 | return (sides === 1) ? 2890 | (1 - jStat.studentt.cdf(Math.abs(this.tscore(value)), this.cols()-1)) : 2891 | (jStat.studentt.cdf(-Math.abs(this.tscore(value)), this.cols()-1)*2); 2892 | } 2893 | }); 2894 | 2895 | // F Statistics 2896 | jStat.extend({ 2897 | // Paramter list is as follows: 2898 | // (array1, array2, array3, ...) 2899 | // or it is an array of arrays 2900 | // array of arrays conversion 2901 | anovafscore : function() { 2902 | var args = slice.call(arguments), 2903 | expVar, sample, sampMean, sampSampMean, tmpargs, unexpVar, i, j; 2904 | if (args.length === 1) { 2905 | tmpargs = new Array(args[0].length); 2906 | for (i = 0; i < args[0].length; i++) { 2907 | tmpargs[i] = args[0][i]; 2908 | } 2909 | args = tmpargs; 2910 | } 2911 | // 2 sample case 2912 | if (args.length === 2) { 2913 | return jStat.variance(args[0]) / jStat.variance(args[1]); 2914 | } 2915 | // Builds sample array 2916 | sample = new Array(); 2917 | for (i = 0; i < args.length; i++) { 2918 | sample = sample.concat(args[i]); 2919 | } 2920 | sampMean = jStat.mean(sample); 2921 | // Computes the explained variance 2922 | expVar = 0; 2923 | for (i = 0; i < args.length; i++) { 2924 | expVar = expVar + args[i].length * Math.pow(jStat.mean(args[i]) - sampMean, 2); 2925 | } 2926 | expVar /= (args.length - 1); 2927 | // Computes unexplained variance 2928 | unexpVar = 0; 2929 | for (i = 0; i < args.length; i++) { 2930 | sampSampMean = jStat.mean(args[i]); 2931 | for (j = 0; j < args[i].length; j++) { 2932 | unexpVar += Math.pow(args[i][j] - sampSampMean, 2); 2933 | } 2934 | } 2935 | unexpVar /= (sample.length - args.length); 2936 | return expVar / unexpVar; 2937 | }, 2938 | 2939 | // 2 different paramter setups 2940 | // (array1, array2, array3, ...) 2941 | // (anovafscore, df1, df2) 2942 | anovaftest : function() { 2943 | var args = slice.call(arguments), 2944 | df1, df2, n, i; 2945 | if (isNumber(args[0])) { 2946 | return 1 - jStat.centralF.cdf(args[0], args[1], args[2]); 2947 | } 2948 | anovafscore = jStat.anovafscore(args); 2949 | df1 = args.length - 1; 2950 | n = 0; 2951 | for (i = 0; i < args.length; i++) { 2952 | n = n + args[i].length; 2953 | } 2954 | df2 = n - df1 - 1; 2955 | return 1 - jStat.centralF.cdf(anovafscore, df1, df2); 2956 | }, 2957 | 2958 | ftest : function(fscore, df1, df2) { 2959 | return 1 - jStat.centralF.cdf(fscore, df1, df2); 2960 | } 2961 | }); 2962 | 2963 | jStat.extend(jStat.fn, { 2964 | anovafscore : function() { 2965 | return jStat.anovafscore(this.toArray()); 2966 | }, 2967 | 2968 | anovaftest: function() { 2969 | var n = 0, 2970 | i; 2971 | for (i = 0; i < this.length; i++) { 2972 | n = n + this[i].length; 2973 | } 2974 | return jStat.ftest(this.anovafscore(), this.length - 1, n - this.length); 2975 | } 2976 | }); 2977 | 2978 | // Error Bounds 2979 | jStat.extend({ 2980 | // 2 different parameter setups 2981 | // (value, alpha, sd, n) 2982 | // (value, alpha, array) 2983 | normalci : function() { 2984 | var args = slice.call(arguments), 2985 | ans = new Array(2), 2986 | change; 2987 | if (args.length === 4) { 2988 | change = Math.abs(jStat.normal.inv(args[1] / 2, 0, 1) * args[2] / Math.sqrt(args[3])); 2989 | } else { 2990 | change = Math.abs(jStat.normal.inv(args[1] / 2, 0, 1) * jStat.stdev(args[2]) / Math.sqrt(args[2].length)); 2991 | } 2992 | ans[0] = args[0] - change; 2993 | ans[1] = args[0] + change; 2994 | return ans; 2995 | }, 2996 | 2997 | // 2 different parameter setups 2998 | // (value, alpha, sd, n) 2999 | // (value, alpha, array) 3000 | tci : function() { 3001 | var args = slice.call(arguments), 3002 | ans = new Array(2), 3003 | change; 3004 | if (args.length === 4) { 3005 | change = Math.abs(jStat.studentt.inv(args[1] / 2, args[3] - 1) * args[2] / Math.sqrt(args[3])); 3006 | } else { 3007 | change = Math.abs(jStat.studentt.inv(args[1] / 2, args[2].length) * jStat.stdev(args[2], true) / Math.sqrt(args[2].length)); 3008 | } 3009 | ans[0] = args[0] - change; 3010 | ans[1] = args[0] + change; 3011 | return ans; 3012 | }, 3013 | 3014 | significant : function(pvalue, alpha) { 3015 | return pvalue < alpha; 3016 | } 3017 | }); 3018 | 3019 | jStat.extend(jStat.fn, { 3020 | normalci : function(value, alpha) { 3021 | return jStat.normalci(value, alpha, this.toArray()); 3022 | }, 3023 | 3024 | tci : function(value, alpha) { 3025 | return jStat.tci(value, alpha, this.toArray()); 3026 | } 3027 | }); 3028 | 3029 | }(this.jStat, Math)); 3030 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | margin: 0; 4 | font-size: 14px; 5 | font-family: 'Helvetica Neue', sans-serif; 6 | background: #F1F1F1; 7 | color: #000; 8 | } 9 | 10 | h1, h2, h3, h4 { 11 | margin: 0; 12 | } 13 | 14 | h1 { 15 | font-size: 22px; 16 | font-weight: 500; 17 | text-align: center; 18 | margin: 20px 0; 19 | } 20 | 21 | h3 { 22 | font-size: 15px; 23 | font-weight: 500; 24 | margin-bottom: 10px; 25 | } 26 | 27 | h2 { 28 | font-size: 18px; 29 | font-weight: 500; 30 | } 31 | 32 | h4 { 33 | font-size: 12px; 34 | font-weight: 500; 35 | } 36 | 37 | p { 38 | margin: 15px 0; 39 | line-height: 22px; 40 | } 41 | 42 | ol { 43 | line-height: 22px; 44 | list-style: none; 45 | margin: 0; 46 | padding: 0; 47 | } 48 | 49 | ol li { 50 | margin: 15px 0; 51 | padding: 0 0 0 35px; 52 | position: relative; 53 | } 54 | 55 | ol .number { 56 | display: block; 57 | position: absolute; 58 | left: 0; 59 | top: 2px; 60 | margin-right: 10px; 61 | width: 20px; 62 | height: 20px; 63 | font-size: 12px; 64 | line-height: 19px; 65 | text-align: center; 66 | border-radius: 15px; 67 | background: #E9E9E9; 68 | font-weight: 500; 69 | color: #333; 70 | } 71 | 72 | /* Form Styling */ 73 | .form-group { 74 | margin: 0 0 30px 0; 75 | } 76 | 77 | .form-title { 78 | margin: 20px; 79 | } 80 | 81 | .form-group-title { 82 | padding: 10px 20px; 83 | color: #212121; 84 | border-bottom: 1px solid #E9E9E9; 85 | } 86 | 87 | .form-buttons { 88 | padding: 15px; 89 | text-align: center; 90 | } 91 | 92 | fieldset { 93 | margin: 0; 94 | padding: 0 10px 0 20px; 95 | border: none; 96 | border-bottom: 1px solid #E9E9E9; 97 | overflow: hidden; 98 | cursor: pointer; 99 | } 100 | 101 | fieldset:hover { 102 | background: #F7F7F7; 103 | } 104 | 105 | label { 106 | display: block; 107 | float: left; 108 | width: 82px; 109 | line-height: 40px; 110 | font-size: 14px; 111 | cursor: pointer; 112 | } 113 | 114 | input { 115 | font-family: 'Helvetica Neue', sans-serif; 116 | } 117 | 118 | input[type="number"] { 119 | padding: 5px 0 5px 5px; 120 | margin: 0; 121 | width: 95px; 122 | float: left; 123 | font-size: 15px; 124 | background: none; 125 | box-sizing: border-box; 126 | text-align: right; 127 | border: none; 128 | line-height: 30px; 129 | cursor: pointer; 130 | } 131 | 132 | input[type="submit"], 133 | input[type="reset"] { 134 | border-radius: 3px; 135 | padding: 10px 15px; 136 | background: #F7F7F7; 137 | border: 1px solid #E9E9E9; 138 | font-weight: 500; 139 | cursor: pointer; 140 | color: #000; 141 | font-size: 13px; 142 | display:inline-block; 143 | } 144 | 145 | input[type="submit"]:hover, 146 | input[type="reset"]:hover { 147 | color: #555; 148 | } 149 | 150 | input[type="submit"]:active, 151 | input[type="reset"]:active { 152 | position: relative; 153 | top: 1px; 154 | } 155 | 156 | input[type="reset"] { 157 | background: #FFF; 158 | color: #444; 159 | margin-left: 5px; 160 | } 161 | 162 | input[type="reset"]:hover { 163 | color: #E84A4A; 164 | } 165 | 166 | input:focus { 167 | outline: none; 168 | } 169 | 170 | /* Wrappers */ 171 | .wrapper { 172 | margin: 30px auto; 173 | width:940px; 174 | overflow: hidden; 175 | position: relative; 176 | } 177 | 178 | .wrapper, 179 | .sidebar, 180 | .content { 181 | box-sizing: border-box; 182 | } 183 | 184 | /* Preamble Styling */ 185 | .header { 186 | padding: 35px 0 25px; 187 | } 188 | 189 | .logo { 190 | margin-bottom: 0; 191 | } 192 | 193 | .tagline { 194 | color: #999; 195 | font-size: 15px; 196 | text-align: center; 197 | margin-top: 8px; 198 | } 199 | 200 | .note-wrapper { 201 | padding: 20px; 202 | } 203 | 204 | .note { 205 | font-size: 14px; 206 | line-height: 22px; 207 | margin-bottom: 15px; 208 | } 209 | 210 | 211 | 212 | /* Sidebar Styling */ 213 | .sidebar { 214 | width: 210px; 215 | position: absolute; 216 | left: 0; 217 | top: 0; 218 | height: 100%; 219 | background: #FFF; 220 | height: 100%; 221 | border-radius:3px 0 0 3px; 222 | border-right: 3px solid #F7F7F7; 223 | } 224 | 225 | .form-group-title.control:before, 226 | .form-group-title.test:before { 227 | content: ""; 228 | display: inline-block; 229 | width: 6px; 230 | height: 6px; 231 | border-radius: 100px; 232 | margin-right: 5px; 233 | position: relative; 234 | top: -1px; 235 | } 236 | 237 | .form-group-title.control:before { 238 | background: #007AFF; 239 | } 240 | 241 | .form-group-title.test:before { 242 | background: #E93A5E; 243 | } 244 | 245 | /* Content Styling */ 246 | .content { 247 | width: 730px; 248 | padding: 20px; 249 | margin-left: 210px; 250 | background: #FCFCFC; 251 | border-radius:0 3px 3px 0; 252 | } 253 | 254 | .content.full-width { 255 | margin-left: 0; 256 | width: 940px; 257 | border-radius: 3px; 258 | background: #FFF; 259 | padding: 20px 20px 40px 40px; 260 | } 261 | 262 | .chart { 263 | margin: 10px 0 0 0; 264 | } 265 | 266 | .chart + .chart { 267 | margin-top: 80px; 268 | } 269 | 270 | .chart-title { 271 | text-align: center; 272 | margin-bottom: 5px; 273 | } 274 | 275 | .chart-description { 276 | font-size: 13px; 277 | text-align: center; 278 | margin: 0 0 20px; 279 | color: #999; 280 | } 281 | 282 | .chart text { /* Axis labels */ 283 | font-size: 12px; 284 | } 285 | 286 | .tick line { /* The little ticks on the X & Y axis */ 287 | stroke: #C9C9C9; 288 | } 289 | 290 | .axis path { /* The X & Y Axis paths */ 291 | stroke: #27344B; 292 | stroke-width: 1px; 293 | shape-rendering: crispEdges; 294 | fill: none; 295 | } 296 | 297 | /* Columns */ 298 | .columns { 299 | overflow: hidden; 300 | } 301 | 302 | .column { 303 | box-sizing: border-box; 304 | padding-right: 30px; 305 | width: 50%; 306 | float:left; 307 | } 308 | 309 | 310 | /* PDF Plot */ 311 | #pdfplot #testLine, 312 | #pdfplot #controlLine { 313 | opacity: 0.95; 314 | } 315 | 316 | #pdfplot #testLine { 317 | fill: #E93A5E; 318 | } 319 | 320 | #pdfplot #controlLine { 321 | fill: #007AFF; 322 | } 323 | 324 | 325 | /* Histogram Plot */ 326 | #histogram .bar rect { 327 | fill: #007AFF; 328 | shape-rendering: crispEdges; 329 | } 330 | #histogram .bar:nth-child(2n){ 331 | opacity: 0.95; 332 | } 333 | 334 | 335 | /* Quantile Table */ 336 | #quantileTable { 337 | border: none; 338 | border-collapse: collapse; 339 | } 340 | 341 | #quantileTable td { 342 | cellspacing: 0px; 343 | border-right : 1px solid #000; 344 | padding: 10px; 345 | } 346 | 347 | .table-row-title { 348 | font-weight: 500; 349 | } 350 | 351 | #quantileTable td:last-child { 352 | border-right: none; 353 | } 354 | 355 | #quantileTable tr:nth-child(2n) { 356 | border-top: 1px solid #000; 357 | } 358 | --------------------------------------------------------------------------------