標準正規分布の確率密度関数をテイラー展開してから積分して累積分布関数を求めてみた。

By | 2018年8月24日 , Last update: 2022年8月7日

はじめに

前の記事で正規分布に従う2個の独立な確率変数の和と差の確率密度関数を計算しました。

Google先生に聞いてみたり、思い出しながら書いているためにところどころ用語の使い方が怪しい部分があるかもしれませんが、そのあたりは気づき次第修正します。

前の記事でちょっとエンジンがかかってきたところで、この記事では標準正規分布の確率密度関数をテイラー展開し、さらにそいつを積分して累積分布関数を計算してみます。

ついでに収束判定のところで少し寄り道をします。

スポンサーリンク

テイラー展開します。

本記事では冪乗及び階乗の計算式が頻繁に登場しますが、とします。

指数関数のテイラー展開

指数関数ののまわりにおけるテイラー展開は(1)式で表されます。

(1)

次節以降の話の展開の都合上、のまわりでテイラー展開した式が必要になるので、以下((2)式)に示しておきます。

(2)

標準正規分布の確率密度関数のテイラー展開

次に、標準正規分布の確率密度関数ののまわりにおけるテイラー展開を計算します。

といっても、テイラー展開の(一般的な)展開式

(3)


(4)

を直接当てはめようとして、を順次計算してみたところで収拾がつかなくなるだけなので、ここは前節の結果を利用することを考えます。

(2),(3)及び(4)式をじーっと見比べると、とおいて、さらにを(2)式を使って計算してみると、

(5)

(ただし、とします。)となります。

収束することの確認。

標準正規分布の確率密度関数は(5)式の通りに計算ができそうですが、(5)式の右辺が収束することを示さないと使い物になりません。(5)式の左辺をあらためてと置きなおして、右辺をの級数と考えると、級数の各項の係数

(6)

となります。

ここで、ダランベールの収束判定法を使いたい… ところではありますが、(6)式より、隣接する項のどちらか片方が0になりますので、そのままでは使用できません。


スポンサーリンク

そこで、ここはコーシーの冪根判定法を使用します。(6)式のより、

(7)

となることと、であること(証明はこちら参照… と最初は書いていたのですが、参照先ではを証明してました(汗)。これをもとには証明できます。証明の方法はこちらに書きました。)から、になります。したがって(5)式の級数は(について)収束することがわかります。

累積分布関数を計算します。

この節では、前節の結果を利用して累積分布関数を計算します。なお、この節では累積分布関数をと置きます。

積分します。

まず、不定積分を計算してみます。(5)式はについて項別積分できますので、積分定数をCとすると、

(8)

となります。

次に、であることを利用するととなります。

したがって、は(9)式のように定まります。

(9)

もうちょい変形。

スポンサーリンク

数学的には(9)式の形で良いような気もしますが、ここまで計算できると、実際に検算してみたくなります。

そこでとおいて、以下のように変形します。


スポンサーリンク

すると、なので、

(10)

収束があまり速くなさそうな気もしますが、上式のように変形すると級数の前の項の係数の計算結果及び変数並びに項番号のみを使って次の項の係数を計算することができるようになります。

プログラムを書いて検算です。

(10)式が導けたので、正しいかどうか以下のようなプログラムを書いて確認してみます。なお、テスト用のプログラムですので、デバッグ用と思しき出力が残っているのはご容赦いただければと思います。

コードを差し替えました。[2018/09/03追記,2018/09/16計算式を訂正]

normDist関数中のwhile文のループの継続条件を変化率の絶対値(を直前の項(項)までのの計算結果で割った値の絶対値)が以上である場合に変更しています(なお、コードの差し替え前はである場合としていました)。

これに伴い、いわゆる「ゼロ割りエラー」が発生するのを防止するため、normDist関数の引数に0が指定された場合には計算を行わずに0.5を返すためのコードを関数の先頭部分に追加しました。

package info.pandanote.normdist;
public class NormDistTest {
public double normDist(double x) {
if (x == 0.0) {
return 0.5;
}
double ax = x;
double deltaax = x;
int k = 0;
while (Math.abs(deltaax/ax) > 1e-12) {
deltaax *= x*x*((double)(-(2*k+1)))/(2*k+3)/(2*k+2);
//System.out.println("deltaax="+deltaax);
ax += deltaax;
//System.out.println("ax="+ax);
k++;
}
//System.out.println("k="+k);
return ax/Math.sqrt(2.0*Math.PI)+0.5;
}
public static void main(String[] args) {
NormDistTest ndt = new NormDistTest();
double x = 0.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = 1.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = 2.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = 3.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = 4.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = -1.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = -2.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = -3.0;
System.out.println("f("+x+")="+ndt.normDist(x));
x = -4.0;
System.out.println("f("+x+")="+ndt.normDist(x));
}
}

Excelと答え合わせ。

実はExcelの関数に同様の計算を行うもの(NORM.S.DIST関数)がありますので、その結果と照らし合わせて確認してみます。

正しい計算結果を返していることが確認できました。(`・ω・´)シャキーン

まとめ

ここまでの考察で確率密度関数が正規分布である確率変数がある値以下の値をとる確率を計算するための計算式で、かつプログラムで比較的容易に実装できるものを導出し、実際にプログラムとして実装して動作を確認してみました。

前項の動作確認でで小数点以下10桁程度の精度の計算が36回の反復計算(の計算を除く)でできていたので、最初の予想よりは計算の効率は良いと思います。に10とか100とかといった極端な値を指定されてしまった場合には、精度との兼ね合いで反復計算を行わず、1.0(または0.0)を返してしまう実装にするのもありだとは思いますが、それはシステムなどに関数を実装して組み込むときに解決せねばならない課題ですね。

この記事は以上です。