Online reference for ggplots

ggplot2 Reference

Online Book

https://github.com/hadley/ggplot2-book

Facets

Flatling Murder Rates in U.S. Cities. Apart from Chicago, it is flat or declining. Notice the use of a co-ordinated multipanel display the top row is large cities and bottom cities medium sized.

Flatling Murder Rates in U.S. Cities. Apart from Chicago, it is flat or declining. Notice the use of a co-ordinated multipanel display the top row is large cities and bottom cities medium sized.



One way to add additional variables is with aesthetics. Another way, particularly useful for categorical variables, is to split your plot into facets, subplots that each display one subset of the data.

To facet your plot by a single variable, use facet_wrap(). The first argument of facet_wrap() should be a formula, which you create with ~ followed by a variable name (here “formula” is the name of a data structure in R, not a synonym for “equation”). The variable that you pass to facet_wrap() should be discrete.

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) + 
  facet_wrap(~ class, nrow = 2)

To facet your plot on the combination of two variables, add facet_grid() to your plot call. The first argument of facet_grid() is also a formula. This time the formula should contain two variable names separated by a ~.

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) + 
  facet_grid(drv ~ cyl)

If you prefer to not facet in the rows or columns dimension, use a . instead of a variable name, e.g. + facet_grid(. ~ cyl).

Exercise B1.1

What happens if you facet on a continuous variable?

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point() +
  facet_grid(. ~ cty)

It converts the continuous variable to a factor and creates facets for all unique values of it.



Exercise B1.2

What do the empty cells in plot with facet_grid(drv ~ cyl) mean? How do they relate to this plot?

    ggplot(data = mpg) + 
      geom_point(mapping = aes(x = drv, y = cyl))

They are cells in which there are no values of the combination of drv and cyl.

The locations in the above plot without points are the same cells in facet_grid(drv ~ cyl) that have no points.


Exercise B1.3

What plots does the following code make? What does . do?

The symbol . ignores that dimension for faceting. This plot facets by values of drv on the y-axis:

    ggplot(data = mpg) + 
      geom_point(mapping = aes(x = displ, y = hwy)) +
      facet_grid(drv ~ .)

This plot facets by values of cyl on the x-axis:

    ggplot(data = mpg) + 
      geom_point(mapping = aes(x = displ, y = hwy)) +
      facet_grid(. ~ cyl)

Exercise B1.4

Take the first faceted plot in this section:

    ggplot(data = mpg) + 
      geom_point(mapping = aes(x = displ, y = hwy)) + 
      facet_wrap(~ class, nrow = 2)

What are the advantages to using faceting instead of the colour aesthetic?
What are the disadvantages? How might the balance change if you had a 
larger dataset?

This is what the plot looks like when class is represented by the color aesthetic instead of faceting.

ggplot(data = mpg) +
  geom_point(mapping = aes(x = displ, y = hwy, color = class))

Advantages of encoding class with facets instead of color include the ability to encode more distinct categories. For me, it is difficult to distinguish color of “midsize” and the teal of “minivan” points are difficult to distinguish. Given human visual perception, the max number of colors to use when encoding unordered categorical (qualitative) data is nine, and in practice, often much less than that. Also, while placing points in different categories in different scales makes it difficult to directly compare values of individual points in different categories, it can make it easier to compare patterns between categories.

A disadvantages of encoding class with facets instead of color is that the points for each category are on different plots, making it more difficult to directly compare the locations of individual points. Using the same x- and y-scales for all facets lessens this disadvantage. Since encoding class within color also places all points on the same plot, it visualizes the unconditional relationship between the x and y variables; with facets, the unconditional relationship is no longer visualized since the points are spread across multiple plots.

The benefits encoding a variable through facetting over color become more advantageous as either the number of points or the number of categories increase. In the former, as the number of points increase, there is likely to be more overlap. It is difficult to handle overlapping points with color. Jittering will still work with color. But jittering will only work well if there are few points and the classes do not overlap much, otherwise the colors of areas will no longer be distinct and it will be hard to visually pick out the patterns of different categories. Transparency (alpha) does not work well with colors since the mixing of overlapping transparent colors will no longer represent the colors of the categories. Binning methods use already color to encode density, so color cannot be used to encode categories. As noted before, as the number of categories increases, the difference between colors decreases, to the point that the color of categories will no longer be visually distinct.

Exercise B1.5

Read ?facet_wrap. What does nrow do? What does ncol do? What other options control the layout of the individual panels? Why doesn’t facet_grid() have nrow and ncol argument?

The arguments nrow (ncol) determines the number of rows (columns) to use when laying out the facets. It is necessary since facet_wrap() only facets on one variable. These arguments are unnecessary for facet_grid() since the number of rows and columns are determined by the number of unique values of the variables specified.

Exercise B1.6

When using facet_grid() you should usually put the variable with more unique levels in the columns. Why?

IF the plot is laid out horizontally (ie. this is appropriate for most computer screens), there will be more space for columns. You should put the variable with more unique levels in the columns if the plot is laid out landscape. It is easier to compare relative levels of y by scanning horizontally, so it may be easier to visually compare these levels.

Superposition or Juxtaposition

Sometimes small graphic multiples provide a better visualization of data than superposition of on the same plot. The following plot of the change in bone density over one-year for female and male children is shown in the plot below which uses superposition.

ggplot(data=bone, aes(x=age, y=spnbmd, color=gender)) +
 geom_point() +
 geom_smooth() +
 ylab("change in bone density")
## `geom_smooth()` using method = 'loess'

Remark: We use message=FALSE to suppress the message from geom_smooth() that is used the loess smoother!

With the advent of high-resolution colour displays, superposition usually produces an effective display when there are only two or very few classes and there are not too many data values.

The following plot uses the ggplots::facet() juxtaposition.

ggplot(data=bone, aes(x=age, y=spnbmd)) +
 geom_point() +
 geom_smooth() +
 facet_wrap(~gender, ncol=1, nrow=2) +
 ylab("change in bone density")

In this case, I used a column or vertical juxtaposition since I thought if was more effective in comparing the effect on age in the females and males than row/horizontal juxtaposition. But the square aspect-ratio in the superimposed plot is better for visualizating the interesting part of the curves. In general, a judicious choice of aspect-ratio play an important role when comparing curves or underlying trends in data. In the plot below we use a square aspect-ratio.

ggplot(data=bone, aes(x=age, y=spnbmd)) +
 geom_point() +
 geom_smooth() +
 facet_wrap(~gender, ncol=1, nrow=2) +
 ylab("change in bone density") +
 theme(aspect.ratio=1)

Figure from research paper with Yuanhao

Since the code is somewhat lengthy, I have not displayed it here but is is available in the associated RMD file.

Note this use plotmath for the Greek math symbols.



Geometric objects

How are these two plots similar?

Remark:See Rmd-file for details on how to create juxtaposed plots using markdown. Listing of ggplot call is given below.

ggplot(data = mpg) + 
 geom_point(mapping = aes(x = displ, y = hwy))

ggplot(data = mpg) + 
 geom_smooth(mapping = aes(x = displ, y = hwy))

Both plots contain the same x variable, the same y variable, and both describe the same data. But the plots are not identical. Each plot uses a different visual object to represent the data. In ggplot2 syntax, we say that they use different geoms.

A geom is the geometrical object that a plot uses to represent data. People often describe plots by the type of geom that the plot uses. For example, bar charts use bar geoms, line charts use line geoms, boxplots use boxplot geoms, and so on. Scatterplots break the trend; they use the point geom. As we see above, you can use different geoms to plot the same data. The plot on the left uses the point geom, and the plot on the right uses the smooth geom, a smooth line fitted to the data.

Every geom function in ggplot2 takes a mapping argument. However, not every aesthetic works with every geom. You could set the shape of a point, but you couldn’t set the “shape” of a line. On the other hand, you could set the linetype of a line. geom_smooth() will draw a different line, with a different linetype, for each unique value of the variable that you map to linetype.

ggplot(data = mpg) + 
geom_smooth(mapping = aes(x = displ, y = hwy, linetype = drv))

Here geom_smooth() separates the cars into three lines based on their drv value, which describes a car’s drivetrain. One line describes all of the points with a 4 value, one line describes all of the points with an f value, and one line describes all of the points with an r value. Here, 4 stands for four-wheel drive, f for front-wheel drive, and r for rear-wheel drive.

If this sounds strange, we can make it more clear by overlaying the lines on top of the raw data and then coloring everything according to drv.

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = drv)) + 
 geom_point() +
 geom_smooth(mapping = aes(linetype = drv))

Notice that this plot contains two geoms in the same graph! If this makes you excited, buckle up. In the next section, we will learn how to place multiple geoms in the same plot.

ggplot2 provides over 30 geoms, and extension packages provide even more (see https://www.ggplot2-exts.org for a sampling). The best way to get a comprehensive overview is the ggplot2 cheatsheet, which you can find at http://rstudio.com/cheatsheets. To learn more about any single geom, use help: ?geom_smooth.

Many geoms, like geom_smooth(), use a single geometric object to display multiple rows of data. For these geoms, you can set the group aesthetic to a categorical variable to draw multiple objects. ggplot2 will draw a separate object for each unique value of the grouping variable. In practice, ggplot2 will automatically group the data for these geoms whenever you map an aesthetic to a discrete variable (as in the linetype example). It is convenient to rely on this feature because the group aesthetic by itself does not add a legend or distinguishing features to the geoms.

To create juxtaposed plots in markdown as in the above plot we use option outwidth. It is also important to specify also echo=FALSE. The argument fig.asp=1/2 sets the aspect-ratio to 1/2. Note this method of juxtaposition does not automatically enforce same scaling so it is like using the layout() function in base R. Here is the listing.

markdown arguments: echo=FALSE, out.width = "33%", fig.asp = 1/2, 
                    fig.align = 'default',message = FALSE}
ggplot(data = mpg) +
 geom_smooth(mapping = aes(x = displ, y = hwy))
ggplot(data = mpg) +
 geom_smooth(mapping = aes(x = displ, y = hwy, group = drv))
ggplot(data = mpg) +
 geom_smooth(
  mapping = aes(x = displ, y = hwy, color = drv),
  show.legend = FALSE
 )

To display multiple geoms in the same plot, add multiple geom functions to ggplot():

r ggplot(data = mpg) + geom_point(mapping = aes(x = displ, y = hwy)) + geom_smooth(mapping = aes(x = displ, y = hwy))

This, however, introduces some duplication in our code. Imagine if you wanted to change the y-axis to display cty instead of hwy. You’d need to change the variable in two places, and you might forget to update one. You can avoid this type of repetition by passing a set of mappings to ggplot(). ggplot2 will treat these mappings as global mappings that apply to each geom in the graph. In other words, this code will produce the same plot as the previous code:

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
geom_point() + 
geom_smooth()

If you place mappings in a geom function, ggplot2 will treat them as local mappings for the layer. It will use these mappings to extend or overwrite the global mappings for that layer only. This makes it possible to display different aesthetics in different layers.

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
geom_point(mapping = aes(color = class)) + 
geom_smooth()

You can use the same idea to specify different data for each layer. Here, our smooth line displays just a subset of the mpg dataset, the subcompact cars. The local data argument in geom_smooth() overrides the global data argument in ggplot() for that layer only.

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
geom_point(mapping = aes(color = class)) + 
geom_smooth(data = filter(mpg, class == "subcompact"), se = FALSE)

Exercise B2.1
  1. What geom would you use to draw a line chart? A boxplot? A histogram? An area chart?
  • line chart: geom_line()
  • boxplot: geom_boxplot()
  • histogram: geom_hist()
  • area chart: geom_area()
Exercise B2.2

Do you understand how this script works

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = drv)) + 
 geom_point() + 
 geom_smooth(se = FALSE)
## `geom_smooth()` using method = 'loess'

Exercise B2.3

What does show.legend = FALSE do? What happens if you remove it?
Why do you think I used it earlier in the chapter?

In the example earlier in the chapter,

ggplot(data = mpg) +
  geom_smooth(mapping = aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess'

ggplot(data = mpg) +
  geom_smooth(mapping = aes(x = displ, y = hwy, group = drv))
## `geom_smooth()` using method = 'loess'

ggplot(data = mpg) +
  geom_smooth(
    mapping = aes(x = displ, y = hwy, colour = drv),
    show.legend = FALSE)
## `geom_smooth()` using method = 'loess'

the legend is suppressed because there are three plots, and adding a legend that only appears in the last one would make the presentation asymmetric. Additionally, the purpose of this plot is to illustrate the difference between not grouping, using a group aesthetic, and using a color aesthetic (with implicit grouping). In that example, the legend isn’t necessary since looking up the values associated with each color isn’t necessary to make that point.

Exercise B2.4

What does the se argument to geom_smooth() do?

Exercise B2.5

Will these two graphs look different? Why/why not?

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
 geom_point() + 
 geom_smooth()

ggplot() + 
 geom_point(data = mpg, mapping = aes(x = displ, y = hwy)) + 
 geom_smooth(data = mpg, mapping = aes(x = displ, y = hwy))

No. Because both geom_point() and geom_smooth() use the same data and mappings. They will inherit those options from the ggplot() object, and thus don’t need to specified again (or twice).

Exercise B2.6

Compare the following plots. Do you understand the differences.

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
 geom_point() + 
 geom_smooth(se = FALSE)

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
 geom_smooth(aes(group = drv), se = FALSE) +
 geom_point()

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, color = drv)) + 
 geom_point() + 
 geom_smooth(se = FALSE)

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
 geom_point(aes(color = drv)) + 
 geom_smooth(se = FALSE)

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
 geom_point(aes(color = drv)) +
 geom_smooth(aes(linetype = drv), se = FALSE)

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
 geom_point(size = 4, colour = "white") + 
 geom_point(aes(colour = drv))



Statistical transformations

diamonds dataset

The diamonds dataset comes in ggplot2 and contains information about ~54,000 diamonds, including the price, carat, color, clarity, and cut of each diamond. Using glimpse() we see that cut, color and clarity are ordinal (ordered factor in R).

glimpse(diamonds)
## Observations: 53,940
## Variables: 10
## $ carat   <dbl> 0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, ...
## $ cut     <ord> Ideal, Premium, Good, Premium, Good, Very Good, Very G...
## $ color   <ord> E, E, E, I, J, J, I, H, E, H, J, J, F, J, E, E, I, J, ...
## $ clarity <ord> SI2, SI1, VS1, VS2, SI2, VVS2, VVS1, SI1, VS2, VS1, SI...
## $ depth   <dbl> 61.5, 59.8, 56.9, 62.4, 63.3, 62.8, 62.3, 61.9, 65.1, ...
## $ table   <dbl> 55, 61, 65, 58, 58, 57, 57, 55, 61, 61, 55, 56, 61, 54...
## $ price   <int> 326, 326, 327, 334, 335, 336, 336, 337, 337, 338, 339,...
## $ x       <dbl> 3.95, 3.89, 4.05, 4.20, 4.34, 3.94, 3.95, 4.07, 3.87, ...
## $ y       <dbl> 3.98, 3.84, 4.07, 4.23, 4.35, 3.96, 3.98, 4.11, 3.78, ...
## $ z       <dbl> 2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, ...
  • carat: weight of the diamond (0.2-5.01)
  • cut: quality of the cut (Fair, Good, Very Good, Premium, Ideal)
  • color: diamond colour, from J (worst) to D (best)
  • clarity: a measurement of how clear the diamond is (I1 (worst), SI1, SI2, VS1, VS2, VVS1, VVS2, IF (best))
  • depth: total depth percentage = z / mean(x, y) = 2 * z / (x + y) (43-79)
  • table: width of top of diamond relative to widest point (43-95)
  • price: price in US dollars ($326-$18,823)
  • x: length in mm (0-10.74)
  • y: width in mm (0-58.9)
  • z: depth in mm (0-31.8)

Barcharts

Next, let’s take a look at a bar chart. Bar charts seem simple, but they are interesting because they reveal something subtle about plots. Consider a basic bar chart, as drawn with geom_bar().

The following chart displays the total number of diamonds in the diamonds dataset, grouped by cut.

The chart shows that more diamonds are available with high quality cuts than with low quality cuts.

ggplot(data = diamonds) + 
geom_bar(mapping = aes(x = cut))

On the x-axis, the chart displays cut, a variable from diamonds. On the y-axis, it displays count, but count is not a variable in diamonds! Where does count come from? Many graphs, like scatterplots, plot the raw values of your dataset. Other graphs, like bar charts, calculate new values to plot:

  • bar charts, histograms, and frequency polygons bin your data and then plot bin counts, the number of points that fall in each bin.

  • smoothers fit a model to your data and then plot predictions from the model.

  • boxplots compute a robust summary of the distribution and then display a specially formatted box.

The algorithm used to calculate new values for a graph is called a stat, short for statistical transformation. The figure below describes how this process works with geom_bar().

Schematic showing how stat with geom_bar() works

Schematic showing how stat with geom_bar() works

You can learn which stat a geom uses by inspecting the default value for the stat argument. For example, ?geom_bar shows that the default value for stat is “count”, which means that geom_bar() uses stat_count(). stat_count() is documented on the same page as geom_bar(), and if you scroll down you can find a section called “Computed variables”. That describes how it computes two new variables: count and prop.

You can generally use geoms and stats interchangeably. For example, you can recreate the previous plot using stat_count() instead of geom_bar():

ggplot(data = diamonds) + 
stat_count(mapping = aes(x = cut))

This works because every geom has a default stat; and every stat has a default geom. This means that you can typically use geoms without worrying about the underlying statistical transformation. There are three reasons you might need to use a stat explicitly:

  1. You might want to override the default stat. In the code below, I change the stat of geom_bar() from count (the default) to identity. This lets me map the height of the bars to the raw values of a \(y\) variable. Unfortunately when people talk about bar charts casually, they might be referring to this type of bar chart, where the height of the bar is already present in the data, or the previous bar chart where the height of the bar is generated by counting rows.
demo <- tribble(
~cut,         ~freq,
"Fair",       1610,
"Good",       4906,
"Very Good",  12082,
"Premium",    13791,
"Ideal",      21551
)
ggplot(data = demo) +
geom_bar(mapping = aes(x = cut, y = freq), stat = "identity")

Override default mapping from transformed variables

You might want to override the default mapping from transformed variables to aesthetics. For example, you might want to display a bar chart of proportion, rather than count:

r ggplot(data = diamonds) + geom_bar(mapping = aes(x = cut, y = ..prop.., group = 1))

To find the variables, such as prop, computed by the stat, look for the help section titled “computed variables”.

Specify the statistical transformation for emphasis

You might want to draw greater attention to the statistical transformation in your code. For example, you might use stat_summary(), which summarises the y values for each unique x value, to draw attention to the summary that you’re computing:

ggplot(data = diamonds) + 
stat_summary(
mapping = aes(x = cut, y = depth),
fun.ymin = min,
fun.ymax = max,
fun.y = median
)

args(stat_summary)
## function (mapping = NULL, data = NULL, geom = "pointrange", position = "identity", 
##     ..., fun.data = NULL, fun.y = NULL, fun.ymax = NULL, fun.ymin = NULL, 
##     fun.args = list(), na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
## NULL

ggplot2 provides over 20 stats for you to use. Each stat is a function, so you can get help in the usual way, e.g. ?stat_bin. To see a complete list of stats, try the ggplot2 cheatsheet.

Exercise B3.1

What is the default geom associated with stat_summary()? How could you rewrite the previous plot to use that geom function instead of the stat function?

The default geom for stat_summary() is geom_pointrange() (see the stat) argument.

But, the default stat for geom_pointrange() is identity(), so use geom_pointrange(stat = “summary”).

ggplot(data = diamonds) +
  geom_pointrange(
    mapping = aes(x = cut, y = depth),
    stat = "summary"
  )
## No summary function supplied, defaulting to `mean_se()

The default message says that stat_summary() uses the mean and sd to calculate the point, and range of the line. So lets use the previous values of fun.ymin, fun.ymax, and fun.y:

ggplot(data = diamonds) +
  geom_pointrange(
    mapping = aes(x = cut, y = depth),
    stat = "summary",
    fun.ymin = min,
    fun.ymax = max,
    fun.y = median
  )

Exercise B3.1

What does geom_col() do? How is it different to geom_bar()?

The geom_col() function has different default than geom_bar(). The default stat of geom_col() isidentity()stat. This means thatgeom_col()expects that the data is already preprocessed intoxvalues andyvalues representing the bar height. The defult stat ofgeom_bar()iscount(). This means thatgeom_bar()expects thexvariable to contain multiple observations for each values, and it will handle counting the number of observations for each value ofx` in order to create the bar heights.

Exercise B3.1
  1. In our proportion bar chart, we need to set group = 1. Why? In other words what is the problem with these two graphs?

If group is not set to 1, then all the bars have prop == 1. The function geom_bar() assumes that the groups are equal to the x values, since the stat computes the counts within the group.

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, y = ..prop..))

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, y = ..prop.., group = 1))

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, fill = color, y = ..prop..))

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, fill = color, y = ..prop.., group = color))

By default, the groups are defined by the set of all interactions of the factor variables that are included in the plot. Usually this works correct but sometimes the default must be overridden.



Position adjustments

There’s one more piece of magic associated with bar charts. You can colour a bar chart using either the colour aesthetic, or, more usefully, fill:

r ggplot(data = diamonds) + geom_bar(mapping = aes(x = cut, colour = cut))

r ggplot(data = diamonds) + geom_bar(mapping = aes(x = cut, fill = cut))

Note what happens if you map the fill aesthetic to another variable, like clarity: the bars are automatically stacked. Each colored rectangle represents a combination of cut and clarity.

ggplot(data = diamonds) + 
 geom_bar(mapping = aes(x = cut, fill = clarity)) +
 ggtitle("Example of a stacked barchart")

Bonus Problem Cleveland in his book “Elements of Graphing Data” presents a convincing argument that the grouped dotchart performs better than the stacked barchart. Compare the above plot with the base R dotchart(). Can you use ggplot2 for a grouped dotchart?

The stacking is performed automatically by the position adjustment specified by the position argument. If you don’t want a stacked bar chart, you can use one of three other options: "identity", "dodge" or "fill".

ggplot(data = diamonds, mapping = aes(x = cut, fill = clarity)) + 
geom_bar(alpha = 1/5, position = "identity")

ggplot(data = diamonds, mapping = aes(x = cut, colour = clarity)) + 
geom_bar(fill = NA, position = "identity")

The identity position adjustment is more useful for 2d geoms, like points, where it is the default.

ggplot(data = diamonds) + 
geom_bar(mapping = aes(x = cut, fill = clarity), position = "fill") +
ggtitle("Example Spline Plot")

The Economist, Sept. 9-15, 2017

The Economist, Sept. 9-15, 2017

ggplot(data = diamonds) + 
geom_bar(mapping = aes(x = cut, fill = clarity), position = "dodge")

There’s one other type of adjustment that’s not useful for bar charts, but it can be very useful for scatterplots. Recall our first scatterplot. Did you notice that the plot displays only 126 points, even though there are 234 observations in the dataset?

The values of hwy and displ are rounded so the points appear on a grid and many points overlap each other. This problem is known as overplotting. This arrangement makes it hard to see where the mass of the data is. Are the data points spread equally throughout the graph, or is there one special combination of hwy and displ that contains 109 values?

You can avoid this gridding by setting the position adjustment to “jitter”. position = "jitter" adds a small amount of random noise to each point. This spreads the points out because no two points are likely to receive the same amount of random noise.

ggplot(data = mpg) + 
geom_point(mapping = aes(x = displ, y = hwy), position = "jitter")

Adding randomness seems like a strange way to improve your plot, but while it makes your graph less accurate at small scales, it makes your graph more revealing at large scales. Because this is such a useful operation, ggplot2 comes with a shorthand for geom_point(position = "jitter"): geom_jitter().

To learn more about a position adjustment, look up the help page associated with each adjustment: ?position_dodge, ?position_fill, ?position_identity, ?position_jitter, and ?position_stack.

Exercise B4.1

What is the problem with this plot? How could you improve it?

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) + 
geom_point()

I’d fix it by using a jitter position adjustment.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_point(position = "jitter")

Exercise B4.2

What parameters to geom_jitter() control the amount of jittering?

From the geom_jitter() documentation, there are two arguments to jitter:

width controls the amount of vertical displacement, and height controls the amount of horizontal displacement. The defaults values of width and height will introduce noise in both directions. Here is what the plot looks like with the default values of height and width.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_point(position = position_jitter(width = 0))

However, we can adjust them. Here are few examples to understand how adjusting these parameters affects the look of the plot.

With width = 0 there is no horizontal jitter.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_jitter(width = 0)

With width = 20, there is too much horizontal jitter.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_jitter(width = 20)

With height = 0, there is no vertical horizontal jitter:

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_jitter(height = 0)

With height = 15, there is too much vertical jitter.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_point(height = 15)
## Warning: Ignoring unknown parameters: height

Note that the height and width arguments are in the units of the data. Thus height = 1 corresponds to different relative amounts of jittering depending on the scale of the y variable. The default values of height and width are defined to be 80% of the resolution() of the data, which is the smallest non-zero distance between adjacent values of a variable. This means that if x and y are discrete variables, their resolutions are both equal to 1, and height = 0.8 and width = 0.8.

Exercise B4.3

Compare and contrast geom_jitter() with geom_count().

geom_jitter() adds random noise to the locations points of the graph. In other words, it “jitters” the points. This method reduces overplotting since no two points are likely to have the same location after the random noise is added to their locations.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_jitter()

However, the reduction in overlapping comes at the cost of changing the x and y values of the points.

geom_count() resizes the points relative to the number of observations at each location. In other words, points with more observations will be larger than those with fewer observations.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
  geom_count()

This method does not change the x and y coordinates of the points. However, if the points are close together and counts are large, the size of some points can itself introduce overplotting. For example, in the following example a third variable mapped to color is added to the plot. In this case, geom_count() is less readable than geom_jitter() when adding a third variable as color aesthetic.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy, color = class)) +
  geom_jitter()

ggplot(data = mpg, mapping = aes(x = cty, y = hwy, color = class)) +
  geom_count()

Unfortunately, there is no universal solution to overplotting. The costs and benefits of different approaches will depend on the structure of the data and the goal of the data scientist.

Exercise B4.4
  1. What’s the default position adjustment for geom_boxplot()? Create a visualisation of the mpg dataset that demonstrates it.

The default position for geom_boxplot() is position_dodge() (see its docs).

When we add colour = class to the box plot, the different classes within drv are placed side by side, i.e. dodged. If it was position_identity(), they would be overlapping.

ggplot(data = mpg, aes(x = drv, y = hwy, colour = class)) +
  geom_boxplot()

ggplot(data = mpg, aes(x = drv, y = hwy, colour = class)) +
  geom_boxplot(position = "identity")



Coordinate systems

Coordinate systems are probably the most complicated part of ggplot2. The default coordinate system is the Cartesian coordinate system where the x and y positions act independently to determine the location of each point. There are a number of other coordinate systems that are occasionally helpful.

ggplot(data = mpg, mapping = aes(x = class, y = hwy)) + 
 geom_boxplot()

ggplot(data = mpg, mapping = aes(x = class, y = hwy)) + 
 geom_boxplot() +
 coord_flip()

nz <- map_data("nz")

ggplot(nz, aes(long, lat, group = group)) +
 geom_polygon(fill = "white", colour = "black")

ggplot(nz, aes(long, lat, group = group)) +
 geom_polygon(fill = "white", colour = "black") +
 coord_quickmap()

Barchart and Coxcomb chart

Coxcomb or Exploded Pie Chart

Coxcomb or Exploded Pie Chart

Example of polar area diagram by w:Florence Nightingale (1820-1910). This “Diagram of the causes of mortality in the army in the East” was published in Notes on Matters Affecting the Health, Efficiency, and Hospital Administration of the British Army and sent to Queen Victoria in 1858. This graphic indicates the number of deaths that occured from preventable diseases (in blue), those that were the results of wounds (in red), and those due to other causes (in black). The legend reads: The Areas of the blue, red, & black wedges are each measured from the centre as the common vertex. The blue wedges measured from the centre of the circle represent area for area the deaths from Preventable or Mitigable Zymotic diseases, the red wedges measured from the centre the deaths from wounds, & the black wedges measured from the centre the deaths from all other causes. The black line across the red triangle in Nov. 1854 marks the boundary of the deaths from all other causes during the month. In October 1854, & April 1855, the black area coincides with the red, in January & February 1856, the blue coincides with the black. The entire areas may be compared by following the blue, the red, & the black lines enclosing them

  • coord_polar() uses polar coordinates. Polar coordinates reveal an interesting connection between a bar chart and a Coxcomb chart.
bar <- ggplot(data = diamonds) + 
 geom_bar(
  mapping = aes(x = cut, fill = cut), 
  show.legend = FALSE,
  width = 1
 ) + 
 theme(aspect.ratio = 1) +
 labs(x = NULL, y = NULL)

bar + coord_flip()

bar + coord_polar()


Exercise B5.1

Turn a stacked bar chart into a pie chart using coord_polar().

This is a stacked bar chart with a single category

ggplot(mpg, aes(x = factor(1), fill = drv)) +
  geom_bar()

See the documentation for coord_polar for an example of making a pie chart. In particular, theta = “y”, meaning that the angle of the chart is the y variable which has to be specified.

ggplot(mpg, aes(x = factor(1), fill = drv)) +
  geom_bar(width = 1) +
  coord_polar(theta = "y")

If theta = “y” is not specified, then you get a bull’s-eye chart

ggplot(mpg, aes(x = factor(1), fill = drv)) +
  geom_bar(width = 1) +
  coord_polar()

If you had a multiple stacked bar chart,

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, fill = clarity), position = "fill")

and apply polar coordinates to it, you end up with a multi-doughnut chart,

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, fill = clarity), position = "fill") +
  coord_polar(theta = "y")

Exercise B5.2

What does labs() do? Read the documentation.

The labs function adds labels for different scales and the title of the plot.

ggplot(data = mpg, mapping = aes(x = class, y = hwy)) + geom_boxplot() + coord_flip() + labs(y = “Highway MPG”, x = “”, title = “Highway MPG by car class”)

Exercise B5.3

What’s the difference between coord_quickmap() and coord_map()?

coord_map() uses map projection to project 3-dimensional Earth onto a 2-dimensional plane. By default, coord_map() uses the Mercator projection. However, this projection must be applied to all geoms in the plot. coord_quickmap() uses a faster, but approximate map projection. This approximation ignores the curvature of Earth and adjusts the map for the latitude/longitude ratio. This transformation is quicker than coord_map() because the coordinates of the individual geoms do not need to be transformed.

Exercise B5.4

What does the plot below tell you about the relationship between city and highway mpg? Why is coord_fixed() important? What does geom_abline() do?

The function coord_fixed() ensures that the line produced by geom_abline() is at a 45 degree angle. The 45 degree line makes it easy to compare the highway and city mileage to the case in which city and highway MPG were equal.

ggplot(data = mpg, mapping = aes(x = cty, y = hwy)) +
geom_point() + 
geom_abline() +
coord_fixed()



The layered grammar of graphics

In the previous sections, you learned much more than how to make scatterplots, bar charts, and boxplots. You learned a foundation that you can use to make any type of plot with ggplot2. To see this, let’s add position adjustments, stats, coordinate systems, and faceting to our code template:

 ggplot(data = <DATA>) + 
   <GEOM_FUNCTION>(
      mapping = aes(<MAPPINGS>),
      stat = <STAT>, 
      position = <POSITION>
   ) +
   <COORDINATE_FUNCTION> +
   <FACET_FUNCTION>

Our new template takes seven parameters, the bracketed words that appear in the template. In practice, you rarely need to supply all seven parameters to make a graph because ggplot2 will provide useful defaults for everything except the data, the mappings, and the geom function.

The seven parameters in the template compose the grammar of graphics, a formal system for building plots. The grammar of graphics is based on the insight that you can uniquely describe any plot as a combination of a dataset, a geom, a set of mappings, a stat, a position adjustment, a coordinate system, and a faceting scheme.

To see how this works, consider how you could build a basic plot from scratch: you could start with a dataset and then transform it into the information that you want to display (with a stat).

select data, compute counts

select data, compute counts

Next, you could choose a geometric object to represent each observation in the transformed data. You could then use the aesthetic properties of the geoms to represent variables in the data. You would map the values of each variable to the levels of an aesthetic.

geom for bars

geom for bars

You’d then select a coordinate system to place the geoms into. You’d use the location of the objects (which is itself an aesthetic property) to display the values of the x and y variables. At that point, you would have a complete graph, but you could further adjust the positions of the geoms within the coordinate system (a position adjustment) or split the graph into subplots (faceting). You could also extend the plot by adding one or more additional layers, where each additional layer uses a dataset, a geom, a set of mappings, a stat, and a position adjustment.

Set geom, coordinates, x and y

Set geom, coordinates, x and y

You could use this method to build any plot that you imagine. In other words, you can use the code template that you’ve learned in this chapter to build hundreds of thousands of unique plots.