Roll 2 dice and plot a histogram - Simplipy and animate

I have recently got back into Python code after ill health and slowly using a PC again.
I have written the following code and run it in Jupyter notebook.

  1. Can the code be simplipyed - I know that there are so many ways to produce the same result.
  2. I’d like to animate the chart if possible, showing the charts at 10,50,100,500,1000,5000 and 10000 runs of the dice. This is to show how the randomness of a few throws of the dice can be modified into a more structured probability as the number of throws increases. Thanks, Matt
"""
Create a routine to run 'n' random dice rolls
and present them in a histogram.
"""
import matplotlib.pyplot as plt
import numpy as np
def dice(n):
    rolls = []
    for i in range(n):
        two_dice = ( np.random.randint(1, 7) + np.random.randint(1, 7) )
        rolls.append(two_dice)
    return rolls
"""
now lets use this to build a histogram of 'n' results
"""
data = dice(99000)
# print(data)
bins = np.arange(14) - 0.5
plt.hist(data, bins=bins, color="blue", rwidth=0.85)
plt.grid(True)
plt.title("2 dice rolling histogram")
plt.xlabel("Sum of 2 dice")
plt.ylabel("frequency of rolling the number")
plt.xticks(range(1,14))
# Set axes limit
plt.xlim(1,13)
"""
this chart is exactly as I want it, but can the code be simplipyed?
"""
plt.show()

The dice function can be written more compactly using a list comprehension:

def dice(n):
    return [np.random.randint(1, 7) + np.random.randint(1, 7) for _ in range(n)]

As for making it interactive, I am not sure how to do it with MPL, but in case it is useful, here is a Bokeh version that has a dropdown for updating the sample size using a tiny JS callback:

import numpy as np
from bokeh.models import CustomJS, Select
from bokeh.plotting import column, output_notebook, figure, show

# output_notebook() # uncomment this for use in a notebook

SIZES = (10, 50, 100, 500, 1000, 5000, 10000, 50000)
BINS = np.arange(14) - 0.5

def dice(n):
    return [np.random.randint(1, 7) + np.random.randint(1, 7) for _ in range(n)]

plot = figure(title="2-dice histogram for 10 rolls", height=280,
              x_axis_label="sum of 2 dice", y_axis_label="frequency")
plot.y_range.start = 0
plot.xgrid.grid_line_color = None

# plot a histogram for every size, but start all of them off hidden
for size in SIZES:
    hist, edges = np.histogram(dice(size), density=True, bins=BINS)
    plot.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:],
              line_color="white", name=str(size), visible=False)

# pick one to make visible at first
plot.select_one({"name": "10"}).visible = True

# callback updates visibility based on select widget value
choices = Select(value="10", options=[str(x) for x in SIZES])
choices.js_on_change("value", CustomJS(args=dict(plot=plot), code="""
    const N = cb_obj.value
    for (const r of plot.renderers) {
        r.visible = (r.name == N)
    }
    plot.title.text = `2 dice histogram for ${N} rolls`
"""))

show(column(plot, choices))

ScreenFlow

1 Like

For just a few “slides”, I’d save to individual frames using plt.savefig(f"{n:05}.png"), then use an image viewer program – nowadays those commonly have a “gallery view” for switching between images and auto-reload when the images change.

If your goal is to explain/explore concepts of probability/statistics, I suggest adding more informative output:

def dice(n):
    rolls = []
    for i in range(n):
        two_dice = np.random.randint(1, 7), np.random.randint(1, 7)
        print(f"Rolled {two_dice}, that sums up to {sum(two_dice)}!")
        rolls.append(sum(two_dice))
    return rolls

If you’re going for simplification of the code, NumPy’s randint can generate a matrix of throws all at once, which you can then sum along columns:

n = 10_000
data = np.random.randint(1, 7, size=(n, 2)).sum(axis=1)

But that won’t really help with explaining statistics.