Generowanie listy liczb losowych, sumującej się do 1


84

Jak sporządziłbym listę N (powiedzmy 100) liczb losowych, tak aby ich suma wynosiła 1?

Mogę utworzyć listę liczb losowych za pomocą

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

Jak bym to zmodyfikował, aby lista sumowała się do 1 (to jest dla symulacji prawdopodobieństwa).


5
Jeśli ich suma wynosi 1, nie są one całkowicie przypadkowe.
fjarri,

20
Podziel każdy numer na liście przez sumę listy
aragaer

1
@Bogdan to nie jest problem.
Tom Kealy,

2
@Bogdan, to nie jest poprawne. Są przypadkowe, ale ograniczenie zużywa jeden stopień swobody.
pjs

2
@pjs, co oznacza, że ​​(w najlepszym przypadku) 99 z nich jest losowych, a 1 nie. Innymi słowy, „nie całkowicie przypadkowe”.
fjarri,

Odpowiedzi:


155

Najprostszym rozwiązaniem jest rzeczywiście wybranie N losowych wartości i podzielenie ich przez sumę.

Bardziej ogólnym rozwiązaniem jest użycie dystrybucji Dirichlet http://en.wikipedia.org/wiki/Dirichlet_distribution, która jest dostępna w numpy.

Zmieniając parametry rozkładu można zmienić „losowość” poszczególnych liczb

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

W zależności od głównego parametru rozkład Dirichleta da wektory, w których wszystkie wartości są bliskie 1 / N, gdzie N jest długością wektora, lub da wektorów, w których większość wartości wektorów będzie wynosić ~ 0, i tam będzie pojedynczym 1 lub da coś pomiędzy tymi możliwościami.

EDYCJA (5 lat po pierwotnej odpowiedzi): Kolejnym użytecznym faktem dotyczącym rozkładu Dirichleta jest to, że otrzymujesz go w naturalny sposób, jeśli wygenerujesz zestaw zmiennych losowych o rozkładzie Gamma, a następnie podzielisz je przez ich sumę.


5
+1 za bycie jedynym, który wspomniał o dystrybucji Dirichleta. To powinna być odpowiedź.
Timothy Shields

2
Zmieniłem moją akceptowaną odpowiedź na tę, ponieważ skalowanie niekoniecznie zapewnia jednolity rozkład.
Tom Kealy,

1
@Tom, nie żałują wyboru, i ta odpowiedź jest ładny, ale chcę zrobić coś jasne: Skalowanie nie musi stanowić jednolitej dystrybucji (ponad [0,1/s)). Będzie dokładnie tak jednolity, jak nieskalowana dystrybucja, od której zacząłeś, ponieważ skalowanie nie zmienia dystrybucji, a jedynie ją kompresuje. Ta odpowiedź daje różne rozkłady, z których tylko jeden jest jednolity. Jeśli to nie ma dla Ciebie sensu, uruchom przykłady i spójrz na niektóre histogramy, aby było jasne. Spróbuj również tego samego z rozkładem Gaussa ( np.random.normal).
askewchan

@askewchan, nie masz racji tutaj. wzięcie liczb losowych i podzielenie przez sumę NIE da równomiernego rozkładu (będzie zbliżony do jednorodnego dla bardzo dużego N, ale nigdy nie będzie ściśle jednorodny, a także wcale niejednorodny przy mniejszym N). Rozkład Dirichleta również nie da rozkładów równomiernych (ponieważ niemożliwe jest uzyskanie rozkładów równomiernych i sumy 1).
sega_sai

@sega_sai W tym duchu nie ma ściśle jednolitej dystrybucji, którą można wygenerować pseudolosowo. Chodzi mi o to, że renormalizacja „jednolitej” dystrybucji nie czyni go mniej jednolitym. Odpowiedziałem na komentarz Toma, który sugerował, że ta odpowiedź została wybrana, ponieważ chciał jednolitej dystrybucji. Chyba że zasadniczo się mylę?
askewchan

39

Najlepszym sposobem, aby to zrobić, jest po prostu sporządzenie listy dowolnej liczby liczb, a następnie podzielenie ich wszystkich przez sumę. W ten sposób są całkowicie przypadkowe.

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

lub, jak sugeruje @TomKealy, utrzymuj sumę i tworzenie w jednej pętli:

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

Aby uzyskać najszybszą wydajność, użyj numpy:

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

I możesz nadać liczbom losowym dowolny rozkład, który chcesz, aby uzyskać rozkład prawdopodobieństwa:

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

---- Wyczucie czasu ----

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 Bez obaw, łatwo jest utknąć, próbując uczynić te rzeczy dużo trudniejszymi niż są :) Teraz jest tu dla następnej osoby.
askewchan

3
Myślę, że czas na piwo.
Tom Kealy,

1
To dobre rozwiązanie, ale wydaje się, że powinien istnieć sposób na zrobienie tego w jednym przejściu, który zapewni dobry rozkład w całym zakresie. Tworzenie, sumowanie, modyfikowanie to operacja 3-przebiegowa. Możesz przynajmniej zoptymalizować jeden przebieg, sumując podczas generowania.
Silas Ray,

2
Skalowanie niekoniecznie jest dobre. Zobacz moją odpowiedź po więcej. Istnieje wiele możliwych odwzorowań z [0,1) ^ n na przestrzeń docelową (suma x_i = 1) i nie wszystkie mogą być jednolite!
Mike Housky,

1
To jest złe , przynajmniej jeśli zależy ci na rzeczywistych jednolitych dystrybucjach stackoverflow.com/a/8068956/2075003
n1000

7

Dzielenie każdej liczby przez całość może nie dać pożądanego rozkładu. Na przykład przy dwóch liczbach para x, y = random.random (), random.random () wybiera punkt równomiernie na kwadracie 0 <= x <1, 0 <= y <1. Dzieląc przez sumę „rzutuje” ten punkt (x, y) na linię x + y = 1 wzdłuż linii od (x, y) do początku. Punkty w pobliżu (0,5,0,5) będą znacznie bardziej prawdopodobne niż punkty w pobliżu (0,1,0,9).

Zatem dla dwóch zmiennych x = random.random (), y = 1-x daje równomierny rozkład wzdłuż geometrycznego odcinka linii.

Mając 3 zmienne, wybierasz losowy punkt w sześcianie i rzutujesz (promieniowo, przez początek), ale punkty w pobliżu środka trójkąta będą bardziej prawdopodobne niż punkty w pobliżu wierzchołków. Wynikowe punkty znajdują się na trójkącie w płaszczyźnie x + y + z. Jeśli potrzebujesz obiektywnego wyboru punktów w tym trójkącie, skalowanie nie jest dobre.

Problem komplikuje się w n-wymiarach, ale można uzyskać niską precyzję (ale wysoką dokładność, dla wszystkich fanów nauk laboratoryjnych!), Wybierając jednolicie ze zbioru wszystkich n-krotek nieujemnych liczb całkowitych, które sumują się do N, a następnie podzielenie każdego z nich przez N.

Niedawno wymyśliłem algorytm, który robi to dla skromnych n, N. Powinien działać dla n = 100 i N = 1000000, aby dać ci 6-cyfrowe losy. Zobacz moją odpowiedź na:

Utworzyć ograniczone liczby losowe?


Powinieneś sprawdzić dystrybucję Dirichleta .
Jonathan H,

6

Utwórz listę składającą się z 0 i 1, a następnie dodaj 99 losowych liczb. Sortuj listę. Kolejne różnice będą długościami przedziałów, które sumują się do 1.

Nie mówię biegle w Pythonie, więc wybacz mi, jeśli istnieje bardziej Pythonowy sposób na zrobienie tego. Mam jednak nadzieję, że zamiar jest jasny:

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

Oto zaktualizowana implementacja w Pythonie 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

Oprócz rozwiązania @ pjs możemy zdefiniować funkcję z dwoma parametrami.

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

wygeneruj 100 liczb losowych, nie ma znaczenia w jakim zakresie. zsumuj wygenerowane liczby, podziel każdą osobę przez sumę.


1

W przypadku, gdy chcesz mieć minimalny próg dla losowo wybranych liczb (tj. Generowane liczby powinny być co najmniej 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

Po prostu upewnij się, że masz num_of_values ​​(liczbę wartości do wygenerowania), aby można było wygenerować wymagane liczby ( num_values <= 1/min_thesh)

Zasadniczo ustalamy część 1 dla progu minimalnego, a następnie tworzymy liczby losowe w innej części. Dodajemy min_theshdo wszystkich liczb, aby otrzymać sumę 1. Na przykład: powiedzmy, że chcesz wygenerować 3 liczby, z min_thresh = 0.2. Tworzymy porcję do wypełnienia liczbami losowymi [1 - (0,2x3) = 0,4]. Wypełniamy tę porcję i dodajemy 0,2 do wszystkich wartości, więc możemy również wypełnić 0,6.

Jest to standardowe skalowanie i przesuwanie używane w teorii generowania liczb losowych. Podziękowania należą się mojej przyjaciółce Jeel Vaishnav (nie jestem pewien, czy ma profil SO) i @sega_sai.


0

Możesz łatwo zrobić z:

r.append(1 - sum(r))

1
Ostatnia liczba jest następnie skorelowana z pierwszymi N-1liczbami.
askewchan

0

W duchu „podziel każdy element na liście przez sumę listy”, definicja ta utworzy listę liczb losowych o długości = CZĘŚCI, suma = TOTAL, z każdym elementem zaokrąglonym do MIEJSC (lub Żaden):

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

wynik:

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

W duchu metody 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))]

Jeśli chcesz, aby zostały zaokrąglone do miejsc dziesiętnych:

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.