y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

PHP の統計関数を使う

PHP では、PECL stats 拡張モジュールを導入することで、さまざまな統計関数を利用できます。今回は、このモジュールを試してみます。公式の情報は以下にあります。

なお、このモジュールは DCDFLIB, RANDLIB のラッパーになっています。後述するように、統計関数の使い方にはかなり癖があるのですが、DCDFLIB や RANDLIB を知っていれば簡単かもしれません。

stats 拡張モジュールの導入

まず、stats 拡張モジュールをインストールします。CentOS 6.5 では、pecl コマンド自体は php-pear パッケージで提供されます。また、pecl コマンドでモジュールをビルドする際には phpize が必要になります。phpizephp-devel パッケージで提供されます。

$ sudo yum install php-pear php-devel
$ sudo pecl install stats
...
Build process completed successfully
Installing '/usr/lib64/php/modules/stats.so'
install ok: channel://pecl.php.net/stats-1.0.3
configuration option "php_ini" is not set to php.ini location
You should add "extension=stats.so" to php.ini

PHP の起動時に拡張モジュールがロードされるように extension の記述を追加します。上記のメッセージにあるように /etc/php.ini に書いてもよいのですが、せっかく /etc/php.d ディレクトリが用意されているので、ファイルを分けて書くことにします。

$ sudo vi /etc/php.d/stats.ini
; Enable stats extension module
extension=stats.so

stats 拡張モジュールがロードされることを確認します。

$ php -i | grep stats
/etc/php.d/stats.ini,
stats
使い方の例: 正規分布確率密度関数を描く

統計関数の使い方の例として、ありきたりですが、標準正規分布確率密度関数を描いてみます。

<?php
for ($x = -2.0; $x <= 2.0; $x += 0.2) {
    $p = stats_dens_normal($x, 0, 1);
    printf("%9.6f\t%f\t%s\n", $x, $p, str_repeat('*', round($p * 100)));
}

stats_dens_normal が標準正規分布確率密度関数を求める関数です。関数名は、おおむね以下のルールで付けられているようです。

関数名 内容
stats_cdf_* 積分布関数
stats_dens_* 確率密度関数
stats_rand_gen_* 分布からの乱数生成

PHP の統計関数は、公式のドキュメントの説明が不十分なものが多く、たとえば stats_dens_normal についても説明は何もないのですが (下記ページを参照)、幸いなことに $x, $ave, $stdev というパラメータ名から使い方を想像できます。平均 $ave, 標準偏差 $stdev の正規分布確率密度関数 f($x) の値を返してくれることを期待できそうです。
PHP: stats_dens_normal - Manual

実行結果は次のようになります。よさそうです。

$ php normpdf.php
-2.000000       0.053991        *****
-1.800000       0.078950        ********
-1.600000       0.110921        ***********
-1.400000       0.149727        ***************
-1.200000       0.194186        *******************
-1.000000       0.241971        ************************
-0.800000       0.289692        *****************************
-0.600000       0.333225        *********************************
-0.400000       0.368270        *************************************
-0.200000       0.391043        ***************************************
-0.000000       0.398942        ****************************************
 0.200000       0.391043        ***************************************
 0.400000       0.368270        *************************************
 0.600000       0.333225        *********************************
 0.800000       0.289692        *****************************
 1.000000       0.241971        ************************
 1.200000       0.194186        *******************
 1.400000       0.149727        ***************
 1.600000       0.110921        ***********
 1.800000       0.078950        ********
 2.000000       0.053991        *****
使い方の例: 正規分布の累積分布関数を描く

次の例として、累積分布関数を描いてみます。関数名は stats_cdf_normal だろうと想像して公式のドキュメントを調べても、この関数は見つかりません。これは、ソースコード中のドキュメントコメントが誤っているためで、実は stats_cdf_normal 関数はちゃんと存在しています。ドキュメントコメントの誤りについては、下記で報告されています。このうち stats_dens_uniform は修正されていましたが、そのほかは現在でも未修正でした。
PHP :: Bug #57832 :: name mismatches in statistics.c

さて、stats_cdf_normal 関数が存在していることはわかったので、先ほどのプログラムの stats_dens_normal を stats_cdf_normal に変えてみます。ところが、単純に関数を置き換えて実行すると以下のようなエラーが出てしまいます。どうやら、確率密度関数と累積分布関数では使い方が異なるようです。

$ sed 's/stats_dens_normal/stats_cdf_normal/' normpdf.php >normcdf.php
$ php normcdf.php
PHP Warning:  stats_cdf_normal() expects exactly 4 parameters, 3 given in /.../normcdf.php on line 3

ここで種明かしのようになりますが、結局のところ PHP の統計関数の使い方については、ソースコードを読むのが一番簡単です。ソースコードを stats_cdf_normal で検索すると、400 行目に関数のエントリが見つかります。その手前に (誤った) ドキュメントコメントが書かれていますが、それよりもさらに上を見ると、とても丁寧な説明が書かれています。
PHP: Contents of /pecl/stats/trunk/php_stats.c

stats_cdf_normal 関数は 4 引数を受け取る関数で、末尾の第 4 引数 $which によって「何を求めるか」を指示します。$which の値によって、第 1 引数から第 3 引数の意味が変わります。具体的には、累積分布関数を F(x) として、以下のような関係になります。

which (第 4 引数) 戻り値 第 1 引数 第 2 引数 第 3 引数
1 F(x) x 平均 標準偏差
2 x F(x) 平均 標準偏差
3 平均 F(x) x 標準偏差
4 標準偏差 F(x) x 平均

このようにまとめてみると、今回は累積分布関数を描こうとしていたので、$which に 1 を指定すればよいことがわかります。以下のようなコードになります。

<?php
for ($x = -2.0; $x <= 2.0; $x += 0.2) {
    $which = 1;
    $p = stats_cdf_normal($x, 0, 1, $which);
    printf("%9.6f\t%f\t%s\n", $x, $p, str_repeat('*', round($p * 100)));
}

実行結果は以下のようになります。

$ php normcdf.php
-2.000000       0.022750        **
-1.800000       0.035930        ****
-1.600000       0.054799        *****
-1.400000       0.080757        ********
-1.200000       0.115070        ************
-1.000000       0.158655        ****************
-0.800000       0.211855        *********************
-0.600000       0.274253        ***************************
-0.400000       0.344578        **********************************
-0.200000       0.420740        ******************************************
-0.000000       0.500000        **************************************************
 0.200000       0.579260        **********************************************************
 0.400000       0.655422        ******************************************************************
 0.600000       0.725747        *************************************************************************
 0.800000       0.788145        *******************************************************************************
 1.000000       0.841345        ************************************************************************************
 1.200000       0.884930        ****************************************************************************************
 1.400000       0.919243        ********************************************************************************************
 1.600000       0.945201        ***********************************************************************************************
 1.800000       0.964070        ************************************************************************************************
 2.000000       0.977250        **************************************************************************************************