Chapter 21 Exploring categorical variables
This chapter will consider how to go about exploring the sample distribution of a categorical variable. Using the storms
data from the nasaweather package (remember to load and attach the package), we’ll review some basic descriptive statistics and visualisations that are appropriate for categorical variables.
21.1 Understanding categorical variables
Exploring categorical variables is generally simpler than working with numeric variables because we have fewer options, or at least life is simpler if we only require basic summaries. We’ll work with the year
and type
variables in storms
to illustrate the key ideas.
Which kind of categorical variable is type
? There are four storm categories in type. We can use the unique
function to print these for us:
unique(storms$type)
## [1] "Tropical Depression" "Tropical Storm" "Hurricane"
## [4] "Extratropical"
The first question we should ask is, is type
an ordinal or nominal variable? It’s hard to know how to classify type
without knowing something about tropical storms. Some googling indicates that type
can reasonably be considered an ordinal variable: a tropical depression is the least severe storm and a hurricane is the most severe class; in between are extra tropical and tropical storm categories.
What about the year
variable? Years are obviously ordered from early to late and we might be interested in how some aspect of our data changes over time. In this case we might consider treating year either as a numeric variable, or perhaps as an ordinal categorical variable. Alternatively, if the question is simply, ‘do the data vary from one year to the next’ without any concern for trends, it’s perfectly reasonable to treat year as a nominal categorical variable.
This illustrates an important idea: the classification of a variable will often depend on the objectives of an analysis. The classification of a variable matters because it influences how we choose to summarise it, how we interpret its relationship with other variables, and whether a specific statistical model is appropriate for our data or not. Fortunately, our choice of classification is less important when we are just trying to summarise the variable numerically or graphically. For now, let’s assume that it’s fine to treat year as a categorical variable.
21.1.1 Numerical summaries
When we calculate summaries of categorical variables we are aiming to describe the sample distribution of the variable, just as with numeric variables. The general question we need to address is, ‘what are the relative frequencies of different categories?’ We need to understand which categories are common and which are rare. Since a categorical variable takes a finite number of possible values, the simplest thing to do is tabulate the number of occurances of each type. We’ve seen how the table
function is used to do this:
table(storms$type)
##
## Extratropical Hurricane Tropical Depression
## 412 896 513
## Tropical Storm
## 926
This shows that the number of observations associated with hurricanes and tropical storms are about equal, that the number of observations associated with extratropical and tropical systems is similar, and the former pair of categories are more common than the latter. This indicates that in general, storm systems in Central America spend relatively more time in the more severe classes.
Raw frequencies give us information about the rates of occurance of different categories in a dataset. However, it’s difficult to compare raw counts across different data sets if the sample sizes vary (which they usually do). This is why we often convert counts to proportions. To do this, we have to divide each count by the total count across all categories. This is easy to do in R because it’s vectorised:
type_counts <- table(storms$type)
type_counts / sum(type_counts)
##
## Extratropical Hurricane Tropical Depression
## 0.1499818 0.3261740 0.1867492
## Tropical Storm
## 0.3370950
So about 2/3 of observations are associated with hurricanes and tropical storms, with a roughly equal split, and the remaining 1/3 associated with less severe storms.
What about measuring the central tendency of a categorical sample distribution? Various measures exist, but these tend to be less useful than those used to describe numeric variables. We can find the sample mode of ordinal and nominal variables easily though (in contrast to numeric variables, where it is difficult to define). This is just the most common category. For example, the tropical storm category is the modal value of the type
variable. Only just though. The proportion of tropical storm observations is 0.34, while the proportion of hurricane observations is 0.33. These are very similar, and it’s not hard to imagine that modal observation might have been the hurricane category in a different sample. The sample mode is sensitive to chance variation when two categories occur at similar frequencies.
It is possible to calculate a sample median of a categorical variable, but only for the ordinal case. The median value is the one that lies in the middle of an ordered set of values—it makes no sense to talk about “the middle” of a set of nominal values that have no inherent order. Unfortunately, even for ordinal variables the sample median is not precisely defined. Imagine that we’re working with a variable with only two categories: ‘big’ vs. ‘small’, and exactly 50% of the values are ‘small’ value and 50% are large. What is the median in this case? Because the median is not always well-defined, the developers of base R have chosen not to implement a function to calculate the median of ordinal variables (a few packages contain functions to do this though).
Be careful with median
Unfortunately, if we apply the median
function to a character vector it will give us an answer, e.g. median(storms$type)
will spit something out. It is very likely to give us the wrong answer though. R has no way of knowing which categories are “high” and which are “low”, so just sorts the elements of type
alphabetically and then finds the middle value of this vector. If we really have to find the median value of an ordinal value we can do it by first converting the categories to integers— assigning 1 to the lowest category, 2 to the next lowest, and so on— and then use the median function to find out which value is the median.
21.2 Graphical summaries of categorical variables
The most common graphical tool used to summarise a categorical variable is a bar chart. A bar chart (or bar graph) is a plot that presents summaries of grouped data with rectangular bars. The lengths of the bars is proportional to the values they represent. When summarising a single categorical variable, the length of the bars should show the raw counts or proportions of each category.
Constructing a bar graph to display the counts is very easy with ggplot2
. We will do this for the type
variable. As always, we start by using the ggplot
function to construct a graphical object containing the necessary default data and aesthetic mapping.
bar_plt <- ggplot(storms, aes(x = type))
We’ve called the object bar_plt
, for obvious reasons. Notice that we only need to define one aesthetic mapping: we mapped type
to the x axis. This produces a bar plot with vertical bars.
From here we follow the usual ggplot2 workflow, meaning the next step is to add a layer using one of the geom_XX
functions. There are two functions we can use to create bar charts in ggplot, geom_bar
and geom_col
. By default geom_col
counts the number of observations in each category, whilst geom_bar
plots the actual numbers in the data frame. In this case as we want the number of storms of each type
we will use geom_bar
:
bar_plt <- bar_plt + geom_bar()
summary(bar_plt)
## data: name, year, month, day, hour, lat, long, pressure, wind,
## type, seasday [2747x11]
## mapping: x = ~type
## faceting: <ggproto object: Class FacetNull, Facet, gg>
## compute_layout: function
## draw_back: function
## draw_front: function
## draw_labels: function
## draw_panels: function
## finish_data: function
## init_scales: function
## map_data: function
## params: list
## setup_data: function
## setup_params: function
## shrink: TRUE
## train_scales: function
## vars: function
## super: <ggproto object: Class FacetNull, Facet, gg>
## -----------------------------------
## geom_bar: width = NULL, na.rm = FALSE
## stat_count: width = NULL, na.rm = FALSE
## position_stack
Look at the layer information below ----
. The geom_bar
function sets the stat to “count”. Counting a categorical variable is analogous to binning a numeric variable. The only difference is that there is no need to specify bin widths because type
is categorical, i.e. ggplot2
will sum up the number of observations associated with every category of type
. Here’s the resulting figure:
bar_plt
This is the same summary information we produced using the table
function, only now it’s presented in graphical form. We can customise this bar graph if needed with functions like xlab
and ylab
, and by setting various properties inside geom_bar
. For example:
ggplot(storms, aes(x = type)) +
geom_bar(fill = "orange", width = 0.7) +
xlab("Storm Type") + ylab("Number of Observations")
The only new thing here is that we used the width
argument of geom_bar
to make the bars a little narrower than the default.
There is one slight problem with this graph: the order in which the different groups is presented does not reflect the ordinal scale. This occurs because ggplot2
does not “know” that we want to treat type
as a ordinal variable. There is no way for ggplot2
to “guess” the appropriate order, so it uses the alphabetical ordering of the category names to set the order of the bars.
To fix this we need to customise the scale associated with the ‘x’ aesthetic. We can start by making a short character vector containing all the category names in the focal variable, ensuring these are listed in the order they need to be plotted in:
ords <- c("Tropical Depression", "Extratropical", "Tropical Storm", "Hurricane")
Keep an eye on the spelling too—R is not forgiving of spelling errors. We use this with the limits
argument of the scale_x_discrete
function to fix the ordering:
ggplot(storms, aes(x = type)) +
geom_bar(fill = "orange", width = 0.7) +
scale_x_discrete(limits = ords) +
xlab("Storm Type") + ylab("Number of Observations")
We had to use one of the scale_x_YY
functions here because we needed to change the way an aesthetic appears. We use scale_x_discrete
because ‘discrete’ is gplot2-speak for ‘categorical’, which is what we have mapped to the ‘x’ aesthetic.
What else might we change? The categories of type
have quite long names, meaning the axis labels are all bunched together. One way to fix this is to make the labels smaller or rotate them via the ‘themes’ system. Here’s an alternative solution: just flip the x and y axes to make a horizontal bar chart. We can do this with the coord_flip
function (this is new):
ggplot(storms, aes(x = type)) +
geom_bar(fill = "orange", width = 0.7) +
scale_x_discrete(limits = ords) +
coord_flip() +
xlab("Storm Type") + ylab("Number of Observations")