クーポンコレクター問題の理論的な解やグラフ、分布関数出力プログラムや推移行列など

タイトルのとおりです。

クーポンコレクター問題の理論的な解やグラフ、分布関数などを出力するpythonプログラムを書きます。

 

もちろん、「ピクミンブルーム」のデコ収集の計画立てにも使えます。

確率分布の理論的な解やグラフを出力するプログラム

  • x回目ピッタリにコンプする確率のグラフ
  • x回目以下でコンプできる確率の累積分布のグラフ
  • k種類のクーポンコレクター問題の平均、分散、標準偏差と、x回目以下でコンプできる確率表

を出力するプログラムを以下に記載します。

引数、パラメータ(#で解説している部分)
  • k:収集するべきクーポンの数
  • numTrial:クーポン入手回数の上限値。この回数を超える試行の確率計算は行われず、グラフ描画も打ち切り
  • step=10:グラフのx軸のメモリの間隔。適宜変更すること
from sympy.functions.combinatorial.numbers import stirling
import matplotlib.pyplot as plt
import math
import numpy as np


k = CouponKind = 7  #クーポンの数
numTrial = 50  #入手回数の上限値


x = []
y = []
for n in range(1, numTrial+1):
    stir = stirling(n-1, k-1, kind = 2)
    prob = math.factorial(k) * stir / (k**n)
    x.append(n)
    y.append(prob)
plt.bar(x, y, label='k = {}'.format(k))
plt.xticks(np.arange(0, numTrial+1, step=10))  #stepは適宜変更
plt.grid(linestyle="--")
plt.legend()
plt.show()

z=[0]*numTrial
psum=0
yy=[0]+y
for i in range(0,numTrial):
    psum += y[i]
    z[i] = psum
plt.plot(x, z, label='k = {} cdf'.format(k),)
plt.xticks(np.arange(0, numTrial+1, step=10))  #stepは適宜変更
plt.yticks(np.arange(0, 1.1, step=0.1))
plt.grid(linestyle="--")
plt.legend()
plt.show()
print()


hk=0
for i in range(1,CouponKind+1):
    hk += 1/i
var=0
for j in range(1,CouponKind+1):
    var += (1-j/CouponKind)/((j/CouponKind)**2)
print("●",CouponKind,"種類")
print("平均:",CouponKind*hk,"回")
print("分散:",var)
print("標準偏差:",math.sqrt(var))
print()

zz=[0]*numTrial
for i in range(0,numTrial):
    zz[i]=float(z[i])
    print(i+1,"回目まで:",100*zz[i],"%")

 

出力結果

  • x回目ピッタリにコンプする確率のグラフ
  • x回目以下でコンプできる確率の累積分布のグラフ
  • k種類のクーポンコレクター問題の平均、分散、標準偏差と、x回目以下でコンプできる確率表

が以下の通り出力されます。パラメータを変えていろいろ遊んでください。

 

● 7 種類
平均: 18.15 回
分散: 55.92805555555557
標準偏差: 7.4785062382507626

1 回目まで: 0.0 %
2 回目まで: 0.0 %
3 回目まで: 0.0 %
4 回目まで: 0.0 %
5 回目まで: 0.0 %
6 回目まで: 0.0 %
7 回目まで: 0.6119899021666143 %
8 回目まで: 2.4479596086664572 %
9 回目まで: 5.770190506142363 %
10 回目まで: 10.491255465713387 %
11 回目まで: 16.309620104096272 %
12 回目まで: 22.845244044726908 %
13 回目まで: 29.730654528306395 %
14 回目まで: 36.65749237277169 %
15 回目まで: 43.39188263291682 %
16 回目まで: 49.7719823098905 %
17 回目まで: 55.69727113639329 %
18 回目まで: 61.11536513899397 %
19 回目まで: 66.00938106040012 %
20 回目まで: 70.387162811751 %
21 回目まで: 74.27272334102909 %
22 回目まで: 77.69978107148697 %
23 回目まで: 80.70707585218226 %
24 回目まで: 83.33510368557423 %
25 回目まで: 85.62393433687656 %
26 回目まで: 87.61182815894524 %
27 回目まで: 89.33442601060572 %
28 回目まで: 90.82433888810554 %
29 回目まで: 92.11100811268625 %
30 回目まで: 93.22074209219114 %
31 回目まで: 94.17686268489011 %
32 回目まで: 94.99991441024481 %
33 回目まで: 95.70790458200574 %
34 回目まで: 96.31655313519538 %
35 回目まで: 96.83953851336375 %
36 回目まで: 97.28873129188554 %
37 回目まで: 97.67441086247938 %
38 回目まで: 98.00546296305103 %
39 回目まで: 98.28955745219614 %
40 回目まで: 98.53330675218872 %
41 回目まで: 98.74240600054526 %
42 回目まで: 98.92175628871851 %
43 回目まで: 99.07557251949932 %
44 回目まで: 99.20747744705896 %
45 回目まで: 99.32058342021548 %
46 回目まで: 99.4175632612879 %
47 回目まで: 99.50071160064738 %
48 回目まで: 99.57199786459056 %
49 回目まで: 99.63311199035935 %
50 回目まで: 99.68550382254783 %

 

異なる種類数の確率分布グラフを比較、表示するプログラム

引数、パラメータ
  • CouponKind:確率分布を比較したいクーポンの種類。CouponKind = [3,4,7,14]なら3,4,7,14種類のときの分布を比較
  • numTrial:クーポン入手回数の上限値。この回数を超える試行のグラフ描画は打ち切り
  • step=5:グラフのx軸のメモリの間隔。適宜変更すること
from sympy.functions.combinatorial.numbers import stirling
import matplotlib.pyplot as plt
import math
import numpy as np


CouponKind = [3,4,7,14]  #デコピクミンの種類数。[]の中にコンマ付きで入力
numTrial = 70  #苗の入手回数の上限値


for k in CouponKind:
  x = []
  y = []
  for n in range(1, numTrial+1):
    stir = stirling(n-1, k-1, kind = 2)
    prob = math.factorial(k) * stir / (k**n)
    x.append(n)
    y.append(prob)
  plt.plot(x, y, label='k = {}'.format(k))
  plt.xticks(np.arange(0, numTrial+1, step=5))  #stepは適宜変更
  plt.grid(linestyle="--")
plt.legend()
plt.show()


for k in CouponKind:
  x = []
  y = []
  for n in range(1, numTrial+1):
    stir = stirling(n-1, k-1, kind = 2)
    prob = math.factorial(k) * stir / (k**n)
    x.append(n)
    y.append(prob)
  z=[0]*numTrial
  psum=0
  yy=[0]+y
  for i in range(0,numTrial):
    psum += y[i]
    z[i] = psum
  plt.plot(x, z, label='k = {} cdf'.format(k),)
  plt.xticks(np.arange(0, numTrial+1, step=5))  #stepは適宜変更
  plt.yticks(np.arange(0, 1.1, step=0.1))
  plt.grid(linestyle="--")
plt.legend()
plt.show()

 

出力結果

CouponKind = [3,4,7,14]のときの分布の比較グラフが、

  • x回目ピッタリにコンプする確率のグラフ
  • x回目以下でコンプできる確率の累積分布のグラフ

の2つ出力されます。

行列のn乗を利用する方法

手持ちデコ数(クーポン数≧1)を行と列の番号に取った行列(推移行列)をn乗すると、「n回のクーポンゲットで、手持ちクーポンがk個になる確率はいくつか」を求めることができます。

 

7種類の場合、推移行列は下のとおりです。上の青数字1~7が現在所持のデコ数、左が1個のデコゲット後の所持デコ数。

 ij  1 2 3 4 5 6 7
1 1/7 0 0 0 0 0 0
2 6/7 2/7 0 0 0 0 0
3 0 5/7 3/7 0 0 0 0
4 0 0 4/7 4/7 0 0 0
5 0 0 0 3/7 5/7 0 0
6 0 0 0 0 2/7 6/7 0
7 0 0 0 0 0 1/7 1

 

行列のn乗(カシオ計算機)を頼りに上の行列を10乗すると、下のようになります。

これは「n回のクーポンゲットで、手持ちクーポンがk個になる確率はいくつか」を表す行列です。

例えば、赤字の0.457は「10個のクーポン入手で1個所持から6個所持」になる確率を表し、j=1の列は「10個入手で1個所持からk個(1~7)所持になる確率」になります。

 ij  1 2 3 4 5 6 7
1 0.000
2 0.000 0.000
3 0.003 0.001 0.000
4 0.062 0.033 0.014 0.004
5 0.314 0.241 0.164 0.093 0.035
6 0.457 0.486 0.485 0.446 0.359 0.214
7 0.163 0.239 0.337 0.458 0.606 0.786 1

0個の場合は、1回クーポンを引けば確率1でj=1になるので、行列のn乗で「n-1個入手で0個所持からk個所持になる確率ベクトル」が求まります。

 

行列のn乗(カシオ計算機)で、お好きなn(クーポン入手回数)とk(クーポン種類数)いろいろ試してみてください。

 

3種類の場合

下の3×3行列をn乗

 ij  1 2 3
1 1/3 0 0
2 2/3 2/3 0
3 0 1/3 1
4種類の場合

下の4×4行列をn乗

 ij  1 2 3 4
1 1/4 0 0 0
2 3/4 2/4 0 0
3 0 2/4 3/4 0
4 0 0 1/4 1

 

ダブらない確率を求めるプログラム【誕生日問題】

何回かのクーポンを引いたとき、それらが既存のものを含めてダブらない確率を求めるプログラムを書きます。

 

  • Coupon = 24 #デコ数、クーポン数
  • have = 0 #すでに持っているデコ数。
  • z[i] = Daburanai(Coupon,i,have) #★ ダブり確率。ダブる確率"Daburi(Coupon,i,have)"に変更可

を変更してお使いください。

import matplotlib.pyplot as plt
import numpy as np
import math


Coupon = 24 #デコ数
have = 0 #すでに持っているデコ数。ただしk + have ≦ Couponであること。超えると必ずダブる;Daburanai = 0
k = Coupon - have #何個の苗を取るか

def Daburanai(C,get,havekind):
    return math.factorial(C-havekind)/math.factorial(C-get-havekind)/C**get

def Daburi(C,get,havekind):
    return 1-Daburanai(C,get,havekind)


z=[0]*(k+1)
left=[x for x in range(0,k+1)]
for i in range(0,k+1):
    #print(i,"個:",100-100*Daburanai(365,i,have),"%")
    z[i] = Daburanai(Coupon,i,have) #★ ダブり確率。適宜ダブらない"Daburanai(Coupon,i,have)"に変更可


plt.xticks(np.arange(0, k+1, step=2)) #stepはx軸の目盛り幅。適宜変更
plt.yticks(np.arange(0, 1.1, step=0.1))
plt.xlabel('Number of Coupon')
plt.ylabel('Probability')
plt.grid(linestyle="--")
plt.plot(left, z)
plt.show()

for i in range(1,k+1):
    print(i,"個:",100*z[i],"%")

 

なお、このプログラムはCoupon = 365にし、z[i] = Daburi(Coupon,i,have)にすれば「誕生日問題」(k人の中で同じ誕生日同士の人がいる確率)のシミュレーションプログラムになります。

 

出力結果

k個クーポンを引いてダブらない確率のグラフ
確率表
1 個: 100.0 %
2 個: 95.83333333333334 %
3 個: 87.84722222222221 %
4 個: 76.86631944444444 %
5 個: 64.05526620370371 %
6 個: 50.7104190779321 %
7 個: 38.032814308449076 %
8 個: 26.93991013515143 %
9 個: 17.95994009010095 %
10 個: 11.224962556313095 %
11 個: 6.547894824515972 %
12 個: 3.546776363279485 %
13 個: 1.7733881816397425 %
14 個: 0.8128029165848819 %
15 個: 0.3386678819103675 %
16 個: 0.12700045571638782 %
17 個: 0.04233348523879594 %
18 個: 0.012347266527982148 %
19 個: 0.0030868166319955362 %
20 個: 0.0006430867983324035 %
21 個: 0.0001071811330554006 %
22 個: 1.3397641631925072e-05 %
23 個: 1.116470135993756e-06 %
24 個: 4.6519588999739835e-08 %

 

z[i] = Daburanai(Coupon,i,have) #★ ダブり確率。ダブる確率"Daburi(Coupon,i,have)"に変更可

をDaburiにすれば、ダブる確率に変更可能です。

 

参考サイト

・確率分布の解析値とそれに基づくコンピューターシミュレーション:
クーポンコレクター問題の確率分布を解き明かす

・解析値とそれに基づくコンピューターシミュレーションその2:
クーポンコレクター問題の拡張とその確率分布

・コンピューターシミュレーション:
クーポンコレクター問題:どうぶつの森で73種類の化石コンプリートには何日かかる?

・Wikipedia:
クーポンコレクター問題

 

コメント

タイトルとURLをコピーしました