I have a file with some probabilities for different values e.g.:
1 0.1
2 0.05
3 0.05
4 0.2
5 0.4
6 0.2
I would like to generate random numbers using this distribution. Does an existing module that handles this exist? It's fairly simple to code on your own (build the cumulative density function, generate a random value [0,1] and pick the corresponding value) but it seems like this should be a common problem and probably someone has created a function/module for it.
I need this because I want to generate a list of birthdays (which do not follow any distribution in the standard random
module).
based on other solutions, you generate accumulative distribution (as integer or float whatever you like), then you can use bisect to make it fast
this is a simple example (I used integers here)
the
get_cdf
function would convert it from 20, 60, 10, 10 into 20, 20+60, 20+60+10, 20+60+10+10now we pick a random number up to 20+60+10+10 using
random.randint
then we use bisect to get the actual value in a fast wayNone of these answers is particularly clear or simple.
Here is a clear, simple method that is guaranteed to work.
accumulate_normalize_probabilities takes a dictionary
p
that maps symbols to probabilities OR frequencies. It outputs usable list of tuples from which to do selection.Yields:
Why it works
The accumulation step turns each symbol into an interval between itself and the previous symbols probability or frequency (or 0 in the case of the first symbol). These intervals can be used to select from (and thus sample the provided distribution) by simply stepping through the list until the random number in interval 0.0 -> 1.0 (prepared earlier) is less or equal to the current symbol's interval end-point.
The normalization releases us from the need to make sure everything sums to some value. After normalization the "vector" of probabilities sums to 1.0.
The rest of the code for selection and generating a arbitrarily long sample from the distribution is below :
Usage :
An advantage to generating the list using CDF is that you can use binary search. While you need O(n) time and space for preprocessing, you can get k numbers in O(k log n). Since normal Python lists are inefficient, you can use
array
module.If you insist on constant space, you can do the following; O(n) time, O(1) space.
you might want to have a look at NumPy Random sampling distributions
Make a list of items, based on their
weights
:An optimization may be to normalize amounts by the greatest common divisor, to make the target list smaller.
Also, this might be interesting.
Since Python 3.6, there's a solution for this in Python's standard library, namely
random.choices
.Example usage: let's set up a population and weights matching those in the OP's question:
Now
choices(population, weights)
generates a single sample:The optional keyword-only argument
k
allows one to request more than one sample at once. This is valuable because there's some preparatory work thatrandom.choices
has to do every time it's called, prior to generating any samples; by generating many samples at once, we only have to do that preparatory work once. Here we generate a million samples, and usecollections.Counter
to check that the distribution we get roughly matches the weights we gave.