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.