多くのプログラムでは、現実世界の出来事をシミュレーションするために、(一見すると)ランダムな値が必要になります。
よくある身近な例としては、次のようなものがあります。
('Heads', 'Tails')からランダムに選ばれる値。コンピューターで真にランダムな値を生成するのは驚くほど難しい技術的課題なので、こうした結果は「疑似乱数」と呼ばれることがあります。
実際には、Pythonの標準ライブラリにあるrandomモジュールのような、よく設計されたライブラリは高速で柔軟であり、モデリングやシミュレーション、ゲームのほとんどの用途に十分な結果をもたらします。
このページの残りでは、randomでよく使われる関数をいくつか紹介します。
ここで扱うものよりもはるかに多くの選択肢がありますので、randomのドキュメント全体もぜひ読んでみてください。
randomモジュールの機能を使うには、まずインポートする必要があります。
>>> import random
# Choose random integer from a range
>>> random.randrange(1000)
360
>>> random.randrange(-1, 500)
228
>>> random.randrange(-10, 11, 2)
-8
# Choose random integer between two values (inclusive)
>>> random.randint(5, 25)
22
モジュール名を毎回入力するのを避けるには、特定の関数を名前でインポートできます。
>>> from random import choice, choices
# Using choice() to pick Heads or Tails 10 times
>>> tosses = []
... for side in range(10):
... tosses.append(choice(['H', 'T']))
>>> print(tosses)
['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
# Using choices() to pick Heads or Tails 8 times
>>> picks = []
... picks.extend(choices(['H', 'T'], k=8))
... print(picks)
['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
randrange()関数には3つの形式があり、range(start, stop, step)からランダムな値を選びます。
randrange(stop)は、0 <= n < stopとなる整数nを返します。randrange(start, stop)は、start <= n < stopとなる整数nを返します。randrange(start, stop, step)は、start <= n < stopであり、かつnがstart, start + step, start + 2*step...という数列に含まれるような整数nを返します。step == 1というよくある場合には、randint(a, b)関数のほうが便利で読みやすいこともあります。
randint()の結果には上限も_含まれる_ため、randint(a, b)はrandrange(a, b+1)を使うのと同じです。
>>> import random
# Select one number at random from the range 0, 499
>>> random.randrange(500)
219
# Select 10 numbers at random between 0 and 9 two steps apart.
>>> numbers = []
... for integer in range(10):
... numbers.append(random.randrange(0, 10, 2))
>>> print(numbers)
[2, 8, 4, 0, 4, 2, 6, 6, 8, 8]
# roll a die
>>> random.randint(1, 6)
4
このセクションの関数は、何らかのシーケンスなどのコンテナから始めることを前提としています。
通常はlistですが、いくつかの制約はあるもののtupleやsetの場合もあります(tupleはイミュータブルで、setは順序を持ちません)。
choice()とchoices()
choice()関数は、与えられたシーケンスからランダムに選んだ1つの要素を返します。
最も単純なのは、コイントスのような場合です。
# This will pick one of the two values in the list at random 5 separate times
>>> [random.choice(['H', 'T']) for _ in range(5)]
['T', 'H', 'H', 'T', 'H']
choices()関数を使って、リストの長さをキーワード引数として指定すれば、ほぼ同じことができます。
>>> random.choices(['H', 'T'], k=5)
['T', 'H', 'T', 'H', 'H']
上の例では、表と裏の確率が等しい公平なコインを仮定しましたが、重みを指定することもできます。 たとえば、袋の中に赤いボールが10個、緑のボールが15個入っていて、そこからランダムに1つ取り出したいとします。
>>> random.choices(['red', 'green'], [10, 15])
['red']
sample()上のchoices()の例は、統計学者が「復元抽出」と呼ぶものを想定しています。
それぞれの選択は、その後の選択の確率に何の影響も与えず、選ばれうるものの分布は毎回同じままです。
赤と緑のボールの例では、1つ選ぶたびにボールを袋に_戻し_、よく振ってから次を選びます。 これは、赤いボールを取り出したら_それを袋の外に出したままにする_場合とは対照的です。 ボールを戻さないということは、袋の中の赤いボールが減るということであり、次の選択で赤が出る可能性は_低く_なります。
この「非復元抽出」をシミュレーションするために、randomモジュールにはsample()関数があります。
sample()の構文はchoices()と似ていますが、countsというキーワード引数が追加されています。
>>> random.sample(['red', 'green'], counts=[10, 15], k=10)
['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green']
サンプルは選ばれた順に返されます。
shuffle()choices()とsample()はどちらも、k > 1のとき新しいリストを返します。
これに対して、shuffle()はリストの順序を_その場で_ランダムに並べ替え、元の順序は失われます。
>>> my_list = [1, 2, 3, 4, 5]
>>> random.shuffle(my_list)
>>> my_list
[4, 1, 5, 2, 3]
ここまでは、どの結果も同じ確率で起こる場合に焦点を当ててきました。
たとえば、random.randrange(100)は0から99までのどの整数も同じ確率で返します。
現実世界の多くの状況は、これよりもはるかに複雑です。
そのため、統計学者は「現実世界」の結果を数学的に記述するために、さまざまなdistributionsを考案してきました。
整数の場合、すべての確率が等しいときにはrandrange()とrandint()を使います。
これはuniform分布と呼ばれます。
randrange()とrandint()には、浮動小数点数を扱う同等の関数があります。
__random()__は、0.0 <= x < 1.0となるfloat値xを返します。
__uniform(a, b)__は、a <= x <= bとなるxを返します。
>>> [round(random.random(), 3) for _ in range(5)]
[0.876, 0.084, 0.483, 0.22, 0.863]
>>> [round(random.uniform(2, 5), 3) for _ in range(5)]
[2.798, 2.539, 3.779, 3.363, 4.33]
「正規」分布、「ベル型」曲線とも呼ばれ、測定値の不正確さを表すごく一般的な方法です。
たとえば、勤め先の工場が、同じはずのボルトを1万本購入したとします。
それらを扱えるように工場のロボットを設定したいので、100本をサンプルとして重さを量ったところ、平均(mean)の重さが4.731gであることがわかりました。
これは、すべてがちょうど4.731gであることを意味する可能性はきわめて低いです。
値は4.627から4.794gの範囲にありながら、4.731gの周辺に集まっていることがわかるかもしれません。
これがGaussian distributionであり、平均で確率が最も高くなり、両側に対称に裾を引きます(これが「ベル型」と呼ばれるゆえんです)。
これをソフトウェアでシミュレーションするには、曲線の幅を指定する方法が必要です(一般に、高価なボルトは安価なボルトよりも平均の周りに密集します!)。
慣例として、これはstandard deviationで表します。値が小さいと鋭く狭い曲線に、大きいと低く広い曲線になります。
数学者はギリシャ文字が大好きなので、平均を表すのにμ('mu')、標準偏差を表すのにσ('sigma')を使います。
したがって、「95%の値がμの2σ以内にある」とか「ヒッグス粒子が5シグマの信頼度で検出された」といった記述を目にしたら、それは標準偏差に関するものです。
>>> mu = 4.731
>>> sigma = 0.316
>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)]
[4.72, 4.957, 4.64, 4.556, 4.968]