¿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)?
Respuestas:
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.
fuente
[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
).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
fuente
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?
fuente
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))
fuente
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]
fuente
generar 100 números aleatorios no importa qué rango. Sume los números generados, divida a cada individuo por el total.
fuente
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_thesh
a 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.
fuente
Podrías hacerlo fácilmente con:
r.append(1 - sum(r))
fuente
N-1
números.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
fuente
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
fuente