Open In Colab

Assignment 7: Data visualization

As we’ve discussed in class, there are many different ways you can visualize data! You’ve learned several techniques for data visualization in this class thus far. This assignment will focus explicitly on data visualization, and include more of an emphasis on plot customization.

The dataset we’ll be using in this assignment is the popular “Baby names” dataset from the Social Security Administration, available at https://www.ssa.gov/oact/babynames/limits.html. We’ll be using a pre-processed version of the national dataset, which is a long-form table of baby names from 1880 to 2025, available in the course GitHub repository. Next, import the necessary libraries for this assignment, then read in the dataset and take a quick look:

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_style("white")

df = pd.read_csv('https://raw.githubusercontent.com/walkerke/geog30323/main/data/babynames.csv')
# Converting the year to integer for better display
df = df.assign(year = df.year.astype(int))

df.head()
year sex name n prop
0 1880 F Mary 7065 0.077642
1 1880 F Anna 2604 0.028617
2 1880 F Emma 2003 0.022012
3 1880 F Elizabeth 1939 0.021309
4 1880 F Minnie 1746 0.019188

The data frame has the following columns: year, which is the year the baby was born; sex, the sex of the baby; name, the name of the baby; n, the number of babies born with that name for that sex in that year; and prop, the proportion of babies of that sex in that year with that name. (The SSA only reports names given to at least five babies in a year, so prop is the share among those names.) As you can see, over 7 percent of female babies in 1880 were given the name Mary! Now let’s take a look at the size of our data frame.

df.shape
(2181032, 5)

Our data frame has 2.18 million rows! As such, this isn’t a dataset that you could reasonably deal with manually. Also: Excel worksheets cannot handle data of this size, as they have row limits of 1,048,576 rows, which takes us up to around 1989. This is not a dataset that is “big” by standard definitions, as it is only about 83 MB in size given the small number of columns. However, it is much-better suited to a computational approach to data analysis like Python/pandas.

Granted, with 2.18 million rows in our dataset, we’ll need to carefully consider our research questions and how they can help us cut down the size of our dataset. In this notebook, I’d like you to get experience with three skills in Python plot customization:

  1. Modifying chart properties
  2. Annotation/labeling
  3. Small multiples

To do this, we are going to focus on three topics:

  1. What were the most popular names in 2025 (the last year in the dataset), and how did their popularity change over the past 10 years?
  2. How does the release of Disney princess movies influence the popularity of baby names?
  3. How have various gender-neutral names shifted in popularity between male & female over time?

You’ll then get a chance to do some of this on your own at the end of the assignment.

Question 2: How does the release of Disney movies influence the popularity of baby names?

Baby names can sometimes be responsive to trends in popular culture. For example, “Daenerys” showed up in the dataset for the first time in 2012, and 82 baby girls were named Daenerys in 2015!. In this exercise, we’ll examine how the release of Disney Princess movies relates to baby names.

Let’s examine trends in female baby names since 1980 for four Disney Princess names: Jasmine, Ariel, Elsa, and Tiana.

sns.set_style("darkgrid")
princesses = ['Jasmine', 'Ariel', 'Elsa', 'Tiana']

princess_df = (df
    .query('name in @princesses & sex == "F" & year > 1979')
    .assign(per1000 = df.prop * 1000)
)

princess_df.head()
year sex name n prop per1000
864266 1980 F Jasmine 1187 0.000715 0.714928
864565 1980 F Tiana 377 0.000227 0.227066
864749 1980 F Elsa 244 0.000147 0.146961
864903 1980 F Ariel 181 0.000109 0.109016
883695 1981 F Jasmine 1402 0.000840 0.840497

Clearly, Jasmine was a popular name in the early 1980s prior to the release of Aladdin. Tiana, Ariel, and Elsa, however, were not as popular. So how did their popularity shift over time?

We’ll make a line chart using the lineplot() function in seaborn. sns.lineplot() takes a long-form data frame like our babynames data frame along with a mapping of x and y values for a given dataset. The hue argument, if specified, will divide up the data into groups within a given column and plot a separate line, with different colors, for each group.

sns.lineplot(data = princess_df, x = "year", y = "per1000", hue = "name")

We can start to get a sense here of some “spikes” in the data - for example, a clear spike in babies named Ariel is evident after 1989, which is when The Little Mermaid was released. We can also note small spikes for Tiana and Elsa after the release of their respective movies.

However - how can we express this on the chart in clearer terms? One way to accomplish this is through annotation, which refers to the placement of text on the plot to highlight particular data points. Before doing this, let’s figure out approximately what the values are for each princess name when its movie was released:

disney_yrs = {'Ariel': 1989,
             'Elsa': 2013,
             'Jasmine': 1992,
             'Tiana': 2009}

for princess, year in disney_yrs.items():
    # Subset the data frame for princess and year
    subset = princess_df.query('name == @princess & year == @year')
    # Extract the value for per1000 with .values
    # .values returns a list, so we'll extract the 0th element
    value = subset.per1000.values[0]
    print(princess + ': ' + str(value))
Ariel: 0.6964815746992762
Elsa: 0.32255380550803364
Jasmine: 5.6829675296960005
Tiana: 0.27434334505098756

I accomplished this with a little new Python code. I’ve mentioned before the dict, a type of Python object enclosed in curly braces ({}) that can hold key-value pairs. The key comes before the colon, the value comes after the colon, and each element of the dictionary is separated by a comma.

In this case, our dictionary holds the name of the Disney princess, and the year that the corresponding film was released. Dictionaries can be iterated through with for and the .items() method; in this example, princess represents the key in the loop, and year represents the value. Within the loop, we can first create a princess and year-specific subset of our data frame, then extract the corresponding value from it.

Spend some time reading through the plt.annotate() code below so that you can understand it. We’re using a number of parameters here:

  • The annotation text is the first argument. Python will interpret the string ‘’ as a line break, which allows us to put the text on multiple lines.
  • The xy parameter refers to the data coordinates of the point we want to annotate, given that we’ve specified this with the 'data' argument supplied to the xycoords parameter. We’ll use the year of the film release for the X value, and the data values we obtained above (approximately) for the Y value.
  • In this case, however, we don’t want to put the text right on top of the lines themselves; as such, we can specify an offset, and connect our text with the data point with an arrow. We use the xytext and textcoords parameters to do this; have a look at the plot and see where this puts the text. The argument supplied to arrowprops, which are in the form of a dict, govern the appearance of the arrow.

Annotation often takes iteration and patience to get it right. Try changing some of the arguments in the plt.annotate() calls below and see how the text and arrows move around!

# Set the figure size
plt.figure(figsize = (8, 6))
# Generate the chart
sns.lineplot(data = princess_df, x = "year", y = "per1000", hue = "name")

# Build each annotation individually
plt.annotate('The Little \nMermaid', xy = (1989, 0.70), xycoords = 'data', 
            xytext = (1983, 2.5), textcoords = 'data', arrowprops = dict(arrowstyle = 'simple'),
            fontsize = 10)

plt.annotate('Aladdin', xy = (1992, 5.68), xycoords = 'data', 
            xytext = (1993, 3.6), textcoords = 'data', arrowprops = dict(arrowstyle = 'simple'),
            fontsize = 10)

plt.annotate('Frozen', xy = (2013, 0.32), xycoords = 'data', 
            xytext = (2011, 2.5), textcoords = 'data', arrowprops = dict(arrowstyle = 'simple'),
            fontsize = 10)

plt.annotate('The Princess \nand the Frog', xy = (2009, 0.27), xycoords = 'data', 
            xytext = (2007, 1.6), textcoords = 'data', arrowprops = dict(arrowstyle = 'simple'),
            fontsize = 10)

plt.ylabel("Female baby names per 1000", fontsize = 12)
plt.xlabel("")
plt.title("Influence of Disney movies on baby names", fontsize = 15)
plt.legend(title = "", fontsize = 12)

Question 3: How have gender-neutral names shifted in popularity between male and female over time?

For the third and final question, we’ll be looking at how four gender-neutral names have shifted between male and female over time. Let’s produce a new data frame from our original data frame that subsets for four popular gender-neutral names: Jordan, Riley, Peyton, and Taylor. We’ll take rows for years 1960 and later, and fill NaN values with 0.

neutral_names = ['Jordan', 'Peyton', 'Taylor', 'Riley']
neutral_df = (df
    .query('name in @neutral_names & year > 1959')
    .fillna(0)
)

neutral_df.head()
year sex name n prop
576962 1960 F Taylor 10 0.000005
578528 1960 F Jordan 6 0.000003
580543 1960 M Jordan 137 0.000064
580620 1960 M Riley 107 0.000050
580670 1960 M Taylor 93 0.000044

In Assignment 5, you learned how to make faceted plots are available using the catplot() function, which is appropriate for charts that have a categorical axis. The companion relplot() function can be used for plots with two continuous axes, such as scatterplots or lineplots. Let’s try plotting faceted line charts that show how counts for these names vary by gender over time:

sns.relplot(data = neutral_df, x = "year", y = "n", hue = "sex", col = "name", kind = "line")

We can start to get a sense of some of the variations here; Taylor is more popular among girls than boys, whereas the opposite is true for Jordan. Let’s make a few modifications to the plot to improve its clarity. We will add a col_wrap argument to specify how many columns to create in our plot grid. We can also change the colors with the argument supplied to palette, and we can specify a height argument to modify the plot size.

Additionally, plot objects themselves have methods that you can use to modify the chart appearance; we’ll use .set_axis_labels() to improve the look of our axes, and we can modify the title of the legend as well.

# Note the font_scale argument that increases all font size by 25 percent
sns.set_theme(style = "white", font_scale = 1.25)

chart = sns.relplot(data = neutral_df, x = "year", y = "n", 
                    hue = "sex", col = "name", kind = "line",
                    col_wrap = 2, palette = ["darkred", "navy"],
                    height = 4)

chart.set_axis_labels('Year', 'Number of names')
chart._legend.set_title('Sex')

Exercises

To get credit for this assignment, you are going to apply what you’ve learned to some additional tasks using the baby names dataset. Some of this will involve re-producing some of the analyses in the notebook, but for different prompts.

Exercise 1: Re-create the heatmap from Question 1, but this time for females. What trends do you observe?

Exercise 2: Create a line chart that shows how a name of your choice has varied in popularity over time. Find out the year when your chosen name peaked in popularity, and annotate your chart to show where this peak is located on the line.

Exercise 3: In Question 2, we looked at the possible influence of Disney princess movies on female baby names. Pick four other names (male or female) from popular culture over the past 30 years and produce a chart that illustrates their influence (or lack thereof) on baby names. Be strategic with your name decisions! You can create a single line chart with four series, or a small multiples chart with facets - pick the one you think is ideal!

To submit your assignment, click the Share button and share your assignment with kwalkertcu@gmail.com.