Google Sheets NORMDIST( x, mean, standard deviation, cumulative T/F) to JavaScript - javascript

Google Sheets has the NORMDIST( x, mean, standard deviation, cumulative T/F) function. I have not been able to find something equivalent in JavaScript. The examples I've found do not allow for all the variables that are in the Google function or I don't have the understanding to implement them.
I am trying to write a reiterative JavaScript function to replace a set of reiterative Google Sheets calculations and need something like this;
x = NORMDIST(x,0,1, TRUE);
y = NORMDIST(x,0,1, FALSE);

At a high-level, there's two things that the NORMDIST function can do: calculate the Normal Distribution's PDF when false is supplied as the last parameter and CDF when true is supplied.
The PDF part is easy: just follow the formula given on Wikipedia for Normal Distribution.
The CDF part is less trivial as it's a non-elementary integral. Your best shot is to find some open-source implementation and translate it into JS. (Preferably a non-iterative one.) It won't be 100% exact due to roundoff errors, but it can get you extremely close. I found one here.
Here's what I came up with:
function _getNDistPDF(x, mean, stdev)
{
const sqrt2PI = Math.SQRT2 * Math.sqrt(Math.PI);
let frac = (x - mean) / stdev;
return Math.exp(-.5 * frac * frac) / (sqrt2PI * stdev);
}
// Adapted from https://people.sc.fsu.edu/~jburkardt/c_src/asa066/alnorm.c
function _getNDistCDF(x, mean, stdev)
{
const a1 = 5.75885480458;
const a2 = 2.62433121679;
const a3 = 5.92885724438;
const b1 = -29.8213557807;
const b2 = 48.6959930692;
const c1 = -0.000000038052;
const c2 = 0.000398064794;
const c3 = -0.151679116635;
const c4 = 4.8385912808;
const c5 = 0.742380924027;
const c6 = 3.99019417011;
const con = 1.28;
const d1 = 1.00000615302;
const d2 = 1.98615381364;
const d3 = 5.29330324926;
const d4 = -15.1508972451;
const d5 = 30.789933034;
const ltone = 7.0;
const p = 0.398942280444;
const q = 0.39990348504;
const r = 0.398942280385;
const utzero = 18.66;
let up = false;
let value;
let y;
// For non-standard NDist
let z = (x - mean) / stdev;
if (z < 0)
{
up = true;
z = -z;
}
if (ltone < z && (!up || utzero < z))
{
value = !up * 1;
return value;
}
y = 0.5 * z * z;
if (z <= con)
{
value = 0.5 - z * (p - q * y
/ (y + a1 + b1
/ (y + a2 + b2
/ (y + a3))));
}
else
{
value = r * Math.exp(-y)
/ (z + c1 + d1
/ (z + c2 + d2
/ (z + c3 + d3
/ (z + c4 + d4
/ (z + c5 + d5
/ (z + c6))))));
}
if (!up)
value = 1 - value;
return value;
}
function NDistJS(x, mean, stdev, cumulative)
{
return cumulative
? _getNDistCDF(x, mean, stdev)
: _getNDistPDF(x, mean, stdev);
}
This gives you a function NDistJS that takes the exact same parameters as NORMDIST.
The PDF is 100% accurate to the Sheets formula.
The CDF is 99.9999992% accurate to the Sheets formula for (x=1, m=0, s=1).

Related

How to use big.js?

From the examples of big.js they show this example
0.3 - 0.1 // 0.19999999999999998
x = new Big(0.3)
x.minus(0.1) // "0.2"
x // "0.3"
x.div(y).plus(z).times(9).minus('1.234567801234567e+8').plus(976.54321).div('2598.11772')
Which is a very simple example. In my case I would like to calculate
res = a + (b / c) + (d + 1) / (e * f * g);
I cannot see how that can be calculated without introducing 7 temporary variables, which doesn't seam correct.
Question
Does anyone know how to calculate the above with big.js?
You can do this "inside-out", i.e. first convert the parts in the inner parentheses.
For example:
const temp1 = b.div(c),
temp2 = d.plus(1),
temp3 = e.times(f).times(g),
temp4 = temp2.div(temp3),
result = a.plus(temp1).plus(temp4);
But you don't actually need those temporary variables. Just take that last expression and inject the definition of the temporary variables, so your expression expands to this:
const res = a.plus(b.div(c)).plus(d.plus(1).div(e.times(f).times(g)));
// a + (b / c ) + (d + 1) / (e * f * g )
Demo:
const params = [1, 100, 5, 99, 2, 5, 2];
{ // First with native types:
const [a,b,c,d,e,f,g] = params;
const res = a + (b / c) + (d + 1) / (e * f * g);
console.log(res);
}
{ // Then with big.js
const [a,b,c,d,e,f,g] = params.map(Big);
const res = a.plus(b.div(c)).plus(d.plus(1).div(e.times(f).times(g)));
console.log(res.toNumber());
}
<script src='https://cdn.jsdelivr.net/npm/big.js#6.2.1/big.min.js'></script>

How to get a random value from truncated normal distribution with mean and std in JavaScript?

What is a JS alternative to the same Python implementation?
import matplotlib.pyplot as plt
from scipy.stats import truncnorm
import numpy as np
mean = 1
std = 2
clip_a = -4
clip_b = 3
a, b = (clip_a - mean) / std, (clip_b - mean) / std
x_range = np.linspace(-3 * std, 3 * std, 1000)
plt.plot(x_range, truncnorm.pdf(x_range, a, b, loc = mean, scale = std));
I'd like to get a random value according to the distribution (in JS the same code with size=1):
dist = truncnorm.rvs(a, b, loc = mean, scale = std, size=1000000)
plt.hist(dist);
Here is a JS function that implements a truncated, skew-normal pseudo random number generator (PRNG). It is based on this blog post by Tom Liao and has been extended to consider lower and upper bounds (truncation).
Essentially, the function is called recursively until a variate within the desired bounds is found.
You can pass your own random number generator using the rng property, though Math.random will be used by default. Also, as you didn't ask for a skew-normal distribution, you can just ignore the skew property as it defaults to 0. This will leave you with a truncated normal PRNG, just as you asked for.
function randomTruncSkewNormal({
rng = Math.random,
range = [-Infinity, Infinity],
mean,
stdDev,
skew = 0
}) {
// Box-Muller transform
function randomNormals(rng) {
let u1 = 0,
u2 = 0;
//Convert [0,1) to (0,1)
while (u1 === 0) u1 = rng();
while (u2 === 0) u2 = rng();
const R = Math.sqrt(-2.0 * Math.log(u1));
const Θ = 2.0 * Math.PI * u2;
return [R * Math.cos(Θ), R * Math.sin(Θ)];
}
// Skew-normal transform
// If a variate is either below or above the desired range,
// we recursively call the randomSkewNormal function until
// a value within the desired range is drawn
function randomSkewNormal(rng, mean, stdDev, skew = 0) {
const [u0, v] = randomNormals(rng);
if (skew === 0) {
const value = mean + stdDev * u0;
if (value < range[0] || value > range[1])
return randomSkewNormal(rng, mean, stdDev, skew);
return value;
}
const sig = skew / Math.sqrt(1 + skew * skew);
const u1 = sig * u0 + Math.sqrt(1 - sig * sig) * v;
const z = u0 >= 0 ? u1 : -u1;
const value = mean + stdDev * z;
if (value < range[0] || value > range[1])
return randomSkewNormal(rng, mean, stdDev, skew);
return value;
}
return randomSkewNormal(rng, mean, stdDev, skew);
}
Calling this function in the following manner
const data = [];
for (let i = 0; i < 50000; i++) {
data.push({
x: i,
y: randomTruncSkewNormal({
range: [-4,3],
mean: 1,
stdDev: 2
})
});
}
and plotting the data using your charting library of choice should give your the desired output.
I also made a small Observable notebook interactively demonstrating the function which you might want to check out as well.

Linear regression with two independent variables in javascript

The following will output the slope, intercept and correlation coefficient R^2 for a given set of x and y values.
let linearRegression = (y,x) => {
let lr = {}
let n = y.length
let sum_x = 0
let sum_y = 0
let sum_xy = 0
let sum_xx = 0
let sum_yy = 0
for (let i = 0; i < y.length; i++) {
sum_x += x[i]
sum_y += y[i]
sum_xy += (x[i]*y[i])
sum_xx += (x[i]*x[i])
sum_yy += (y[i]*y[i])
}
lr['slope'] = (n * sum_xy - sum_x * sum_y) / (n*sum_xx - sum_x * sum_x)
lr['intercept'] = (sum_y - lr.slope * sum_x)/n
lr['r2'] = Math.pow((n*sum_xy - sum_x*sum_y)/Math.sqrt((n*sum_xx-sum_x*sum_x)*(n*sum_yy-sum_y*sum_y)),2)
return lr
}
How can I adapt this to accept two independent variables x1, x2 rather than one?
This page goes into the modified formulas:
http://faculty.cas.usf.edu/mbrannick/regression/Reg2IV.html
But i've been struggling to adapt this to the above function.
Step-by-step
First, look at the input line:
let linearRegression = (y,x) => {. You have 2 variables, so we could call them x1 and x2: let linearRegression = (y,x1,x2) => {
Now the formulae for the regression depend on dot products between the pairs of variables - x1.x1, x1.x2, x1.y etc.
So instead of calculating sum_xx, sum_xy, sum_yy, we need to calculate the sums for all of those pairs (with 3 variables instead of 2, there are 6 sums to calculate).
Finally, the two-variable equation is y=a + b1.x1 + b2.x2, so there are 2 slopes to calculate rather than 1, and the page you linked to gives all the formulae you would need there.

Javascript Maths Node Red Thermistor Function

have node-red running with arduino analogue output through serial to pi, where I'm collecting thermistor data as a msg payload on change.
Trying to turn the Arduino signal into a Temperature - it's the most common 10K enamelised thermistor that adafruit sell. So quite useful code.
The issue is that I am a total noob at JS.
Here's my code so far - using a function node - and trying to replicate the steinhart equation (https://learn.adafruit.com/thermistor/using-a-thermistor)
var input = { payload: msg.payload };
var R0 = 10000;
var R = R0 / ((1023 / input)-1);
var T0 = 273 + 25;
var B = 3950;
var T = 1 / ( (1/T0) + (1/B) * Math.log(R/R0) );
return T;
i am not sure whether will msg.payload will return number in an actual datatype "Number" meaning or a string that will be a numeric, but something like this should take care of any anomalies when trying to divide strings
var numInput = Number(msg.payload);
var R0 = 10000;
var R = R0 / ((1023 / input)-1);
var T0 = 273 + 25;
var B = 3950;
var T = 1 / ( (1/T0) + (1/B) * Math.log(R/R0) );
return T;
EDIT: this should fix the errors:
var numInput = Number(msg.payload);
var R0 = 10000;
var R = R0 / ((1023 / numInput)-1);
var T0 = 273 + 25;
var B = 3950;
var T = 1 / ( (1/T0) + (1/B) * Math.log(R/R0) );
msg.payload = T;
return msg;

Cumulative distribution function in Javascript

I am searching for a way to calculate the Cumulative distribution function in Javascript. Are there classes which have implemented this? Do you have an idea to get this to work? It does not need to be 100% percent accurate but I need a good idea of the value.
http://en.wikipedia.org/wiki/Cumulative_distribution_function
I was able to write my own function with the help of Is there an easily available implementation of erf() for Python? and the knowledge from wikipedia.
The calculation is not 100% correct as it is just a approximation.
function normalcdf(mean, sigma, to)
{
var z = (to-mean)/Math.sqrt(2*sigma*sigma);
var t = 1/(1+0.3275911*Math.abs(z));
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var erf = 1-(((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*Math.exp(-z*z);
var sign = 1;
if(z < 0)
{
sign = -1;
}
return (1/2)*(1+sign*erf);
}
normalcdf(30, 25, 1.4241); //-> 0.12651187738346226
//wolframalpha.com 0.12651200000000000
The math.js library provides an erf function. Based on a definition found at Wolfram Alpha , the cdfNormalfunction can be implemented like this in Javascript:
const mathjs = require('mathjs')
function cdfNormal (x, mean, standardDeviation) {
return (1 - mathjs.erf((mean - x ) / (Math.sqrt(2) * standardDeviation))) / 2
}
In the node.js console:
> console.log(cdfNormal(5, 30, 25))
> 0.15865525393145707 // Equal to Wolfram Alpha's result at: https://sandbox.open.wolframcloud.com/app/objects/4935c1cb-c245-4d8d-9668-4d353ad714ec#sidebar=compute
This formula will give the correct normal CDF unlike the currently accepted answer
function ncdf(x, mean, std) {
var x = (x - mean) / std
var t = 1 / (1 + .2315419 * Math.abs(x))
var d =.3989423 * Math.exp( -x * x / 2)
var prob = d * t * (.3193815 + t * ( -.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))))
if( x > 0 ) prob = 1 - prob
return prob
}
This answer comes from math.ucla.edu
Due to some needs in the past, i put together an implementation of distribution function in javascript. my library is available at github. You can take a look at https://github.com/chen0040/js-stats
it provides javascript implementation of CDF and inverse CDF for Normal distribution, Student's T distribution, F distribution and Chi-Square Distribution
To use the js lib for obtaining CDF and inverse CDF:
jsstats = require('js-stats');
//====================NORMAL DISTRIBUTION====================//
var mu = 0.0; // mean
var sd = 1.0; // standard deviation
var normal_distribution = new jsstats.NormalDistribution(mu, sd);
var X = 10.0; // point estimate value
var p = normal_distribution.cumulativeProbability(X); // cumulative probability
var p = 0.7; // cumulative probability
var X = normal_distribution.invCumulativeProbability(p); // point estimate value
//====================T DISTRIBUTION====================//
var df = 10; // degrees of freedom for t-distribution
var t_distribution = new jsstats.TDistribution(df);
var t_df = 10.0; // point estimate or test statistic
var p = t_distribution.cumulativeProbability(t_df); // cumulative probability
var p = 0.7;
var t_df = t_distribution.invCumulativeProbability(p); // point estimate or test statistic
//====================F DISTRIBUTION====================//
var df1 = 10; // degrees of freedom for f-distribution
var df2 = 20; // degrees of freedom for f-distribution
var f_distribution = new jsstats.FDistribution(df1, df2);
var F = 10.0; // point estimate or test statistic
var p = f_distribution.cumulativeProbability(F); // cumulative probability
//====================Chi Square DISTRIBUTION====================//
var df = 10; // degrees of freedom for cs-distribution
var cs_distribution = new jsstats.ChiSquareDistribution(df);
var X = 10.0; // point estimate or test statistic
var p = cs_distribution.cumulativeProbability(X); // cumulative probability
This is a brute force implementation, but accurate to more digits of precision. The approximation above is accurate within 10^-7. My implementation runs slower (700 nano-sec) but is accurate within 10^-14. normal(25,30,1.4241) === 0.00022322110257305683, vs wolfram's 0.000223221102572082.
It takes the power series of the standard normal pdf, i.e. the bell-curve, and then integrates the series.
I originally wrote this in C, so I concede some of the optimizations might seem silly in Javascript.
function normal(x, mu, sigma) {
return stdNormal((x-mu)/sigma);
}
function stdNormal(z) {
var j, k, kMax, m, values, total, subtotal, item, z2, z4, a, b;
// Power series is not stable at these extreme tail scenarios
if (z < -6) { return 0; }
if (z > 6) { return 1; }
m = 1; // m(k) == (2**k)/factorial(k)
b = z; // b(k) == z ** (2*k + 1)
z2 = z * z; // cache of z squared
z4 = z2 * z2; // cache of z to the 4th
values = [];
// Compute the power series in groups of two terms.
// This reduces floating point errors because the series
// alternates between positive and negative.
for (k=0; k<100; k+=2) {
a = 2*k + 1;
item = b / (a*m);
item *= (1 - (a*z2)/((a+1)*(a+2)));
values.push(item);
m *= (4*(k+1)*(k+2));
b *= z4;
}
// Add the smallest terms to the total first that
// way we minimize the floating point errors.
total = 0;
for (k=49; k>=0; k--) {
total += values[k];
}
// Multiply total by 1/sqrt(2*PI)
// Then add 0.5 so that stdNormal(0) === 0.5
return 0.5 + 0.3989422804014327 * total;
}
You can also take a look here, it's a scientific calculator implemented in javascript, it includes erf and its author claims no copyright on the implementation.

Categories