난수 목록 생성, 합산 1


84

그 합이 1이되도록 N (100 개)의 난수 목록을 어떻게 만들까요?

난수 목록을 만들 수 있습니다.

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

목록의 합이 1이되도록 수정하려면 어떻게해야합니까 (확률 시뮬레이션을위한 것입니다).


5
합이 1이면 완전히 무작위가 아닙니다.
fjarri 2014 년

19
목록의 각 숫자를 목록의 합계로
나눕니다

1
@Bogdan은 실제로 문제가 아닙니다.
Tom Kealy 2013 년

2
@Bogdan이 올바르지 않습니다. 무작위이지만 제약 조건에 의해 1 자유도가 사용됩니다.
pjs 2014 년

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]]

주 매개 변수에 따라 Dirichlet 분포는 모든 값이 1에 가까운 벡터를 제공합니다. 여기서 N은 벡터의 길이이며, 대부분의 벡터 값이 ~ 0 인 벡터를 제공합니다. 단일 1이거나 그 가능성 사이에 무언가를 줄 것입니다.

편집 (원래 답변 5 년 후) : Dirichlet 분포에 대한 또 다른 유용한 사실은 감마 분포 랜덤 변수 집합을 생성 한 다음 합계로 나누면 자연스럽게 얻을 수 있다는 것입니다.


4
Dirichlet 배포판을 언급 한 유일한 사람은 +1입니다. 이것이 답이되어야합니다.
디모데 방패

2
스케일링이 반드시 균일 한 분포를 제공하는 것은 아니기 때문에 이에 대한 내 대답을 변경했습니다.
Tom Kealy 2013 년

1
@Tom, 나는 당신의 선택을 괴롭히지 않으며이 대답은 좋지만 분명히하고 싶습니다. 확장 반드시 균일 한 분포를 제공합니다 (이상 [0,1/s)). 스케일링은 분포를 변경하지 않고 압축하기 때문에 시작한 스케일링되지 않은 분포와 똑같이 균일합니다. 이 답변은 다양한 분포를 제공하며 그중 하나만 균일합니다. 이것이 이해가되지 않는 경우 예제를 실행하고 몇 가지 히스토그램을 살펴보고 명확하게하십시오. 가우스 분포 ( np.random.normal) 에서도 동일한 작업을 시도하십시오 .
askewchan 2013-09-09

@askewchan, 당신은 여기에 맞지 않습니다. 난수를 취하고 합계로 나누면 균일 한 분포를 얻을 수 없습니다 (매우 큰 N의 경우 균일에 가깝지만 엄격하게 균일하지 않으며 작은 N에서는 전혀 균일하지 않습니다). Dirichlet 분포도 균일 분포를 제공하지 않습니다 (균등 분포와 합 1을 얻을 수 없기 때문에).
sega_sai

@sega_sai 그 맥락에서 의사 무작위로 생성 할 수있는 엄격하게 균일 한 분포는 없습니다. 내 말은 '균일 한'분포를 재 정규화한다고해서 덜 균일 해지지는 않는다는 것입니다. 나는 그가 획일적 인 배포를 원했기 때문에이 답변이 선택되었음을 암시하는 Tom의 의견에 응답했습니다. 내가 더 근본적으로 착각하지 않는 한?
askewchan

39

이를 수행하는 가장 좋은 방법은 원하는만큼의 숫자 목록을 만든 다음 모두 합계로 나누는 것입니다. 이런 식으로 완전히 무작위입니다.

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

또는 @TomKealy가 제안한대로 합계와 생성을 하나의 루프로 유지합니다.

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
@Tom 걱정하지 마세요. 이러한 것들을보다 더 어렵게 만들려고 애쓰는 것은 쉽습니다. :) 이제 다음 사람을 위해 여기 있습니다.
askewchan 2014 년

3
맥주 시간 인 것 같아요.
Tom Kealy 2013 년

1
이것은 좋은 솔루션이지만 범위 전체에 걸쳐 좋은 분포를 얻는 단일 패스로이를 수행하는 방법이 있어야하는 것 같습니다. 생성, 합산, 수정은 3 단계 작업입니다. 그래도 생성 할 때 합산하여 적어도 하나의 패스를 최적화 할 수 있습니다.
Silas Ray

2
확장이 반드시 좋은 것은 아닙니다. 자세한 내용은 내 대답을 참조하십시오. [0,1) ^ n에서 대상 공간 (x_i의 합계 = 1)으로의 가능한 매핑이 많이 있으며 모두 균일 할 수는 없습니다!
Mike Housky 2013 년

1
적어도 실제 균일 한 배포에 관심이있는 경우에는 이것은 잘못된 것입니다 . stackoverflow.com/a/8068956/2075003
n1000

7

각 숫자를 합계로 나누면 원하는 분포를 얻지 못할 수 있습니다. 예를 들어, 숫자가 두 개인 경우 x, y = random.random (), random.random () 쌍은 사각형 0 <= x <1, 0 <= y <1에서 균일하게 점을 선택합니다. (x, y)에서 원점까지의 선을 따라 x + y = 1 선으로 (x, y)를 가리키는 "projects"합계로 나눕니다. (0.5,0.5) 근처의 포인트는 (0.1,0.9) 근처의 포인트보다 훨씬 더 가능성이 높습니다.

두 변수의 경우 x = random.random (), y = 1-x는 기하학적 선분을 따라 균일 한 분포를 제공합니다.

3 개의 변수를 사용하면 큐브에서 임의의 점을 선택하고 투영 (원점을 통해 방사형)하지만 삼각형 중심 근처의 점이 정점 근처의 점보다 가능성이 높습니다. 결과 점은 x + y + z 평면의 삼각형에 있습니다. 해당 삼각형에서 편향되지 않은 점 선택이 필요한 경우 크기 조정은 좋지 않습니다.

문제는 n 차원에서 복잡해 지지만, 음이 아닌 정수의 모든 n- 튜플 집합에서 다음을 합산하여 균일하게 선택하여 낮은 정밀도 (하지만 높은 정확도) 추정치를 얻을 수 있습니다. N, 그리고 각각 N으로 나눕니다.

저는 최근에 적당한 크기의 n, N에 대해이를 수행하는 알고리즘을 고안했습니다. 6 자리 랜덤을 제공하려면 n = 100 및 N = 1,000,000에서 작동해야합니다. 내 대답은 다음에서 참조하십시오.

제한된 난수를 만드시겠습니까?


Dirichlet 배포판을 확인해야합니다 .
Jonathan H

6

0과 1로 구성된 목록을 만든 다음 99 개의 난수를 추가합니다. 목록을 정렬하십시오. 연속적인 차이는 1이되는 간격의 길이입니다.

나는 파이썬에 능통하지 않으므로 이것을 수행하는 더 파이썬적인 방법이 있다면 나를 용서하십시오. 그래도 의도가 분명하기를 바랍니다.

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

다음은 Python 3의 업데이트 된 구현입니다.

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의 솔루션 외에도 두 개의 매개 변수로 함수를 정의 할 수 있습니다.

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_values <= 1/min_thesh) 를 생성 할 수 있도록 num_of_values ​​(생성 할 값 수)가 있는지 확인하십시오.

따라서 기본적으로 최소 임계 값에 대해 1의 일부를 고정한 다음 다른 부분에 난수를 만듭니다. 우리는 추가 min_thesh예를 들어 합계 1을 얻기 위해 모든 번호 : min_thresh는 0.2 = 당신은, 3 개 개의 숫자를 생성 할 말을 할 수 있습니다. 난수 [1-(0.2x3) = 0.4]로 채울 부분을 만듭니다. 그 부분을 채우고 모든 값에 0.2를 더하여 0.6도 채울 수 있습니다.

이것은 난수 생성 이론에서 사용되는 표준 스케일링 및 이동입니다. 신용은 내 친구 Jeel Vaishnav (SO 프로필이 있는지 확실하지 않음)와 @sega_sai에게갑니다.


0

다음과 같이 쉽게 할 수 있습니다.

r.append(1 - sum(r))

1
그런 다음 마지막 숫자는 첫 번째 N-1숫자 와 상관됩니다 .
askewchan

0

"목록의 각 요소를 목록의 합계로 나누기"의 정신으로,이 정의는 각 요소가 PLACES (또는 없음)로 반올림 된 길이 = PARTS, 합계 = TOTAL의 난수 목록을 만듭니다.

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.