web-dev-qa-db-fra.com

Génération d'une liste de nombres aléatoires, sommant à 1

Comment pourrais-je faire une liste de N (disons 100) nombres aléatoires, de sorte que leur somme soit 1?

Je peux faire une liste de nombres aléatoires avec

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

Comment pourrais-je modifier cela pour que la liste soit égale à 1 (c'est pour une simulation de probabilité).

56
Tom Kealy

La solution la plus simple est en effet de prendre N valeurs aléatoires et de diviser par la somme.

Une solution plus générique consiste à utiliser la distribution Dirichlet http://en.wikipedia.org/wiki/Dirichlet_distribution qui est disponible en numpy.

En modifiant les paramètres de la distribution, vous pouvez modifier le "caractère aléatoire" des nombres individuels

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

Selon le paramètre principal, la distribution de Dirichlet donnera soit des vecteurs où toutes les valeurs sont proches de 1./N où N est la longueur du vecteur, soit des vecteurs où la plupart des valeurs des vecteurs seront ~ 0, et là sera un seul 1, ou donner quelque chose entre ces possibilités.

EDIT (5 ans après la réponse d'origine): Un autre fait utile à propos de la distribution de Dirichlet est que vous l'obtenez naturellement, si vous générez un ensemble de variables aléatoires distribuées par Gamma, puis les divisez par leur somme.

100
sega_sai

La meilleure façon de le faire est simplement de faire une liste d'autant de nombres que vous le souhaitez, puis de les diviser tous par la somme. Ils sont totalement aléatoires de cette façon.

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

ou, comme suggéré par @TomKealy, gardez la somme et la création dans une boucle:

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

Pour des performances plus rapides, utilisez numpy:

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

Et vous pouvez donner aux nombres aléatoires toute distribution que vous voulez, pour une distribution de probabilité:

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

---- Calendrier ----

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
33
askewchan

La division de chaque nombre par le total peut ne pas vous donner la distribution souhaitée. Par exemple, avec deux nombres, la paire x, y = random.random (), random.random () sélectionne un point uniformément sur le carré 0 <= x <1, 0 <= y <1. La division par la somme "projette" ce point (x, y) sur la ligne x + y = 1 le long de la ligne de (x, y) à l'origine. Les points proches (0,5,0,5) seront beaucoup plus probables que les points proches (0,1,0,9).

Pour deux variables, alors, x = random.random (), y = 1-x donne une distribution uniforme le long du segment de ligne géométrique.

Avec 3 variables, vous choisissez un point aléatoire dans un cube et vous projetez (radialement, à travers l'origine), mais les points proches du centre du triangle seront plus susceptibles que les points proches des sommets. Les points résultants sont sur un triangle dans le plan x + y + z. Si vous avez besoin d'un choix non biaisé de points dans ce triangle, la mise à l'échelle n'est pas bonne.

Le problème se complique en n dimensions, mais vous pouvez obtenir une estimation de faible précision (mais de haute précision, pour tous les fans de science de laboratoire!) En choisissant uniformément dans l'ensemble de tous les n-tuples d'entiers non négatifs totalisant jusqu'à N, puis en divisant chacun d'eux par N.

J'ai récemment mis au point un algorithme pour le faire pour n, N de taille modeste. Il devrait fonctionner pour n = 100 et N = 1 000 000 pour vous donner des aléas à 6 chiffres. Voir ma réponse sur:

Créer des nombres aléatoires contraints?

7
Mike Housky

Créez une liste composée de 0 et 1, puis ajoutez 99 nombres aléatoires. Triez la liste. Les différences successives seront les longueurs d'intervalles qui s'additionnent à 1.

Je ne parle pas couramment Python, alors pardonnez-moi s'il y a une façon plus Pythonique de le faire. J'espère cependant que l'intention est claire:

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

Voici une implémentation mise à jour dans 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))
6
pjs

En plus de la solution de @ pjs, nous pouvons également définir une fonction avec deux paramètres.

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]  
2
Caner Erden

générer 100 nombres aléatoires n'a pas d'importance quelle plage. additionner les nombres générés, diviser chaque individu par le total.

1
guessing

Vous pourriez facilement faire avec:

r.append(1 - sum(r))
0
Paul Evans

Dans l'esprit de "diviser chaque élément de la liste par la somme de la liste", cette définition créera une liste de nombres aléatoires de longueur = PARTS, somme = TOTAL, chaque élément étant arrondi à LIEUX (ou Aucun):

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

résultat:

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
litepresence

Dans l'esprit de la méthode de 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))]

Si vous souhaitez les arrondir à la décimale:

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
0
litepresence