合計が1になる乱数のリストを生成する


84

合計が1になるように、N個(たとえば100個)の乱数のリストを作成するにはどうすればよいですか?

で乱数のリストを作ることができます

r = [ran.random() for i in range(1,100)]

リストの合計が1になるようにこれをどのように変更しますか(これは確率シミュレーション用です)。


5
それらの合計が1の場合、完全にランダムではありません。
fjarri 2013

19
リスト内の各数値をリストの合計で
割り

1
@Bogdanそれは実際には問題ではありません。
トムキーリー2013

2
@Bogdanそれは正しくありません。それらはランダムですが、1つの自由度が制約によって使い果たされます。
pjs 2013

2
@pjsは、(せいぜい)99個がランダムで、1個がランダムではないことを意味します。言い換えれば、「完全にランダムではない」ということです。
fjarri 2013

回答:


151

最も簡単な解決策は、実際にN個のランダムな値を取り、合計で割ることです。

より一般的な解決策は 、numpyで入手可能なDirichletディストリビューション http://en.wikipedia.org/wiki/Dirichlet_distributionを使用することです。

分布のパラメーターを変更することにより、個々の数値の「ランダム性」を変更できます。

>>> import numpy as np, numpy.random
>>> print np.random.dirichlet(np.ones(10),size=1)
[[ 0.01779975  0.14165316  0.01029262  0.168136    0.03061161  0.09046587
   0.19987289  0.13398581  0.03119906  0.17598322]]

>>> print np.random.dirichlet(np.ones(10)/1000.,size=1)
[[  2.63435230e-115   4.31961290e-209   1.41369771e-212   1.42417285e-188
    0.00000000e+000   5.79841280e-143   0.00000000e+000   9.85329725e-005
    9.99901467e-001   8.37460207e-246]]

>>> print np.random.dirichlet(np.ones(10)*1000.,size=1)
[[ 0.09967689  0.10151585  0.10077575  0.09875282  0.09935606  0.10093678
   0.09517132  0.09891358  0.10206595  0.10283501]]

主なパラメーターに応じて、ディリクレ分布は、すべての値が1./Nに近いベクトルを与えるか、Nがベクトルの長さであるか、ベクトルの値のほとんどが〜0になるベクトルを与えます。単一の1になるか、それらの可能性の間に何かを与えます。

編集(元の回答から5年後):ディリクレ分布に関するもう1つの有用な事実は、ガンマ分布の確率変数のセットを生成し、それらをそれらの合計で割ると、自然に得られることです。


4
ディリクレ分布について言及しているのは+1だけです。これが答えになるはずです。
ティモシーシールド

2
スケーリングは必ずしも一様分布を与えるとは限らないため、私はこれに対する私の受け入れられた答えを変更しました。
トムキーリー2013

1
@トム、私はあなたの選択を恨みません、そしてこの答えは素晴らしいです、しかし私は何かを明確にしたいと思います:スケーリング必然的に均一な分布を与えます(以上[0,1/s))。スケーリングは分布を変更せず、圧縮するだけなので、最初にスケーリングされていない分布とまったく同じように均一になります。この答えはさまざまな分布を示しますが、そのうちの1つだけが均一です。これが意味をなさない場合は、例を実行し、いくつかのヒストグラムを見て明確にします。また、ガウス分布(np.random.normal)で同じことを試してください。
askewchan 2013

@askewchan、あなたはここで正しくありません。乱数を取り、合計で割ると、一様分布にはなりません(非常に大きいNの場合は一様に近くなりますが、厳密に一様になることはなく、小さいNではまったく一様ではありません)。ディリクレ分布も一様分布を与えません(一様分布と1の合計を取得することは不可能であるため)。
sega_sai 2013

@sega_saiその意味で、疑似ランダムに生成できる厳密に均一な分布はありません。私が言いたいのは、「一様」分布を再正規化しても、それがそれほど均一にならないということです。私は、一様分布を望んでいたためにこの回答が選択されたことを示唆するトムのコメントに応えていました。私がもっと根本的に間違っていない限り?
askewchan 2013

39

これを行う最良の方法は、必要な数の数値のリストを作成し、それらをすべて合計で割ることです。このように完全にランダムです。

r = [ran.random() for i in range(1,100)]
s = sum(r)
r = [ i/s for i in r ]

または、@ TomKealyによって提案されているように、合計と作成を1つのループに保持します。

rs = []
s = 0
for i in range(100):
    r = ran.random()
    s += r
    rs.append(r)

最速のパフォーマンスを得るには、次を使用しますnumpy

import numpy as np
a = np.random.random(100)
a /= a.sum()

また、確率分布の場合、乱数に任意の分布を与えることができます。

a = np.random.normal(size=100)
a /= a.sum()

----タイミング----

In [52]: %%timeit
    ...: r = [ran.random() for i in range(1,100)]
    ...: s = sum(r)
    ...: r = [ i/s for i in r ]
   ....: 
1000 loops, best of 3: 231 µs per loop

In [53]: %%timeit
   ....: rs = []
   ....: s = 0
   ....: for i in range(100):
   ....:     r = ran.random()
   ....:     s += r
   ....:     rs.append(r)
   ....: 
10000 loops, best of 3: 39.9 µs per loop

In [54]: %%timeit
   ....: a = np.random.random(100)
   ....: a /= a.sum()
   ....: 
10000 loops, best of 3: 21.8 µs per loop

2
@トム心配しないでください、これらのものを彼らよりもずっと難しくしようとして立ち往生するのは簡単です:)今それは次の人のためにここにあります。
askewchan 2013

3
ビールの時間だと思います。
トムキーリー2013

1
これは良い解決策ですが、範囲全体に適切に分散される単一のパスでこれを行う方法があるはずです。作成、合計、変更は3パス操作です。ただし、生成時に合計することで、少なくとも1つのパスを最適化できます。
Silas Ray

2
スケーリングは必ずしも良いとは限りません。詳細については、私の答えを参照してください。[0,1)^ nからターゲット空間(x_i = 1の合計)へのマッピングは多数あり、すべてを均一にすることはできません。
Mike Housky 2013

1
これは間違っています。少なくとも、実際の一様分布を気にする場合stackoverflow.com
a / 8068956/2075003

7

各数値を合計で割ると、希望する分布が得られない場合があります。たとえば、2つの数値の場合、ペアx、y = random.random()、random.random()は、正方形0 <= x <1、0 <= y <1上で均一に点を選択します。(x、y)から原点までの線に沿って(x、y)を線x + y = 1に向ける「投影」の合計で除算します。(0.5,0.5)に近いポイントは、(0.1,0.9)に近いポイントよりもはるかに可能性が高くなります。

2つの変数の場合、x = random.random()、y = 1-xは、幾何学的線​​分に沿って一様分布を与えます。

3つの変数を使用して、立方体内のランダムな点を選択し、(放射状に、原点を介して)投影しますが、三角形の中心に近い点は、頂点に近い点よりも可能性が高くなります。結果の点は、x + y + z平面の三角形上にあります。その三角形のポイントを公平に選択する必要がある場合、スケーリングは適切ではありません。

問題はn次元で複雑になりますが、非負の整数のすべてのnタプルのセットから均一に選択することで、低精度(ただし、すべての実験科学ファンにとっては高精度!)の推定値を得ることができます。 N、次にそれらのそれぞれをNで除算します。

私は最近、適度なサイズのn、Nに対してそれを行うアルゴリズムを思いつきました。6桁のランダムを与えるには、n = 100およびN = 1,000,000で機能するはずです。で私の答えを参照してください:

制約付き乱数を作成しますか?


ディリクレ分布を確認する必要があります。
ジョナサンH

6

0と1で構成されるリストを作成し、99個の乱数を追加します。リストを並べ替えます。連続する違いは、合計が1になる間隔の長さです。

私はPythonに堪能ではないので、これを行うためのよりPython的な方法がある場合はご容赦ください。ただし、意図が明確であることを願っています。

import random

values = [0.0, 1.0]
for i in range(99):
    values.append(random.random())
values.sort()
results = []
for i in range(1,101):
    results.append(values[i] - values[i-1])
print results

これがPython3の更新された実装です。

import random

def sum_to_one(n):
    values = [0.0, 1.0] + [random.random() for _ in range(n - 1)]
    values.sort()
    return [values[i+1] - values[i] for i in range(n)]

print(sum_to_one(100))

3

@pjsのソリューションに加えて、2つのパラメーターを使用して関数を定義することもできます。

import numpy as np

def sum_to_x(n, x):
    values = [0.0, x] + list(np.random.uniform(low=0.0,high=x,size=n-1))
    values.sort()
    return [values[i+1] - values[i] for i in range(n)]

sum_to_x(10, 0.6)
Out: 
[0.079058655684546,
 0.04168649034779022,
 0.09897491411670578,
 0.065152293196646,
 0.000544800901222664,
 0.12329662037166766,
 0.09562168167787738,
 0.01641359261155284,
 0.058273232428072474,
 0.020977718663918954]  

1

100個の乱数を生成するのはどの範囲でも構いません。生成された数を合計し、各個人を合計で割ります。


1

ランダムに選択された数値の最小しきい値が必要な場合(つまり、生成される数値は少なくとも必要ですmin_thresh)、

rand_prop = 1 - num_of_values * min_thresh
random_numbers = (np.random.dirichlet(np.ones(10),size=1)[0] * rand_prop) + min_thresh

必要な数を生成できるように、num_of_values(生成される値の数)があることを確認してください(num_values <= 1/min_thesh

したがって、基本的に、最小しきい値として1の一部を修正してから、他の部分に乱数を作成します。我々は追加min_theshなどのために合計1を取得するために、すべての番号に:min_thresh = 0.2で、あなたは3つの数字を生成したいと言うことができます。乱数で埋める部分を作成します[1-(0.2x3)= 0.4]。その部分を埋めて、すべての値に0.2を追加するので、0.6も埋めることができます。

これは、乱数生成理論で使用される標準のスケーリングとシフトです。クレジットは私の友人のJeelVaishnav(SOプロファイルがあるかどうかはわかりません)と@sega_saiに送られます。



0

「リスト内の各要素をリストの合計で除算する」という精神で、この定義により、長さ= PARTS、合計= TOTALの乱数のリストが作成され、各要素はPLACES(またはなし)に丸められます。

import random
import time

PARTS       = 5
TOTAL       = 10
PLACES      = 3

def random_sum_split(parts, total, places):

    a = []
    for n in range(parts):
        a.append(random.random())
    b = sum(a)
    c = [x/b for x in a]    
    d = sum(c)
    e = c
    if places != None:
        e = [round(x*total, places) for x in c]
    f = e[-(parts-1):]
    g = total - sum(f)
    if places != None:
        g = round(g, places)
    f.insert(0, g)

    log(a)
    log(b)
    log(c)
    log(d)
    log(e)
    log(f)
    log(g)

    return f   

def tick():

    if info.tick == 1:

        start = time.time()

        alpha = random_sum_split(PARTS, TOTAL, PLACES)

        log('********************')
        log('***** RESULTS ******')
        log('alpha: %s' % alpha)
        log('total: %.7f' % sum(alpha))
        log('parts: %s' % PARTS)
        log('places: %s' % PLACES)

        end = time.time()  

        log('elapsed: %.7f' % (end-start))

結果:

Waiting...
Saved successfully.
[2014-06-13 00:01:00] [0.33561018369775897, 0.4904215932650632, 0.20264927800402832, 0.118862130636748, 0.03107818050878819]
[2014-06-13 00:01:00] 1.17862136611
[2014-06-13 00:01:00] [0.28474809073311597, 0.41609766067850096, 0.17193755673414868, 0.10084844382959707, 0.02636824802463724]
[2014-06-13 00:01:00] 1.0
[2014-06-13 00:01:00] [2.847, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] [2.848, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] 2.848
[2014-06-13 00:01:00] ********************
[2014-06-13 00:01:00] ***** RESULTS ******
[2014-06-13 00:01:00] alpha: [2.848, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] total: 10.0000000
[2014-06-13 00:01:00] parts: 5
[2014-06-13 00:01:00] places: 3
[2014-06-13 00:01:00] elapsed: 0.0054131

0

pjsの方法の精神で:

a = [0, total] + [random.random()*total for i in range(parts-1)]
a.sort()
b = [(a[i] - a[i-1]) for i in range(1, (parts+1))]

小数点以下を四捨五入する場合:

if places == None:
    return b
else:    
    b.pop()
    c = [round(x, places) for x in b]  
    c.append(round(total-sum(c), places))
    return c
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.