The Python random module has a variety of uses. You can do many different things that include simulations, getting a random number, picking an item from a list, generating data for unit tests, and generating data for load tests. Pretty much everyone will run into a use for random data at some points.

Random Method

The random module has a built-in random method that will return a value between 0 and 1. Alternatively, you can call the uniform method to get a float value between two values of you’re choosing:

import random

# returns a float value between 0 and 1
print(random.random())

# returns a float value between 100 and 200
print(random.uniform(100, 200))

Random Ranges for Integers

The random randrange method allows you to set boundaries so you can limit what range a value will fall into. When a single parameter is passed in, it will return any value between 0 and the parameter value.

Alternatively, you can pass in 2 parameters for a lower bound and upper bound limit. An optional third parameter for a step value is available. Passing in a step value of 2 will only return even numbers. A step of 5, on the other hand, will return only value ending in 5 or 0.

import random

# randrange with an upper limit
random.randrange(1000)

# randrange with a lower and upper limit
random.randrange(100, 200)

# any random int between 2 values
# equivalent to randrange(a, b+1)
random.randint(100, 200)

# randrange with a lower and upper limit with a step value
# will only return even numbers
random.randrange(100, 200, 2)

# a step of 5
# any result will end in either 5 or 0
random.randrange(100, 200, 5)

Random Functions for Sequences

In this section, the choice method can be used with any data that consists of a sequence. This can be a list, tuple, or even a string. Within a string, each character will count for a single item in the sequence.

import random

number_list = [1, 2, 3, 4, 5]
number_tuple = (1, 2, 3, 4, 5)
number_string = 'abcdef'

print(random.choice(number_list)) # returns any item in the list
print(random.choice(number_tuple)) # returns any item in the tuple
print(random.choice(number_string)) # returns any character in the string

Random Shuffling and Sampling

Randomly rearranging the order of a sequence is very easy. The shuffle method will handle this for you. The shuffle method happens in place, meaning that there a new sequence will not be returned. Our original variable will be mutated.

If we want to return a new sequence and leave the original variable alone, there are a couple of options: the sample method, generating a sample that’s the same size as the original variable. Or, the built-in sorted method using the random method as the sorting function.

Sampling is relatively straightforward. The sample method can be used by taking the sequence as the first parameter with the sample size being the second parameter.

import random

number_list = [1, 2, 3, 4, 5]
number_tuple = (1, 2, 3, 4, 5)
number_string = 'abcdef'


random.shuffle(number_list) # shuffling happens in place, method will return None
# now, we can print our new value
print(number_list)

# resetting our values
number_list = [1, 2, 3, 4, 5]
# create new shuffled list
# all values will be shuffled because the length matches the length of the list
shuffled = random.sample(number_list, len(number_list))


number_list = [1, 2, 3, 4, 5]
# sorted method provides another alternative
shuffled = sorted(number_list, key=lambda x: random.random())

number_list = [1, 2, 3, 4, 5]
# get a sample of any 3 values
sample = random.sample(number_list, 3)

Random Strings

Generating random strings can be done with the help of the string module. This allows us to import character sets where we can select random data from. In this first example, we’ll set a default of 32 characters. The characters in which we will be selecting random choices from are going to include any upper or lower case character, along with any number between 0 and 9. Similar to the sequence examples described earlier, the choice method can be used with a range to concatenate a string of random characters.

import random
import string

def random_string(length=32):
    # string.ascii_letters same as string.ascii_uppercase + string.ascii_lowercase
    character_set = string.ascii_letters + string.digits
    return ''.join(random.choice(character_set) for i in range(length))

my_random = random_string()  # oyKR3MwyzcWpVT42wFUJTYYlYxbZRC09

To make our random_string function more flexible, we can allow parameters to choose whether we want uppercase, lowercase, and numbers to be included. We will use keyword parameters so we can have defaults that will include everything. We simply start with an empty string and add any characters pass the boolean parameter values.

import random
import string


def random_string(length=32, uppercase=True, lowercase=True, numbers=True):
    character_set = ''

    if uppercase:
        character_set += string.ascii_uppercase
    if lowercase:
        character_set += string.ascii_lowercase
    if numbers:
        character_set += string.digits

    return ''.join(random.choice(character_set) for i in range(length))

my_random = random_string(length=25, uppercase=False, lowercase=False)  # 8210504801539685297626160

Above: if we don’t pass in any additional parameters, the result would be the same as before. However, if we wanted to generate a random sequence of numbers, we can set uppercase and lowercase to false.

One of the more important uses for generating random values is simulations. Simulations themselves have a variety of uses. You may need to do a load test in terms of how much traffic your website can handle. You also may want to speed up the cause and effect of a tedious task. For example, let’s imagine you need to better understand how dropping grains of sand on a pile will trigger an avalanche (see Abelian Sandpile Model). It would be an extremely long and boring task to actually go out and do this. Instead, you can use a computer simulation dropping one grain of sand on top of another. And, repeat it as many times as you wish.

For now, we’re going to try tackling a simpler task. In the next section, we will create a simulation for rolling dice.

Dice Simulation

Dice

Photo by Jonathan Petersson on Unsplash

In this section, we will use random values to simulate throwing a set of dice. Since two dice get thrown at once, the smallest possible outcome is “snake eyes”, or 2. This is simply because each die ranges from 1 to 6. This is also reflected in the fact that the maximum possible outcome is 12.

With that in mind, we can go ahead and setup our simulation. We will create a constant for the number of simulations that we will do.

SIMULATIONS = 1000

possible_values = list(range(2, 13))

In the example above, we’ve also setup a list of possible values. Since 2 is the minimum value, we passed that into the built-in range function. We also passed in 13 as our stop value. The reason for using 13 is because the range index values are 0-based and the stop value is not inclusive.

In our simulation, it will be necessary to keep track of each outcome as we iterate through each simulation. Setting up a dictionary can handle that.

SIMULATIONS = 1000

possible_values = list(range(2, 13))

# create a dictionary of the outcomes
# start each possible outcome with an initial value of 0
outcomes = {i: 0 for i in possible_values}

Our outcomes dictionary sets up each possible outcome with an initial value of 0:

{2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0}

The next step will be to setup another range to actually do our simulations. We will setup 2 variables: 1 for each die. Calculating an individual outcome will simply require adding the the values of the dice. The final step will consist of incrementing the outcome value to our outcomes dictionary.

import random

SIMULATIONS = 1000

possible_values = list(range(2, 13))

# create a dictionary of the outcomes
# start each possible outcome with an initial value of 0
outcomes = {i: 0 for i in possible_values}

for i in range(SIMULATIONS):
    d1 = random.randint(1, 6)
    d2 = random.randint(1, 6)

    outcome = d1 + d2
    outcomes[outcome] += 1

print(outcomes)

Running the script will return a distribution like the following:

{2: 28, 3: 50, 4: 92, 5: 120, 6: 141, 7: 156, 8: 154, 9: 106, 10: 74, 11: 58, 12: 21}

On the surface, this appears normal. For example, outcomes of 6 and 7 should happen more often than 2 or 12. This is due to the simple fact that there are multiple combinations that would result in a 6 or a 7. And, only one way to roll a 2 or a 12.

If we want to verify that we have 1,000 simulations, we can use the reduce function to get a count of all the values for each outcome.

We will need to import reduce from functools, which is a module that’s available from the Python standard library.

import random
from functools import reduce

...

check_accuracy = reduce((lambda x, y: x + y), outcomes.values())
print(check_accuracy) # should match the SIMULATIONS constant

Above: inside the reduce function, we pass in a lambda function as the first argument. The second argument entails the sequence we will be iterating through. Which, in this case, are the outcomes from our results dictionary. As it iterates, it will add the next value y to the accumulated value x. When calling reduce, the result will be a single value.

One final thing we can do is sort the outcomes based on the totals. The built-in sorted method can help us accomplish the task:

...

# sort by outcome counts (dictionary values)
sorted_outcomes = dict(sorted(outcomes.items(), key=lambda x: x[1], reverse=True))

for item in sorted_outcomes.items():
    print(item)
    print('pct {}%'.format(round(item[1] / SIMULATIONS * 100, 1)))
    print('---')

Above: outcome.items() returns a type of dict_items, thus resulting in the need to convert the sorted outcomes back to a dictionary. By default, sorting will sort from the lowest values to the highest. In our situation we want to sort from highest to lowest (the outcomes with the highest frequencies to be at the top). Setting the reverse parameter to True will accomplish that.

Finally, the very last thing we did was iterate through the sorted outcomes and printed out a percentage of the total for each outcome.

Here is an example of the final results. Obviously, each result will vary slightly. However, 5 through 9 should consistently appear at the top of the list.

Random Outcome:

(7, 179)
pct 17.9%
---
(8, 157)
pct 15.7%
---
(6, 122)
pct 12.2%
---
(9, 119)
pct 11.9%
---
(5, 105)
pct 10.5%

Full source for dice simulation:


Posted in