"The R Foundation for Statistical Computing Platform"を実感する

:::note info
information
本記事は2025年4月26日（土）開催のTokyoRにて発表予定の初心者セッション資料その1です。
TokyoR発表資料
[その2 「Rの統計関連(dpqr)関数チートシート」](https://qiita.com/hige_megane/private/8383b8926cb97f5040cf)
[その3 「Rの統計関連(dpqr)関数の書き方」](https://qiita.com/hige_megane/private/416c0d6b5742dfcf4050)
[その4 「誕生日のパラドックスをRの関数で求める方法」](https://qiita.com/hige_megane/private/617064410e1f8008c45d)
:::

## Rの起動画面の謳い文句を改めて実感する出来事

Rを起動するとこんな表示が出てきます。
> R version 4.5.0 (2025-04-11 ucrt) -- "How About a Twenty-Six"
> Copyright (C) 2025 The R Foundation for Statistical Computing
> Platform

統計に特化したプラットフォームであることを謳っており、それがために統計を学び統計で生計を立てている人はRを利用するのですが、それが紛う事無き真実であると改めて実感した出来事がありました。

## 統計検定のポアソン分布の問題を解いているときに…

統計検定準１級受けるための勉強しなきゃいかんなあとウダウダ考えているときに下記の問題に出会いました。

#### 問題

ネット通販サイトへの注文が1分間に平均3件入るポアソン過程を仮定する。1時間(60分)で150件以上の注文がある確率を求めよ。


#### 解答
60分間では平均で$3 \times 60 = 180$件の注文が入る。よってポアソン分布$X \sim Po(180)$に従う。ポアソン分布$X \sim Po(180)$は正規分布$Y \sim N(180, 180)$に近似するので連続補正を使い

```math
P(X \geq 150) \approx P(Y > 149.5) = P\left(Z > \dfrac{149.5 - 180}{\sqrt{180}}\right)
```
```math
= P(Z > -2.27) = 0.988
```

#### でもRならポアソン分布を直接計算できる
おそらく上記が求められている模範解答ですが、せっかくRを使っているのだから、ポアソン分布の計算をしてみようと思いました。確率変数$X$がポアソン分布に従う場合、ある期間に平均$\\lambda$回おこる現象が$k$回起こる確率は、

```math
P(X=k) = \frac{\exp(-\lambda)\lambda^k}{k!}
```

ポアソン分布は離散型確率分布であり、$k$は0以上の整数なので、計算式をコーディングして149件以下の注文確率の総和を求めて、1から引けばいいじゃん、と思いました。計算式にすると、

```math
P(X \geq 150) = 1 - P(X \leq 149) = 1 - \displaystyle\sum_{k = 0}^{149} P(X = k) = 1 - \sum_{k = 0}^{149} \frac{\exp(-\lambda)\lambda^k}{k!}
```

ということでこんなRコードを作ってみました。

```R:poisson1.r
library(conflicted)
library(tidyverse)

lambda <- 180
k <- 149
start_num <- 0
end_num <- k

df <- dplyr::tibble(num = seq(start_num, end_num)) |> 
  mutate(x_n = str_c("P(X=", num, ")")) |> 
  mutate(po = (exp(-lambda) * lambda^num) / factorial(num))
> head(df)
# A tibble: 6 × 3
    num x_n          po
  <int> <chr>     <dbl>
1     0 P(X=0) 6.71e-79
2     1 P(X=1) 1.21e-76
3     2 P(X=2) 1.09e-74
4     3 P(X=3) 6.53e-73
5     4 P(X=4) 2.94e-71
6     5 P(X=5) 1.06e-69

vec_total <- sum(df$po)
1 - vec_total
```


しかし結果は

```R:poisson1_result.r
> vec_total
[1] Inf
> 1 - vec_total
[1] -Inf
```

なぜ???

```poisson_tail.r
> tail(df, n = 15)
# A tibble: 15 × 3
     num x_n               po
   <int> <chr>          <dbl>
 1   135 P(X=135)   0.0000723
 2   136 P(X=136)   0.0000957
 3   137 P(X=137) Inf        
 4   138 P(X=138) Inf        
 5   139 P(X=139) Inf        
 6   140 P(X=140) Inf        
 7   141 P(X=141) Inf        
 8   142 P(X=142) Inf        
 9   143 P(X=143) Inf        
10   144 P(X=144) Inf        
11   145 P(X=145) Inf        
12   146 P(X=146) Inf        
13   147 P(X=147) Inf        
14   148 P(X=148) Inf        
15   149 P(X=149) Inf     
```

po列の値から見て$X \geq 137$以降の値がinfinityになるとは考えられない。

しかしですねー、冷静に考えればこれは計算中のオーバーフローに違いない。そりゃ$180^{137}$や$137!$は相当な値だからして！実際Rに計算させてみると、

```cul.r
> 180^136
[1] 5.212676e+306
> 180^137
[1] Inf
```

むしろ、よくぞ$180^{136}$まで計算していただきました。

:::note info
2025年4月22日追記
私の環境（windows10、R4.5.0)では、下記がdouble型で取り扱える最大値と最小値のようです。
```r_max_min.r
> .Machine$double.xmax
[1] 1.797693e+308
> .Machine$double.xmin
[1] 2.225074e-308
```
:::

#### 「自然対数をとる」の意味を知る

そこで、どうすれば良いか生成AIに尋ねてみると、自然対数とれば良いとの回答。理系の皆様には当然のことかもしれませんが、先人たちが確立した数学の世界は素晴らしい！ということを文系のわたしくめも50歳となりようやく感じ入りました。

```math
P(X=k) = \frac{\exp(-\lambda)\lambda^k}{k!} \rightarrow \ln\{P(X=k)\} = -\lambda + k \ln \lambda - \ln k!
```

上記の式でもまだ$k!$があるので、そのときはガンマ関数を使えば良いとの生成AIのご託宣がありました。ガンマ関数とは正の実数$x>0$について、

```math
\Gamma(x) = \int^{\infty}_0 t^{x-1} e^{-t} dt
```

と定義され、自然数$n$の場合

```math
\Gamma(n) = (n-1)!
```

が成り立つそうです（ここは理解していないのでそのまま写しています）。

つまり$\ln(k!) = \ln \Gamma(n + 1)$であるので、ガンマ関数の自然対数をとる関数 `lgamma(k+1)`を使用することで、階乗のオーバーフローも防げるということですね。で、最後に求めた$-\lambda + k \ln(\lambda) - \ln(k!)$の値を`exp()`関数で元に戻せば良いです。以上をまとめると

```math
\ln P(X = k) = -\lambda + k \ln \lambda - \ln \Gamma(k + 1)
```

```math
P(X = k) = \exp\{\ln P(X = k)\}
```

```R:poisson_improve.r
df_improve <- dplyr::tibble(num = seq(start_num, end_num)) |> 
  mutate(x_n = str_c("P(X=", num, ")")) |> 
  mutate(ln_po = - lambda + num * log(lambda) - lgamma(num + 1)) |> 
  mutate(po = exp(ln_po))
vec_total_improve <- sum(df_improve$po)

> vec_total_improve
[1] 0.009910119
> 1 - vec_total_improve
[1] 0.9900899
```

バッチリ解答が出ました。

#### ♪こんな計算しなくても

しかし、生成AIより更にこんなご託宣も。

```ppois.r
> ppois(149, 180)
[1] 0.009910119
> ppois(149, 180, lower.tail = FALSE)
[1] 0.9900899
```

なんと関数一発じゃないですか！！！

そりゃそーですよね。Rは統計のために作られたものですからこんなことは朝飯前のはずでした…。

今の生成AIは本当に気が利いていて、ついでに149件の注文が入る確率$P(X = 149)$を求める関数までお示ししてくださいました。

```dpois.r
> dpois(149, 180)
[1] 0.00191335
```

ここまできてやっと「あー、`prorm()`と`dnorm()`の関係かあ」と気付くのでした。

![poisson_149.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1015152/f9740859-fae2-46f1-b563-26a0b26794c2.png)


Rの統計に関する関数が便利なことを改めて思い知らされたので、せっかくだから一覧にしてまとめようというのが趣旨です。が、前置きが長くなりましたので、dpqrチートシートの記事は以下のリンクへ。

:::note info
information
TokyoRの発表はその2に続きます。
[その2 「Rの統計関連(dpqr)関数チートシート」](https://qiita.com/hige_megane/private/8383b8926cb97f5040cf)
[その3 「Rの統計関連(dpqr)関数の書き方」](https://qiita.com/hige_megane/private/416c0d6b5742dfcf4050)
[その4 「誕生日のパラドックスをRの関数で求める方法」](https://qiita.com/hige_megane/private/617064410e1f8008c45d)
:::