Javascriptで正規分布の乱数発生(rnorm)、確率密度関数(dnorm)、累積分布関数(pnorm)、累積分布の逆関数(qnorm)を実装する(逆関数は参照で)。すべて標準正規分布を想定。
Javascriptに限らず使えるアルゴリズムだが、日本語でまとまっている情報があまりないのと、ブラウザ上でA/Bテストなど有意性をみる検定などできたら面白いということでJSでやってみる。
目次
正規乱数の生成(rnorm)
1行でBox-Muller法で。
Box-Muller法とは?
$$X_1, X_2 \stackrel{i.i.d.}{\sim} {\rm Unif} (0, 1) $$
とするとき
$$Y_1 = \sqrt{-2 \log{X_1}} \cos{2 \pi X_2} $$
$$Y_2 = \sqrt{-2 \log{X_1}} \sin{2 \pi X_2} $$
で生成される
$$Y_1, Y_2 \stackrel{i.i.d.}{\sim} {\rm N} (0, 1) $$
というもの。
今回は1個の正規乱数でいいので、$Y_1$か$Y_2$の一方を採用すればいい。
Javascriptで実装
function rnorm(){
return Math.sqrt(-2 * Math.log(1 - Math.random())) * Math.cos(2 * Math.PI * Math.random());
}
後ろの係数はMath.cos()
でもMath.sin()
でもどちらでもいい。JavascriptのMath.random()
は戻り値の区間が[0,1)なので、$log {0}$で発散しないように1-Math.random()
としている。
確率密度関数(dnorm)
$$Z(x) = \frac{ e^{ -\frac{x^2}{2}} }{\sqrt{2 \pi}} $$
そのまんま
function dnorm(x){
return Math.exp(-x * x / 2) / Math.sqrt(2 * Math.PI);
}
累積分布関数(pnorm)
Abramowitz and Stegun, Handbook of Mathematical Functions (1964)から。
http://people.math.sfu.ca/~cbm/aands/
26.2が正規分布の累積分布関数の項目。
実際はC. Hastings, Jr., Approximations for Digital Computers (1955)に基づいているとのこと。
26.2.17の
$$P(x) = 1 – Z(x) \left( b_1 t + b_2 t^2 + b_3 t^3 + b_4 t^4 + b_5 t^5 \right) + \epsilon(x) $$
$$t = \frac{1}{1+px}, \quad Z(x) = \frac{ e^{ -\frac{x^2}{2}} }{\sqrt{2 \pi}} $$
$$|\epsilon(x)| \lt 7.5 \times 10^{-8} $$
$$p = .23164 19 $$
$$b_1 = .31938 1530 $$
$$b_2 = -.35656 3782 $$
$$b_3 = 1.78147 7937 $$
$$b_4 = -1.82125 5978 $$
$$b_5 = 1.33027 4429 $$
をそのまま実装
// Abramowitz and Stegun (1964) formula 26.2.17
// precision: abs(err) < 7.5e-8
function cdf(x) {
// constants
var p = 0.2316419;
var b1 = 0.31938153;
var b2 = -0.356563782;
var b3 = 1.781477937;
var b4 = -1.821255978;
var b5 = 1.330274429;
var t = 1 / (1 + p * Math.abs(x));
var Z = Math.exp(-x * x / 2) / Math.sqrt(2 * Math.PI);
var y = 1 - Z * ((((b5 * t + b4) * t + b3) * t + b2) * t + b1) * t;
return (x > 0) ? y : 1 - y;
}
別の近似式を使って実装したものの例
http://www.johndcook.com/blog/normal_cdf_inverse/
参考までに前著の7.1が誤差関数の項目で、こちらを使う場合もある。
cdf()
が累積分布関数で、erf()
が誤差関数。
// Abramowitz and Stegun (1964) formula 7.1.26
// precision: abs(err) < 1.5e-7
function cdf(x) {
return (1 + erf( x / Math.sqrt(2) )) / 2;
}
function erf(x) {
// constants
var p = 0.3275911;
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var t = 1 /(1 + p * Math.abs(x));
var y = 1 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return (x > 0) ? y : -y;
}
誤差関数と正規分布の関係は
http://www.fbs.osaka-u.ac.jp/labs/ishijima/error-01.html
http://bio-info.biz/statistics/distribution_normal_distribution.html
あたりが参考になる。
前著の数式では
- 7.1.25 と 26.2.16
- 7.1.26 と 26.2.17
- 7.1.27 と 26.2.18
- 7.1.28 と 26.2.19
がそれぞれ対応している。
累積分布関数の逆関数(qnorm)
アルゴリズムは
http://home.online.no/~pjacklam/notes/invnorm/
Javascriptでの実装は
http://home.online.no/~pjacklam/notes/invnorm/impl/misra/normsinv.html
データ分析 の記事一覧