Summarizing Categorical Data

From data frames to tables. From tables to bar charts.

  • Using “help” pages to understand how a function works
  • Arguments to functions: required or optional, named or unnamed
  • More about packages (tidyverse, stat20data)
  • Making plots with ggplot
  • Making bar charts
  • Setting colors and other “aesthetic attributes”
  • Stacked, dodged, and stacked+normalized bar charts.

In this tutorial we’ll grow your R toolbox to visualize categorical data but first, a very general skill: how to ask for help.

Help and Arguments

Help pages

Every function in R has a built-in help file that tells you about how it works. It can be accessed using ?.

Screenshot of the helpfile from the mean function.
Figure 1: Help file for mean().
?mean

When you run this, RStudio will open a help file in the bottom right pane of your screen. We’ve shown you this help file in the image on the right of the page here ⮕

What you always see at the top of such a help file is how to use that function:

Usage:

     mean(x, ...)
     
     ## Default S3 method:
     mean(x, trim = 0, na.rm = FALSE, ...)

“Usage” – basically, how to use this function. The first line mean(x, ...) is saying, “mean is a function that requires at least one argument.”

Required and optional arguments, named and unnamed arguments

All arguments have names. In this case, the first argument is named x (you saw mean(x, ...) in the help page). You don’t usually need to know this – if you run the code cell below, it works fine:

No mention of x anywhere. You could, if you wanted, explicitly give the name, though this is unnecessary:

That still works just the same.

OK so who cares?

Many functions offer extra optional arguments, which fine-tune how the function runs. The second “Usage” line in the help file is showing you this:

     mean(x, trim = 0, na.rm = FALSE, ...)

All of the arguments that end in = (something) are “optional.” To demonstrate, let’s see what trim and na.rm do. Here is mean without either of them:

The average of these numbers is huge (2030) because you have a giant outlier value (1000) pulling things up. Sometimes you want to discard such outliers. That is, you want to trim off extreme values before taking the mean. This is what trim does:

This says, “Get rid of the highest 20% of the data and the lowest 20% of the data. Take the mean of the rest.” There’s 5 numbers in the list, so 20% means we cut off the 1 lowest value (0) and the 1 highest value (10000). The result is that we only compute the mean of 46, 49, and 55 (whose mean is 50).

Look again at our code line, mean(scores, trim = 0.2). We called mean with two arguments: one required argument (whose name we just omitted), and one optional argument (whose name we gave).

Also notice the = 0 in the help page line:

mean(x, trim = 0, na.rm = FALSE, ...)

The trim = 0 means that 1) trim is an optional argument, and 2) if you don’t use this argument, mean is going to default to trimming nothing. You can explicitly set trim = 0 to see for yourself that it does nothing:

The na.rm argument, which you’ll use far more often

Let’s say we take some measurements of a bunch of penguins (height, weight, bill size, etc), but we forgot to measure the weight of one of them. In our final dataset, there will be a missing value (we don’t know one penguin’s weight). When loaded into R, such missing values show up as NA (“Not Available”). Anytime you try to do math involving NA, the result is NA:

That means if we try to compute the average weight of some penguins, including some NA (missing) values, this happens:

The result is NA. The mean function is basically telling you, “Look, you have some missing data, so I’m not going to compute a sketchy average. I’m giving you NA back.”

You have to explicitly tell R that you are okay dropping the NA values from the calculation. This is the purpose of the argument na.rm (as in, “remove NAs”).

Again, look at the documentation where it says:

mean(x, trim = 0, na.rm = FALSE, ...)

Notice the na.rm = FALSE. So unless you say otherwise, it’s not going to remove NA values before doing the mean. You have to explicitly set it to TRUE:

Now this works. You have changed a default argument (na.rm) from FALSE to TRUE.

One last point about named arguments and optional arguments

Whenever you don’t name an argument, R uses the order of arguments to guess which is which. The help page tells you this expected order: mean(x, trim = 0, na.rm = FALSE, ...). So the code below actually works, since R expects trim to be the second argument and na.rm to be third:

This is equivalent to saying mean(penguin_weights, trim = 0.2, na.rm = TRUE). You put the argument values in the right places, so R got the idea.

More about packages

We talked about packages in an earlier tutorial. Let’s learn a bit more.

The tidyverse package, which contains many useful packages for data science

In Intro to Coding (linked to above), we learned about a few different functions that can be used on vectors, such as mean(). We also learned about a function called data.frame(), which allowed us to bring vectors together as part of a new structure called a data frame, with each of the vectors used as columns.

While the base functionality provided by R is powerful, developers often seek to find more efficient ways to complete tasks. In doing so, they push the power of R forward. R has a vast ecosystem of libraries that add new functions. Any installed package can be loaded with the library() function. Here, we will load the tidyverse package, one of the core external libraries that we will be using this semester.

library(tidyverse)

To use these functions within this package (or any other package), you will need to run the above line of code each time you start an RStudio session!

The tidyverse package is actually a collection of smaller packages, all of which are useful to us. You don’t need to know their names, but here’s the main ones we care about:

  • ggplot - for making plots and graphs
  • dplyr - for creating summary tables, cleaning up your data frame, making new columns, and other “data wrangling” tasks (coming soon)

Today, our focus is to summarize categorical data, and one avenue we have explored already is a visual summary; a bar chart. Let’s explore how the plots shown earlier in this set of notes were made. We will use ggplot, tidyverse’s resident visualization package.

First, we should load the penguins data into our environment. The penguins data is actually located in a special package made just for this course called stat20data. This package hosts the datasets which will be the subjects of your labs. Therefore, we need to load this package first.

library(stat20data)

You can use penguins right away. But it won’t show up in your environment (upper right pane, which shows all your variables) until you run:

data(penguins)

Once you do this, you will see the penguins dataset, in all of its glory, appear in the environment pane at the top right of your RStudio session (you may need to click in the area once or twice).

You can click the blue dropdown arrow to see each variable in the dataset, or for a more traditional view, you can click the white spreadsheet icon to the right:

Now, we’re ready to write some ggplot code and make our first visualization of the year! We will create the exact stacked, normalized bar chart you saw earlier in the notes.

A first visualization with ggplot

Before we start, here’s the plot we’re going to build towards:

The main function within the ggplot package is, well, ggplot().

ggplot(data = penguins)

The ggplot() function requires, as its first argument, a data frame (the data argument). By itself, there’s not much to look at; it just creates a blank canvas. We haven’t told it what kind of plot to make, what about the penguins we want to visualize, or anything else.

In making a plot from a dataframe, you have to map variables to attributes in the plot. What’s the x axis? What’s the y? Should something be different colors?

Here we want the species of penguin on the horizontal (x) axis. This piece of information (which is known as an aesthetic attribute of the plot), goes into a second argument called mapping and within a function called aes(). It’s a bit confusing, but you’ll quickly get used to typing your plotting code this way:

ggplot(data = penguins, 
       mapping = aes(x = species))

Now we’re getting somewhere. We can see the x axis has been set up with labels for the species of penguins. We have a canvas, with axes. But we haven’t told it what to draw yet.

To do this, after we create the base plot (above), we have to add layers to draw stuff or change how it looks. Literally with the addition + symbol to “add” the layer to the plot.

Geometry: Many “layers” draw actual shapes (“geometries”), and have names like geom_bar (for bar shapes), geom_point (for dots), or geom_boxplot (for boxplot boxes).

geom_bar is the simplest. It says not only “draw bars” but also “make each bar’s height equal to the number of rows with that value.” Here’s what we mean:

ggplot(data = penguins, 
       mapping = aes(x = species)) +
  geom_bar()

Here we can see that we have about 150 Adelie penguins, about 70 Chinstraps, and about 120 Gentoos. More precistly, the plot is saying “I found 150 rows where the species column was equal to "Adelie", 70 rows where the species column was "Chinstrap", and 120 rows where it was "Gentoo".

Also notice: we didn’t have to tell it anything about the y axis! This is special to geom_bar, which inherently makes the y axis mean “count how many rows have this value.” It’s even labeled “count” (see the y axis in the plot above).

Note that the bars are not colored yet, just a boring gray. One thing we can do is just make every bar a different color; specifically, by adding an extra aesthetic mapping. Note that for bars, fill means the actual bar color, while color just means the outline color of the bar:

ggplot(data = penguins, 
       mapping = aes(x = species, 
                     fill = species)) +
  geom_bar()

We’ve used species twice here. Both the x-axis and the fill color are determined by the penguin species.

But we’re not yet at our goal. We don’t want the bars to be uniform in color, but rather to be filled in with colors according to how many of those penguins live on each island. With a small change, we can get there:

ggplot(data = penguins, 
       mapping = aes(x = species, 
                     fill = island)) +
  geom_bar()

All we did was change fill = species to fill = island. The fill color within each bar is going to be determined by the number of those penguins living on that island.

Last step

What we have right now is just a stacked bar chart. As you learned in the notes today, this isn’t the easiest way to compare the mixture of islands within each species, since the bars are all different heights. Ideally all bars would be the same height, and the proportion filled with each color tells us the proportion of that species living on that island.

The solution is to tweak geom_bar. geom_bar is absolutely a function as well. One of its optional arguments is position. It defaults to a stacked bar chart (above), but we can fix this by setting the position argument to the text "fill" (as in, “fill up the whole y axis!”).

ggplot(data = penguins, 
       mapping = aes(x = species, 
                     fill = island)) +
  geom_bar(position = "fill")

And there we have it. Each bar has the same height, and the y axis shows us what proportion of those specific penguins live on each island. You can see that Chinstraps exclusively live on Deam island, Gentoos on Biscoe island, and Adelies live on all three islands in roughly equal measure.

One more type of bar chart: dodged.

A side-by-side (dodged) bar chart can be produced by replacing "fill" with "dodge".

ggplot(data = penguins, 
       mapping = aes(x = species, 
                     fill = island)) +
  geom_bar(position = "dodge")

Depending on what you’re doing, any of these plots might be the right choice. There are always judgment calls in data visualization – how do you make it easy for the right story to jump off the page?

We have a whole unit on the art of data visualization in a few weeks. We think you’ll enjoy it.