Generando una lista de números aleatorios, sumando 1

84

¿Cómo haría una lista de N (digamos 100) números aleatorios, de modo que su suma sea 1?

Puedo hacer una lista de números aleatorios con

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

¿Cómo modificaría esto para que la lista sume 1 (esto es para una simulación de probabilidad)?

Tom Kealy
fuente
5
Si su suma es 1, no son completamente aleatorios.
fjarri
19
Divide cada número en la lista por la suma de la lista
aragaer
1
@Bogdan eso no es realmente un problema.
Tom Kealy
2
@Bogdan eso no es correcto. Son aleatorios, pero la restricción consume un grado de libertad.
pjs
2
@pjs, lo que significa que (en el mejor de los casos) 99 de ellos son aleatorios y 1 no lo es. En otras palabras, "no completamente aleatorio".
fjarri

Respuestas:

151

De hecho, la solución más simple es tomar N valores aleatorios y dividir por la suma.

Una solución más genérica es utilizar la distribución de Dirichlet http://en.wikipedia.org/wiki/Dirichlet_distribution que está disponible en numpy.

Al cambiar los parámetros de la distribución, puede cambiar la "aleatoriedad" de los números individuales

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

Dependiendo del parámetro principal, la distribución de Dirichlet dará vectores donde todos los valores están cerca de 1./N donde N es la longitud del vector, o dará vectores donde la mayoría de los valores de los vectores serán ~ 0, y allí será un solo 1, o dar algo entre esas posibilidades.

EDITAR (5 años después de la respuesta original): Otro dato útil sobre la distribución de Dirichlet es que, naturalmente, la obtiene si genera un conjunto de variables aleatorias distribuidas en gamma y luego las divide por su suma.

sega_sai
fuente
4
+1 por ser el único en mencionar la distribución de Dirichlet. Esta debería ser la respuesta.
Timothy Shields
2
He cambiado mi respuesta aceptada a esta, ya que la escala no proporciona necesariamente una distribución uniforme.
Tom Kealy
1
@ Tom, yo no envidio su elección, y esta respuesta es agradable, pero quiero dejar algo claro: Scaling no dan necesariamente una distribución uniforme (más [0,1/s)). Será exactamente tan uniforme como la distribución sin escala con la que comenzó, porque el escalado no cambia la distribución, sino que simplemente la comprime. Esta respuesta da una variedad de distribuciones, solo una de las cuales es uniforme. Si esto no tiene sentido para usted, ejecute los ejemplos y observe algunos histogramas para aclararlo. También intente lo mismo con una distribución gaussiana ( np.random.normal).
askewchan
@askewchan, no tienes razón aquí. tomar números aleatorios y dividir por la suma NO dará la distribución uniforme (será casi uniforme para N muy grande, pero nunca estrictamente uniforme y tampoco uniforme en absoluto para N más pequeño). La distribución de Dirichlet tampoco dará las distribuciones uniformes (porque es imposible obtener distribuciones uniformes y la suma de 1).
sega_sai
@sega_sai En ese sentido, no existe una distribución estrictamente uniforme que pueda generarse de forma pseudoaleatoria. Lo que quiero decir es que renormalizar una distribución 'uniforme' no la hace menos uniforme. Estaba respondiendo al comentario de Tom que implicaba que esta respuesta fue seleccionada porque quería una distribución uniforme. ¿A menos que me equivoque más fundamentalmente?
askewchan
39

La mejor manera de hacer esto es simplemente hacer una lista de tantos números como desee y luego dividirlos todos por la suma. Son totalmente aleatorios de esta manera.

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

o, como lo sugiere @TomKealy, mantenga la suma y la creación en un ciclo:

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

Para un rendimiento más rápido, use numpy:

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

Y puede dar a los números aleatorios cualquier distribución que desee, para una distribución de probabilidad:

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

---- Sincronización ----

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
fuente
2
@Tom No te preocupes, es fácil quedarse atascado tratando de hacer estas cosas mucho más difíciles de lo que son :) Ahora está aquí para la siguiente persona.
askewchan
3
Creo que es hora de la cerveza.
Tom Kealy
1
Esta es una buena solución, pero parece que debería haber una forma de hacer esto en una sola pasada que obtenga una buena distribución en todo el rango. Crear, sumar, modificar es una operación de 3 pasos. Sin embargo, podría optimizar al menos una pasada sumando a medida que genera.
Silas Ray
2
La escala no es necesariamente buena. Vea mi respuesta para más. Hay muchas asignaciones posibles de [0,1) ^ n al espacio de destino (suma de x_i = 1) y no todas pueden ser uniformes.
Mike Housky
1
Esto está mal , al menos en caso de que le interesen las distribuciones uniformes reales stackoverflow.com/a/8068956/2075003
n1000
7

Es posible que dividir cada número por el total no le proporcione la distribución que desea. Por ejemplo, con dos números, el par x, y = random.random (), random.random () elige un punto uniformemente en el cuadrado 0 <= x <1, 0 <= y <1. Dividir por la suma "proyecta" ese punto (x, y) sobre la línea x + y = 1 a lo largo de la línea desde (x, y) hasta el origen. Los puntos cercanos (0.5,0.5) serán mucho más probables que los puntos cercanos (0.1,0.9).

Para dos variables, entonces, x = random.random (), y = 1-x da una distribución uniforme a lo largo del segmento de línea geométrica.

Con 3 variables, está eligiendo un punto aleatorio en un cubo y proyectando (radialmente, a través del origen), pero los puntos cerca del centro del triángulo serán más probables que los puntos cerca de los vértices. Los puntos resultantes están en un triángulo en el plano x + y + z. Si necesita una elección imparcial de puntos en ese triángulo, la escala no es buena.

El problema se complica en n dimensiones, pero puede obtener una estimación de baja precisión (¡pero alta, para todos los fanáticos de las ciencias de laboratorio!) Eligiendo uniformemente del conjunto de todas las n-tuplas de enteros no negativos que suman N, y luego dividiendo cada uno de ellos por N.

Recientemente se me ocurrió un algoritmo para hacer eso para n, N de tamaño modesto. Debería funcionar para n = 100 y N = 1,000,000 para darle aleatorias de 6 dígitos. Vea mi respuesta en:

¿Crear números aleatorios restringidos?

Mike Housky
fuente
Debería consultar la distribución de Dirichlet .
Jonathan H
6

Cree una lista que consta de 0 y 1, luego agregue 99 números aleatorios. Ordena la lista. Las diferencias sucesivas serán las longitudes de los intervalos que suman 1.

No hablo Python con fluidez, así que perdóneme si hay una forma más Pythonic de hacer esto. Sin embargo, espero que la intención sea clara:

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

Aquí hay una implementación actualizada 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))
pjs
fuente
3

Además de la solución de @ pjs, también podemos definir una función con dos parámetros.

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
fuente
1

generar 100 números aleatorios no importa qué rango. Sume los números generados, divida a cada individuo por el total.

adivinación
fuente
1

En caso de que desee tener un umbral mínimo para los números elegidos al azar (es decir, los números generados deben ser al menos 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

Solo asegúrese de tener num_of_values ​​(número de valores que se generarán) de modo que sea posible generar los números requeridos ( num_values <= 1/min_thesh)

Básicamente, estamos fijando una parte de 1 para el umbral mínimo, luego creamos números aleatorios en la otra parte. Agregamos min_thesha todos los números para obtener la suma 1. Por ejemplo: digamos que desea generar 3 números, con min_thresh = 0.2. Creamos una porción para llenar con números aleatorios [1 - (0.2x3) = 0.4]. Llenamos esa porción y agregamos 0.2 a todos los valores, por lo que también podemos llenar 0.6.

Esta es la escala y el desplazamiento estándar que se utilizan en la teoría de generación de números aleatorios. El crédito es para mi amigo Jeel Vaishnav (no estoy seguro si tiene un perfil SO) y @sega_sai.

Parthesh Soni
fuente
0

Podrías hacerlo fácilmente con:

r.append(1 - sum(r))
Paul Evans
fuente
1
Luego, el último número se correlaciona con los primeros N-1números.
askewchan
0

Con el espíritu de "dividir cada elemento en la lista por la suma de la lista", esta definición creará una lista de números aleatorios de longitud = PARTES, suma = TOTAL, con cada elemento redondeado a LUGARES (o Ninguno):

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

resultado:

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
presencia ligera
fuente
0

En el espíritu del método 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 los desea redondear a lugares decimales:

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
presencia ligera
fuente