二次元カーネル密度推定

Rで二次元カーネル密度推定してみる。別に2次元が好きなわけではなく、これはたまたまだ。1次元の場合は、Rにデフォルトで組み込まれているstatsパッケージのdensity関数を使えばヨロシ。

デフォルトで入ってくるパッケージの調べ方とインストール

MASSパッケージにあるkde2d関数を使いたいので、このパッケージを入れるんだが、MASSってデフォルトで入っていたような気もする。気もするが、どうだったかは覚えていない。こんな時、実際に入っているかいないかを調べてみる。RでロードされるデフォルトのパッケージはgetOption関数にdefaultPackagesを文字列として与えると取得できる。

> getOption("defaultPackages")
[1] "datasets"  "utils"     "grDevices" "graphics"  "stats"     "methods"  

あぁ、入ってない。というわけで、install.packages関数でインストールする必要がある。そしてMASSパッケージもロードだ。

install.packages("MASS")
library(MASS)

Rでやる

適当に2次元混合正規分布を作ってカーネル密度推定してみる。pipeRパッケージをちゃんと使いこなせるようになるのがいい大人の条件だと強く信じて疑わないので、pipeRしてます。

library(MASS)
library(dplyr)
library(pipeR)

#データ数
N <- 10^3
#中心位置
mu <- list(mu1=c(1,1), mu2=c(5, 8))
#データ生成
x <- lapply(mu, function(mu)data.frame(x=rnorm(N, mu[1]), y=rnorm(N, mu[2]))) %>>% rbind_all
plot(x)

#二次元カーネル密度推定
x %>>% with({
  #バンド幅の計算
  h <- c(bandwidth.nrd(x),bandwidth.nrd(y))
  #密度推定
  kde2d(x,y,h,n=80)
}) %>>%
(~ res) %>>% #結果をresに格納
image

kde2d関数で密度推定した結果をres変数に突っ込みつつ、そのまま画像出力(image関数)させているわけですpipeR素晴らしい。

ちなみに、kde2d関数で使えるカーネルはガウシアンカーネル限定だ。その他のパッケージについては

のPDFを見て、どんなのがあるのか調べていくのがよさげ。気がついたらこの資料の著者の1人も神Hadleyでびっくりだ。そしてこのPDFは一次元の方法(univariate)のみに特化してるそうで、二次元の場合はまた別なソースを探す必要がある。。。どこ。。。

ksパッケージなるものがあって、これを使えばよさげか?

このパッケージだと1〜6次元まで対応とのこと。ただ、マニュアルを読む限り、こいつもガウシアンカーネル限定だそうだ。そういうもんなのか?

バンド幅の選択など

kde2d関数の引数でいうところのhで指定するのがカーネルのバンド幅だ。こいつの選択方法・その良し悪しは、あまりよくわかってないが、必要に応じて以下のサイトの日本語役と手法名を照らし合わせて、テキスト/原著論文読んで〜ってやらないとだめだろうな。

上のコードで使ったバンド幅計算関数であるbandwidth.nrd関数はHELPで確認した限り

  • Venables, W. N. and Ripley, B. D. (2002) Modern Applied Statistics with S. Springer, equation (5.5) on page 130.

に詳細があるそうで、その実装も簡単でHELPに記載があり、データの25% & 75%クオンタイル点の差を1.34で除した量をむにゃむにゃルート取ったり計算しているだけだった、なんだこれ。

ちゃんと確率密度関数になってる?

ここでやってるカーネル密度推定は、データを\left\{ x_i; i=1,\dots,N\right\}として、確率密度関数p(x)を、元のデータが属している標本空間\Omega上に定義された適当なカーネル関数K : \Omega \times \Omega \rightarrow \mathbb{R}を使って

  •  p(x) = \frac{1}{N} \sum_{i=1}^{N} k(x, x_i)

として表現する手法なわけだ。従って、計算結果(res変数)がちゃんと確率密度関数になっているかどうかは、xについて\mathbb{R}、あるいは高次元(D次元)の場合は\mathbb{R}^Dの範囲で積分して、その答えが1になるかを確認すればよい。ここでもpipeRを使う。単に結果を台形公式で数値積分しているだけですわ。

> #確率が大体1になるか?
> res %>>% 
+   (~ dx <- . %>>% (x) %>>% diff %>>% first) %>>%
+   (~ dy <- . %>>% (y) %>>% diff %>>% first) %>>%
+   (z) %>>% 
+   sum %>>% `*`(dx*dy)
[1] 0.9928492

荒々な計算で大体1になってるので、多分大丈夫だろう。

こいつって正定値性は担保されてるんでしたっけ?

機械学習の文脈でいうカーネル法カーネル関数に正定値性を持たせて、再生核ヒルベルト空間を作らせて〜のノリでいくわけですが、昔からある、ここでやってるようなカーネル法では正定値性を特に意識はしてないみたい*1

参考

↑二番目の資料の「用語に関する注意」が脳の混乱を防ぐために大事。

カーネル密度推定のまとめ。

*1:参考LINKの2番目、福水先生の資料より、パルツェン窓関数〜って話がここでやってる話に相当