Step 1: Setting up our tools¶
Python on its own does not know how to handle tables of data or draw charts, so we bring in two extra libraries that do this work for us:
- pandas is used for storing and working with data in tables. We give it the short nickname
pdso we do not have to type the full name every time. - matplotlib is used for drawing charts and graphs. We use the
pyplotpart of it and nickname itplt.
The %pip install line at the top makes sure both libraries are actually present before we try to use them. You only need to run this cell once each time you open the notebook.
%pip --quiet --disable-pip-version-check install pandas matplotlib
import pandas as pd
import matplotlib.pyplot as plt
Note: you may need to restart the kernel to use updated packages.
Step 2: Loading the data¶
Our data is stored in a file called rolls.csv. A CSV (comma-separated values) file is just a plain text file where each row is a line and the values in each row are separated by commas. It is one of the most common ways to share data.
The line pd.read_csv('rolls.csv', ...) reads that file into a pandas DataFrame, which you can think of as a spreadsheet-style table held in memory. We store it in a variable called df, which is a very common short name for a DataFrame.
We include keep_default_na=False so that pandas does not automatically turn empty entries into 'missing value' markers. We want to keep empty text as it is for now, because we will deal with it ourselves in a later step.
Writing df on its own at the end of the cell tells the notebook to display the table so we can see what we are working with. Notice that the data is a bit messy: the names are written in different ways (alice, Alice, ALICE), the hand column mixes single letters with full words, and a few rows have no name at all. We will fix all of this next.
df = pd.read_csv('rolls.csv', keep_default_na=False)
df
| name | hand | roll | |
|---|---|---|---|
| 0 | alice | L | 4 |
| 1 | Alice | Right | 6 |
| 2 | ALICE | Right | 4 |
| 3 | Alice | Left | 1 |
| 4 | alice | Right | 3 |
| 5 | Bob | R | 1 |
| 6 | BOB | Right | 2 |
| 7 | bob | L | 6 |
| 8 | Bob | R | 6 |
| 9 | bob | Left | 1 |
| 10 | Carlos | L | 6 |
| 11 | carlos | Left | 4 |
| 12 | CARLOS | Right | 6 |
| 13 | carlos | L | 3 |
| 14 | Carlos | L | 6 |
| 15 | Left | 5 | |
| 16 | Left | 6 | |
| 17 | Left | 5 | |
| 18 | L | 2 | |
| 19 | Left | 5 |
Step 3: Tidying the names¶
The same person's name appears in several different styles, such as alice, Alice and ALICE. As far as the computer is concerned these are three different names, which would mess up any grouping or counting we do later.
To fix this we use .str.title(), which puts every name into title case: the first letter becomes a capital and the rest become lowercase. So alice, ALICE and aLiCe all become Alice.
We then store the tidied column back into df['name'], replacing the old messy version.
df['name'] = df['name'].str.title()
df
| name | hand | roll | |
|---|---|---|---|
| 0 | Alice | L | 4 |
| 1 | Alice | Right | 6 |
| 2 | Alice | Right | 4 |
| 3 | Alice | Left | 1 |
| 4 | Alice | Right | 3 |
| 5 | Bob | R | 1 |
| 6 | Bob | Right | 2 |
| 7 | Bob | L | 6 |
| 8 | Bob | R | 6 |
| 9 | Bob | Left | 1 |
| 10 | Carlos | L | 6 |
| 11 | Carlos | Left | 4 |
| 12 | Carlos | Right | 6 |
| 13 | Carlos | L | 3 |
| 14 | Carlos | L | 6 |
| 15 | Left | 5 | |
| 16 | Left | 6 | |
| 17 | Left | 5 | |
| 18 | L | 2 | |
| 19 | Left | 5 |
Step 4: Dealing with missing names¶
Some rows have no name at all, just an empty entry. Leaving these blank can be confusing and can cause problems later, so we replace every empty name with the word Unknown.
The .replace('', 'Unknown') method looks for any empty text in the column and swaps it for Unknown. This way every row clearly says who rolled the dice, even if that person was not recorded.
df['name'] = df['name'].replace('', 'Unknown')
df
| name | hand | roll | |
|---|---|---|---|
| 0 | Alice | L | 4 |
| 1 | Alice | Right | 6 |
| 2 | Alice | Right | 4 |
| 3 | Alice | Left | 1 |
| 4 | Alice | Right | 3 |
| 5 | Bob | R | 1 |
| 6 | Bob | Right | 2 |
| 7 | Bob | L | 6 |
| 8 | Bob | R | 6 |
| 9 | Bob | Left | 1 |
| 10 | Carlos | L | 6 |
| 11 | Carlos | Left | 4 |
| 12 | Carlos | Right | 6 |
| 13 | Carlos | L | 3 |
| 14 | Carlos | L | 6 |
| 15 | Unknown | Left | 5 |
| 16 | Unknown | Left | 6 |
| 17 | Unknown | Left | 5 |
| 18 | Unknown | L | 2 |
| 19 | Unknown | Left | 5 |
Step 5: Making the 'hand' column consistent¶
The hand column records which hand was used to roll the dice, but it has been written inconsistently. Sometimes it uses a single letter (L or R) and sometimes the full word (Left or Right).
Here we pass a dictionary to .replace(). A dictionary is a set of pairs where each item on the left is matched and swapped for the item on the right. So 'L' becomes 'Left' and 'R' becomes 'Right'. Any values that are already written in full are left untouched.
After this step the whole column uses the same clear wording, which makes it much easier to read and to work with.
df['hand'] = df['hand'].replace(
{'L': 'Left', 'R': 'Right'}
)
df
| name | hand | roll | |
|---|---|---|---|
| 0 | Alice | Left | 4 |
| 1 | Alice | Right | 6 |
| 2 | Alice | Right | 4 |
| 3 | Alice | Left | 1 |
| 4 | Alice | Right | 3 |
| 5 | Bob | Right | 1 |
| 6 | Bob | Right | 2 |
| 7 | Bob | Left | 6 |
| 8 | Bob | Right | 6 |
| 9 | Bob | Left | 1 |
| 10 | Carlos | Left | 6 |
| 11 | Carlos | Left | 4 |
| 12 | Carlos | Right | 6 |
| 13 | Carlos | Left | 3 |
| 14 | Carlos | Left | 6 |
| 15 | Unknown | Left | 5 |
| 16 | Unknown | Left | 6 |
| 17 | Unknown | Left | 5 |
| 18 | Unknown | Left | 2 |
| 19 | Unknown | Left | 5 |
Step 6: Calculating averages for all rolls¶
Now that the data is clean, we can start to summarise it. Two common ways to describe a typical value are the mean and the median.
- The mean is the everyday average: add up all the rolls and divide by how many there are.
- The median is the middle value when all the rolls are put in order. It is useful because it is not pulled up or down by a few unusually high or low values.
We calculate both for the whole roll column and then print them out. The f"..." text is an f-string, which lets us drop the calculated values straight into the message inside the curly brackets.
mean_roll = df['roll'].mean()
median_roll = df['roll'].median()
print(f"Mean roll: {mean_roll}")
print(f"Median roll: {median_roll}")
Mean roll: 4.1 Median roll: 4.5
Step 7: Comparing the mean roll for each person¶
A single average for everyone hides the differences between individual people. To compare them, we group the data by name and work out the mean roll for each person separately.
df.groupby('name')['roll'].agg(['mean']) does this in one line: it gathers all the rows that share a name, then calculates the mean of their rolls. The result is a small table with one row per person.
We then draw this as a bar chart so the comparison is easy to see at a glance. The remaining lines add a title, label the x-axis (Name) and y-axis (Roll), keep the names upright so they are readable, tidy up the spacing, and finally display the chart with plt.show().
mean_roll_by_name = df.groupby('name')['roll'].agg(['mean'])
mean_roll_by_name.plot(kind='bar')
plt.title('Mean Roll per Name')
plt.xlabel('Name')
plt.ylabel('Roll')
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
Step 8: Comparing the median roll for each person¶
This step is almost identical to the previous one, but instead of the mean we calculate the median roll for each person.
Comparing the two charts can be revealing. If a person's mean and median are quite different, it suggests their rolls were uneven, perhaps with a few very high or very low results pulling the mean away from the middle. If the two values are close, their rolls were fairly balanced.
Try looking at both charts together and see whether you can spot anyone whose mean and median tell slightly different stories.
median_roll_by_name = df.groupby('name')['roll'].agg(['median'])
median_roll_by_name.plot(kind='bar')
plt.title('Median Roll per Name')
plt.xlabel('Name')
plt.ylabel('Roll')
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()