The purpose of this lab is to get a little concrete practice making data visualizations in order to sharpen your thinking about what visualization is and does in general. Learning all the details of producing excellent graphics is not the main goal. But there are a lot of fiddly details to keep track of, and it may help to refer to the ggplot cheat sheet.
I have made some new example data available for you, so please update the class dataculture package:
remotes::install_github("agoldst/dataculture")
Now load the package:
library(dataculture)
which will also load ggplot and other data-wrangling packages automatically.
Note to visitors in 2023 and after: not any more, since I updated my dataculture package. To run the code below you will also need to explicitly load the tidyverse:
library(tidyverse)
(0) Housekeeping
To save your work in RStudio, you need to put your data-analysis code in a text file. In the File menu, choose “New File > R Script.” The new script should include everything necessary to produce the computational outputs you want. It shouldn’t usually include install_github or anything like that. It should start with the libraries you need, like library(dataculture). Save the script with a name like lab7.R in a place you can find it.
To run a script you have open in RStudio, click the “Source” button. Plots—and information printed to the console—are technically “side effects.” Any graphic you make will be lost when you quit R. To save the last-plotted graphic, use the ggsave function. If I had a script like this:
library(tidyverse)
library(dataculture)
library(babynames)
babynames |>
filter(name == "Andrew", sex == "M") |>
ggplot(aes(year, prop)) + geom_line() + geom_point()
ggsave("lab7-andrewline.pdf")
Running this with the “Source” button, I would find a new PDF file lab7-andrewline.pdf saved in the default location for that RStudio session (usually your Home folder).
There are more elaborate ways to manage code and data files, but that will do for the moment. You can, of course, also use ggsave at the regular R prompt, or you can use the “Export” button at the top of the RStudio plot window. But then you won’t have saved the code to produce the plot.
For attendance today, submit one R script that creates at least one plot from the lab on Canvas.
(1) Let’s get cooking
The recipes data frame includes recipe data gathered from digitized editions of a selection of 74 American cookbooks published between 1803 and 1922. I have added some notes on the data in help(recipes) (KNOW THY DATA).
The ethnicgroup column indicates whether the recipe had been assigned to an ethnic group by the editors of the digital edition (based on whether the cookbook labeled it as such). Try:
recipes |> count(ethnicgroup, sort=T)
Consider: what does “ethnic” mean here? What is the opposite of “ethnic”?
The count function creates a new data frame recording how many times each value of ethnicgroup occurs in recipes (the counts are in a new column, n). sort=T means to give the results in descending order of frequency. The same result can be obtained more verbosely with
recipes |> group_by(ethnicgroup) |>
summarize(n=n()) |>
arrange(desc(n))
n() is a function which gives the number of rows in a group. arrange(desc(n)) means to arrange rows in descending order of n (the column).
Scrolling to the top, you will see that the most frequent value is NA. Remember that NA is a special value indicating missing data. The following lines compute a tally of NA recipes by year:
ethnic <- recipes |>
group_by(date) |>
summarize(nonethnic=sum(is.na(ethnicgroup)),
ethnic=sum(!is.na(ethnicgroup))) |>
mutate(ethnic_prop=ethnic / (ethnic + nonethnic))
Follow the pattern from lab 6 to produce a line chart, with
dateon the x axis andethnic_propon the y axis.R for Data Science, chap. 3, begins with the following simple pattern for plotting
ggplot(data = <DATA>) + <GEOM_FUNCTION>(mapping = aes(<MAPPINGS>))Notice carefully that there is no pipe
|>afterggplot(...). Instead, there is a+followed by the function call. It might occur to you wonder what is being added to what with+here. There is no numerical addition here, just some goofy computerese.1 Nonetheless, when you are usingggplotyou chain together the functions that build and modify plots with+.This is not exactly the lab 6 pattern. Reproduce the line chart following this pattern exactly.
Is this a good visualization? What are its limitations?
Let’s try a different “geom.” Replace
geom_linewithgeom_point. Now trygeom_col. Make sure you can explain to each other exactly how the very same data can be used to draw three different pictures. (Hint: What is a “mapping”?)
Now make a really ugly graphic with:
ggplot(data=ethnic, mapping=aes(x=date, y=ethnic_prop)) +
geom_point() +
geom_line() +
geom_col()
Notice that the aes mapping has been moved inside ggplot(). This mapping is then “passed along” to each of the successive geom layers. Try combinations of two geoms (combined with +).
I have supplied a wrangled version of recipes, ingreds, which will make it a little easier to explore questions about ingredients. Here is code to set up for a slightly more interesting graphic by calculating a basic statistic about tomatoes:
tomatoes <- ingreds |> group_by(date, recipe_id) |>
summarize(tom=any(ingredients == "tomato")) |>
summarize(prop=sum(tom) / n())
In the first summarize, we calculate, for each recipe, whether any of its ingredients is tomato. In the second summarize, we calculate, for each year, how many recipes have a tomato ingredient. (Taking the sum() of a logical variable counts up how many TRUE elements it has, so we are counting how many rows have a tom value of TRUE.)
Create a plot showing the proportion of tomato-recipes from over time as points
tomatoes, withdateon the x axis andpropon the y axis.- Would using lines or bars be an improvement? Try both options, using
geom_lineandgeom_col. - Replace
geom_linewithgeom_line(color="red"). Is this an aesthetic mapping of the data? - Discuss hypotheses that might explain this historical trend. (You don’t have to try to verify these hypotheses today.)
- Would using lines or bars be an improvement? Try both options, using
(2) Let’s make a (square) pie
So far, in lab 6 and today, we have been making pictures of two columns of data at a time: we have usually had time on the x axis and something like name- or ingredient-frequency on the y axis.
Let’s make things more complicated and look at just one variable.2
The recipes data contains a column region. How many recipes belong to each region? Sure, we could count them with the count function above, but today is visualization day:
ggplot(recipes) +
geom_bar(aes(region))
Remember the template for
ggplots above. What are the mappings here?What is on the x axis? What determines the order? Is this a good choice?
What is on the y axis? Where in the
recipesdata are these values?Try—and decide if you prefer—
ggplot(recipes) + geom_bar(aes(y=region))
Produce the same chart, using
geom_colinstead ofgeom_bar. Hint: it doesn’t startggplot(recipes). What’s the difference betweengeom_colandgeom_bar?Now make a bar plot showing counts of
ethnicgroup. Is this a useful visualization? How could it be improved?
recipes also has a column classifying each recipe according to what sort of dish it is, recipe_class. Let’s try to cross-tabulate regions and recipe types visually:
ggplot(recipes) +
geom_bar(aes(x=region, fill=recipe_class))
Consider once again the aesthetic mappings. What data is on the x axis and y axis? What about the color of the bars? (This is called the
fillby ggplot becausecoloris reserved for the edges of bars—trycolorinstead offill.) How did ggplot choose what colors to use?- Can you tell from this plot whether one kind of recipe is more common to one region or another? Why not?
- Without doing it in R, can you imagine a better way to show the same information on the plot?
Now try
ggplot(recipes) +
geom_bar(aes(x=region, fill=recipe_class), position="dodge")
Think carefully: what is the rule determining where on the x axis each bar is placed?
- Does this make patterns any clearer?
- What about
position="stack"?
These position settings are called “position adjustments” in the grammar of graphics, because they change how graphical elements that would otherwise lie on top of each other are positioned. They are often used to squeeze extra information onto plots. That’s sometimes helpful but often creates problems.
We can address one perceptual problem by throwing away information. Try:
ggplot(recipes) +
geom_bar(aes(x=region, fill=recipe_class), position="fill")
Look carefully at the y axis. What happened when you added
position="fill"?- Now discuss again the interpretation of the figure: which patterns become more visible? Are there still perceptual difficulties?
I won’t tell you how to make a pie chart. Pie charts are bad and wrong (because the eye is not very adept at comparing the relative size of angles).
So far we have only changed geoms. The next important piece of the puzzle is what the grammar of graphics calls the scale, that is, the method by which data values are mapped to graphical properties. Let’s revisit the colors in the previous plot.
Try:
ggplot(recipes) +
geom_bar(aes(x=region, fill=recipe_class), position="fill") +
scale_fill_hue()
- Nothing new here, but try other colors by replacing
scale_fill_hue()with:scale_fill_viridis_d()andscale_fill_grey()andscale_fill_brewer(type="qual"). Choose your favorite. But ask yourself whether we could do even better with this data.
None of this gets us very far into the substantive questions you could ask of the recipes data, but let us turn to another dataset.
(3) That’s just noise
The gssmusic data frame includes responses about music tastes from a 1993 national sample of US adults, collected as part of the General Social Survey, together with some demographic and other information.3
The pertinent interview question went like this:
I’m going to read you a list of some types of music. Can you tell me which of the statements on this card comes closest to your feeling about each type of music (HAND CARD CA TO RESPONDENT.) Let’s start with big band music. Do you like it very much, like it, have mixed feelings, dislike it, dislike it very much, or is this a type of music that you don’t know much about?
The interview continued with the same question about 17 other music genres. These columns of gssmusic give each respondent’s rating:
gssmusic_doc |> filter(id %in% c(gssmusic_musicvars)) |>
select(id, text) |>
mutate(text=str_extract(text, "\\. [^.]*$")) |>
mutate(text=str_sub(text, start=3)) |>
knitr::kable()
Let’s begin by visualizing a few individual item scores.4
The
countrycolumn gives responses about country and western music. Make a bar chart of these responses.5Pay attention to the warning message. Given the formulation of the question above, what will be the consequences of ignoring it?
Make similar bar charts for other genres that interest you—can you find the genres with differently shaped bar charts?
People in different age groups have different tastes. Let’s explore this a little:
Start by making a scatterplot with age on the x axis and the response to the question about rap music on the y axis. What can you see, and what is hard to see?
- Look at
gssmusic |> count(age). What further problem does this suggest? - One solution to the problem in part (a) is to add random noise to each plotted point (think carefully about the nature of the aesthetic mapping this entails). There is another position adjustment,
position="jitter", that adds random noise to each point. Try it here and see what you can see.
- Look at
Another solution is to look at age groups. The following incantation produces a new column where respondents are grouped in ten-year intervals:
music_age <- gssmusic |>
mutate(age_group=cut(age, breaks=18 + 10 * (0:7))) |>
select(age_group, all_of(gssmusic_musicvars))
- Now make a plot with bars for how each age group rates rap music. Hint: you did this already with the recipes data.
I have included a set of other variables to explore in addition to age. sex codes for sex; educ is years of education (goes up to 20); degree codes for degrees (see code values on the GSS website); realinc shows the respondent’s reported household income category in 1986 dollars. I included the GSS race variable, but KNOW THY DATA: there are only three values in the data, “white,” “black,” and “other.”
(4) Go forth and multiply
Since the 1990s, a great deal of sociological research has explored the degree to which people express preferences for a variety of different kinds of music (or other kinds of cultural goods). I have created two additional columns that summarize the 18 music-genre questions by showing how many the respondent “liked” or “liked very much” (music_likes) and how many they “disliked” or “disliked very much” (music_dislikes).
We could repeat the bivariate analyses above. But since we’ve had gender and education on the brain, let’s look at those variables. Try:
ggplot(gssmusic) +
geom_bar(aes(music_likes)) +
facet_grid(cols=vars(degree))
- Explain what each sub-plot shows, and why there are six of them. What do you observe about the relation between educational credentials and music tastes here?
One can make a “smooth” version of these charts using a so-called kernel density estimate, which is a fancy way of averaging nearby bars together. Try
geom_densityand consider the advantages and disadvantages of this display. (Hint: look at the axes.)- Try the same multiple plot but with
facet_grid(rows=vars(degree)). Better or worse? Why?
- Try the same multiple plot but with
Though it is stretching the data, we can try a further cross-tabulation:
ggplot(gssmusic) +
geom_density(aes(music_likes)) +
facet_grid(cols=vars(degree), rows=vars(sex))
Now begin to explore musical dislikes. Can you make a small-multiples plot which reveals whether particular demographic categories are more or less disposed to express intolerance for one kind of music or another?
Return to one of the “dodged” bar charts above and reformulate it as a faceted small-multiples plot.
Lab 6 had an example of the other faceting option, facet_wrap. Try
ggplot(gssmusic) +
geom_density(aes(music_likes)) +
facet_wrap(vars(degree), nrow=2)
(5) Not just decorative: axes
There is much more to learn about fine-tuning the appearance of plots. You have probably heard before about the importance of choosing a good scale for the axes. The range of data displayed on each axis is adjusted with coord_cartesian:
ggplot(gssmusic) +
geom_point(aes(educ, realinc)) +
coord_cartesian(xlim=c(0, 12), ylim=c(0, 50000))
Finally, and perhaps most importantly of all, label axes and title your plots! This can be done with the labs layer:
ggplot(gssmusic) +
geom_point(aes(educ, realinc, color=sex), position="jitter") +
coord_cartesian(xlim=c(0, 13), ylim=c(0, 50000)) +
labs(x="years of education",
y="reported income (1986$)",
title="Income vs. education",
color="Respondent sex",
subtitle="This is DEFINITELY NOT THE WHOLE STORY",
caption="Data from the 1993 General Social Survey, graphic by YOU")
- Try to make the most deceptive possible version of an income vs. education plot.
Bonus note for computer programmers: like many other object-oriented languages, R allows operator overloading, in which you can change the behavior of things like
+,/,[, depending on the type of their arguments. Theggplotlibrary overloads+to give it a special meaning when used with the classes it defines. Theggplotdeveloper has said he regrets this decision and he would use the pipe if he were doing it all over again. Butggplotgot popular before the pipe was implemented in R.↩︎From a statistical point of view, it doesn’t really make sense to say that time is a “variable” in the same way as, say, the count of babies named Angel is. In particular there is no randomness or error associated with time here—unlike in, say, a physics experiment where we might be recording the times of events. In a historical/cultural context it is usually more exact to say that we are observing one variable over time: for example, the count of babies named Angel, observed once per year. This is a time series, and our standing assumption should be that each successive case of a time series depends on previous ones (whereas our standing assumption about an ordinary variable is that each case is independently sampled, all things being equal).↩︎
According to an article about these particular questions, in the 1993 survey: “The General Social Survey is an almost-annual sample survey in which a randomly selected cross-section of English-speaking residents of U.S. households is interviewed. The survey covers a wide variety of topics, including many sociopolitical attitudes and behaviors; it also provides substantial sociodemographic data on both the current social positions and the social backgrounds of the roughly 1500 persons interviewed each year. A total of 1606 persons–more than 82 percent of the eligible persons contacted–responded to the 1993 survey.” Peter V. Marsden and Joseph F. Swingle, “Conceptualizing and Measuring Culture in Surveys: Values, Strategies, and Symbols,” Poetics 22, no. 4 (June 1994). The GSS is still being administered regularly and is one of the most-cited datasets in the social sciences.↩︎
In the source data, responses about music are scored from 1 to 5, and, confusingly, a score of 1 corresponds to “like very much” and 5 corresponds to “dislike very much.KNOW THY DATA. This detail can be found in the 3050-page GSS Codebook. To make your life a little easier I have recoded these numbers to words.↩︎
When you tally up responses here and for the rest of this lab, you will be making “unweighted” tallies, which for this dataset is technically incorrect. The survey comes from a random sample of U.S. households, with one adult in each selected household randomly chosen for interview. People in big households are thus underrepresented, and, to the extent that such people are systematically different from people in smaller households (which is plausible), any summary results, including visualizations, are biased. Furthermore, any estimates of sampling error also need to be adjusted (but we aren’t looking at error here.) The most rigorous approach is to assign extra “weight” to those people in calculating any statistic or making any chart, but the technical details of how to do this are beyond the scope of this course. Also, the most-cited analysis of this particular data doesn’t seem to use the survey weights either: Bethany Bryson, “‘Anything But Heavy Metal’: Symbolic Exclusion and Musical Dislikes,” American Sociological Review 61, no. 5 (October 1996): 884–899.↩︎