はじめに
この記事は統計学入門1のを読んだことをまとめた振り返り記事です。
ポアソン分布
二項分布
f(x)=nCxpx(1−p)n−x
においてnが大きいがpが小さいとき状況は往々にしてあります。つまり大量の観測データはあるが、特定のイベントが非常にレアで起こりにくいときです。二項分布の期待値はnpでした。なので、たとえ確率pが非常に小さいレアなイベントでもnが大きいので、現実的にはxがnp周りでそこそこ大きくなるはずです。
さらに二項分布の計算として冪の数が非常に多くなります。数値計算すれば問題ないかもしれませんが、nが大きく、pが非常に小さいという状況は近似が使えて、より簡単な形式でこの系を記述することができるはずです。
つまり、二項分布に対して
n→∞,p→0,np→λ
です。このとき、二項分布はポアソン分布に近似するといえます。
導出
まずは愚直に計算して、
Bi(n,p)(x)=nCxpx(1−p)n−x=x!(n)xpx(1−p)n−x=nx(n)xx!1⋅(np)x(1−nnp)n−x(1−p)−x=nx(n)xx!λx(1−nλ)n(1−p)−x
となります。ここで(n)xはポッドハマー記号で(n)x:=n!/(n−x)!と定義されます。
ここで、n→∞,p→0をとると
n→∞,p→0limBi(n,p)(x)=n→∞,p→0limnx(n)xx!λx(1−nλ)n(1−p)−x=x!λxe−λ
となります。ここで
n→∞limnx(n)xn→∞lim(1−nλ)np→0lim(1−p)−x=n→∞limi=0∏x−1(1−ni)=1=e−λ=1
を用いました。よってポアソン分布とは
f(x)=x!λxe−λ
と表されます。
ポアソン分布は明らかに確率の総和が1であることを満たします:
x=0∑∞x!λxe−λ=e−λx=0∑∞x!λx=e−λeλ=1
となります。
期待値と分散
定義により
E[X]=x=0∑∞xx!λxe−λ=λe−λx=1∑∞(x−1)!λx−1=λ
です。
2次のモーメントは
E[X2]=x=0∑∞x2x!λxe−λ=λe−λx=1∑∞x(x−1)!λx−1=λe−λx=1∑∞[(x−1)(x−1)!λx−1+(x−1)!λx−1]=λe−λx=1∑∞[(x−1)(x−1)!λx−1+(x−1)!λx−1]=λe−λ[λx=2∑∞(x−2)!λx−2+x=1∑∞(x−1)!λx−1]=λ2+λ
となります。
よって分散は
V[X]=E[X2]−(E[X])2=λ2+λ−λ2=λ
となります。
ポアソン分布では期待値と分散が等しいという性質があります。また二項分布の期待値と分散をよく観察して、n→∞,p→0,np→λを思い出しておくと、二項分布の期待値npは明らかにポアソン分布の期待値λですし、二項分布の分散np(1−p)もまた、λ(1−p)p→0⟶λとなります。
図で比較する
Kotlinで二項分布とポアソン分布を比較してみましょう。コードは下記のような感じで書いています。
import kotlin.math.ln
import kotlin.math.exp
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.roundToInt
fun factorial(n: Int, acc: Int = 1): Int {
if (n == 0) return acc
return factorial(n - 1, n * acc)
}
fun Int.logCombination(k: Int): Double {
if (k < 0 || k > this) return Double.NEGATIVE_INFINITY
val kk = min(k, this - k)
return (1..kk).sumOf { i ->
ln((this - i + 1).toDouble()) - ln(i.toDouble())
}
}
fun binomialDistribution(n: Int, p: Double, x: Int): Double {
val nDouble = n.toDouble()
val xDouble = x.toDouble()
val pp = exp(n.logCombination(x)) *
p.pow(xDouble).toDouble() *
(1.0 - p).pow(nDouble - xDouble).toDouble()
return pp
}
fun poissonDistribution(lambda: Double, x: Int): Double {
return exp(- lambda) * lambda.pow(x.toDouble()) / factorial(x).toDouble()
}
fun main() {
println("factorial: ${factorial(10)}")
println("combination: ${exp(10.logCombination(3)).roundToInt()}")
val totalTrials = 1000
val probability = 0.002
val lambda = totalTrials * probability
val tryal = 3
println("binomial: ${binomialDistribution(totalTrials, probability, tryal)}")
println("poisson: ${poissonDistribution(lambda, tryal)}")
}
実際のプロットとして、4パターンで見ています。
- n=5,p=0.4,λ=2 (左上)
- n=10,p=0.2,λ=2 (右上)
- n=100,p=0.02,λ=2 (左下)
- n=1000,p=0.002,λ=2 (右下)
確かにnが大きくなるほど、pが大きくなるほど、2つの分布を示す赤と青の点は互いに近づいていくことが見てとれます。
プロット用のコードは
val tries = (0..10).toList()
val binomialDistData = tries.map { binomialDistribution(totalTrials, probability, it) }
val poissonDistData = tries.map { poissonDistribution(lambda, it) }
val triesLong = tries + tries
val probabilities = binomialDistData + poissonDistData
val types = List(tries.size) { "Binomial" } + List(tries.size) { "Poisson" }
val data = mapOf(
"tries" to triesLong,
"probability" to probabilities,
"type" to types
)
letsPlot(data) +
geomPoint(size = 4.0) { x = "tries"; y = "probability"; color = "type" } +
ggtitle("n = $totalTrials, p = $probability, and lambda = $lambda ") +
xlab("tries") +
ylab("Probability")
です(実行できません)。