Pregunta Generar números aleatorios con una distribución dada (numérica)


Tengo un archivo con algunas probabilidades para diferentes valores, p. Ej .:

1 0.1
2 0.05
3 0.05
4 0.2
5 0.4
6 0.2

Me gustaría generar números aleatorios usando esta distribución. ¿Existe un módulo existente que maneja esto? Es bastante simple codificar por su cuenta (construir la función de densidad acumulativa, generar un valor aleatorio [0,1] y elegir el valor correspondiente) pero parece que esto debería ser un problema común y probablemente alguien haya creado una función / módulo para eso.

Necesito esto porque quiero generar una lista de cumpleaños (que no siguen ninguna distribución en el estándar) random módulo).


74
2017-11-24 10:56


origen


Respuestas:


scipy.stats.rv_discrete podría ser lo que quieras Puede proporcionar sus probabilidades a través del values parámetro. Puede usar el rvs() método del objeto de distribución para generar números aleatorios.

Como señaló Eugene Pakhomov en los comentarios, también puede aprobar un p parámetro de palabra clave a numpy.random.choice(), p.ej.

numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])

Si usa Python 3.6 o superior, puede usar random.choices() de la biblioteca estándar - ver el respuesta por Mark Dickinson.


71
2017-11-24 12:15



Una ventaja de generar la lista usando CDF es que puedes usar la búsqueda binaria. Mientras necesita O (n) tiempo y espacio para el preprocesamiento, puede obtener k números en O (k log n). Como las listas normales de Python son ineficientes, puede usar array módulo.

Si insiste en el espacio constante, puede hacer lo siguiente; O (n) tiempo, O (1) espacio.

def random_distr(l):
    r = random.uniform(0, 1)
    s = 0
    for item, prob in l:
        s += prob
        if s >= r:
            return item
    return item  # Might occur because of floating point inaccuracies

22
2017-11-24 12:06



Desde Python 3.6, hay una solución para esto en la biblioteca estándar de Python, es decir random.choices.

Ejemplo de uso: configuremos una población y pesos que coincidan con los de la pregunta del OP:

>>> from random import choices
>>> population = [1, 2, 3, 4, 5, 6]
>>> weights = [0.1, 0.05, 0.05, 0.2, 0.4, 0.2]

Ahora choices(population, weights) genera una sola muestra:

>>> choices(population, weights)
4

El argumento opcional de palabra clave k permite solicitar más de una muestra a la vez. Esto es valioso porque hay algunos trabajos preparatorios que random.choices tiene que hacer cada vez que se llama, antes de generar cualquier muestra; al generar muchas muestras a la vez, solo tenemos que hacer ese trabajo preparatorio una vez. Aquí generamos un millón de muestras y utilizamos collections.Counter para verificar que la distribución que obtenemos coincida aproximadamente con los pesos que le dimos.

>>> million_samples = choices(population, weights, k=10**6)
>>> from collections import Counter
>>> Counter(million_samples)
Counter({5: 399616, 6: 200387, 4: 200117, 1: 99636, 3: 50219, 2: 50025})

22
2018-01-25 12:59



Tal vez es un poco tarde. Pero puedes usar numpy.random.choice()pasando el p parámetro:

val = numpy.random.choice(numpy.arange(1, 7), p=[0.1, 0.05, 0.05, 0.2, 0.4, 0.2])

13
2017-12-01 00:59



(De acuerdo, sé que estás pidiendo envoltorios, pero tal vez esas soluciones locales no fueron lo suficientemente simples para tu gusto. :-)

pdf = [(1, 0.1), (2, 0.05), (3, 0.05), (4, 0.2), (5, 0.4), (6, 0.2)]
cdf = [(i, sum(p for j,p in pdf if j < i)) for i,_ in pdf]
R = max(i for r in [random.random()] for i,c in cdf if c <= r)

Me pseudo-confirmó que esto funciona mirando el resultado de esta expresión:

sorted(max(i for r in [random.random()] for i,c in cdf if c <= r)
       for _ in range(1000))

11
2017-11-24 11:32



es posible que desee echar un vistazo a NumPy Distribuciones de muestreo aleatorias


1
2017-11-24 11:15



Haga una lista de artículos, en función de su weights:

items = [1, 2, 3, 4, 5, 6]
probabilities= [0.1, 0.05, 0.05, 0.2, 0.4, 0.2]
# if the list of probs is normalized (sum(probs) == 1), omit this part
prob = sum(probabilities) # find sum of probs, to normalize them
c = (1.0)/prob # a multiplier to make a list of normalized probs
probabilities = map(lambda x: c*x, probabilities)
print probabilities

ml = max(probabilities, key=lambda x: len(str(x)) - str(x).find('.'))
ml = len(str(ml)) - str(ml).find('.') -1
amounts = [ int(x*(10**ml)) for x in probabilities]
itemsList = list()
for i in range(0, len(items)): # iterate through original items
  itemsList += items[i:i+1]*amounts[i]

# choose from itemsList randomly
print itemsList

Una optimización puede ser para normalizar las cantidades por el máximo divisor común, para hacer que la lista de objetivos sea más pequeña.

También, esta podría ser interesante


1
2017-11-24 11:34



Otra respuesta, probablemente más rápido :)

distribution = [(1, 0.2), (2, 0.3), (3, 0.5)]  
# init distribution  
dlist = []  
sumchance = 0  
for value, chance in distribution:  
    sumchance += chance  
    dlist.append((value, sumchance))  
assert sumchance == 1.0 # not good assert because of float equality  

# get random value  
r = random.random()  
# for small distributions use lineair search  
if len(distribution) < 64: # don't know exact speed limit  
    for value, sumchance in dlist:  
        if r < sumchance:  
            return value  
else:  
    # else (not implemented) binary search algorithm  

1
2017-11-24 11:38