Tạo danh sách các số ngẫu nhiên, tổng thành 1


84

Làm cách nào để tạo danh sách N (giả sử 100) số ngẫu nhiên, sao cho tổng của chúng là 1?

Tôi có thể lập danh sách các số ngẫu nhiên với

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

Tôi sẽ sửa đổi điều này như thế nào để danh sách tổng bằng 1 (đây là để mô phỏng xác suất).


5
Nếu tổng của chúng là 1, chúng không hoàn toàn ngẫu nhiên.
fjarri

19
Chia mỗi số trong danh sách bằng cách tổng hợp danh sách
aragaer

1
@Bogdan đó không thực sự là một vấn đề.
Tom Kealy

2
@Bogdan không đúng. Chúng là ngẫu nhiên, nhưng một mức độ tự do được sử dụng hết bởi ràng buộc.
pjs

2
@pjs, có nghĩa là (tốt nhất) 99 trong số đó là ngẫu nhiên và 1 thì không. Nói cách khác, "không hoàn toàn ngẫu nhiên".
fjarri

Câu trả lời:


151

Giải pháp đơn giản nhất thực sự là lấy N giá trị ngẫu nhiên và chia cho tổng.

Một giải pháp chung chung hơn là sử dụng bản phân phối Dirichlet http://en.wikipedia.org/wiki/Dirichlet_distribution có sẵn ở dạng numpy.

Bằng cách thay đổi các tham số của phân phối, bạn có thể thay đổi "tính ngẫu nhiên" của các số riêng lẻ

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

Tùy thuộc vào tham số chính, phân phối Dirichlet sẽ cung cấp các vectơ trong đó tất cả các giá trị gần bằng 1./N trong đó N là độ dài của vectơ hoặc cung cấp các vectơ trong đó hầu hết các giá trị của các vectơ sẽ là ~ 0, và ở đó sẽ là một 1, hoặc đưa ra một cái gì đó ở giữa những khả năng đó.

CHỈNH SỬA (5 năm sau câu trả lời ban đầu): Một thực tế hữu ích khác về phân phối Dirichlet là bạn sẽ nhận được nó một cách tự nhiên, nếu bạn tạo một tập hợp các biến ngẫu nhiên được phân phối Gamma và sau đó chia chúng cho tổng của chúng.


4
+1 vì là người duy nhất đề cập đến phân phối Dirichlet. Đây sẽ là câu trả lời.
Timothy Shields

2
Tôi đã thay đổi câu trả lời được chấp nhận của mình cho câu trả lời này, vì việc chia tỷ lệ không nhất thiết phải cung cấp phân phối đồng đều.
Tom Kealy

1
@ Tom, tôi không ganh tị sự lựa chọn của bạn, và câu trả lời này là tốt đẹp, nhưng tôi muốn làm một cái gì đó rõ ràng: Mở rộng quy mô không nhất thiết phải đưa ra một phân bố đều (trên [0,1/s)). Nó sẽ chính xác như phân phối không theo tỷ lệ mà bạn đã bắt đầu, bởi vì việc chia tỷ lệ không thay đổi phân phối mà chỉ nén nó. Câu trả lời này đưa ra nhiều cách phân bổ, chỉ một trong số đó là đồng nhất. Nếu điều này không hợp lý với bạn, hãy chạy các ví dụ và xem một số biểu đồ để làm rõ điều đó. Cũng thử điều tương tự với phân phối gaussian ( np.random.normal).
askewchan

@askewchan, bạn nói không đúng ở đây. lấy các số ngẫu nhiên và chia cho tổng sẽ KHÔNG cho phân phối đồng nhất (nó sẽ gần đồng nhất đối với N rất lớn, nhưng không bao giờ đồng nhất hoàn toàn và cũng không đồng nhất ở N nhỏ hơn). Phân phối Dirichlet cũng sẽ không cung cấp các phân phối đồng nhất (vì không thể có được các phân phối đồng nhất và tổng của 1).
sega_sai

@sega_sai Trong mạch đó, không có sự phân bố đồng nhất nghiêm ngặt nào có thể được tạo giả ngẫu nhiên. Ý tôi là việc tái chuẩn hóa một phân phối 'đồng nhất' không làm cho nó kém đồng đều hơn. Tôi đã trả lời bình luận của Tom ngụ ý rằng câu trả lời này được chọn vì anh ấy muốn có sự phân bố đồng đều. Trừ khi tôi nhầm lẫn về cơ bản hơn?
askewchan

39

Cách tốt nhất để làm điều này là chỉ cần tạo một danh sách bao nhiêu số tùy thích, sau đó chia tất cả cho tổng. Chúng hoàn toàn ngẫu nhiên theo cách này.

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

hoặc, theo đề xuất của @TomKealy, giữ tổng và tạo trong một vòng lặp:

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

Để có hiệu suất nhanh nhất, hãy sử dụng numpy:

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

Và bạn có thể cung cấp cho các số ngẫu nhiên bất kỳ phân phối nào bạn muốn, đối với phân phối xác suất:

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

---- Thời gian ----

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 Đừng lo, bạn sẽ dễ gặp khó khăn khi cố gắng làm những thứ này khó hơn rất nhiều :) Bây giờ nó ở đây cho người tiếp theo.
askewchan

3
Tôi nghĩ đã đến lúc uống bia.
Tom Kealy

1
Đây là một giải pháp tốt, nhưng có vẻ như cần phải có một cách để làm điều này trong một đường chuyền duy nhất có được sự phân phối tốt trong phạm vi. Tạo, tính tổng, sửa đổi là một hoạt động 3 lần. Bạn có thể tối ưu hóa ít nhất một lượt đi bằng cách tính tổng khi bạn tạo.
Silas Ray

2
Mở rộng quy mô không nhất thiết phải tốt. Xem câu trả lời của tôi để biết thêm. Có thể có nhiều ánh xạ từ [0,1) ^ n vào không gian đích (tổng x_i = 1) và chúng không thể đồng nhất!
Mike Housky

1
Điều này là sai , ít nhất là trong trường hợp bạn quan tâm đến phân phối đều liên tục thực tế stackoverflow.com/a/8068956/2075003
n1000

7

Chia từng số cho tổng có thể không mang lại cho bạn sự phân phối mà bạn muốn. Ví dụ: với hai số, cặp x, y = random.random (), random.random () chọn một điểm đồng nhất trên hình vuông 0 <= x <1, 0 <= y <1. Chia cho tổng "chiếu" điểm (x, y) lên đường thẳng x + y = 1 dọc theo đường thẳng từ (x, y) đến gốc tọa độ. Các điểm gần (0,5,0,5) sẽ có nhiều khả năng hơn các điểm gần (0,1,0,9).

Khi đó, đối với hai biến, x = random.random (), y = 1-x cho phép phân phối đồng đều dọc theo đoạn đường hình học.

Với 3 biến, bạn đang chọn một điểm ngẫu nhiên trong một khối lập phương và chiếu (theo hướng tâm, qua gốc tọa độ), nhưng các điểm gần tâm của tam giác sẽ có nhiều khả năng hơn các điểm gần các đỉnh. Các điểm kết quả nằm trên một tam giác trong mặt phẳng x + y + z. Nếu bạn cần lựa chọn không thiên vị các điểm trong tam giác đó, thì việc chia tỷ lệ là không tốt.

Vấn đề trở nên phức tạp ở thứ nguyên n, nhưng bạn có thể nhận được ước tính có độ chính xác thấp (nhưng độ chính xác cao, dành cho tất cả những người hâm mộ khoa học trong phòng thí nghiệm!) Bằng cách chọn đồng nhất từ ​​tập hợp tất cả n bộ số nguyên không âm cộng lại N, và sau đó chia mỗi người trong số họ cho N.

Gần đây tôi đã nghĩ ra một thuật toán để thực hiện điều đó đối với n có kích thước khiêm tốn, N. Xem câu trả lời của tôi tại:

Tạo số ngẫu nhiên bị ràng buộc?


Bạn nên kiểm tra bản phân phối Dirichlet .
Jonathan H

6

Tạo một danh sách bao gồm 0 và 1, sau đó thêm 99 số ngẫu nhiên. Sắp xếp danh sách. Sự khác biệt liên tiếp sẽ là độ dài của các khoảng cộng lại bằng 1.

Tôi không thông thạo Python, vì vậy hãy tha thứ cho tôi nếu có cách Pythonic khác để làm điều này. Tôi hy vọng ý định là rõ ràng mặc dù:

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

Đây là một triển khai được cập nhật trong 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

Ngoài giải pháp của @ pjs, chúng ta cũng có thể xác định một hàm với hai tham số.

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

tạo 100 số ngẫu nhiên không quan trọng phạm vi nào. tổng các số được tạo ra, chia từng cá nhân cho tổng số.


1

Trong trường hợp bạn muốn có một ngưỡng tối thiểu cho các số được chọn ngẫu nhiên (tức là các số được tạo ít nhất 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

Chỉ cần đảm bảo rằng bạn có num_of_values ​​(số lượng giá trị sẽ được tạo) để có thể tạo các số bắt buộc ( num_values <= 1/min_thesh)

Vì vậy, về cơ bản, chúng tôi đang cố định một số phần của 1 cho ngưỡng tối thiểu, sau đó chúng tôi tạo các số ngẫu nhiên ở phần khác. Chúng tôi thêm min_theshvào tất cả các số để có tổng 1. Ví dụ: giả sử bạn muốn tạo 3 số, với min_thresh = 0.2. Chúng tôi tạo một phần để điền bởi các số ngẫu nhiên [1 - (0,2x3) = 0,4]. Chúng tôi điền vào phần đó và thêm 0,2 vào tất cả các giá trị, vì vậy chúng tôi cũng có thể lấp đầy 0,6.

Đây là tỷ lệ và dịch chuyển tiêu chuẩn được sử dụng trong lý thuyết tạo số ngẫu nhiên. Tín dụng thuộc về bạn của tôi, Jeel Vaishnav (tôi không chắc liệu có hồ sơ SO) và @sega_sai.


0

Bạn có thể dễ dàng thực hiện với:

r.append(1 - sum(r))

1
Số cuối cùng sau đó tương quan với các N-1số đầu tiên .
askewchan

0

Với tinh thần "chia mỗi phần tử trong danh sách cho tổng danh sách", định nghĩa này sẽ tạo ra một danh sách các số ngẫu nhiên có độ dài = PARTS, sum = TOTAL, với mỗi phần tử được làm tròn thành PLACES (hoặc None):

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

kết quả:

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

Theo tinh thần của phương pháp 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))]

Nếu bạn muốn chúng được làm tròn đến chữ số thập phân:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.