https://github.com/jenlij/GAS-power-calculator
Tip revision: 8eaebbaae9e303f355126481773dd9453132ed12 authored by Jennifer Li Johnson on 25 September 2017, 00:49:20 UTC
adds license
adds license
Tip revision: 8eaebba
gas_power_calculator.js
/*
Genetic Association Study (GAS) Power Calculator
Copyright (C) 2017 Jennifer Li Johnson
University of Michigan School of Public Health
Department of Biostatistics Center for Statistical Genetics
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//slider functionality
(function()
{
//cases
var cases_slider = document.getElementById('cases_slider');
noUiSlider.create(cases_slider, {
start:1000,
behaviour:'tap',
connect:'lower',
step: 10,
range: {
'min':100,
'30%':1000,
'70%':10000,
'max':100000
},
format: wNumb({
decimals:0
})
});
cases_slider.noUiSlider.on('update', function(values, handle){
cases_input.value = values[handle];
});
cases_slider.noUiSlider.on('set', function(){
update();
});
cases_input.addEventListener('change', function(){
cases_slider.noUiSlider.set(this.value);
});
//contrals
var controls_slider = document.getElementById('controls_slider');
noUiSlider.create(controls_slider, {
start: 1000,
behaviour: 'tap',
connect: 'lower',
step: 10,
range: {
'min':100,
'30%':1000,
'70%':10000,
'max':100000
},
format: wNumb({
decimals: 0
})
});
controls_slider.noUiSlider.on('update', function(values, handle){
controls_input.value = values[handle];
});
controls_slider.noUiSlider.on('set', function(){
update();
});
controls_input.addEventListener('change', function(){
controls_slider.noUiSlider.set(this.value);
});
//significance level
var sig_slider = document.getElementById('sig_slider');
noUiSlider.create(sig_slider, {
start: 0.000007,
behaviour: 'tap',
connect: 'lower',
range: {
'min': 1e-10,
'10%':1e-9,
'20%':1e-8,
'30%':1e-7,
'40%':1e-6,
'50%':1e-5,
'60%':1e-4,
'70%':1e-3,
'80%':1e-2,
'90%':1e-1,
'max': 1
},
format: wNumb({
decimals: 10,
})
});
//slider to input
sig_slider.noUiSlider.on('update', function(values, handle){
var val = parseFloat(values[handle]);
//2 significant digits
sig_input.value = val.toFixed(Math.abs(Math.ceil(Math.log10(val)))+2);
});
sig_slider.noUiSlider.on('set', function(){
update();
});
//input to slider
sig_input.addEventListener('change', function(){
sig_slider.noUiSlider.set(this.value);
});
//prevalence
var prev_slider = document.getElementById('prev_slider');
noUiSlider.create(prev_slider, {
start: 0.1,
behaviour: 'tap',
connect: 'lower',
range: {
'min': 0.0001,
'max': 1
},
format: wNumb({
decimals:4
})
});
prev_slider.noUiSlider.on('update', function(values, handle){
prev_input.value = values[handle];
});
prev_slider.noUiSlider.on('set', function(){
update();
});
prev_input.addEventListener('change', function(){
prev_slider.noUiSlider.set(this.value);
});
//DAF
var allele_slider = document.getElementById('allele_slider');
noUiSlider.create(allele_slider, {
start: 0.5,
behaviour: 'tap',
connect: 'lower',
range: {
'min': 0.0001,
'max': 1
},
format: wNumb({
decimals:4
})
});
allele_slider.noUiSlider.on('update', function(values, handle){
allele_input.value = values[handle];
});
allele_slider.noUiSlider.on('set', function(){
update();
});
allele_input.addEventListener('change', function(){
allele_slider.noUiSlider.set(this.value);
});
//GRR
var rr_slider = document.getElementById('rr_slider');
noUiSlider.create(rr_slider, {
start: 1.5,
behaviour: 'tap',
connect: 'lower',
range: {
'min':1,
'50%':3,
'max':10
},
format: wNumb({
decimals:4
})
});
rr_slider.noUiSlider.on('update', function(values, handle){
rr_input.value = values[handle];
});
rr_slider.noUiSlider.on('set', function(){
update();
});
rr_input.addEventListener('change', function(){
rr_slider.noUiSlider.set(this.value);
});
})();
//helper functions to calculate power
var LOWER_TAIL_ONE = 7.5;
var UPPER_TAIL_ZERO = 20;
//squares a number
function square(x)
{
return x * x;
}
// Inverse normal distribution
// adapted from Wichura's PPND16, Algorithm AS241, Applied Statistics Vol 37 1988 pp 477 - 484
function ninv(p)
{
var SPLIT1 = 0.425;
var SPLIT2 = 5.0;
var CONST1 = 0.180625;
var CONST2 = 1.6;
var a = [
3.3871328727963666080E0,
1.3314166789178437745E2,
1.9715909503065514427E3,
1.3731693765509461125E4,
4.5921953931549871457E4,
6.7265770927008700853E4,
3.3430575583588128105E4,
2.5090809287301226727E3
] ;
var b = [
4.2313330701600911252E1,
6.8718700749205790830E2,
5.3941960214247511077E3,
2.1213794301586595867E4,
3.9307895800092710610E4,
2.8729085735721942674E4,
5.2264952788528545610E3
] ;
var c = [
1.42343711074968357734E0,
4.63033784615654529590E0,
5.76949722146069140550E0,
3.64784832476320460504E0,
1.27045825245236838258E0,
2.41780725177450611770E-1,
2.27238449892691845833E-2,
7.74545014278341407640E-4
] ;
var d = [
2.05319162663775882187E0,
1.67638483018380384940E0,
6.89767334985100004550E-1,
1.48103976427480074590E-1,
1.51986665636164571966E-2,
5.47593808499534494600E-4,
1.05075007164441684324E-9
] ;
var e = [
6.65790464350110377720E0,
5.46378491116411436990E0,
1.78482653991729133580E0,
2.96560571828504891230E-1,
2.65321895265761230930E-2,
1.24266094738807843860E-3,
2.71155556874348757815E-5,
2.01033439929228813265E-7
] ;
var f = [
5.99832206555887937690E-1,
1.36929880922735805310E-1,
1.48753612908506148525E-2,
7.86869131145613259100E-4,
1.84631831751005468180E-5,
1.42151175831644588870E-7,
2.04426310338993978564E-15
] ;
var q = p - 0.5;
var r;
var x;
if ( Math.abs( q ) < SPLIT1 ) {
r = CONST1 - q * q ;
return q * ((((((( a[7] * r + a[6] ) * r + a[5] ) * r + a[4] ) * r
+ a[3] ) * r + a[2] ) * r + a[1] ) * r + a[0] ) /
((((((( b[6] * r + b[5] ) * r + b[4] ) * r + b[3] ) * r
+ b[2] ) * r + b[1] ) * r + b[0] ) * r + 1.0 ) ;
} else {
if ( q < 0 )
r = p ;
else
r = 1.0 - p ;
if ( r < 1e-10)
return (q < 0 ? -20.0 : 20.0);
if ( r > 0.0 )
{
r = Math.sqrt( -Math.log( r ) ) ;
if ( r <= SPLIT2 )
{
r -= CONST2 ;
x = ((((((( c[7] * r + c[6] ) * r + c[5] ) * r + c[4] ) * r
+ c[3] ) * r + c[2] ) * r + c[1] ) * r + c[0] ) /
((((((( d[6] * r + d[5] ) * r + d[4] ) * r + d[3] ) * r
+ d[2] ) * r + d[1] ) * r + d[0] ) * r + 1.0 ) ;
}
else
{
r -= SPLIT2 ;
x = ((((((( e[7] * r + e[6] ) * r + e[5] ) * r + e[4] ) * r
+ e[3] ) * r + e[2] ) * r + e[1] ) * r + e[0] ) /
((((((( f[6] * r + f[5] ) * r + f[4] ) * r + f[3] ) * r
+ f[2] ) * r + f[1] ) * r + f[0] ) * r + 1.0 ) ;
}
}
else
x = 9;
if ( q < 0 )
x = -x ;
return x ;
}
}
// The standard normal distribution
// ID Hill, "The Normal Integral" Applied Statistics, Vol 22, pp. 424-427
function ndist(z, upper)
{
if (z < 0)
{
upper = !upper;
z = -z;
}
if ( ((z > LOWER_TAIL_ONE) && !upper) || (z > UPPER_TAIL_ZERO))
return (upper) ? 0.0 : 1.0;
var p;
var y = 0.5 * z * z;
if (z < 1.28)
{
p = 0.5 - z * (0.398942280444 - 0.399903438504 * y /
(y + 5.75885480458 - 29.8213557808 /
(y + 2.62433121679 + 48.6959930692 /
(y + 5.92885724438))));
}
else
{
p = 0.398942270385 * Math.exp (-y) /
(z - 2.8052e-8 + 1.00000615302 /
(z + 3.98064794e-4 + 1.98615381364 /
(z - 0.151679116635 + 5.29330324926 /
(z + 4.8385912808 - 15.1508972451 /
(z + 0.742380924027 + 30.789933034 /
(z + 3.99019417011))))));
}
return (upper) ? p : 1 - p;
}
//calculates all variables
//graphs power for selected variable
function calculate(ncases, ncontrols, freq, risk, prevalence, alpha)
{
// Genotype frequencies
var p = [square(freq), 2 * freq * (1. - freq), square(1. - freq)];
var aa_freq = p[0];
var ab_freq = p[1];
var bb_freq = p[2];
var model = $("#disease_model option:selected").val();
//genotype probabilities
var f;
if(model == "Multiplicative"){
f = [risk * risk, risk, 1.0];
}
else if(model == "Additive"){
f = [2.0 * risk - 1.0, risk, 1.0];
}
else if(model == "Dominant"){
f = [risk, risk, 1.0];
}
else if(model == "Recessive"){
f = [risk, 1.0, 1.0];
}
var scale = prevalence / (f[0]*p[0] + f[1]*p[1] + f[2]*p[2]);
// Adjusted penetrances
f[0] *= scale;
f[1] *= scale;
f[2] *= scale;
var error = false;
if (f[0] > 1.0)
{
//this will result in error
error = true;
}
var aa_prob = f[0];
var ab_prob = f[1];
var bb_prob = f[2];
var C = - ninv(alpha * 0.5);
var pcases = (f[0] * p[0] + f[1] * p[1] * 0.5) / prevalence; //disease allele freq cases
var pcontrols = ( (1. - f[0]) * p[0] + (1. - f[1]) * p[1] * 0.5) / (1. - prevalence); //disease allele freq controls
var vcases = pcases * (1.0 - pcases);
var vcontrols = pcontrols * (1.0 - pcontrols);
var ncp = (pcases - pcontrols) / Math.sqrt( (vcases / ncases + vcontrols / ncontrols) * 0.5 );
var P = ndist(-C - ncp, false) + ndist(C - ncp, true); //results power
var results_array = [P, pcases, pcontrols, aa_freq, aa_prob, ab_freq, ab_prob, bb_freq, bb_prob, error];
return results_array;
}
//updates progress bar section
function print(ncases, ncontrols, freq, risk, prevalence, alpha, error)
{
var a = calculate(ncases, ncontrols, freq, risk, prevalence, alpha);
var P = a[0]; var pcases = a[1]; var pcontrols = a[2]; var aa_freq = a[3]; var aa_prob = a[4];
var ab_freq = a[5]; var ab_prob = a[6]; var bb_freq = a[7]; var bb_prob = a[8]; var error = a[9];
if (error){return;}
$("#casesControlsRatio").html("Cases/Controls = " + (ncases/ncontrols).toFixed(3));
$("#power_progress").html(P.toFixed(3)).attr("style","width:" + (P * 100) + "%;");
$('#cases_progress').html(pcases.toFixed(3)).attr("style","width:" + (pcases * 100) + "%;");
$('#controls_progress').html(pcontrols.toFixed(3)).attr("style","width:" + (pcontrols * 100) + "%;");
$('#AA_freq').html(aa_freq.toFixed(3));
$('#AA_progress').html(aa_prob.toFixed(3)).attr("style","width:" + (aa_prob * 100) + "%;");
$('#AB_freq').html(ab_freq.toFixed(3));
$('#AB_progress').html(ab_prob.toFixed(3)).attr("style","width:" + (ab_prob * 100) + "%;");
$('#BB_freq').html(bb_freq.toFixed(3));
$('#BB_progress').html(bb_prob.toFixed(3)).attr("style","width:" + (bb_prob * 100) + "%;");
}
//updates graph section
//selectparam comes from select picker
function graph(ncases, ncontrols, freq, risk, prevalence, alpha, selectparam)
{
//check initial condition errors
var a = calculate(ncases, ncontrols, freq, risk, prevalence, alpha);
var error = a[9];
if (error)
{
alert("I don't like the genetic model you requested! \nPlease try different parameters");
return;
}
scaleArray = function(controls) {
var sampleSizeRatio = ncases/ncontrols;
return controls * sampleSizeRatio; //returns scaled cases array
}
//find points
var i; var j; var calc; var x=[]; var y=[]; var err; var testx=[]; var graphdata=[]; var graphtype;
if(selectparam == "Cases"){
testx = [100,125,150,200,250,300,350,400,450,500,600,700,800,900,1000,1250,1500,2000,3000,5000,7000,10000,20000,30000,50000,100000];
for (i=0; i < testx.length; i++) {
calc = calculate(testx[i], ncontrols, freq, risk, prevalence, alpha);
err = calc[9];
if (err){continue;}
x.push(testx[i]);
y.push(parseFloat(calc[0].toFixed(3)));
}
graphtype = "logarithmic";
}
else if(selectparam == "Controls"){
testx = [100,125,150,200,250,300,350,400,450,500,600,700,800,900,1000,1250,1500,2000,3000,5000,7000,10000,20000,30000,50000,100000];
for (i=0; i < testx.length; i++) {
calc = calculate(ncases, testx[i], freq, risk, prevalence, alpha);
err = calc[9];
if (err){continue;}
x.push(testx[i]);
y.push(parseFloat(calc[0].toFixed(3)));
}
graphtype = "logarithmic";
}
else if(selectparam == "Cases + Controls"){
var xcontrols = [100,125,150,200,250,300,350,400,450,500,600,700,800,900,1000,1250,1500,2000,3000,5000,7000,10000,20000,30000,50000,100000];
var xcases = xcontrols.map(scaleArray);
for (i=0; i < xcontrols.length; i++) {
calc = calculate(xcases[i], xcontrols[i], freq, risk, prevalence, alpha);
err = calc[9];
if (err){continue;}
var xVal = xcases[i]+xcontrols[i];
x.push(xVal);
y.push(parseFloat(calc[0].toFixed(3)));
}
graphtype = "logarithmic";
}
else if (selectparam == "Significance level"){
testx = [1e-10,2e-10,3e-10,4e-10,5e-10,1e-9,5e-9,1e-8,5e-8,1e-7,5e-7,1e-6,7e-6,1e-5,5e-5,1e-4,5e-4,1e-3,5e-3,1e-2,5e-2,1e-1,5e-1,1];
for (i=0; i < testx.length; i++) {
calc = calculate(ncases, ncontrols, freq, risk, prevalence, testx[i]);
err = calc[9];
if (err){continue;}
x.push(testx[i]);
y.push(parseFloat(calc[0].toFixed(3)));
}
graphtype = "logarithmic";
}
else if (selectparam == "Prevalence"){
testx = [0.001,0.01,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,1];
for (i=0; i < testx.length; i++) {
calc = calculate(ncases, ncontrols, freq, risk, testx[i], alpha);
err = calc[9];
if (err){continue;}
x.push(testx[i]);
y.push(parseFloat(calc[0].toFixed(3)));
}
graphtype = "linear";
}
else if (selectparam == "Disease Allele Frequency"){
testx = [0.001,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.12,0.15,0.17,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.9,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99];
for (i=0; i < testx.length; i++) {
calc = calculate(ncases, ncontrols, testx[i], risk, prevalence, alpha);
err = calc[9];
if (err){continue;}
x.push(testx[i]);
y.push(parseFloat(calc[0].toFixed(3)));
}
graphtype = "linear";
}
else if (selectparam == "Genotype Relative Risk"){
testx = [1,1.1,1.15,1.2,1.25,1.3,1.35,1.4,1.45,1.5,1.6,1.7,1.8,1.9,2,2.1,2.2,2.3,2.4,2.5,2.6,2.7,2.8,2.9,3,3.5,4,4.5,5,6,7,8,9,10];
for (i=0; i < testx.length; i++) {
calc = calculate(ncases, ncontrols, freq, testx[i], prevalence, alpha);
err = calc[9];
if (err)
{continue;}
x.push(testx[i]);
y.push(parseFloat(calc[0].toFixed(3)));
}
graphtype = "logarithmic";
}
//fill data array for graph
for (j=0; j<x.length; j++) {
graphdata.push([x[j],y[j]]);
}
//graph using highcharts api
$('#highcharts_graph').highcharts({
chart: {
type: 'scatter',
zoomType: 'x'
},
colors: ['#40658f'],
title:{
text:''
},
subtitle: {
text: document.ontouchstart === undefined ?
'Click and drag in the plot area to zoom in' : 'Pinch the chart to zoom in'
},
plotOptions:{
scatter:{
lineWidth:2
}
},
xAxis: {
title: {
text: selectparam
},
type: graphtype
},
yAxis: {
title: {
text: 'Statistical Power'
},
max: 1
},
legend: {
enabled: false
},
series: [{
name: selectparam,
data: graphdata
}]
});
}
//updates both progress bar and graph sections. Called from sliders
function update()
{
//get values from sliders and x variable selection for graph
var ncases = cases_slider.noUiSlider.get(); //cases
var ncontrols = controls_slider.noUiSlider.get(); //controls
var freq = allele_slider.noUiSlider.get(); //disease allele frequency (DAF)
var risk = rr_slider.noUiSlider.get(); //genotype relative risk (GRR)
var prevalence = prev_slider.noUiSlider.get(); //prevalence
var alpha = sig_input.value; //significance level
var selectparam = $("#x_graph option:selected").val(); //graph variable selection
//update progress bars and graph
print(ncases, ncontrols, freq, risk, prevalence, alpha);
graph(ncases, ncontrols, freq, risk, prevalence, alpha, selectparam);
}
//updates results and graph when disease model is changed
$("#disease_model").change(function ()
{
update();
})
//updates graph (and results) section when selectpicker is changed
$("#x_graph").change(function ()
{
update()
})
//stand-in graph for default parameters
$(function ()
{
$('#highcharts_graph').highcharts({
chart: {
type: 'scatter',
zoomType: 'x'
},
colors: ['#40658f'],
title:{
text:''
},
subtitle: {
text: document.ontouchstart === undefined ?
'Click and drag in the plot area to zoom in' : 'Pinch the chart to zoom in'
},
plotOptions:{
scatter:{
lineWidth:2
}
},
xAxis: {
title: {
text: 'Cases'
},
type: 'logarithmic'
},
yAxis: {
title: {
text: 'Statistical Power'
},
max: 1
},
legend: {
enabled: false
},
series: [{
name: 'Cases',
data: [[100,0.075],[125,0.131],[150,0.2],[200,0.357],[250,0.51],[300,0.639],[350,0.74],[400,0.815],[450,0.869],[500,0.907],[600,0.953],[700,0.975],[800,0.986],[900,0.992],[1000,0.995],[1250,0.999],[1500,0.999],[2000,1],[3000,1],[5000,1],[7000,1],[10000,1],[20000,1],[30000,1],[50000,1],[100000,1]]
}]
});
})