Generieren einer Liste von Zufallszahlen, Summieren zu 1

84

Wie würde ich eine Liste von N (sagen wir 100) Zufallszahlen erstellen, so dass ihre Summe 1 ist?

Ich kann eine Liste von Zufallszahlen mit erstellen

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

Wie würde ich dies so ändern, dass die Liste 1 ergibt (dies ist für eine Wahrscheinlichkeitssimulation).

Tom Kealy
quelle
5
Wenn ihre Summe 1 ist, sind sie nicht völlig zufällig.
Fjarri
19
Teilen Sie jede Zahl in der Liste durch die Summe der Liste
Aragaer
1
@ Bogdan das ist nicht wirklich ein Problem.
Tom Kealy
2
@ Bogdan das ist nicht richtig. Sie sind zufällig, aber ein Freiheitsgrad wird durch die Einschränkung aufgebraucht.
pjs
2
@pjs, was bedeutet, dass (bestenfalls) 99 von ihnen zufällig sind und 1 nicht. Mit anderen Worten, "nicht völlig zufällig".
Fjarri

Antworten:

151

Die einfachste Lösung besteht in der Tat darin, N Zufallswerte zu nehmen und durch die Summe zu dividieren.

Eine allgemeinere Lösung ist die Verwendung der Dirichlet-Verteilung http://en.wikipedia.org/wiki/Dirichlet_distribution, die in numpy verfügbar ist.

Durch Ändern der Verteilungsparameter können Sie die "Zufälligkeit" einzelner Zahlen ändern

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

Abhängig vom Hauptparameter gibt die Dirichlet-Verteilung entweder Vektoren an, bei denen alle Werte nahe bei 1./N liegen, wobei N die Länge des Vektors ist, oder Vektoren, bei denen die meisten Werte der Vektoren ~ 0 sind, und dort wird eine einzelne 1 sein oder etwas zwischen diesen Möglichkeiten geben.

BEARBEITEN (5 Jahre nach der ursprünglichen Antwort): Eine weitere nützliche Tatsache über die Dirichlet-Verteilung ist, dass Sie sie natürlich erhalten, wenn Sie einen Gamma-verteilten Satz von Zufallsvariablen generieren und diese dann durch ihre Summe dividieren.

sega_sai
quelle
4
+1 für die einzige Erwähnung der Dirichlet-Verteilung. Dies sollte die Antwort sein.
Timothy Shields
2
Ich habe meine akzeptierte Antwort auf diese geändert, da die Skalierung nicht unbedingt eine gleichmäßige Verteilung ergibt.
Tom Kealy
1
@ Tom, ich gönne dir deine Wahl nicht und diese Antwort ist nett, aber ich möchte etwas klarstellen: Skalierung ergibt notwendigerweise eine gleichmäßige Verteilung (über [0,1/s)). Es ist genau so einheitlich wie die nicht skalierte Verteilung, mit der Sie begonnen haben, da die Skalierung die Verteilung nicht ändert, sondern nur komprimiert. Diese Antwort gibt eine Vielzahl von Verteilungen an, von denen nur eine einheitlich ist. Wenn dies für Sie keinen Sinn ergibt, führen Sie die Beispiele aus und sehen Sie sich einige Histogramme an, um dies zu verdeutlichen. Versuchen Sie dasselbe auch mit einer Gaußschen Verteilung ( np.random.normal).
Askewchan
@askewchan, du bist hier nicht korrekt. Wenn man Zufallszahlen nimmt und durch die Summe dividiert, erhält man KEINE gleichmäßige Verteilung (sie ist für sehr große N nahezu gleichförmig, aber niemals streng einheitlich und auch bei kleineren N überhaupt nicht einheitlich). Die Dirichlet-Verteilung ergibt auch keine gleichmäßigen Verteilungen (da es unmöglich ist, gleichmäßige Verteilungen und eine Summe von 1 zu erhalten).
Sega_sai
@sega_sai In diesem Sinne gibt es keine streng gleichmäßige Verteilung, die pseudozufällig erzeugt werden kann. Was ich meine ist, dass die Renormierung einer "gleichmäßigen" Verteilung sie nicht weniger gleichmäßig macht. Ich antwortete auf Toms Kommentar, der implizierte, dass diese Antwort ausgewählt wurde, weil er eine gleichmäßige Verteilung wollte. Es sei denn, ich irre mich grundlegender?
Askewchan
39

Der beste Weg, dies zu tun, besteht darin, einfach eine Liste mit so vielen Zahlen zu erstellen, wie Sie möchten, und sie dann alle durch die Summe zu teilen. Sie sind auf diese Weise völlig zufällig.

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

oder, wie von @TomKealy vorgeschlagen, halten Sie die Summe und die Erstellung in einer Schleife:

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

Verwenden Sie für die schnellste Leistung numpy:

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

Und Sie können den Zufallszahlen jede gewünschte Verteilung für eine Wahrscheinlichkeitsverteilung geben:

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

---- Zeitliche Koordinierung ----

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
askewchan
quelle
2
@ Tom Keine Sorge, es ist leicht, stecken zu bleiben und zu versuchen, diese Dinge viel schwieriger zu machen als sie sind :) Jetzt ist es für die nächste Person da.
Askewchan
3
Ich denke es ist Zeit für Bier.
Tom Kealy
1
Dies ist eine gute Lösung, aber es scheint, dass es eine Möglichkeit geben sollte, dies in einem einzigen Durchgang zu tun, der eine gute Verteilung über den Bereich erhält. Erstellen, summieren, ändern ist eine 3-Pass-Operation. Sie können jedoch mindestens einen Durchgang optimieren, indem Sie beim Generieren summieren.
Silas Ray
2
Skalierung ist nicht unbedingt gut. Siehe meine Antwort für mehr. Es gibt viele mögliche Abbildungen von [0,1) ^ n auf den Zielraum (Summe von x_i = 1) und sie können nicht alle einheitlich sein!
Mike Housky
1
Dies ist falsch , zumindest für den Fall, dass Sie sich für tatsächliche gleichmäßige Verteilungen interessieren. Stackoverflow.com/a/8068956/2075003
n1000
7

Wenn Sie jede Zahl durch die Summe dividieren, erhalten Sie möglicherweise nicht die gewünschte Verteilung. Beispielsweise wählt bei zwei Zahlen das Paar x, y = random.random (), random.random () einen Punkt gleichmäßig auf dem Quadrat 0 <= x <1, 0 <= y <1 aus. Teilen durch die Summe "projiziert" diesen Punkt (x, y) auf die Linie x + y = 1 entlang der Linie von (x, y) zum Ursprung. Punkte in der Nähe von (0,5,0,5) sind viel wahrscheinlicher als Punkte in der Nähe von (0,1,0,9).

Für zwei Variablen ergibt x = random.random (), y = 1-x eine gleichmäßige Verteilung entlang des geometrischen Liniensegments.

Mit 3 Variablen wählen Sie einen zufälligen Punkt in einem Würfel aus und projizieren (radial durch den Ursprung), aber Punkte in der Nähe der Mitte des Dreiecks sind wahrscheinlicher als Punkte in der Nähe der Eckpunkte. Die resultierenden Punkte befinden sich auf einem Dreieck in der x + y + z-Ebene. Wenn Sie eine unvoreingenommene Auswahl von Punkten in diesem Dreieck benötigen, ist die Skalierung nicht gut.

Das Problem wird in n-Dimensionen kompliziert, aber Sie können eine Schätzung mit geringer Genauigkeit (aber hoher Genauigkeit für alle Laborwissenschaftler!) Erhalten, indem Sie einheitlich aus der Menge aller n-Tupel nicht negativer Ganzzahlen auswählen, die sich zu summieren N, und dann teilen Sie jeden von ihnen durch N.

Ich habe kürzlich einen Algorithmus entwickelt, um dies für n, N mit bescheidener Größe zu tun. Er sollte für n = 100 und N = 1.000.000 funktionieren, um 6-stellige Zufälle zu erhalten. Siehe meine Antwort unter:

Eingeschränkte Zufallszahlen erstellen?

Mike Housky
quelle
Sie sollten die Dirichlet-Verteilung überprüfen .
Jonathan H
6

Erstellen Sie eine Liste bestehend aus 0 und 1 und fügen Sie dann 99 Zufallszahlen hinzu. Sortieren Sie die Liste. Aufeinanderfolgende Unterschiede sind die Intervalllängen, die sich zu 1 addieren.

Ich spreche Python nicht fließend, also vergib mir, wenn es einen pythonischeren Weg gibt, dies zu tun. Ich hoffe, die Absicht ist klar:

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

Hier ist eine aktualisierte Implementierung in 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))
pjs
quelle
3

Zusätzlich zur Lösung von @ pjs können wir auch eine Funktion mit zwei Parametern definieren.

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]  
Caner Erden
quelle
1

Generieren Sie 100 Zufallszahlen, egal in welchem ​​Bereich. Summiere die generierten Zahlen, dividiere jedes Individuum durch die Summe.

raten
quelle
1

Wenn Sie einen Mindestschwellenwert für die zufällig ausgewählten Zahlen haben möchten (dh die generierten Zahlen sollten mindestens sein 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

Stellen Sie einfach sicher, dass Sie num_of_values ​​(Anzahl der zu generierenden Werte) haben, damit die erforderlichen Zahlen generiert werden können ( num_values <= 1/min_thesh)

Im Grunde genommen legen wir einen Teil von 1 für den Mindestschwellenwert fest und erstellen dann Zufallszahlen in einem anderen Teil. Wir fügen hinzumin_thesh zu allen Zahlen, um die Summe 1 zu erhalten. Zum Beispiel: Nehmen wir an, Sie möchten 3 Zahlen mit min_thresh = 0,2 generieren. Wir erstellen einen Teil, der durch Zufallszahlen gefüllt werden soll [1 - (0,2x3) = 0,4]. Wir füllen diesen Teil und addieren 0,2 zu allen Werten, damit wir auch 0,6 füllen können.

Dies ist eine Standardskalierung und -verschiebung, die in der Theorie der Zufallszahlengenerierung verwendet wird. Der Kredit geht an meinen Freund Jeel Vaishnav (ich bin nicht sicher, ob er ein SO-Profil hat) und an @sega_sai.

Parthesh Soni
quelle
0

Sie könnten leicht tun mit:

r.append(1 - sum(r))
Paul Evans
quelle
1
Die letzte Zahl wird dann mit den ersten N-1Zahlen korreliert .
Askewchan
0

Im Sinne von "Teilen Sie jedes Element in der Liste durch die Summe der Liste" erstellt diese Definition eine Liste von Zufallszahlen mit der Länge = PARTS, sum = TOTAL, wobei jedes Element auf PLACES (oder None) gerundet wird:

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

Ergebnis:

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
Präsenz
quelle
0

Im Geiste der Methode von 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))]

Wenn Sie möchten, dass sie auf Dezimalstellen gerundet werden:

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
Präsenz
quelle