Skip to main content

Section 3.4 ggplot2

Before we go into visualizing our data, we should probably see what data we will be working with! Similar to how R comes preinstalled with datasets, ggplot2 also comes with prepacked data that can be utilized.
kable(head(mpg), caption = "A base R dataset: Fuel economy data from 1999 to 2008 for 38 popular models of cars.")
Table: A base R dataset: Fuel economy data from 1999 to 2008 for 38 popular models of cars.

|manufacturer |model | displ| year| cyl|trans      |drv | cty| hwy|fl |class   |
|:------------|:-----|-----:|----:|---:|:----------|:---|---:|---:|:--|:-------|
|audi         |a4    |   1.8| 1999|   4|auto(l5)   |f   |  18|  29|p  |compact |
|audi         |a4    |   1.8| 1999|   4|manual(m5) |f   |  21|  29|p  |compact |
|audi         |a4    |   2.0| 2008|   4|manual(m6) |f   |  20|  31|p  |compact |
|audi         |a4    |   2.0| 2008|   4|auto(av)   |f   |  21|  30|p  |compact |
|audi         |a4    |   2.8| 1999|   6|auto(l5)   |f   |  16|  26|p  |compact |
|audi         |a4    |   2.8| 1999|   6|manual(m5) |f   |  18|  26|p  |compact |
kable(head(economics), caption = "A base R dataset: US Economic Time Series.")
Table: A base R dataset: US Economic Time Series.

|date       |   pce|    pop| psavert| uempmed| unemploy|
|:----------|-----:|------:|-------:|-------:|--------:|
|1967-07-01 | 506.7| 198712|    12.6|     4.5|     2944|
|1967-08-01 | 509.8| 198911|    12.6|     4.7|     2945|
|1967-09-01 | 515.6| 199113|    11.9|     4.6|     2958|
|1967-10-01 | 512.2| 199311|    12.9|     4.9|     3143|
|1967-11-01 | 517.4| 199498|    12.8|     4.7|     3066|
|1967-12-01 | 525.1| 199657|    11.8|     4.8|     3018|
kable(head(diamonds), caption = "A base R dataset: Prices of over 50,000 round cut diamonds.")
Table: A base R dataset: Prices of over 50,000 round cut diamonds.

| carat|cut       |color |clarity | depth| table| price|    x|    y|    z|
|-----:|:---------|:-----|:-------|-----:|-----:|-----:|----:|----:|----:|
|  0.23|Ideal     |E     |SI2     |  61.5|    55|   326| 3.95| 3.98| 2.43|
|  0.21|Premium   |E     |SI1     |  59.8|    61|   326| 3.89| 3.84| 2.31|
|  0.23|Good      |E     |VS1     |  56.9|    65|   327| 4.05| 4.07| 2.31|
|  0.29|Premium   |I     |VS2     |  62.4|    58|   334| 4.20| 4.23| 2.63|
|  0.31|Good      |J     |SI2     |  63.3|    58|   335| 4.34| 4.35| 2.75|
|  0.24|Very Good |J     |VVS2    |  62.8|    57|   336| 3.94| 3.96| 2.48|
kable(head(mtcars), caption = "A base R dataset: Motor Trend Car Road Tests.")
Table: A base R dataset: Motor Trend Car Road Tests.

|                  |  mpg| cyl| disp|  hp| drat|    wt|  qsec| vs| am| gear| carb|
|:-----------------|----:|---:|----:|---:|----:|-----:|-----:|--:|--:|----:|----:|
|Mazda RX4         | 21.0|   6|  160| 110| 3.90| 2.620| 16.46|  0|  1|    4|    4|
|Mazda RX4 Wag     | 21.0|   6|  160| 110| 3.90| 2.875| 17.02|  0|  1|    4|    4|
|Datsun 710        | 22.8|   4|  108|  93| 3.85| 2.320| 18.61|  1|  1|    4|    1|
|Hornet 4 Drive    | 21.4|   6|  258| 110| 3.08| 3.215| 19.44|  1|  0|    3|    1|
|Hornet Sportabout | 18.7|   8|  360| 175| 3.15| 3.440| 17.02|  0|  0|    3|    2|
|Valiant           | 18.1|   6|  225| 105| 2.76| 3.460| 20.22|  1|  0|    3|    1|
Additionally, ggplot2 is part of the tidyverse package. So, you can either load ggplot2 or tidyverse if you want to visualize using ggplot2. Because ggplot2 is part of the tidyverse, everything you learned in SectionΒ 2.1 about pipelines and data manipulation carries directly into visualizations.

Subsection 3.4.1 Basics

When using ggplot2, there are unlimited possibilities on what you can manipulate/influence. This may be daunting, but always remember that all plots work on the same framework.
No matter what ggplot you are making, no matter how many characteristics you influence, all ggplot2 needs are three things:
  1. The data: the data being used to make the plot
  2. The aesthetics: x/y/color/shape/etc. In ggplot2 aesthetics is shortened to aes.
  3. The geometry: plot type (e.g., scatterplot, boxplot, etc.)
With only those three things, you can make any type of visualization you want or need. From there, you can build as far as your mind can see. When you do want to add more levels to your plot, you do so by using the + sign.
Here is an example of a basic graph made with ggplot:
ggplot(data = mpg, aes(x = cty, y = hwy)) +
  geom_point()
A scatterplot of city miles per gallon (x-axis) versus highway miles per gallon (y-axis) for the mpg dataset, showing a positive relationship between the two variables.
Figure 3.4.1. An example of a visualization made using ggplot2.
This is our first ggplot (a scatterplot) we have created, so let’s break this down:
With only two lines of code, a scatterplot was created! Since the framework has been established, it is time to build some visualizations!

How to move on from here:.

In this chapter, there will be basic examples of each visualization type, and advanced examples of each visualization type. This is done to display the scope of what can be done with this package (ggplot2). It is encouraged to experiment with the code, change numbers, remove things, and compare and contrast the differences between the experimented visuals.

Subsection 3.4.2 Scatterplot - geom_point()

When data has two continuous (for example, numeric) variables, and you want to visualize their relationship, a scatterplot is a fantastic choice. This is the basis for relationship analysis that topics such as linear regression (more about this in SectionΒ 8.1) rely on.
The geometry that needs to be specified is geom_point().
ggplot(data = mpg, aes(x = cty, y = hwy)) +
  geom_point()
A scatterplot showing city MPG on the x-axis and highway MPG on the y-axis, with each point representing a vehicle in the mpg dataset.
Figure 3.4.2. A basic example of a scatterplot using ggplot2.
Typically it is best practice to have a β€œline of best fit” when creating scatterplots, similar to Anscombe’s visualization. To do that, you can utilize the geom_smooth() command.
# Line of Best Fit
ggplot(mpg, aes(x = cty, y = hwy)) +
  geom_point() +
  geom_smooth(method = "lm") # lm stands for "linear model"
A scatterplot of city MPG versus highway MPG with a blue linear regression line and shaded confidence interval added using geom_smooth.
Figure 3.4.3. An example of a scatterplot with a line of best fit.
Notice that to add geom_smooth, which is another layer of our visualization, we needed to add another + sign. It is used quite literally in ggplot2.
Let’s add some more information to our scatterplot.
ggplot(data = mpg, aes(x = cty, y = hwy, color = class, shape = drv)) +
  geom_point(alpha = 0.8, size = 2.5) + # opacity and size
  labs(
    title = "City vs Highway MPG",      # adds a title to the plot
    x = "City MPG",                     # adds a x-axis label to the plot
    y = "Highway MPG",                  # adds a y-axis label to the plot
    color = "Vehicle Class",            # adds a color label to the plot
    shape = "Drivetrain"                # adds a shape label to the plot
  ) + 
  facet_wrap(~ class) +                 # breaks the graph into individual graphs
  geom_smooth(method = "lm", se = TRUE) # adds a line of best fit
A faceted scatterplot of city vs highway MPG broken out by vehicle class. Points are colored by class and shaped by drivetrain, with individual linear trend lines per facet.
Figure 3.4.4. A scatterplot incorporating multiple aesthetics and faceting.

Subsection 3.4.3 Bar Chart (counts) and Column Chart (values)

Bar charts visualize counts of a discrete variable, while column charts visualize pre-summarized numeric values.

Subsubsection 3.4.3.1 Bar Chart - geom_bar()

For a bar chart, the geometry used is geom_bar().
# BASIC (counts by class) - geom_bar() counts rows automatically
ggplot(mpg, aes(x = class)) +
  geom_bar()
A bar chart showing counts of vehicles by class in the mpg dataset. The suv class has the most vehicles, followed by compact and subcompact.
Figure 3.4.5. An example of a basic bar chart.
Depending on how long the variable names are, it may be best to switch the x and y axis. They would still act the same, but they would just flip on the coordinate plane. The x variable would still be the x variable, and the y would still be the y variable, but flipped. To do this, you can utilize the coord_flip() command.
ggplot(mpg, aes(x = manufacturer)) +
  geom_bar(fill = "steelblue", color = "white") +
  coord_flip() +
  labs(title = "Counts by Manufacturer", x = "", y = "Count")
A horizontal bar chart showing vehicle counts by manufacturer, with bars colored steel blue and the axes flipped so manufacturer names appear on the y-axis for readability.
Figure 3.4.6. An example of a coordinate flipped bar chart.
Depending on the audience, a stacked bar chart may be the best way to visualize the data. To do that, you can add position = "stack" and ggplot does the rest.
# What if we want a stacked bar chart (default with fill)?
ggplot(mpg, aes(x = manufacturer, fill = drv)) +
  geom_bar(position = "stack", color = "white") +
  coord_flip() +
  labs(
    title = "Counts by Manufacturer and Drivetrain",
    x = "",
    y = "Count",
    fill = "Drivetrain"
  ) +
  theme_minimal()
A horizontal stacked bar chart showing vehicle counts by manufacturer, with each bar divided by drivetrain type (4-wheel, front-wheel, rear-wheel drive) shown in different colors.
Figure 3.4.7. An example of a stacked bar chart.
If you need to create a grouped bar chart instead, you can add position = "dodge" to create your visual.
#What if we want a grouped bar chart
ggplot(mpg, aes(x = manufacturer, fill = drv)) +
  geom_bar(position = "dodge", color = "white") +
  coord_flip() +
  labs(
    title = "Counts by Manufacturer and Drivetrain",
    x = "",
    y = "Count",
    fill = "Drivetrain"
  ) +
  theme_minimal()
A horizontal grouped bar chart where each manufacturer has separate side-by-side bars for each drivetrain type, making it easier to compare drivetrain counts within each manufacturer.
Figure 3.4.8. An example of a grouped bar chart.

Subsubsection 3.4.3.2 Column Chart - geom_col()

Column charts work best when you have pre-summarized values, and not raw values.
# USING PRE-SUMMARIZED VALUES - geom_col() requires explicit values
class_counts <- mpg |>
  count(class)  # counts rows by class

kable(class_counts, caption = "Pre-summarized values")
Table: Pre-summarized values

|class      |  n|
|:----------|--:|
|2seater    |  5|
|compact    | 47|
|midsize    | 41|
|minivan    | 11|
|pickup     | 33|
|subcompact | 35|
|suv        | 62|
Once the values have been summarized explicitly, you use geom_col() to create a column chart.
ggplot(class_counts, aes(x = class, y = n)) +
  geom_col()
A basic column chart showing the pre-summarized count of vehicles by class in the mpg dataset, with class on the x-axis and count on the y-axis.
Figure 3.4.9. An example of a basic column chart.
There are a few things you can do to a column chart to add some flavor. For example:
  • Use the reorder command to reorder the columns into ascending or descending order based on their n values.
  • Inside of geom_col change the width of the columns.
  • Depending on the bars, you can change the legend.position to a particular spot (or remove it entirely) from the visualization.
# PLUS AESTHETICS (polished)
ggplot(class_counts, aes(x = reorder(class, n), y = n, fill = class)) +
  geom_col(width = 0.7, color = "white") +   # width = bar thickness, color = border
  coord_flip() +                             # flip for readability
  labs(title = "Counts by Vehicle Class", x = "", y = "Count") +
  theme(legend.position = "none")
A horizontal column chart sorted by count, with each vehicle class shown in a different color and the legend removed since class names are on the y-axis.
Figure 3.4.10. An example of a column chart with polished aesthetics.
Legends in a graph and redundancy..
Sometimes having a legend in a graph can be redundant because of the x-axis containing the same information as a legend would. In this case, you can remove the legend not only to avoid the redundancy, but also save space.

Subsection 3.4.4 Histograms and Density Plots (distribution)

Histograms are perfect for when you are looking to display distribution.

Subsubsection 3.4.4.1 Histograms - geom_histogram()

For the geometry of a histogram, you use geom_histogram().
# BASIC
ggplot(mpg, aes(x = hwy)) +
  geom_histogram(binwidth = 3)
A basic histogram of highway miles per gallon from the mpg dataset, with bins of width 3, showing the distribution is roughly bell-shaped centered around 25 mpg.
Figure 3.4.11. An example of a basic histogram.
R automatically assigns bins when creating a histogram unless otherwise instructed. There are several things that can be influenced:
# STYLED (bin edges + colors)
ggplot(mpg, aes(hwy)) +
  geom_histogram(binwidth = 5, boundary = 0,
                 fill = "red", color = "orange") +
  labs(title = "Highway MPG Distribution", x = "Highway MPG", y = "Frequency")
A styled histogram of highway MPG with red bars and orange borders, using bins of width 5 with a boundary at 0, titled "Highway MPG Distribution".
Figure 3.4.12. An example of a styled histogram.
In the case that you need a stacked histogram, below is code to create that. The secret here is using the fill command.
# MAPPED FILL (stacked by class)
ggplot(mpg, aes(hwy, fill = class)) +
  geom_histogram(binwidth = 10, color = "white") +
  labs(title = "Highway MPG Distribution by Vehicle Class",
       x = "Highway MPG", y = "Count", fill = "Vehicle Class") +
  theme(legend.position = "bottom")
A stacked histogram of highway MPG where each bin is colored by vehicle class, showing how different classes contribute to the overall distribution.
Figure 3.4.13. An example of a filled histogram.

Subsubsection 3.4.4.2 Density - geom_density()

Density plots still show the distribution of data, but instead of doing it in bins like a histogram, accomplish this through an outline. The geometry for a density plot is geom_density().
# BASIC
ggplot(diamonds, aes(x = price)) +
  geom_density()
A density plot of diamond prices showing a right-skewed distribution with most diamonds priced under $5,000 and a long tail extending to higher prices.
Figure 3.4.14. An example of a basic density plot.
In the below example, the visualization is filtering within the data portion to only keep the cuts β€œGood”, β€œIdeal” and β€œPremium.” Since it is utilizing color inside the aesthetics, this will create a grouped density plot, showing three different lines for each of the three different cuts.
Regarding the data portion of the graph below:.
If there was no filtering, this code would still separate into different lines.
# GROUPED
ggplot(diamonds |> filter(cut %in% c("Good", "Ideal", "Premium")),
       aes(price, color = cut)) +
  geom_density() +
  labs(title = "Price Density by Cut", x = "Price", y = "Density") +
  theme(legend.position = "bottom")
A grouped density plot showing price distributions for three diamond cuts (Good, Ideal, Premium) as overlapping lines in different colors, with a legend at the bottom.
Figure 3.4.15. An example of a grouped density plot.

Subsection 3.4.5 Boxplot - geom_boxplot()

Commonly known as a box and whisker plot, boxplots are fantastic for providing numerical insights between categorical variables. There are a few different pieces of a boxplot:
  • Whiskers: there are two whiskers on each boxplot
    • Lower Whisker: shows the lower 25% of the data. The bottom is the lowest value in the dataset
    • Upper Whisker: shows the upper 25% of the data. The top is the highest value in the dataset
  • Box: the box itself shows the middle 50% of the data. This includes:
    • Interquartile Range (IQR): the lowest line in the bar is the 25th percentile and the top is the 75th percentile
    • Median: the darker line inside of the box
  • Outliers: any data point that is above or below the upper and lower whisker, respectively.
The geometry for a boxplot is geom_boxplot().
# BASIC
ggplot(mpg, aes(x = class, y = hwy)) +
  geom_boxplot() +
  labs(title = "Highway MPG by Vehicle Class", x = "Vehicle Class", y = "Highway MPG")
A boxplot showing the distribution of highway MPG for each vehicle class. Compact and subcompact vehicles tend to have higher highway MPG, while pickups and SUVs have lower MPG.
Figure 3.4.16. An example of a basic boxplot.
Sometimes the points on a boxplot (or any plot) can be indistinguishable due to them being so close together. In that case, utilize the geom_jitter() command.
# WITH JITTERED POINTS OVERLAID
ggplot(mpg, aes(class, hwy)) +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(width = 0.15, alpha = 0.4, size = 1.5) +
  coord_flip() +
  labs(title = "Highway MPG by Vehicle Class (with Points)",
       x = "", y = "Highway MPG")
A horizontal boxplot showing highway MPG by vehicle class with individual data points jittered around each box, giving a clearer picture of the underlying data distribution.
Figure 3.4.17. An example of a boxplot with jittered points.

Subsection 3.4.6 Lines (time series) - geom_line()

Time and time again, when working with time-series data, a line graph is created. The geometry for a line graph is geom_line().
# BASIC: unemployment over time
ggplot(economics, aes(x = date, y = unemploy)) +
  geom_line() +
  labs(title = "US Unemployment Over Time",
       x = "Date", y = "Number Unemployed (thousands)")
A line graph showing US unemployment over time from the economics dataset, with date on the x-axis and number unemployed (in thousands) on the y-axis. The line shows an overall upward trend with fluctuations.
Figure 3.4.18. An example of a basic line graph.
Like in the scatterplot, you can add a line of best fit.
# PLUS: LM vs LOESS contrast
ggplot(economics, aes(date, unemploy)) +
  geom_line(linewidth = 1.6) +
  geom_smooth(method = "lm", se = FALSE, color = "red") +
  labs(title = "US Unemployment with Linear Trend",
       x = "Date", y = "Number Unemployed (thousands)")
A line graph of US unemployment over time with a red linear regression trend line added, showing the overall upward trend in unemployment despite short-term fluctuations.
Figure 3.4.19. An example of a line graph with a LM line of best fit.
Instead of using β€œlm” for our method, let’s try β€œloess” and see our results.
ggplot(economics, aes(date, unemploy)) +
  geom_line(linewidth = 0.6) +
  geom_smooth(method = "loess", se = TRUE) +  # loess = flexible smoothing; se = confidence band
  labs(title = "US Unemployment with LOESS Smooth",
       x = "Date", y = "Number Unemployed (thousands)")
A line graph of US unemployment over time with a blue LOESS smooth curve and confidence band, showing a flexible fit that captures the non-linear trend in unemployment over the decades.
Figure 3.4.20. An example of a line graph with a LOESS smooth.

Subsection 3.4.7 Put text on the plot - geom_text()

No matter the type of plot, it may help the viewer understand your plot better if you add labels to some of your data points, for example, the most extreme. To do this, utilize the geom_text() command.
# BASIC: label extreme points
mpg_extremes <- mpg |> slice_max(order_by = hwy, n = 5)
mpg_extremes
# A tibble: 6 Γ— 11
  manufacturer model      displ  year   cyl trans      drv     cty   hwy fl    class     
  <chr>        <chr>      <dbl> <int> <int> <chr>      <chr> <int> <int> <chr> <chr>     
1 honda        civic        1.8  2008     4 auto(l5)   f        25    36 r     subcompact
2 honda        civic        1.8  2008     4 auto(l5)   f        24    36 c     subcompact
3 toyota       corolla      1.8  2008     4 manual(m5) f        28    37 r     compact   
4 volkswagen   jetta        1.9  1999     4 manual(m5) f        33    44 d     compact   
5 volkswagen   new beetle   1.9  1999     4 manual(m5) f        35    44 d     subcompact
6 volkswagen   new beetle   1.9  1999     4 auto(l4)   f        29    41 d     subcompact
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  geom_text(data = mpg_extremes, aes(label = model), nudge_y = 1, size = 3) +
  labs(title = "Top Highway MPG Models Labeled",
       x = "Engine Displacement (L)", y = "Highway MPG")
A scatterplot of engine displacement versus highway MPG with text labels added to the five data points with the highest highway MPG, identifying the vehicle models.
Figure 3.4.21. An example of adding text inside a plot.
The code above first identifies the top five hwy values using slice_max(), pairs that with geom_text() and labels only the extreme data points. Now, viewers can read from the plot which model of cars have the best highway miles per gallon.

Subsection 3.4.8 Error bars (requires summary stats) - geom_errorbar()

In times where you want error bars to be displayed, you first need to compute the mean and standard error for your values.
# Compute mean & standard error for hwy by class
summ_hwy <- mpg |>
  group_by(class) |>
  summarize(
    mean_hwy = mean(hwy, na.rm = TRUE),
    se_hwy   = sd(hwy, na.rm = TRUE) / sqrt(n()))

summ_hwy
# A tibble: 7 Γ— 3
  class      mean_hwy se_hwy
  <chr>         <dbl>  <dbl>
1 2seater        24.8  0.583
2 compact        28.3  0.552
3 midsize        27.3  0.334
4 minivan        22.4  0.622
5 pickup         16.9  0.396
6 subcompact     28.1  0.909
7 suv            18.1  0.378
Once completed, you can utilize the geom_errorbar() command to add error bars to your plot.
# Points + error bars (plot error bars first so points sit on top)
ggplot(summ_hwy, aes(class, mean_hwy)) +
  geom_errorbar(aes(ymin = mean_hwy - se_hwy, ymax = mean_hwy + se_hwy), width = 0.2) +
  geom_point(size = 2) +
  coord_flip() +
  labs(title = "Mean Highway MPG (Β± SE) by Class", x = "", y = "Mean Highway MPG")
A horizontal dot plot showing mean highway MPG by vehicle class with error bars representing the standard error. Compact and subcompact vehicles have the highest mean highway MPG.
Figure 3.4.22. An example of a plot with error bars.

Subsection 3.4.9 Reference lines

Let’s say there is a scenario where you are looking for data above and below a particular threshold. In this case, reference lines can become an essential tool for yourself and your viewers. Once the threshold is established (mean, median, really any number of significance to you), you can utilize the geom_hline() or the geom_vline() commands to create horizontal or vertical reference lines, respectively.
# Horizontal line at overall mean
overall_mean <- mean(mpg$hwy, na.rm = TRUE)

ggplot(mpg, aes(displ, hwy)) +
  geom_point(alpha = 0.6) +
  geom_hline(yintercept = overall_mean, linetype = "dashed") +
  labs(title = "Reference Line at Overall Mean Highway MPG",
       x = "Engine Displacement (L)", y = "Highway MPG")
A scatterplot of engine displacement versus highway MPG with a dashed horizontal reference line at the overall mean highway MPG, helping identify which vehicles are above or below average.
Figure 3.4.23. An example of a plot with a horizontal reference line.
# Vertical line at displ = 3
ggplot(mpg, aes(displ, hwy)) +
  geom_point(alpha = 0.6) +
  geom_vline(xintercept = 3, linetype = "dotted") +
  labs(title = "Reference Line at Engine Displacement = 3L",
       x = "Engine Displacement (L)", y = "Highway MPG")
A scatterplot of engine displacement versus highway MPG with a dotted vertical reference line at engine displacement equal to 3L, separating smaller and larger engines.
Figure 3.4.24. An example of a plot with a vertical reference line.