データ発見隊

最終回 乱数思考

この記事を読むのに必要な時間:およそ 6 分

宝クジの購入は非合理的

人間は確率的な思考が得意ではありません。宝クジが当たる確率は非常に低く,賞金の期待値は投資額の半分しかありません。宝クジの購入が非合理的なのは明らかです。にもかかわらず,たくさんの人が一攫千金を夢見て購入するのは不思議です。

宝クジの場合は嫌なら買わなければよいのですが,保険の種類の選択に悩んだり,携帯電話の料金プランに悩んだりと,確率計算と無縁に生活することは困難です。

間違えやすい確率問題

確率計算が必要な問題は,直感に反することがよくあるので注意が必要です。

誕生日一致問題

確率の見積りを間違えやすい例として,⁠N人の人間がいるとき,同じ誕生日の人がいる確率はどれぐらいか?」という問題があります。誕生日は365種ありますし,自分と同じ誕生日の人を知っていることは珍しいので,かなり多くの人間を集めないと誰かの誕生日が一致することはないだろうと思いがちです。しかし実際は,50人ぐらい人間がいれば,ほぼ確実に誕生日が同じである人がいるはずです。

数式による計算

誕生日が一致する2人がいる確率の計算は比較的簡単です。N人の人間の誕生日がすべて異なる確率を計算し,これを1から引けばいいわけです。全員を並べたとき最初の人と2番目の人の誕生日が異なる確率は364/365であり,3番目の人の誕生日がまた異なる確率は363/365であり……ということになるので,N人の誕生日がすべて異なる確率は,

p = 1 * 364/365 * 363/365 * ... (365+1-N)/365

という式で計算できます。

50人いた場合に誕生日が一致する2人がいる確率(1-p)は,次のRubyプログラムで計算できます。

n = (ARGV.shift || 50).to_i

p = 1.0
n.times { |i|
  p *= (365.0 - i) / 365.0
}
puts 1.0 - p

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

% ruby tanjoubi0.rb 50
0.97037357

50人いた場合に誕生日が一致する2人がいる確率は,約97%となりました。この結果は正しいのでしょうか。

シミュレーションによる計算

数式による計算結果が心配な場合,シミュレーションによる計算も行えば,結果を確信できるでしょう。N人の誕生日をランダムに生成し,同じ誕生日の人が見つかるかどうかを何度も計算します。

n = (ARGV.shift || 50).to_i # 人間の数
trials = (ARGV.shift || 10000).to_i # 試行回数

match = 0 # 同じ誕生日の人がいた回数

trials.times {
  birthday = {}
  n.times { |i|
    day = rand(365)
    if birthday[day] then # 誕生日の衝突発見
      puts "#{birthday[day]}番目と#{i}番目が一致"
      match += 1
      break
    end
    birthday[day] = i
  }
}

puts match.to_f / trials.to_f

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

% ruby tanjoubi.rb 50
0番目と20番目が一致
2番目と22番目が一致
...
15番目と23番目が一致
5番目と26番目が一致
0.9728

50人いた場合に誕生日が一致する2人がいる確率も,約97%となりました。シミュレーションなので毎回結果は異なりますが,何度計算しても同じような値が得られるので,数式による計算が正しかったことをほぼ確信できるでしょう。

兄弟の性別問題

誕生日一致問題の場合,きちんとした計算が必要であることはわかりますが,直感が強力な場合は間違った答えをなかなか訂正できないことがあります。

「2人の子どもの一方が男の子の場合,もう一方も男の子である確率は?」という問題も間違えやすいことで有名です。この確率は1/2だと考えてしまいがちですが,正解は1/3です。

場合分けによる計算

2人の子どもをA,Bとするとき,A,Bが男の子か女の子かという場合分けは図1のようになります。

図1 2人の子どもの性別の場合分け

図1 2人の子どもの性別の場合分け

一方が男の子であるケースは3種類ありますが,そのうち2種類のケースでもう一方は女の子になるので,この問題の答えは1/3だということになります。

シミュレーションによる計算

この問題の場合も,実際にシミュレーションによる計算をしてみることで答えを確信できます。

trials = (ARGV.shift || 10000).to_i # 試行回数
boys = 0; girls = 0
BOY = 0; GIRL = 1

trials.times {
  # 2人の子どもの性別をランダムにセット
  gender = []
  gender[0] = rand(2)
  gender[1] = rand(2)

  # 一方が男の子だった場合だけ計算
  if gender[0] == BOY || gender[1] == BOY then
    # 男の子のどちらかを選択してind1に格納
    if gender[0] == BOY && gender[1] == BOY
      ind1 = rand(2)
    elsif gender[0] == BOY
      ind1 = 0
    elsif gender[1] == BOY
      ind1 = 1
    end
    # もう一方をind2に格納
    ind2 = (ind1 == 0 ? 1 : 0)

    boys += 1 if gender[ind2] == BOY
    girls += 1 if gender[ind2] == GIRL
  end
}
puts "BOY: #{boys}"
puts "GIRL: #{girls}"
puts "Ratio: #{boys.to_f/(boys+girls)}"

実際に男の子/女の子をランダムに計算し,どちらかが男の子だったときにもう一方の性別を調べます。2人とも男の子だったとき,どちらを選んだ場合でももう一方が男の子であることは確かなのですが,問題の条件に忠実に従うように,ランダムに一方を選んでからもう一方の性別を調べるようにしています。

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

% ruby danshi.rb
BOY: 2487
GIRL: 5023
Ratio: 0.33115845539281

シミュレーションでも約33%(1/3)という答えを得ることができました。

著者プロフィール

増井俊之(ますいとしゆき)

慶應義塾大学教授。ユビキタス時代のインタフェース技術の研究開発に従事。

本棚.orgGyazoQuickMLFeedTVなど各種のネットサービスを運用中。

http://www.pitecan.com/

コメント

コメントの記入