113 votes

Génération d'une liste de nombres aléatoires, dont la somme est égale à 1

  • Cette question n'est pas un doublon de Obtenir N nombres aléatoires dont la somme est M parce que :
    1. Le plus les réponses qui y sont données concernent la théorie, et non une solution de codage spécifique en python pour répondre à cette question.
    2. La réponse acceptée ici a 5 ans de plus que la réponse dans le duplicata qui répond à cette question.
    3. La réponse acceptée en double ne répond pas à cette question

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

Je peux faire une liste de nombres aléatoires avec

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

Comment puis-je modifier cela pour que la somme de la liste soit égale à 1 (il s'agit d'une simulation de probabilité).

201voto

sega_sai Points 3465

La solution la plus simple consiste en effet à prendre N valeurs aléatoires et à les diviser par la somme.

Une solution plus générique consiste à utiliser l'option distribution de Dirichlet qui est disponible en numpy .

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

En fonction du paramètre principal, la distribution de Dirichlet donnera soit des vecteurs dont toutes les valeurs sont proches de 1./N, où N est la longueur du vecteur, soit des vecteurs dont la plupart des valeurs seront ~0 , et il y aura un seul 1, soit quelque chose entre ces deux possibilités.

EDIT (5 ans après la réponse originale) : Un autre fait utile concernant 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 et que vous les divisez ensuite par leur somme.

44voto

askewchan Points 12215

La meilleure façon de procéder est de dresser une liste d'autant de chiffres 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 seule boucle :

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

Pour les performances les plus rapides, utilisez numpy :

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

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

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

---- Timing ----

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

8voto

Mike Housky Points 3194

En divisant chaque nombre par le total, vous n'obtiendrez peut-être pas la répartition souhaitée. Par exemple, avec deux nombres, la paire x,y = random.random(), random.random() choisit 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 allant de (x,y) à l'origine. Les points proches de (0,5,0,5) seront beaucoup plus probables que les points proches de (0,1,0,9).

Pour deux variables, donc, 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 le projetez (radialement, en passant par l'origine), mais les points proches du centre du triangle seront plus probables que les points proches des sommets. Les points résultants se trouvent 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 grande exactitude, pour tous les amateurs de sciences de laboratoire !) en choisissant uniformément dans l'ensemble des n-tuples d'entiers non négatifs dont la somme est égale à N, puis en divisant chacun d'eux par N.

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

Créer des nombres aléatoires contraints ?

6voto

pjs Points 5208

Créez une liste composée de 0 et de 1, puis ajoutez 99 nombres aléatoires. Triez la liste. Les différences successives seront les longueurs des intervalles dont la somme est égale à 1.

Je ne parle pas couramment Python, alors pardonnez-moi s'il existe une façon plus pythonique de faire cela. 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 mise à jour de l'implémentation en 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))

5voto

Caner Erden Points 166

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]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X