【実験】1%は100回やっても63%らしい
追記: 記事になった
1%で起こる事象は、100回試行しても63%の確率でしか起こらない。
もう少し具体的に言うと、1%で起こる事象Aを出すべく100回試行した際、少なくとも一回事象Aが起こる確率は約63%である。
といっても、私のような確率初心者にとっては、いくら理論がそうだと言っていても直感的には信じがたい話である。100%でないことくらいはわかるけど...
こういうときに役に立つのはいつだってプログラミング。ということでコードを書いて試していく。
用語メモ
- 事象(
event): 試行の結果として起こりうる出来事 - 試行(
experiment): ある結果を得るために行う行為や操作 - 確率(
probability): ある事象が起こる可能性の度合い
63%を計算で求める場合
まず確率とは、望ましい結果の数 / すべての結果の数で求めることができる。
しかしこれは試行回数が1回の場合の確率であり、試行回数が複数回(今回なら100回)の場合に1回でも起こるかの確率を求める場合は変わってくる。
求め方は1 - 事象が起こらない結果の確率、つまり1 - 余事象が起こる確率となる。
この余事象が起こる確率は、1(100%) - 1%つまり99%を100回連続で引くかどうか、という話になる。
これは単純に0.99^100で求められるらしい。
ということで、最終的な式は1 - 0.99^100となり、結果はだいたい63%になる。
確率についてここで解説したような入門的なところから学びたい場合は、中学や高校の数学の教科書をひっぱり出すか、ChatGPTなどのお好みのAiに解説してもらおう。
TypeScriptの場合
interface EventResult {
winning: boolean, // 事象が起こったか
count: number // 何回目で起こったか、デバッグや結果を眺めるとき用
}
// 1%で起こる事象を100回やる
// 計算的には63%の確率でwinningがtrueになる
function randomEvent100(): EventResult {
let result: EventResult | null = null
for (const count of Array(100).keys()) {
const winning = Math.random() < 0.01
if (winning) {
result = { winning, count }
break
}
}
if (result === null) result = { winning: false, count: 100 }
return result
}
// 100回やったときに出るかどうかを試行回数分繰り返し、確率にする
function experiment(trials: number) {
const array = Array(trials).fill(0).map(randomEvent100)
const probability = array.filter(r => r.winning).length / trials
return { probability, results: array }
}
const TRIALS = 1000000
const { probability } = experiment(TRIALS)
console.log(`確率: ${probability * 100}% (試行回数: ${TRIALS})`)
コード解説
-
randomEvent100関数は、100回試行した結果1%で起こる事象が起こったかどうかを返す -
experiment関数は、randomEvent100をtrial引数で指定された試行回数実行する -
experimentはrandomEvent100の呼び出し結果たちの中で何回事象が起こったかを返す - あとは
TRIALS定数で試行回数を決め、experimentを呼び出して出力するだけ
TRIALS定数は、最初は1000くらいで一度試しておいたほうがいい。
それで大丈夫そうなら10倍ずつくらい上げていけば、過度に重くなることはないと思う。
そして、このコードを実行すると、だいたい63.4%くらいになる。
確率: 63.3768% (試行回数: 1000000)
確率: 63.368% (試行回数: 1000000)
確率: 63.429899999999996% (試行回数: 1000000)
...
おまけ:
当初はIteratorクラスを継承した独自のIteratorクラスを使って実装しようとしたが、なんか別にIteratorである必要なかったのでやめた。
それはそれとしてIterator Helpersは楽しい。
Scalaの場合
case class EventResult(winning: Boolean, count: Int)
def randomEvent = {
import scala.util.Random
Random.nextInt(100) == 1
}
def randomEvent100 = {
val itr = LazyList.continually(randomEvent).take(100)
EventResult(
winning = itr.exists(identity),
count = itr.indexWhere(identity)
)
}
def experiment(trials: Int): Float = {
val itr = LazyList.continually(randomEvent100).take(trials)
itr.count(_.winning).toFloat / trials
}
val TRIALS = 100000
val probability = experiment(TRIALS)
println(s"確率: ${probability * 100}% (試行回数: $TRIALS 回)")
おまけ:
- 10%が10回で引ける確率:
0.6513215599(式:1 - 0.9 ** 10) - 1%が10回で引ける確率:
0.6339676587267709(式:1 - 0.99 ** 100) - 0.1%が1000回で引ける確率:
0.6323045752290363(式:1 - 0.999 ** 1000) - 0.01%が10000回で引ける確率:
0.6321389535670295(式:1 - 0.9999 ** 10000)
徐々に減っていくらしい...
おまけ2:
この式の0.9999 ** 10000、つまり(1 - 1 / n) ** nの部分、nが大きければ大きいほど自然対数eの逆数?に近づいてくらしい。
つまりnを無限とした場合、この式は1 / eって置き換えられるらしい。
なお、eは銀行の金利の分割に例えられるらしい。
要するに(1 + 1 / n) ** nって式のnを無限大にした数値がeって定義できるらしい。
そして、この式ってさっきのやつとほぼ同じである。違いは足し算か引き算か。
だからeが計算式に出てくるらしい。ってChatGPTが言ってた。
こんなところでeが出てくるとは思わなかった...
おまけ3:
「1 / n の確率をn回試すと、だいたい63.2%(1 - 1 / e)の確率で1回以上当たる」現象は、どうやらeの話も含めて結構有名らしい。
こうした例は、プログラマやデータサイエンティスト、数学を使う人のあいだでは常識に近いです。(ChatGPTより)
プログラマーの間でも常識なんだって(流れ弾
終わり
確率は奥が深いということがわかった