How to Create a Simple Simulation in Python – Numeric Data

See Python: Tips and Tricks for similar articles.

In this article, I show how to create a simple Simulation class in Python. The purpose of the class is to run a simulation many times and then return stats (e.g., mean, median, etc.) on the results of the simulation.

Let’s start with a simple Die class that has a roll() method that returns an integer between 1 and 6 (inclusive):

import random


class Die:
    def __init__(self, sides=6):
        self.sides = sides

    def roll(self):
        return random.randint(1, self.sides)

We want our Simulation class to take two arguments: a function to run and the number of times to run it (n) and then to run the function n times and return a report like this:

Number of Simulations: 1,000
Max: 6
Min: 1
Mean: 3.522
Median: 4.0
Mode: 4
Standard Deviation: 1.7018849586304041
Variance: 2.8964124124124124

Here is the Simulation class that does just this:

import statistics as stats


class SimulationNumeric:
    """Runs a function that returns a number n times and prints stats

    Args:
        fnct_to_run (function): The function to run
        iterations (integer): The number of times to run it"""

    def __init__(self, fnct_to_run, iterations):
        self._fnct_to_run = fnct_to_run
        self._iterations = iterations
        self._results = []
        self.run()

    @property
    def mode(self):
        try:
            return stats.mode(self._results)
        except:
            return None

    def run(self):
        for i in range(self._iterations):
            result = self._fnct_to_run()
            self._results.append(result)

        self.report()

    def report(self):
        max_num = max(self._results)
        min_num = min(self._results)
        mean = stats.mean(self._results)
        median = stats.median(self._results)
        mode = self.mode
        std_dev = stats.stdev(self._results)
        variance = stats.variance(self._results)
        print(
            f"""Number of Simulations: {self._iterations:,}
Max: {max_num}
Min: {min_num}
Mean: {mean}
Median: {median}
Mode: {mode}
Standard Deviation: {std_dev}
Variance: {variance}"""
        )


die = Die()
sim = SimulationNumeric(die.roll, 1000)

This class should work fine with an function that returns a number.

Note that the reason mode is written as a property is that not all result sets will have a mode. If, for example, two numbers are rolled the same number of times and both those numbers are rolled more often than any other number, then there will be no mode. In that case, stats.mode(self._results) will error. Our code catches that error and returns None for mode in that case.