Visualize part review edits (#1348)
* EDA review edits * Import review edits (move text up) * Visualize review edits (move text up) * Layers review edits * Visualize review edits * (Partial) communication review edits -- more to come * Add note on ordered + remove unclear ex * Reduce annotation plots + review edits * Update EDA.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Update EDA.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Update communication.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Update communication.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Update layers.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Update data-import.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Update data-import.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Update layers.qmd Co-authored-by: Hadley Wickham <h.wickham@gmail.com> * Clarify what's hard to see * Fix typo * Add ggforce::geom_mark_hull * Take legend back * Clarify inherit * Take back reordering --------- Co-authored-by: Hadley Wickham <h.wickham@gmail.com>
This commit is contained in:
parent
a793af0aac
commit
64841ccd3e
124
EDA.qmd
124
EDA.qmd
|
@ -25,7 +25,7 @@ During the initial phases of EDA you should feel free to investigate every idea
|
|||
Some of these ideas will pan out, and some will be dead ends.
|
||||
As your exploration continues, you will home in on a few particularly productive insights that you'll eventually write up and communicate to others.
|
||||
|
||||
EDA is an important part of any data analysis, even if the questions are handed to you on a platter, because you always need to investigate the quality of your data.
|
||||
EDA is an important part of any data analysis, even if the primary research questions are handed to you on a platter, because you always need to investigate the quality of your data.
|
||||
Data cleaning is just one application of EDA: you ask questions about whether your data meets your expectations or not.
|
||||
To do data cleaning, you'll need to deploy all the tools of EDA: visualization, transformation, and modelling.
|
||||
|
||||
|
@ -66,22 +66,6 @@ You can loosely word these questions as:
|
|||
|
||||
The rest of this chapter will look at these two questions.
|
||||
We'll explain what variation and covariation are, and we'll show you several ways to answer each question.
|
||||
To make the discussion easier, let's define some terms:
|
||||
|
||||
- A **variable** is a quantity, quality, or property that you can measure.
|
||||
|
||||
- A **value** is the state of a variable when you measure it.
|
||||
The value of a variable may change from measurement to measurement.
|
||||
|
||||
- An **observation** is a set of measurements made under similar conditions (you usually make all of the measurements in an observation at the same time and on the same object).
|
||||
An observation will contain several values, each associated with a different variable.
|
||||
We'll sometimes refer to an observation as a data point.
|
||||
|
||||
- **Tabular data** is a set of values, each associated with a variable and an observation.
|
||||
Tabular data is *tidy* if each value is placed in its own "cell", each variable in its own column, and each observation in its own row.
|
||||
|
||||
So far, all of the data that you've seen has been tidy.
|
||||
In real-life, most data isn't tidy, so we'll come back to these ideas again in @sec-rectangling.
|
||||
|
||||
## Variation
|
||||
|
||||
|
@ -130,11 +114,7 @@ To turn this information into useful questions, look for anything unexpected:
|
|||
- Can you see any unusual patterns?
|
||||
What might explain them?
|
||||
|
||||
As an example, the histogram below suggests several interesting questions:
|
||||
|
||||
- Why are there more diamonds at whole carats and common fractions of carats?
|
||||
|
||||
- Why are there more diamonds slightly to the right of each peak than there are slightly to the left of each peak?
|
||||
Let's take a look at the distribution of `carat` for smaller diamonds.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
|
@ -151,10 +131,16 @@ ggplot(smaller, aes(x = carat)) +
|
|||
geom_histogram(binwidth = 0.01)
|
||||
```
|
||||
|
||||
Clusters of similar values suggest that subgroups exist in your data.
|
||||
This histogram suggests several interesting questions:
|
||||
|
||||
- Why are there more diamonds at whole carats and common fractions of carats?
|
||||
|
||||
- Why are there more diamonds slightly to the right of each peak than there are slightly to the left of each peak?
|
||||
|
||||
Visualizations can also reveal clusters, which suggest that subgroups exist in your data.
|
||||
To understand the subgroups, ask:
|
||||
|
||||
- How are the observations within each cluster similar to each other?
|
||||
- How are the observations within each subgroup similar to each other?
|
||||
|
||||
- How are the observations in separate clusters different from each other?
|
||||
|
||||
|
@ -162,35 +148,23 @@ To understand the subgroups, ask:
|
|||
|
||||
- Why might the appearance of clusters be misleading?
|
||||
|
||||
The histogram below shows the length (in minutes) of 272 eruptions of the Old Faithful Geyser in Yellowstone National Park.
|
||||
Eruption times appear to be clustered into two groups: there are short eruptions (of around 2 minutes) and long eruptions (4-5 minutes), but little in between.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| A histogram of eruption times. The x-axis ranges from roughly 1.5 to 5,
|
||||
#| and the y-axis ranges from 0 to roughly 40. The distribution is bimodal
|
||||
#| with peaks around 1.75 and 4.5.
|
||||
|
||||
ggplot(faithful, aes(x = eruptions)) +
|
||||
geom_histogram(binwidth = 0.25)
|
||||
```
|
||||
|
||||
Many of the questions above will prompt you to explore a relationship *between* variables, for example, to see if the values of one variable can explain the behavior of another variable.
|
||||
Some of these questions can be answered with the data while some will require domain expertise about the data.
|
||||
Many of them will prompt you to explore a relationship *between* variables, for example, to see if the values of one variable can explain the behavior of another variable.
|
||||
We'll get to that shortly.
|
||||
|
||||
### Unusual values
|
||||
|
||||
Outliers are observations that are unusual; data points that don't seem to fit the pattern.
|
||||
Sometimes outliers are data entry errors; other times outliers suggest important new discoveries.
|
||||
Sometimes outliers are data entry errors, sometimes they are simply values at the extremes that happened to be observed in this data collection, and other times they suggest important new discoveries.
|
||||
When you have a lot of data, outliers are sometimes difficult to see in a histogram.
|
||||
For example, take the distribution of the `y` variable from the diamonds dataset.
|
||||
The only evidence of outliers is the unusually wide limits on the x-axis.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| A histogram of lengths of diamonds. The x-axis ranges from 0 to 60 and the
|
||||
#| y-axis ranges from 0 to 12000. There is a peak around 5, and the data
|
||||
#| appear to be completely clustered around the peak.
|
||||
#| A histogram of lengths of diamonds. The x-axis ranges from 0 to 60 and
|
||||
#| the y-axis ranges from 0 to 12000. There is a peak around 5, and the
|
||||
#| data appear to be completely clustered around the peak.
|
||||
|
||||
ggplot(diamonds, aes(x = y)) +
|
||||
geom_histogram(binwidth = 0.5)
|
||||
|
@ -240,6 +214,8 @@ options(old)
|
|||
|
||||
The `y` variable measures one of the three dimensions of these diamonds, in mm.
|
||||
We know that diamonds can't have a width of 0mm, so these values must be incorrect.
|
||||
By doing EDA, we have discovered missing data that was coded as 0, which we never would have found by simply searching for `NA`s.
|
||||
Going forward we might choose to re-code these values as `NA`s in order to prevent misleading calculations.
|
||||
We might also suspect that measurements of 32mm and 59mm are implausible: those diamonds are over an inch long, but don't cost hundreds of thousands of dollars!
|
||||
|
||||
It's good practice to repeat your analysis with and without the outliers.
|
||||
|
@ -278,7 +254,7 @@ If you've encountered unusual values in your dataset, and simply want to move on
|
|||
filter(between(y, 3, 20))
|
||||
```
|
||||
|
||||
We don't recommend this option because just because one measurement is invalid, doesn't mean all the measurements are.
|
||||
We don't recommend this option because one invalid value doesn't imply that all the other values for that observation are also invalid.
|
||||
Additionally, if you have low quality data, by the time that you've applied this approach to every variable you might find that you don't have any data left!
|
||||
|
||||
2. Instead, we recommend replacing the unusual values with missing values.
|
||||
|
@ -290,13 +266,6 @@ If you've encountered unusual values in your dataset, and simply want to move on
|
|||
mutate(y = if_else(y < 3 | y > 20, NA, y))
|
||||
```
|
||||
|
||||
`if_else()` has three arguments.
|
||||
The first argument `test` should be a logical vector.
|
||||
The result will contain the value of the second argument, `yes`, when `test` is `TRUE`, and the value of the third argument, `no`, when it is false.
|
||||
Alternatively to `if_else()`, use `case_when()`.
|
||||
`case_when()` is particularly useful inside mutate when you want to create a new variable that relies on a complex combination of existing variables or would otherwise require multiple `if_else()` statements nested inside one another.
|
||||
You will learn more about logical vectors in @sec-logicals.
|
||||
|
||||
It's not obvious where you should plot missing values, so ggplot2 doesn't include them in the plot, but it does warn that they've been removed:
|
||||
|
||||
```{r}
|
||||
|
@ -323,7 +292,7 @@ ggplot(diamonds2, aes(x = x, y = y)) +
|
|||
Other times you want to understand what makes observations with missing values different to observations with recorded values.
|
||||
For example, in `nycflights13::flights`[^eda-1], missing values in the `dep_time` variable indicate that the flight was cancelled.
|
||||
So you might want to compare the scheduled departure times for cancelled and non-cancelled times.
|
||||
You can do this by making a new variable with `is.na()`.
|
||||
You can do this by making a new variable, using `is.na()` to check if `dep_time` is missing.
|
||||
|
||||
[^eda-1]: Remember that when we need to be explicit about where a function (or dataset) comes from, we'll use the special form `package::function()` or `package::dataset`.
|
||||
|
||||
|
@ -356,6 +325,10 @@ In the next section we'll explore some techniques for improving this comparison.
|
|||
|
||||
2. What does `na.rm = TRUE` do in `mean()` and `sum()`?
|
||||
|
||||
3. Recreate the frequency plot of `scheduled_dep_time` colored by whether the flight was cancelled or not.
|
||||
Also facet by the `cancelled` variable.
|
||||
Experiment with different values of the `scales` variable in the faceting function to mitigate the effect of more non-cancelled flights than cancelled flights.
|
||||
|
||||
## Covariation
|
||||
|
||||
If variation describes the behavior *within* a variable, covariation describes the behavior *between* variables.
|
||||
|
@ -379,17 +352,10 @@ ggplot(diamonds, aes(x = price)) +
|
|||
geom_freqpoly(aes(color = cut), binwidth = 500, linewidth = 0.75)
|
||||
```
|
||||
|
||||
The default appearance of `geom_freqpoly()` is not that useful for that sort of comparison because the height is given by the count and the overall counts of `cut` in differ so much, making it hard to see the differences in the shapes of their distributions:
|
||||
Note that ggplot2 uses an ordered color scale for `cut` because it's defined as an ordered factor variable in the data.
|
||||
You'll learn more about these in @sec-ordered-factors.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Bar chart of cuts of diamonds showing large variability between the
|
||||
#| frequencies of various cuts. Fair diamonds have the lowest frequency,
|
||||
#| then Good, then Very Good, then Premium, and then Ideal.
|
||||
|
||||
ggplot(diamonds, aes(x = cut)) +
|
||||
geom_bar()
|
||||
```
|
||||
The default appearance of `geom_freqpoly()` is not that useful here because the height, determined by the overall count, differs so much across `cut`s, making it hard to see the differences in the shapes of their distributions.
|
||||
|
||||
To make the comparison easier we need to swap what is displayed on the y-axis.
|
||||
Instead of displaying count, we'll display the **density**, which is the count standardized so that the area under each frequency polygon is one.
|
||||
|
@ -477,7 +443,7 @@ ggplot(mpg, aes(x = hwy, y = fct_reorder(class, hwy, median))) +
|
|||
|
||||
1. Use what you've learned to improve the visualization of the departure times of cancelled vs. non-cancelled flights.
|
||||
|
||||
2. What variable in the diamonds dataset is most important for predicting the price of a diamond?
|
||||
2. Based on EDA, what variable in the diamonds dataset appears to be most important for predicting the price of a diamond?
|
||||
How is that variable correlated with cut?
|
||||
Why does the combination of those two relationships lead to lower quality diamonds being more expensive?
|
||||
|
||||
|
@ -490,11 +456,11 @@ ggplot(mpg, aes(x = hwy, y = fct_reorder(class, hwy, median))) +
|
|||
What do you learn?
|
||||
How do you interpret the plots?
|
||||
|
||||
5. Create a visualization of diamond prices vs. a categorical variable from the `diamonds` dataset using `geom_violin()`, then a faceted `geom_histogram()`, and then a colored `geom_freqpoly()`.
|
||||
Compare and contrast the three plots.
|
||||
5. Create a visualization of diamond prices vs. a categorical variable from the `diamonds` dataset using `geom_violin()`, then a faceted `geom_histogram()`, then a colored `geom_freqpoly()`, and then a colored `geom_density()`.
|
||||
Compare and contrast the four plots.
|
||||
What are the pros and cons of each method of visualizing the distribution of a numerical variable based on the levels of a categorical variable?
|
||||
|
||||
6. If you have a small dataset, it's sometimes useful to use `geom_jitter()` to see the relationship between a continuous and categorical variable.
|
||||
6. If you have a small dataset, it's sometimes useful to use `geom_jitter()` to avoid overplotting to more easily see the relationship between a continuous and categorical variable.
|
||||
The ggbeeswarm package provides a number of methods similar to `geom_jitter()`.
|
||||
List them and briefly describe what each one does.
|
||||
|
||||
|
@ -548,20 +514,19 @@ For larger plots, you might want to try the heatmaply package, which creates int
|
|||
|
||||
1. How could you rescale the count dataset above to more clearly show the distribution of cut within color, or color within cut?
|
||||
|
||||
2. How does the segmented bar chart change if color is mapped to the `x` aesthetic and `cut` is mapped to the `fill` aesthetic?
|
||||
2. What different data insights do you get with a segmented bar chart if color is mapped to the `x` aesthetic and `cut` is mapped to the `fill` aesthetic?
|
||||
Calculate the counts that fall into each of the segments.
|
||||
|
||||
3. Use `geom_tile()` together with dplyr to explore how average flight departure delays vary by destination and month of year.
|
||||
What makes the plot difficult to read?
|
||||
How could you improve it?
|
||||
|
||||
4. Why is it slightly better to use `aes(x = color, y = cut)` rather than `aes(x = cut, y = color)` in the example above?
|
||||
|
||||
### Two numerical variables
|
||||
|
||||
You've already seen one great way to visualize the covariation between two numerical variables: draw a scatterplot with `geom_point()`.
|
||||
You can see covariation as a pattern in the points.
|
||||
For example, you can see an exponential relationship between the carat size and price of a diamond:
|
||||
For example, you can see a positive relationship between the carat size and price of a diamond: diamonds with more carats have a higher price.
|
||||
The relationship is exponential.
|
||||
|
||||
```{r}
|
||||
#| dev: "png"
|
||||
|
@ -575,7 +540,7 @@ ggplot(smaller, aes(x = carat, y = price)) +
|
|||
|
||||
(In this section we'll use the `smaller` dataset to stay focused on the bulk of the diamonds that are smaller than 3 carats)
|
||||
|
||||
Scatterplots become less useful as the size of your dataset grows, because points begin to overplot, and pile up into areas of uniform black (as above).
|
||||
Scatterplots become less useful as the size of your dataset grows, because points begin to overplot, and pile up into areas of uniform black, making it hard to judge differences in the density of the data across the 2-dimensional space as well as making it hard to spot the trend.
|
||||
You've already seen one way to fix the problem: using the `alpha` aesthetic to add transparency.
|
||||
|
||||
```{r}
|
||||
|
@ -602,7 +567,6 @@ You will need to install the hexbin package to use `geom_hex()`.
|
|||
|
||||
```{r}
|
||||
#| layout-ncol: 2
|
||||
#| eval: false
|
||||
#| fig-alt: >
|
||||
#| Plot 1: A binned density plot of price vs. carat. Plot 2: A hexagonal bin
|
||||
#| plot of price vs. carat. Both plots show that the highest density of
|
||||
|
@ -675,7 +639,6 @@ One way to show that is to make the width of the boxplot proportional to the num
|
|||
|
||||
## Patterns and models
|
||||
|
||||
Patterns in your data provide clues about relationships.
|
||||
If a systematic relationship exists between two variables it will appear as a pattern in the data.
|
||||
If you spot a pattern, ask yourself:
|
||||
|
||||
|
@ -689,22 +652,7 @@ If you spot a pattern, ask yourself:
|
|||
|
||||
- Does the relationship change if you look at individual subgroups of the data?
|
||||
|
||||
A scatterplot of Old Faithful eruption lengths versus the wait time between eruptions shows a pattern: longer wait times are associated with longer eruptions.
|
||||
The scatterplot also displays the two clusters that we noticed above.
|
||||
|
||||
```{r}
|
||||
#| fig-height: 2
|
||||
#| fig-alt: >
|
||||
#| A scatterplot of eruption time vs. waiting time to next eruption of the
|
||||
#| Old Faithful geyser. There are two clusters of points: one with low
|
||||
#| eruption times and short waiting times and one with long eruption times and
|
||||
#| long waiting times.
|
||||
|
||||
ggplot(faithful, aes(x = eruptions, y = waiting)) +
|
||||
geom_point()
|
||||
```
|
||||
|
||||
Patterns provide one of the most useful tools for data scientists because they reveal covariation.
|
||||
Patterns in your data provide clues about relationships, i.e., they reveal covariation.
|
||||
If you think of variation as a phenomenon that creates uncertainty, covariation is a phenomenon that reduces it.
|
||||
If two variables covary, you can use the values of one variable to make better predictions about the values of the second.
|
||||
If the covariation is due to a causal relationship (a special case), then you can use the value of one variable to control the value of the second.
|
||||
|
|
|
@ -44,55 +44,6 @@ library(patchwork)
|
|||
|
||||
The easiest place to start when turning an exploratory graphic into an expository graphic is with good labels.
|
||||
You add labels with the `labs()` function.
|
||||
This example adds a plot title:
|
||||
|
||||
```{r}
|
||||
#| message: false
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars, where
|
||||
#| points are colored according to the car class. A smooth curve following
|
||||
#| the trajectory of the relationship between highway fuel efficiency versus
|
||||
#| engine size of cars is overlaid. The plot is titled "Fuel efficiency
|
||||
#| generally decreases with engine size".
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point(aes(color = class)) +
|
||||
geom_smooth(se = FALSE) +
|
||||
labs(title = "Fuel efficiency generally decreases with engine size")
|
||||
```
|
||||
|
||||
The purpose of a plot title is to summarize the main finding.
|
||||
Avoid titles that just describe what the plot is, e.g. "A scatterplot of engine displacement vs. fuel economy".
|
||||
|
||||
If you need to add more text, there are two other useful labels:
|
||||
|
||||
- `subtitle` adds additional detail in a smaller font beneath the title.
|
||||
|
||||
- `caption` adds text at the bottom right of the plot, often used to describe the source of the data.
|
||||
|
||||
```{r}
|
||||
#| message: false
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars, where
|
||||
#| points are colored according to the car class. A smooth curve following
|
||||
#| the trajectory of the relationship between highway fuel efficiency versus
|
||||
#| engine size of cars is overlaid. The plot is titled "Fuel efficiency
|
||||
#| generally decreases with engine size". The subtitle is "Two seaters
|
||||
#| (sports cars) are an exception because of their light weight" and the
|
||||
#| caption is "Data from fueleconomy.gov".
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point(aes(color = class)) +
|
||||
geom_smooth(se = FALSE) +
|
||||
labs(
|
||||
title = "Fuel efficiency generally decreases with engine size",
|
||||
subtitle = "Two seaters (sports cars) are an exception because of their light weight",
|
||||
caption = "Data from fueleconomy.gov"
|
||||
)
|
||||
```
|
||||
|
||||
You can also use `labs()` to replace the axis and legend titles.
|
||||
It's usually a good idea to replace short variable names with more detailed descriptions, and to include the units.
|
||||
|
||||
```{r}
|
||||
#| message: false
|
||||
|
@ -102,7 +53,10 @@ It's usually a good idea to replace short variable names with more detailed desc
|
|||
#| the trajectory of the relationship between highway fuel efficiency versus
|
||||
#| engine size of cars is overlaid. The x-axis is labelled "Engine
|
||||
#| displacement (L)" and the y-axis is labelled "Highway fuel economy (mpg)".
|
||||
#| The legend is labelled "Car type".
|
||||
#| The legend is labelled "Car type". The plot is titled "Fuel efficiency
|
||||
#| generally decreases with engine size". The subtitle is "Two seaters
|
||||
#| (sports cars) are an exception because of their light weight" and the
|
||||
#| caption is "Data from fueleconomy.gov".
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point(aes(color = class)) +
|
||||
|
@ -110,10 +64,20 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
labs(
|
||||
x = "Engine displacement (L)",
|
||||
y = "Highway fuel economy (mpg)",
|
||||
color = "Car type"
|
||||
color = "Car type",
|
||||
title = "Fuel efficiency generally decreases with engine size",
|
||||
subtitle = "Two seaters (sports cars) are an exception because of their light weight",
|
||||
caption = "Data from fueleconomy.gov"
|
||||
)
|
||||
```
|
||||
|
||||
The purpose of a plot title is to summarize the main finding.
|
||||
Avoid titles that just describe what the plot is, e.g. "A scatterplot of engine displacement vs. fuel economy".
|
||||
|
||||
If you need to add more text, there are two other useful labels: `subtitle` adds additional detail in a smaller font beneath the title and `caption` adds text at the bottom right of the plot, often used to describe the source of the data.
|
||||
You can also use `labs()` to replace the axis and legend titles.
|
||||
It's usually a good idea to replace short variable names with more detailed descriptions, and to include the units.
|
||||
|
||||
It's possible to use mathematical equations instead of text strings.
|
||||
Just switch `""` out for `quote()` and read about the available options in `?plotmath`:
|
||||
|
||||
|
@ -123,19 +87,18 @@ Just switch `""` out for `quote()` and read about the available options in `?plo
|
|||
#| fig-width: 3
|
||||
#| fig-alt: >
|
||||
#| Scatterplot with math text on the x and y axis labels. X-axis label
|
||||
#| says sum of x_i squared, for i from 1 to n. Y-axis label says alpha +
|
||||
#| beta + delta over theta.
|
||||
#| says x_i, y-axis label says sum of x_i squared, for i from 1 to n.
|
||||
|
||||
df <- tibble(
|
||||
x = 1:10,
|
||||
y = x ^ 2
|
||||
y = cumsum(x^2)
|
||||
)
|
||||
|
||||
ggplot(df, aes(x, y)) +
|
||||
geom_point() +
|
||||
labs(
|
||||
x = quote(sum(x[i] ^ 2, i == 1, n)),
|
||||
y = quote(alpha + beta + frac(delta, theta))
|
||||
x = quote(x[i]),
|
||||
y = quote(sum(x[i] ^ 2, i == 1, n))
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -195,7 +158,7 @@ label_info
|
|||
Then, we use this new data frame to directly label the three groups to replace the legend with labels placed directly on the plot.
|
||||
Using the `fontface` and `size` arguments we can customize the look of the text labels.
|
||||
They're larger than the rest of the text on the plot and bolded.
|
||||
(`theme(legend.position = "none"`) turns the legend off --- we'll talk about it more shortly.)
|
||||
(`theme(legend.position = "none"`) turns all the legends off --- we'll talk about it more shortly.)
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
|
@ -214,32 +177,10 @@ ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
|
|||
theme(legend.position = "none")
|
||||
```
|
||||
|
||||
Note the use of `hjust` and `vjust` to control the alignment of the label.
|
||||
Note the use of `hjust` (horizontal justification) and `vjust` (vertical justification) to control the alignment of the label.
|
||||
|
||||
However the annotated plot we made above is hard to read because the labels overlap with each other, and with the points.
|
||||
We can make things a little better by switching to `geom_label()` which draws a rectangle behind the text.
|
||||
We also use the `nudge_y` parameter to move the labels slightly above the corresponding points:
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars, where
|
||||
#| points are colored according to the car class. Some points are labelled
|
||||
#| with the car's name. The labels are box with white, transparent background.
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
|
||||
geom_point(alpha = 0.3) +
|
||||
geom_smooth(se = FALSE) +
|
||||
geom_label(
|
||||
data = label_info,
|
||||
aes(x = displ, y = hwy, label = drive_type),
|
||||
fontface = "bold", size = 5, hjust = "right", alpha = 0.5, nudge_y = 2,
|
||||
) +
|
||||
theme(legend.position = "none")
|
||||
```
|
||||
|
||||
That helps a bit, but two of the labels still overlap with each other.
|
||||
This is difficult to fix by applying the same transformation for every label.
|
||||
Instead, we can use the `geom_label_repel()` function from the ggrepel package.
|
||||
We can use the `geom_label_repel()` function from the ggrepel package to address both of these issues.
|
||||
This useful package will automatically adjust labels so that they don't overlap:
|
||||
|
||||
```{r}
|
||||
|
@ -280,69 +221,48 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
geom_point(data = potential_outliers, color = "red", size = 3, shape = "circle open")
|
||||
```
|
||||
|
||||
Alternatively, you might just want to add a single label to the plot, but you'll still need to create a data frame.
|
||||
Often, you want the label in the corner of the plot, so it's convenient to create a new data frame using `summarize()` to compute the maximum values of x and y.
|
||||
Remember, in addition to `geom_text()` and `geom_label()`, you have many other geoms in ggplot2 available to help annotate your plot.
|
||||
A couple ideas:
|
||||
|
||||
- Use `geom_hline()` and `geom_vline()` to add reference lines.
|
||||
We often make them thick (`linewidth = 2`) and white (`color = white`), and draw them underneath the primary data layer.
|
||||
That makes them easy to see, without drawing attention away from the data.
|
||||
|
||||
- Use `geom_rect()` to draw a rectangle around points of interest.
|
||||
The boundaries of the rectangle are defined by aesthetics `xmin`, `xmax`, `ymin`, `ymax`.
|
||||
Alternatively, look into the [ggforce package](https://ggforce.data-imaginist.com/index.html), specifically [`geom_mark_hull()`](https://ggforce.data-imaginist.com/reference/geom_mark_hull.html), which allows you to annotate subsets of points with hulls.
|
||||
|
||||
- Use `geom_segment()` with the `arrow` argument to draw attention to a point with an arrow.
|
||||
Use aesthetics `x` and `y` to define the starting location, and `xend` and `yend` to define the end location.
|
||||
|
||||
Another handy function for adding annotations to plots is `annotate()`.
|
||||
As a rule of thumb, geoms are generally useful for highlighting a subset of the data while `annotate()` is useful for adding one or few annotation elements to a plot.
|
||||
|
||||
To demonstrate using `annotate()`, let's create some text to add to our plot.
|
||||
The text is a bit long, so we'll use `stringr::str_wrap()` to automatically add line breaks to it given the number of characters you want per line:
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars. On the
|
||||
#| top right corner, inset a bit from the corner, is an annotation that
|
||||
#| reads "increasing engine size is related to decreasing fuel economy".
|
||||
#| The text spans two lines.
|
||||
|
||||
label_info <- mpg |>
|
||||
summarize(
|
||||
displ = max(displ),
|
||||
hwy = max(hwy),
|
||||
label = "Increasing engine size is \nrelated to decreasing fuel economy."
|
||||
)
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point() +
|
||||
geom_text(
|
||||
data = label_info, aes(label = label),
|
||||
vjust = "top", hjust = "right"
|
||||
)
|
||||
trend_text <- "Larger engine sizes tend to\nhave lower fuel economy." |>
|
||||
str_wrap(width = 30)
|
||||
trend_text
|
||||
```
|
||||
|
||||
If you want to place the text exactly on the borders of the plot, you can use set `displ = Inf` and `hwy = Inf` in the tibble above, instead of the calculated maximum values.
|
||||
|
||||
We can alternatively add the annotation without creating a new data frame, using `annotate()`.
|
||||
This function adds a geom to a plot, but it doesn't map variables of a data frame to an aesthetic.
|
||||
The first argument of this function, `geom`, is the geometric object you want to use for annotation.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars. On the
|
||||
#| top right corner, flush against the corner, is an annotation that
|
||||
#| reads "increasing engine size is related to decreasing fuel economy".
|
||||
#| The text spans two lines.
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point() +
|
||||
annotate(
|
||||
geom = "text", x = Inf, y = Inf,
|
||||
label = "Increasing engine size is \nrelated to decreasing fuel economy.",
|
||||
vjust = "top", hjust = "right"
|
||||
)
|
||||
```
|
||||
|
||||
You can also use a label geom instead of a text geom like we did earlier, set aesthetics like color.
|
||||
Another approach for drawing attention to a plot feature is using a segment geom with the `arrow` argument.
|
||||
The `x` and `y` aesthetics define the starting location of the segment and `xend` and `yend` to define the end location.
|
||||
Then, we add two layers of annotation: one with a label geom and the other with a segment geom.
|
||||
The `x` and `y` aesthetics in both define where the annotation should start, and the `xend` and `yend` aesthetics in the segment annotation define the starting location of the end location of the segment.
|
||||
Note also that the segment is styled as an arrow.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars. A red
|
||||
#| arrow pointing down follows the trend of the points and the annotation
|
||||
#| placed next to the arrow reads "increasing engine size is related to
|
||||
#| decreasing fuel economy". The arrow and the annotation text is red.
|
||||
#| placed next to the arrow reads "Larger engine sizes tend to have lower
|
||||
#| fuel economy". The arrow and the annotation text is red.
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point() +
|
||||
annotate(
|
||||
geom = "label", x = 3.5, y = 38,
|
||||
label = "Increasing engine size is \nrelated to decreasing fuel economy.",
|
||||
label = trend_text,
|
||||
hjust = "left", color = "red"
|
||||
) +
|
||||
annotate(
|
||||
|
@ -352,28 +272,7 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
)
|
||||
```
|
||||
|
||||
In these examples, we manually broke the label up into lines using `"\n"`.
|
||||
Another approach is to use `stringr::str_wrap()` to automatically add line breaks, given the number of characters you want per line:
|
||||
|
||||
```{r}
|
||||
"Increasing engine size is related to decreasing fuel economy." |>
|
||||
str_wrap(width = 40) |>
|
||||
writeLines()
|
||||
```
|
||||
|
||||
Remember, in addition to `geom_text()`, you have many other geoms in ggplot2 available to help annotate your plot.
|
||||
A couple ideas:
|
||||
|
||||
- Use `geom_hline()` and `geom_vline()` to add reference lines.
|
||||
We often make them thick (`linewidth = 2`) and white (`color = white`), and draw them underneath the primary data layer.
|
||||
That makes them easy to see, without drawing attention away from the data.
|
||||
|
||||
- Use `geom_rect()` to draw a rectangle around points of interest.
|
||||
The boundaries of the rectangle are defined by aesthetics `xmin`, `xmax`, `ymin`, `ymax`.
|
||||
|
||||
- Use `geom_segment()` with the `arrow` argument to draw attention to a point with an arrow.
|
||||
Use aesthetics `x` and `y` to define the starting location, and `xend` and `yend` to define the end location.
|
||||
|
||||
Annotation is a powerful tool for communicating main takeaways and interesting features of your visualizations.
|
||||
The only limit is your imagination (and your patience with positioning annotations to be aesthetically pleasing)!
|
||||
|
||||
### Exercises
|
||||
|
@ -386,7 +285,7 @@ The only limit is your imagination (and your patience with positioning annotatio
|
|||
3. How do labels with `geom_text()` interact with faceting?
|
||||
How can you add a label to a single facet?
|
||||
How can you put a different label in each facet?
|
||||
(Hint: Think about the underlying data.)
|
||||
(Hint: Think about the dataset that is being passed to `geom_text()`.)
|
||||
|
||||
4. What arguments to `geom_label()` control the appearance of the background box?
|
||||
|
||||
|
@ -397,7 +296,7 @@ The only limit is your imagination (and your patience with positioning annotatio
|
|||
## Scales
|
||||
|
||||
The third way you can make your plot better for communication is to adjust the scales.
|
||||
Scales control the mapping from data values to things that you can perceive.
|
||||
Scales control how the aesthetic mappings manifest visually.
|
||||
|
||||
### Default scales
|
||||
|
||||
|
@ -426,6 +325,7 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
|
||||
Note the naming scheme for scales: `scale_` followed by the name of the aesthetic, then `_`, then the name of the scale.
|
||||
The default scales are named according to the type of variable they align with: continuous, discrete, datetime, or date.
|
||||
`scale_x_continuous()` puts the numeric values from `displ` on a continuous number line on the x-axis, `scale_color_discrete()` chooses colors for each of the `class` of car, etc.
|
||||
There are lots of non-default scales which you'll learn about below.
|
||||
|
||||
The default scales have been carefully chosen to do a good job for a wide range of inputs.
|
||||
|
@ -439,6 +339,9 @@ Nevertheless, you might want to override the defaults for two reasons:
|
|||
|
||||
### Axis ticks and legend keys
|
||||
|
||||
Collectively axes and legends are called **guides**.
|
||||
Axes are used for x and y aesthetics; legends are used for everything else.
|
||||
|
||||
There are two primary arguments that affect the appearance of the ticks on the axes and the keys on the legend: `breaks` and `labels`.
|
||||
Breaks controls the position of the ticks, or the values associated with the keys.
|
||||
Labels controls the text label associated with each tick/key.
|
||||
|
@ -446,26 +349,31 @@ The most common use of `breaks` is to override the default choice:
|
|||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars.
|
||||
#| The y-axis has breaks starting at 15 and ending at 40, increasing by 5.
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars,
|
||||
#| colored by drive. The y-axis has breaks starting at 15 and ending at 40,
|
||||
#| increasing by 5.
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
|
||||
geom_point() +
|
||||
scale_y_continuous(breaks = seq(15, 40, by = 5))
|
||||
scale_y_continuous(breaks = seq(15, 40, by = 5))
|
||||
```
|
||||
|
||||
You can use `labels` in the same way (a character vector the same length as `breaks`), but you can also set it to `NULL` to suppress the labels altogether.
|
||||
This is useful for maps, or for publishing plots where you can't share the absolute numbers.
|
||||
This can be useful for maps, or for publishing plots where you can't share the absolute numbers.
|
||||
You can also use `breaks` and `labels` to control the appearance of legends.
|
||||
For discrete scales for categorical variables, `labels` can be a named list of the existing levels names and the desired labels for them.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars.
|
||||
#| The x and y-axes do not have any labels at the axis ticks.
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars, colored
|
||||
#| by drive. The x and y-axes do not have any labels at the axis ticks.
|
||||
#| The legend has custom labels: 4-wheel, front, rear.
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
|
||||
geom_point() +
|
||||
scale_x_continuous(labels = NULL) +
|
||||
scale_y_continuous(labels = NULL)
|
||||
scale_y_continuous(labels = NULL) +
|
||||
scale_color_discrete(labels = c("4" = "4-wheel", "f" = "front", "r" = "rear"))
|
||||
```
|
||||
|
||||
The `labels` argument coupled with labelling functions from the scales package is also useful for formatting numbers as currency, percent, etc.
|
||||
|
@ -506,16 +414,9 @@ Another handy label function is `label_percent()`:
|
|||
|
||||
ggplot(diamonds, aes(x = cut, fill = clarity)) +
|
||||
geom_bar(position = "fill") +
|
||||
scale_y_continuous(
|
||||
name = "Percentage",
|
||||
labels = scales::label_percent()
|
||||
)
|
||||
scale_y_continuous(name = "Percentage", labels = scales::label_percent())
|
||||
```
|
||||
|
||||
You can also use `breaks` and `labels` to control the appearance of legends.
|
||||
Collectively axes and legends are called **guides**.
|
||||
Axes are used for x and y aesthetics; legends are used for everything else.
|
||||
|
||||
Another use of `breaks` is when you have relatively few data points and want to highlight exactly where the observations occur.
|
||||
For example, take this plot that shows when each US president started and ended their term.
|
||||
|
||||
|
@ -534,7 +435,8 @@ presidential |>
|
|||
scale_x_date(name = NULL, breaks = presidential$start, date_labels = "'%y")
|
||||
```
|
||||
|
||||
Note that the specification of breaks and labels for date and datetime scales is a little different:
|
||||
Note that for the `breaks` argument we pulled out the `start` variable as a vector with `presidential$start` because we can't do an aesthetic mapping for this argument.
|
||||
Also note that the specification of breaks and labels for date and datetime scales is a little different:
|
||||
|
||||
- `date_labels` takes a format specification, in the same form as `parse_datetime()`.
|
||||
|
||||
|
@ -567,6 +469,7 @@ base + theme(legend.position = "bottom")
|
|||
base + theme(legend.position = "right") # the default
|
||||
```
|
||||
|
||||
If your plot is short and wide, place the legend at the legend at the top or bottom, and if it's tall and narrow, place the legend at the left or right.
|
||||
You can also use `legend.position = "none"` to suppress the display of the legend altogether.
|
||||
|
||||
To control the display of individual legends, use `guides()` along with `guide_legend()` or `guide_colorbar()`.
|
||||
|
@ -578,16 +481,18 @@ This is particularly useful if you have used a low `alpha` to display many point
|
|||
#| Scatterplot of highway fuel efficiency versus engine size of cars
|
||||
#| where points are colored based on class of car. Overlaid on the plot is a
|
||||
#| smooth curve. The legend is in the bottom and classes are listed
|
||||
#| horizontally in a row. The points in the legend are larger than the points
|
||||
#| horizontally in two rows. The points in the legend are larger than the points
|
||||
#| in the plot.
|
||||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point(aes(color = class)) +
|
||||
geom_smooth(se = FALSE) +
|
||||
theme(legend.position = "bottom") +
|
||||
guides(color = guide_legend(nrow = 1, override.aes = list(size = 4)))
|
||||
guides(color = guide_legend(nrow = 2, override.aes = list(size = 4)))
|
||||
```
|
||||
|
||||
Note that the name of the argument in `guides()` matches the name of the aesthetic, just like in `labs()`.
|
||||
|
||||
### Replacing a scale
|
||||
|
||||
Instead of just tweaking the details a little, you can instead replace the scale altogether.
|
||||
|
@ -636,7 +541,9 @@ ggplot(diamonds, aes(x = carat, y = price)) +
|
|||
Another scale that is frequently customized is color.
|
||||
The default categorical scale picks colors that are evenly spaced around the color wheel.
|
||||
Useful alternatives are the ColorBrewer scales which have been hand tuned to work better for people with common types of color blindness.
|
||||
The two plots below look similar, but there is enough difference in the shades of red and green that the dots on the right can be distinguished even by people with red-green color blindness.
|
||||
The two plots below look similar, but there is enough difference in the shades of red and green that the dots on the right can be distinguished even by people with red-green color blindness.[^communication-1]
|
||||
|
||||
[^communication-1]: You can use a tool like [SimDaltonism](https://michelf.ca/projects/sim-daltonism/) to simulate color blindness to test these images.
|
||||
|
||||
```{r}
|
||||
#| fig-align: default
|
||||
|
@ -657,7 +564,7 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
scale_color_brewer(palette = "Set1")
|
||||
```
|
||||
|
||||
Don't forget simpler techniques.
|
||||
Don't forget simpler techniques for improving accessibility.
|
||||
If there are just a few colors, you can add a redundant shape mapping.
|
||||
This will also help ensure your plot is interpretable in black and white.
|
||||
|
||||
|
@ -680,20 +587,21 @@ This often arises if you've used `cut()` to make a continuous variable into a ca
|
|||
```{r}
|
||||
#| label: fig-brewer
|
||||
#| echo: false
|
||||
#| fig.cap: All colorBrewer scales.
|
||||
#| fig.asp: 2.5
|
||||
#| fig-cap: All colorBrewer scales.
|
||||
#| fig-asp: 2.5
|
||||
#| fig-alt: >
|
||||
#| All colorBrewer scales. One group goes from light to dark colors.
|
||||
#| Another group is a set of non ordinal colors. And the last group has
|
||||
#| diverging scales (from dark to light to dark again). Within each set
|
||||
#| there are a number of palettes.
|
||||
#| diverging scales (from dark to light to dark again). Within each set
|
||||
#| there are a number of palettes.
|
||||
|
||||
par(mar = c(0, 3, 0, 0))
|
||||
RColorBrewer::display.brewer.all()
|
||||
```
|
||||
|
||||
When you have a predefined mapping between values and colors, use `scale_color_manual()`.
|
||||
For example, if we map presidential party to color, we want to use the standard mapping of red for Republicans and blue for Democrats:
|
||||
For example, if we map presidential party to color, we want to use the standard mapping of red for Republicans and blue for Democrats.
|
||||
One approach for assigning these colors is using hex color codes:
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
|
@ -707,7 +615,7 @@ presidential |>
|
|||
ggplot(aes(x = start, y = id, color = party)) +
|
||||
geom_point() +
|
||||
geom_segment(aes(xend = end, yend = id)) +
|
||||
scale_color_manual(values = c(Republican = "red", Democratic = "blue"))
|
||||
scale_color_manual(values = c(Republican = "#E81B23", Democratic = "#00AEF3"))
|
||||
```
|
||||
|
||||
For continuous color, you can use the built-in `scale_color_gradient()` or `scale_fill_gradient()`.
|
||||
|
@ -753,7 +661,7 @@ ggplot(df, aes(x, y)) +
|
|||
labs(title = "Viridis, binned")
|
||||
```
|
||||
|
||||
Note that all color scales come in two variety: `scale_color_x()` and `scale_fill_x()` for the `color` and `fill` aesthetics respectively (the color scales are available in both UK and US spellings).
|
||||
Note that all color scales come in two varieties: `scale_color_*()` and `scale_fill_*()` for the `color` and `fill` aesthetics respectively (the color scales are available in both UK and US spellings).
|
||||
|
||||
### Zooming
|
||||
|
||||
|
@ -776,11 +684,11 @@ Compare the following two plots:
|
|||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point(aes(color = class)) +
|
||||
geom_smooth() +
|
||||
coord_cartesian(xlim = c(5, 7), ylim = c(10, 30))
|
||||
coord_cartesian(xlim = c(5, 6), ylim = c(10, 30))
|
||||
|
||||
# Right
|
||||
mpg |>
|
||||
filter(displ >= 5, displ <= 7, hwy >= 10, hwy <= 30) |>
|
||||
filter(displ >= 5, displ <= 6, hwy >= 10, hwy <= 30) |>
|
||||
ggplot(aes(x = displ, y = hwy)) +
|
||||
geom_point(aes(color = class)) +
|
||||
geom_smooth()
|
||||
|
@ -859,7 +767,7 @@ In this particular case, you could have simply used faceting, but this technique
|
|||
|
||||
3. Change the display of the presidential terms by:
|
||||
|
||||
a. Combining the two variants shown above.
|
||||
a. Combining the two variants that customize colors and x axis breaks.
|
||||
b. Improving the display of the y axis.
|
||||
c. Labelling each term with the name of the president.
|
||||
d. Adding informative plot labels.
|
||||
|
@ -891,10 +799,16 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
theme_bw()
|
||||
```
|
||||
|
||||
ggplot2 includes eight themes by default, as shown in @fig-themes.
|
||||
ggplot2 includes the eight themes shown in @fig-themes, with `theme_gray()` as the default.[^communication-2]
|
||||
Many more are included in add-on packages like **ggthemes** (<https://jrnold.github.io/ggthemes>), by Jeffrey Arnold.
|
||||
You can also create your own themes, if you are trying to match a particular corporate or journal style.
|
||||
|
||||
[^communication-2]: Many people wonder why the default theme has a gray background.
|
||||
This was a deliberate choice because it puts the data forward while still making the grid lines visible.
|
||||
The white grid lines are visible (which is important because they significantly aid position judgments), but they have little visual impact and we can easily tune them out.
|
||||
The gray background gives the plot a similar typographic color to the text, ensuring that the graphics fit in with the flow of a document without jumping out with a bright white background.
|
||||
Finally, the gray background creates a continuous field of color which ensures that the plot is perceived as a single visual entity.
|
||||
|
||||
```{r}
|
||||
#| label: fig-themes
|
||||
#| echo: false
|
||||
|
@ -914,23 +828,28 @@ You can also create your own themes, if you are trying to match a particular cor
|
|||
knitr::include_graphics("images/visualization-themes.png")
|
||||
```
|
||||
|
||||
Many people wonder why the default theme has a gray background.
|
||||
This was a deliberate choice because it puts the data forward while still making the grid lines visible.
|
||||
The white grid lines are visible (which is important because they significantly aid position judgments), but they have little visual impact and we can easily tune them out.
|
||||
The grey background gives the plot a similar typographic color to the text, ensuring that the graphics fit in with the flow of a document without jumping out with a bright white background.
|
||||
Finally, the grey background creates a continuous field of color which ensures that the plot is perceived as a single visual entity.
|
||||
|
||||
It's also possible to control individual components of each theme, like the size and color of the font used for the y axis.
|
||||
We've already seen that `legend.position` controls where the legend is drawn.
|
||||
There are many other aspects of the legend that can be customized with `theme()`.
|
||||
For example, in the plot below we change the direction of the legend as well as put a black border around it.
|
||||
Note that customization of the legend box and plot title elements of the theme are done with `element_*()` functions.
|
||||
These functions specify the styling of non-data components, e.g., the title text is bolded in the `face` argument of `element_text()` and the legend border color is defined in the `color` argument of `element_rect()`.
|
||||
The theme elements that control the position of the title and the caption are `plot.title.position` and `plot.caption.position`, respectively.
|
||||
In the following plot these are set to `"plot"` to indicate these elements are aligned to the entire plot area, instead of the plot panel (the default).
|
||||
A few other helpful `theme()` components are used to change the placement for format of the title and caption text.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
#| Scatterplot of highway fuel efficiency versus engine size of cars, colored
|
||||
#| by drive. The plot is titled 'Larger engine sizes tend to have lower fuel
|
||||
#| economy' with the caption pointing to the source of the data, fueleconomy.gov.
|
||||
#| The caption and title are left justified, the legend is inside of the plot
|
||||
#| with a black border.
|
||||
#|
|
||||
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
|
||||
geom_point() +
|
||||
labs(
|
||||
title = "Highway mileage decreases as engine size increases",
|
||||
title = "Larger engine sizes tend to have lower fuel economy",
|
||||
caption = "Source: https://fueleconomy.gov."
|
||||
) +
|
||||
theme(
|
||||
|
@ -981,7 +900,7 @@ p1 + p2
|
|||
It's important to note that in the above code chunk we did not use a new function from the patchwork package.
|
||||
Instead, the package added a new functionality to the `+` operator.
|
||||
|
||||
You can also create arbitrary plot layouts with patchwork.
|
||||
You can also create complex plot layouts with patchwork.
|
||||
In the following, `|` places the `p1` and `p3` next to each other and `/` moves `p2` to the next line.
|
||||
|
||||
```{r}
|
||||
|
|
|
@ -151,6 +151,12 @@ students <- students |>
|
|||
students
|
||||
```
|
||||
|
||||
A new function here is `if_else()`, which has three arguments.
|
||||
The first argument `test` should be a logical vector.
|
||||
The result will contain the value of the second argument, `yes`, when `test` is `TRUE`, and the value of the third argument, `no`, when it is `FALSE`.
|
||||
Here we're saying if `age` is the character string `"five"`, make it `"5"`, and if not leave it as `age`.
|
||||
You will learn more about `if_else()` and logical vectors in @sec-logicals.
|
||||
|
||||
### Other arguments
|
||||
|
||||
There are a couple of other important arguments that we need to mention, and they'll be easier to demonstrate if we first show you a handy trick: `read_csv()` can read text strings that you've created and formatted like a CSV file:
|
||||
|
|
|
@ -73,7 +73,7 @@ Let's create visualizations that we can use to answer these questions.
|
|||
|
||||
You can test your answer with the `penguins` **data frame** found in palmerpenguins (a.k.a. `palmerpenguins::penguins`).
|
||||
A data frame is a rectangular collection of variables (in the columns) and observations (in the rows).
|
||||
`penguins` contains `r nrow(penguins)` observations collected and made available by Dr. Kristen Gorman and the Palmer Station, Antarctica LTER[^data-visualize-2]. In this context, a variable refers to an attribute of all the penguins, and an observation refers to all the attributes of a single penguin.
|
||||
`penguins` contains `r nrow(penguins)` observations collected and made available by Dr. Kristen Gorman and the Palmer Station, Antarctica LTER[^data-visualize-2].
|
||||
|
||||
[^data-visualize-2]: Horst AM, Hill AP, Gorman KB (2020).
|
||||
palmerpenguins: Palmer Archipelago (Antarctica) penguin data.
|
||||
|
@ -81,6 +81,22 @@ A data frame is a rectangular collection of variables (in the columns) and obser
|
|||
<https://allisonhorst.github.io/palmerpenguins/>.
|
||||
doi: 10.5281/zenodo.3960218.
|
||||
|
||||
To make the discussion easier, let's define some terms:
|
||||
|
||||
- A **variable** is a quantity, quality, or property that you can measure.
|
||||
|
||||
- A **value** is the state of a variable when you measure it.
|
||||
The value of a variable may change from measurement to measurement.
|
||||
|
||||
- An **observation** is a set of measurements made under similar conditions (you usually make all of the measurements in an observation at the same time and on the same object).
|
||||
An observation will contain several values, each associated with a different variable.
|
||||
We'll sometimes refer to an observation as a data point.
|
||||
|
||||
- **Tabular data** is a set of values, each associated with a variable and an observation.
|
||||
Tabular data is *tidy* if each value is placed in its own "cell", each variable in its own column, and each observation in its own row.
|
||||
|
||||
In this context, a variable refers to an attribute of all the penguins, and an observation refers to all the attributes of a single penguin.
|
||||
|
||||
Type the name of the data frame in the console and R will print a preview of its contents.
|
||||
Note that it says `tibble` on top of this preview.
|
||||
In the tidyverse, we use special data frames called **tibbles** that you will learn more about soon.
|
||||
|
@ -775,7 +791,8 @@ You will learn about many other geoms for visualizing distributions of variables
|
|||
|
||||
### Exercises
|
||||
|
||||
1. Which variables in `mpg` are categorical?
|
||||
1. The `mpg` data frame that is bundled with the ggplot2 package contains `r nrow(mpg)` observations collected by the US Environmental Protection Agency on `r mpg |> distinct(model) |> nrow()` car models.
|
||||
Which variables in `mpg` are categorical?
|
||||
Which variables are numerical?
|
||||
(Hint: Type `?mpg` to read the documentation for the dataset.) How can you see this information when you run `mpg`?
|
||||
|
||||
|
|
|
@ -411,7 +411,7 @@ Read the documentation to learn about `fct_lump_min()` and `fct_lump_prop()` whi
|
|||
Why not 10?
|
||||
(Hint: type `?fct_lump`, and find the default for the argument `other_level` is "Other".)
|
||||
|
||||
## Ordered factors
|
||||
## Ordered factors {#sec-ordered-factors}
|
||||
|
||||
Before we go on, there's a special type of factor that needs to be mentioned briefly: ordered factors.
|
||||
Ordered factors, created with `ordered()`, imply a strict ordering and equal distance between levels: the first level is "less than" the second level by the same amount that the second level is "less than" the third level, and so on..
|
||||
|
|
103
layers.qmd
103
layers.qmd
|
@ -24,7 +24,7 @@ We will not cover every single function and option for each of these layers, but
|
|||
|
||||
### Prerequisites
|
||||
|
||||
This chapter focuses on ggplot2, one of the core packages in the tidyverse.
|
||||
This chapter focuses on ggplot2.
|
||||
To access the datasets, help pages, and functions used in this chapter, load the tidyverse by running this code:
|
||||
|
||||
```{r}
|
||||
|
@ -38,7 +38,7 @@ library(tidyverse)
|
|||
|
||||
> "The greatest value of a picture is when it forces us to notice what we never expected to see." --- John Tukey
|
||||
|
||||
The `mpg` data frame that is bundled with the ggplot2 package contains `r nrow(mpg)` observations collected by the US Environmental Protection Agency on `r mpg |> distinct(model) |> nrow()` car models.
|
||||
Remember that the `mpg` data frame bundled with the ggplot2 package contains `r nrow(mpg)` observations on `r mpg |> distinct(model) |> nrow()` car models.
|
||||
|
||||
```{r}
|
||||
mpg
|
||||
|
@ -56,8 +56,6 @@ Among the variables in `mpg` are:
|
|||
3. `class`: Type of car.
|
||||
A categorical variable.
|
||||
|
||||
You can learn about `mpg` on its help page by running `?mpg`.
|
||||
|
||||
Let's start by visualizing the relationship between `displ` and `hwy` for various `class`es 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`.
|
||||
|
||||
|
@ -123,14 +121,14 @@ Both of these produce warnings as well:
|
|||
|
||||
> Using alpha for a discrete variable is not advised.
|
||||
|
||||
Mapping a non-ordinal discrete (categorical) variable (`class`) to an ordered aesthetic (`size` or `alpha`) is generally not a good idea because it implies a ranking that does not in fact exist.
|
||||
Mapping an unordered discrete (categorical) variable (`class`) to an ordered aesthetic (`size` or `alpha`) is generally not a good idea because it implies a ranking that does not in fact exist.
|
||||
|
||||
Once you map an aesthetic, ggplot2 takes care of the rest.
|
||||
It selects a reasonable scale to use with the aesthetic, and it constructs a legend that explains the mapping between levels and values.
|
||||
For x and y aesthetics, ggplot2 does not create a legend, but it creates an axis line with tick marks and a label.
|
||||
The axis line acts as a legend; it explains the mapping between locations and values.
|
||||
The axis line provides the same information as a legend; it explains the mapping between locations and values.
|
||||
|
||||
You can also set the aesthetic properties of your geom manually.
|
||||
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:
|
||||
|
||||
```{r}
|
||||
|
@ -143,13 +141,11 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
```
|
||||
|
||||
Here, the color doesn't convey information about a variable, but only changes the appearance of the plot.
|
||||
You can set an aesthetic manually by name as an argument of your geom function.
|
||||
In other words, it goes *outside* of `aes()`.
|
||||
You'll need to pick a value that makes sense for that aesthetic:
|
||||
|
||||
- The name of a color as a character string.
|
||||
- The size of a point in mm.
|
||||
- The shape of a point as a number, as shown in @fig-shapes.
|
||||
- The name of a color as a character string, e.g., `color = "blue"`
|
||||
- The size of a point in mm, e.g., `size = 1`
|
||||
- The shape of a point as a number, e.g, `shape = 1`, as shown in @fig-shapes.
|
||||
|
||||
```{r}
|
||||
#| label: fig-shapes
|
||||
|
@ -163,7 +159,8 @@ You'll need to pick a value that makes sense for that aesthetic:
|
|||
#| difference comes from the interaction of the `color` and `fill`
|
||||
#| aesthetics. The hollow shapes (0--14) have a border determined by `color`;
|
||||
#| the solid shapes (15--20) are filled with `color`; the filled shapes
|
||||
#| (21--24) have a border of `color` and are filled with `fill`.
|
||||
#| (21--24) have a border of `color` and are filled with `fill`. Shapes are
|
||||
#| arranged to keep similar shapes next to each other.
|
||||
#| fig-alt: >
|
||||
#| Mapping between shapes and the numbers that represent them: 0 - square,
|
||||
#| 1 - circle, 2 - triangle point up, 3 - plus, 4 - cross, 5 - diamond,
|
||||
|
@ -265,7 +262,7 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
geom_smooth()
|
||||
```
|
||||
|
||||
Every geom function in ggplot2 takes a `mapping` argument.
|
||||
Every geom function in ggplot2 takes a `mapping` argument, either defined locally in the geom layer or globally in the `ggoplot()` layer.
|
||||
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.
|
||||
If you try, ggplot2 will silently ignore that aesthetic mapping.
|
||||
|
@ -295,7 +292,6 @@ Here, `4` stands for four-wheel drive, `f` for front-wheel drive, and `r` for re
|
|||
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`.
|
||||
|
||||
```{r}
|
||||
#| echo: false
|
||||
#| message: false
|
||||
#| fig-alt: >
|
||||
#| A plot of highway fuel efficiency versus engine size of cars. The data
|
||||
|
@ -327,7 +323,7 @@ It is convenient to rely on this feature because the `group` aesthetic by itself
|
|||
#| only has these two variables, the center plot has three separate smooth
|
||||
#| curves for each level of drive train, and the right plot not only has the
|
||||
#| same three separate smooth curves for each level of drive train but these
|
||||
#| curves are plotted in different colors, without a legend explaining which
|
||||
#| curves are plotted in different colors, with a legend explaining which
|
||||
#| color maps to which level. Confidence intervals around the smooth curves
|
||||
#| are also displayed.
|
||||
|
||||
|
@ -526,10 +522,9 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
facet_grid(drv ~ cyl)
|
||||
```
|
||||
|
||||
By default each of the facets share the same scale for x and y axes.
|
||||
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.
|
||||
Other options for this argument are `"free_x"` (different scales across rows) and `"free_y"` (different scales across columns).
|
||||
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.
|
||||
|
||||
```{r}
|
||||
#| fig-alt: >
|
||||
|
@ -542,7 +537,7 @@ Other options for this argument are `"free_x"` (different scales across rows) an
|
|||
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point() +
|
||||
facet_grid(drv ~ cyl, scales = "free")
|
||||
facet_grid(drv ~ cyl, scales = "free_y")
|
||||
```
|
||||
|
||||
### Exercises
|
||||
|
@ -601,12 +596,12 @@ ggplot(mpg, aes(x = displ, y = hwy)) +
|
|||
```{r}
|
||||
#| fig-show: hide
|
||||
|
||||
ggplot(mpg) +
|
||||
geom_point(aes(x = displ, y = hwy)) +
|
||||
ggplot(mpg, aes(x = displ)) +
|
||||
geom_histogram() +
|
||||
facet_grid(drv ~ .)
|
||||
|
||||
ggplot(mpg) +
|
||||
geom_point(aes(x = displ, y = hwy)) +
|
||||
ggplot(mpg, aes(x = displ)) +
|
||||
geom_histogram() +
|
||||
facet_grid(. ~ drv)
|
||||
```
|
||||
|
||||
|
@ -648,7 +643,7 @@ Other graphs, like bar charts, calculate new values to plot:
|
|||
|
||||
- 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 that summary as a specially formatted box.
|
||||
- Boxplots compute the five-number summary of the distribution and then display that summary as a specially formatted box.
|
||||
|
||||
The algorithm used to calculate new values for a graph is called a **stat**, short for statistical transformation.
|
||||
@fig-vis-stat-bar shows how this process works with `geom_bar()`.
|
||||
|
@ -691,16 +686,9 @@ However, there are three reasons why you might need to use a stat explicitly:
|
|||
#| Fair, 5000 Good, 12000 Very Good, 14000 Premium, and 22000 Ideal cut
|
||||
#| diamonds.
|
||||
|
||||
cut_frequencies <- tribble(
|
||||
~cut, ~freq,
|
||||
"Fair", 1610,
|
||||
"Good", 4906,
|
||||
"Very Good", 12082,
|
||||
"Premium", 13791,
|
||||
"Ideal", 21551
|
||||
)
|
||||
|
||||
ggplot(cut_frequencies, aes(x = cut, y = freq)) +
|
||||
diamonds |>
|
||||
count(cut) |>
|
||||
ggplot(aes(x = cut, y = n)) +
|
||||
geom_bar(stat = "identity")
|
||||
```
|
||||
|
||||
|
@ -717,7 +705,7 @@ However, there are three reasons why you might need to use a stat explicitly:
|
|||
geom_bar()
|
||||
```
|
||||
|
||||
To find the variables computed by the stat, look for the section titled "computed variables" in the help for `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()`.
|
||||
|
||||
3. 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:
|
||||
|
@ -751,8 +739,9 @@ Each stat is a function, so you can get help in the usual way, e.g. `?stat_bin`.
|
|||
How is it different from `geom_bar()`?
|
||||
|
||||
3. Most geoms and stats come in pairs that are almost always used in concert.
|
||||
Read through the documentation and make a list of all the pairs.
|
||||
Make a list of all the pairs.
|
||||
What do they have in common?
|
||||
(Hint: Read through the documentation.)
|
||||
|
||||
4. What variables does `stat_smooth()` compute?
|
||||
What arguments control its behavior?
|
||||
|
@ -813,18 +802,21 @@ If you don't want a stacked bar chart, you can use one of three other options: `
|
|||
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`.
|
||||
|
||||
```{r}
|
||||
#| layout-ncol: 2
|
||||
#| fig-width: 5.5
|
||||
#| layout-ncol: 3
|
||||
#| fig-width: 4
|
||||
#| fig-height: 2
|
||||
#| fig-alt: >
|
||||
#| Two segmented bar charts of cut of diamonds, where each bar is filled
|
||||
#| Three segmented bar charts of cut of diamonds, where each bar is filled
|
||||
#| with colors for the levels of clarity. Heights of the bars correspond
|
||||
#| to the number of diamonds in each cut category, and heights of the
|
||||
#| colored segments are proportional to the number of diamonds with a
|
||||
#| given clarity level within a given cut level. However the segments
|
||||
#| overlap. In the first plot the segments are filled with transparent
|
||||
#| colors, in the second plot the segments are only outlined with colors.
|
||||
#| overlap. In the first plot the segments are filled with opaque colors,
|
||||
#| in the second with transparent colors, and in the third only
|
||||
#| outlined with colors.
|
||||
|
||||
ggplot(diamonds, aes(x = cut, fill = clarity)) +
|
||||
geom_bar(position = "identity")
|
||||
ggplot(diamonds, aes(x = cut, fill = clarity)) +
|
||||
geom_bar(alpha = 1/5, position = "identity")
|
||||
ggplot(diamonds, aes(x = cut, color = clarity)) +
|
||||
|
@ -909,11 +901,23 @@ To learn more about a position adjustment, look up the help page associated with
|
|||
geom_point()
|
||||
```
|
||||
|
||||
2. What parameters to `geom_jitter()` control the amount of jittering?
|
||||
2. What, if anything, is the difference between the two plots?
|
||||
Why?
|
||||
|
||||
3. Compare and contrast `geom_jitter()` with `geom_count()`.
|
||||
```{r}
|
||||
#| fig-show: hide
|
||||
|
||||
4. What's the default position adjustment for `geom_boxplot()`?
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point()
|
||||
ggplot(mpg, aes(x = displ, y = hwy)) +
|
||||
geom_point(position = "identity")
|
||||
```
|
||||
|
||||
3. What parameters to `geom_jitter()` control the amount of jittering?
|
||||
|
||||
4. Compare and contrast `geom_jitter()` with `geom_count()`.
|
||||
|
||||
5. What's the default position adjustment for `geom_boxplot()`?
|
||||
Create a visualization of the `mpg` dataset that demonstrates it.
|
||||
|
||||
## Coordinate systems
|
||||
|
@ -922,7 +926,7 @@ 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 two other coordinate systems that are occasionally helpful.
|
||||
|
||||
- `coord_quickmap()` sets the aspect ratio correctly for maps.
|
||||
- `coord_quickmap()` sets the aspect ratio correctly for geographic maps.
|
||||
This is very important if you're plotting spatial data with ggplot2.
|
||||
We don't have the space to discuss maps in this book, but you can learn more in the [Maps chapter](https://ggplot2-book.org/maps.html) of *ggplot2: Elegant graphics for data analysis*.
|
||||
|
||||
|
@ -962,8 +966,7 @@ There are two other coordinate systems that are occasionally helpful.
|
|||
show.legend = FALSE,
|
||||
width = 1
|
||||
) +
|
||||
theme(aspect.ratio = 1) +
|
||||
labs(x = NULL, y = NULL)
|
||||
theme(aspect.ratio = 1)
|
||||
|
||||
bar + coord_flip()
|
||||
bar + coord_polar()
|
||||
|
@ -1005,7 +1008,7 @@ Our new template takes seven parameters, the bracketed words that appear in the
|
|||
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.
|
||||
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, a faceting scheme, and a theme.
|
||||
|
||||
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).
|
||||
Next, you could choose a geometric object to represent each observation in the transformed data.
|
||||
|
@ -1018,7 +1021,7 @@ You'd then select a coordinate system to place the geoms into, using the locatio
|
|||
#| fig-alt: >
|
||||
#| A figure demonstrating the steps for going from raw data to table of counts
|
||||
#| where each row represents one level of cut and a count column shows how many
|
||||
#| diamonds are in that cut level.
|
||||
#| diamonds are in that cuit level.
|
||||
|
||||
knitr::include_graphics("images/visualization-grammar.png")
|
||||
```
|
||||
|
|
|
@ -29,7 +29,7 @@ Each chapter addresses one to a few aspects of creating a data visualization.
|
|||
|
||||
- In @sec-exploratory-data-analysis, you'll combine visualization with your curiosity and skepticism to ask and answer interesting questions about data.
|
||||
|
||||
- Finally, in @sec-communication you will learn how to take your exploratory graphics and turn them into expository graphics, graphics that help the newcomer to your analysis understand what's going on as quickly and easily as possible.
|
||||
- Finally, in @sec-communication you will learn how to take your exploratory graphics, elevate them, and turn them into expository graphics, graphics that help the newcomer to your analysis understand what's going on as quickly and easily as possible.
|
||||
|
||||
These three chapters get you started in the world of visualization, but there is much more to learn.
|
||||
The absolute best place to learn more is the ggplot2 book: [*ggplot2: Elegant graphics for data analysis*](https://ggplot2-book.org/).
|
||||
|
|
Loading…
Reference in New Issue