Warning: package 'readr' was built under R version 4.2.3
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.2 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.0
✔ ggplot2 3.4.4 ✔ tibble 3.2.1
✔ lubridate 1.9.2 ✔ tidyr 1.3.0
✔ purrr 1.0.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
data(mpg)mpg
# A tibble: 234 × 11
manufacturer model displ year cyl trans drv cty hwy fl class
<chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
1 audi a4 1.8 1999 4 auto… f 18 29 p comp…
2 audi a4 1.8 1999 4 manu… f 21 29 p comp…
3 audi a4 2 2008 4 manu… f 20 31 p comp…
4 audi a4 2 2008 4 auto… f 21 30 p comp…
5 audi a4 2.8 1999 6 auto… f 16 26 p comp…
6 audi a4 2.8 1999 6 manu… f 18 26 p comp…
7 audi a4 3.1 2008 6 auto… f 18 27 p comp…
8 audi a4 quattro 1.8 1999 4 manu… 4 18 26 p comp…
9 audi a4 quattro 1.8 1999 4 auto… 4 16 25 p comp…
10 audi a4 quattro 2 2008 4 manu… 4 20 28 p comp…
# ℹ 224 more rows
Let’s start by visualizing the relationship between displ and hwy for various classes of cars. We can do this with a scatterplot where the numerical variables are mapped to the x and y aesthetics and the categorical variable is mapped to an aesthetic like color or shape.
# Leftggplot(mpg, aes(x = displ, y = hwy, color = class)) +geom_point()# Rightggplot(mpg, aes(x = displ, y = hwy, shape = class)) +geom_point()
Warning: The shape palette can deal with a maximum of 6 discrete values because
more than 6 becomes difficult to discriminate; you have 7. Consider
specifying shapes manually if you must have them.
Warning: Using alpha for a discrete variable is not advised.
You can also set the visual properties of your geom manually as an argument of your geom function (outside of aes()) instead of relying on a variable mapping to determine the appearance. For example, we can make all of the points in our plot blue:
ggplot(mpg, aes(x = displ, y = hwy)) +geom_point(color ="blue")
9.2.1 Selected Solutions
Question 1: Create a scatterplot of hwy vs. displ where the points are pink filled in triangles.
Question 2: Why did the following code not result in a plot with blue points?
ggplot(mpg) +geom_point(aes(x = displ, y = hwy, color ="blue"))
color should be outside of the aesthetic mapping:
ggplot(mpg, aes(x = displ, y = hwy)) +geom_point(color ="blue")
Question 3: What does the stroke aesthetic do? What shapes does it work with? (Hint: use ?geom_point)
?geom_point()
Here, the stroke aesthetic modifies the width of the border of points for shapes with a border (21-24).
Question 4: What happens if you map an aesthetic to something other than a variable name, like aes(color = displ < 5)? Note, you’ll also need to specify x and y.
It creates a logical variable with values TRUE and FALSE for cars with displacement values below and above 5. In general, mapping an aesthetic to something other than a variable first evaluates that expression then maps the aesthetic to the outcome.
ggplot(mpg, aes(x = displ, y = hwy, color = displ <5)) +geom_point()
9.3 Geometric objects
To change the geom in your plot, change the geom function that you add to ggplot(). For instance:
# Leftggplot(mpg, aes(x = displ, y = hwy)) +geom_point()# Rightggplot(mpg, aes(x = displ, y = hwy)) +geom_smooth()
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Meanwhile, we can have two geoms on the same plot, each specified differently:
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +geom_point() +geom_smooth(aes(linetype = drv))
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
Note that in the example above, aes(..., color = drv) is contained in ggplot, meaning the mapping applies globally to all layers, hence the same color of geom_point() and geom_smooth(). Meanwhile, geom_smooth contains aes(linetype = drv), so ggplot will treat it as the local mapping for that layer only, producing different line types according to different drive train.
# Leftggplot(mpg, aes(x = displ, y = hwy)) +geom_smooth()
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
`geom_smooth()` using method = 'loess' and formula = 'y ~ x'
This feature can applied in different cases. Suppose we want to highlight two-seater cars using red points and open circles. The local data argument in geom_point() overrides the global data argument in ggplot() for that layer only.
# globally defined argumentggplot(mpg, aes(x = displ, y = hwy)) +geom_point() +geom_point(data = mpg |>filter(class =="2seater"), color ="red" ) +# local data argumentgeom_point(data = mpg |>filter(class =="2seater"),shape ="circle open", size =3, color ="red" )
Different geoms can reveal different aspects of your data. For instance, the histogram and density plot below reveal that the distribution of highway mileage is bimodal and right skewed while the boxplot reveals two potential outliers.
What does show.legend = FALSE do here? What happens if you remove it? Why do you think we used it earlier?
show.legend = FALSE removes the legend from the plot. if its removed, the legend will appear since geom_smooth() will take the default specified TRUE value for show.legend. It might be used just to show how different trends across specified groups occur in the scatterplot without mentioning what does groups are.
Question 3: What does the se argument to geom_smooth() do?
se shows the confidence interval around the regression line, with an additional argument level to control its level. Set to TRUE by default.
Question 4: Recreate the R code necessary to generate the following graphs. Note that wherever a categorical variable is used in the plot, it’s drv.
The following code produces the graph above:
ggplot(mpg, aes(x = displ, y = hwy)) +geom_point() +geom_smooth(se =FALSE)ggplot(mpg, aes(x = displ, y = hwy)) +geom_point() +geom_smooth(aes(group = drv), se =FALSE)ggplot(mpg, aes(x = displ, y = hwy, col = drv)) +geom_point() +geom_smooth(se =FALSE)ggplot(mpg, aes(x = displ, y = hwy)) +geom_point(aes(col = drv)) +geom_smooth(se =FALSE)ggplot(mpg, aes(x = displ, y = hwy)) +geom_point(aes(col = drv)) +geom_smooth(aes(linetype = drv), se =FALSE)ggplot(mpg, aes(x = displ, y = hwy, fill = drv)) +geom_point(size =4, col ="white") +geom_point(aes(color = drv))
9.4 Facets
Faceting can be done with facet_wrap(), which splits a plot into subplots at each display one subset of the categorical data based on a categorical variable:
ggplot(mpg, aes(x = displ, y = hwy)) +geom_point() +facet_wrap(~cyl)
Suppose we have two variables, then faceting can be done by switching to facet_grid(). The first argument of facet_grid() is also a formula, but now it’s a double sided formula: rows ~ cols.
By default each of the facets share the same scale and range for x and y axes. This is useful when you want to compare data across facets but it can be limiting when you want to visualize the relationship within each facet better. Setting the scales argument in a faceting function to "free" will allow for different axis scales across both rows and columns, "free_x" will allow for different scales across rows, and "free_y" will allow for different scales across columns.
Since hwy is a continuous variable, we see that faceting it returns a unique facet corresponding to each value of hwy. The points inside each facet correspond to the counts of each instance of hwy.
Question 2: What do the empty cells in the plot above with facet_grid(drv ~ cyl) mean? Run the following code. How do they relate to the resulting plot?
ggplot(mpg) +geom_point(aes(x = drv, y = cyl)) +facet_grid(drv ~ cyl)
There are no cars with front-wheel drive and 5 cylinders, for example. Therefore the facet corresponding to that combination is empty. In general, empty facets mean no observations fall in that category.
Question 3: What plots does the following code make? What does . do?
ggplot(mpg) +geom_point(aes(x = displ, y = hwy)) +facet_grid(drv ~ .)ggplot(mpg) +geom_point(aes(x = displ, y = hwy)) +facet_grid(. ~ cyl)
ggplot(mpg) +geom_point(aes(x = displ, y = hwy)) +facet_grid(drv ~ .)
ggplot(mpg) +geom_point(aes(x = displ, y = hwy)) +facet_grid(. ~ cyl)
facet_grid() follows the double-sided rows ~ columns, so putting . returns the same output as facet_wrap(), only that the facets are plotted with respect to rows (1st) or columns (2nd). In general, the period means “keep everything together”.
Question 4: Take the first faceted plot in this section:
What are the advantages to using faceting instead of the color aesthetic? What are the disadvantages? How might the balance change if you had a larger dataset?
Advantages: Allows us to see the individual classes of cars plotted without overplotting
Disadvantages: Harder to compare with other classes of cars as they’re plotted seperately, and telling classes apart in the same picture.
If we were interested in a specific class, e.g. compact cars, it would be useful to highlight that group only with an additional layer as shown in the last plot below.
# facetggplot(mpg) +geom_point(aes(x = displ, y = hwy)) +facet_wrap(~ class, nrow =2)# colorggplot(mpg) +geom_point(aes(x = displ, y = hwy, color = class))# bothggplot(mpg) +geom_point(aes(x = displ, y = hwy, color = class), show.legend =FALSE) +facet_wrap(~ class, nrow =2)# highlightingggplot(mpg, aes(x = displ, y = hwy)) +geom_point(color ="gray") +geom_point(data = mpg |>filter(class =="compact"),color ="pink" )
Question 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 arguments?
nrow and ncol respectively denote the number of rows and columns. Other options include faceting with different directions with dir.
facet_grid doesn’t have an nrow or ncol because the rows and columns are defined according to the levels of categorical variables facet_grid() plots.
Question 6: Which of the following plots makes it easier to compare engine size (displ) across cars with different drive trains? What does this say about when to place a faceting variable across rows or columns?
The first plot makes it easier to compare engine size (displ) across cars with different drive trains because the axis that plots displ is shared across the panels. What this says is that if the goal is to make comparisons based on a given variable, that variable should be placed on the shared axis.
Question 7: Recreate the following plot using facet_wrap() instead of facet_grid(). How do the positions of the facet labels change?
ggplot(mpg) +geom_point(aes(x = displ, y = hwy)) +facet_grid(drv ~ .)
We can recreate the basic plot using the dir and strip[.position arguments:
ggplot(mpg) +geom_point(aes(x = displ, y = hwy)) +facet_wrap(~drv, dir ="v", strip.position ="right")
9.5 Statistical Transformation
Consider a basic bar chart, drawn with geom_bar() or geom_col(). The following chart displays the total number of diamonds in the diamonds dataset, grouped by cut. The diamonds dataset is in the ggplot2 package and contains information on ~54,000 diamonds, including the price, carat, color, clarity, and cut of each diamond. The chart shows that more diamonds are available with high quality cuts than with low quality cuts.
ggplot(diamonds, aes(x = cut)) +geom_bar()
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(). If you scroll down, the section called “Computed variables” explains that it computes two new variables: count and prop.
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. However, there are three reasons why you might need to use a stat explicitly:
You might want to override the default stat. In the code below, we change the stat of geom_bar() from count (the default) to identity. This lets us map the height of the bars to the raw values of a y variable.
diamonds |>count(cut) |>ggplot(aes(x = cut, y = n)) +geom_bar(stat ="identity")
You might want to override the default mapping from transformed variables to aesthetics. For example, you might want to display a bar chart of proportions, rather than counts:
ggplot(diamonds, aes(x = cut, y =after_stat(prop), group =1)) +geom_bar()
To find the possible variables that can be computed by the stat, look for the section titled “computed variables” in the help for geom_bar().
You might want to draw greater attention to the statistical transformation in your code. For example, you might use stat_summary(), which summarizes the y values for each unique x value, to draw attention to the summary that you’re computing:
ggplot(diamonds) +stat_summary(aes(x = cut, y = depth),fun.min = min,fun.max = max,fun = median )
ggplot2 provides more than 20 stats for you to use. Each stat is a function, so you can get help in the usual way, e.g., ?stat_bin.
9.5.1 Selected Solutions
Question 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?
stat_summary() is by default associated with geom_pointrange(). The previous plot can be rewritten using the geom_pointrange() function:
geom_col() plots the height of the bars based on the values represented in the data, while geom_bar() first calculates the heights from the data then plot them. geom_col() can be used to make a bar plot from a data frame that represents a frequency table while geom_bar() can be used to make a bar plot from a data frame where each row is an observation.
For example, this plot gives us the count of different type of cut, using geom_bar():
diamonds |>count(cut) |>ggplot(aes(x = cut, y = n)) +geom_bar(stat ="identity")
But to replicate the same curve using geom_col(), we must first create a frequency table:
Question 3: Most geoms and stats come in pairs that are almost always used in concert. Make a list of all the pairs. What do they have in common? (Hint: Read through the documentation.)
Question 4: What variables does stat_smooth() compute? What arguments control its behavior?
stat_smooth() computes the following variables:
y or x: Predicted value
ymin or xmin: Lower pointwise confidence interval for the mean
ymax or xmax: Upper pointwise confidence interval for the mean
se: Standard error
Question 5: In our proportion bar chart, we needed to set group = 1. Why? In other words, what is the problem with these two graphs?
ggplot(diamonds, aes(x = cut, y =after_stat(prop))) +geom_bar()ggplot(diamonds, aes(x = cut, fill = color, y =after_stat(prop))) +geom_bar()
In the first pair of plots, we see that setting group = 1 results in the marginal proportions of cuts being plotted. In the second pair of plots, setting group = color results in the proportions of colors within each cut being plotted. Setting position = "fill" changes the proportion of y-axis, such that the proportion of each color can be seen within each cut.
# 1 variableggplot(diamonds, aes(x = cut, y =after_stat(prop))) +geom_bar()ggplot(diamonds, aes(x = cut, y =after_stat(prop), group =1)) +geom_bar()# 2 variablesggplot(diamonds, aes(x = cut, fill = color, y =after_stat(prop))) +geom_bar()ggplot(diamonds) +geom_bar(aes(x = cut, fill = color, y =after_stat(prop), group = color),position ="fill")
9.6 Position adjustments
There’s one more piece of magic associated with bar charts. You can color a bar chart using either the color aesthetic, or, more usefully, the fill aesthetic:
# Leftggplot(mpg, aes(x = drv, color = drv)) +geom_bar()# Rightggplot(mpg, aes(x = drv, fill = drv)) +geom_bar()
Note what happens if you map the fill aesthetic to another variable, like class: the bars are automatically stacked. Each colored rectangle represents a combination of drv and class.
ggplot(mpg, aes(x = drv, fill = class)) +geom_bar()
The stacking is performed automatically using 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".
position = "identity" will place each object exactly where it falls in the context of the graph. This is not very useful for bars, because it overlaps them. To see that overlapping we either need to make the bars slightly transparent by setting alpha to a small value, or completely transparent by setting fill = NA.
# Leftggplot(mpg, aes(x = drv, fill = class)) +geom_bar(alpha =1/5, position ="identity")
# Rightggplot(mpg, aes(x = drv, color = class)) +geom_bar(fill =NA, position ="identity")
The identity position adjustment is more useful for 2d geoms, like points, where it is the default.
position = "fill" works like stacking, but makes each set of stacked bars the same height. This makes it easier to compare proportions across groups.
position = "dodge" places overlapping objects directly beside one another. This makes it easier to compare individual values.
# Leftggplot(mpg, aes(x = drv, fill = class)) +geom_bar(position ="fill")
# Rightggplot(mpg, aes(x = drv, fill = class)) +geom_bar(position ="dodge")
Now, Recall our first scatterplot. Notice that the plot displays only 126 points, even though there are 234 observations in the dataset:
The underlying 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 difficult to see the distribution of the data. 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(mpg, aes(x = displ, y = hwy)) +geom_point(position ="jitter")
Question 1: What is the problem with the following plot? How could you improve it?
ggplot(mpg, aes(x = cty, y = hwy)) +geom_point()
set.seed(100)# left, with distinct pointsmpg |>distinct() |>ggplot(aes(x = cty, y = hwy)) +geom_point()# middle, without distinct pointsmpg |>ggplot(aes(x = cty, y = hwy)) +geom_point()# right, without jitter pointsmpg |>ggplot(aes(x = cty, y = hwy)) +geom_point(position ="jitter")
Setting position = "jitter" on the final two plots allows us to see that plot on the right is denser. This means the plot in question has an overplotting problem.
Question 2: What, if anything, is the difference between the two plots? Why?
ggplot(mpg, aes(x = displ, y = hwy)) +geom_point()ggplot(mpg, aes(x = displ, y = hwy)) +geom_point(position ="identity")
Question 3: What parameters to geom_jitter() control the amount of jittering?
Question 3: What does the following plot tell you about the relationship between city and highway mpg? Why is coord_fixed() important? What does geom_abline() do?