Data Visualization in R with ggplot2
Edited by Dev Kilaru, Senior Data Analyst. How we make our content
Introduction
ggplot2 builds charts in layers, following a grammar of graphics: you map columns of data to visual properties such as position and color, then add geometric layers that draw those mappings. Once the idea clicks, you can produce very different chart types by changing one layer instead of learning a new function. This lesson walks through the common chart types and the finishing touches that make a plot readable by someone who was not there when you made it.
Prerequisites
- The tidyverse installed (ggplot2 is included)
- The wrangling skills from the previous lesson
Step-by-Step Guide
Step 1: Understand the three building blocks
Every ggplot starts with three ideas. The data is a data frame in tidy form. The aesthetics, written inside aes, map columns to visual properties such as x, y, color, size, and shape. The geoms are the layers that draw the data, such as points, lines, or bars. You start with ggplot and the data, then add layers with the plus sign. Notice that the plus sign goes at the end of a line, not the beginning, otherwise R treats the next line as a separate command.
library(tidyverse)
ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point()Step 2: Map more columns to color and shape
Mapping a column to color inside aes makes ggplot pick a color for each value and add a legend automatically. The cylinder column is numeric, so wrap it in factor to treat it as three categories rather than a continuous scale. Setting a property outside aes, such as size = 3, applies one fixed value to every point instead of mapping it to data. Confusing the two is a very common source of strange legends.
ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) +
geom_point(size = 3)Step 3: Add a trend line
Layers stack, so you can draw the points and then a fitted line on top of them. The geom_smooth layer fits a smoother, and method = "lm" requests a straight regression line, with a shaded band showing the uncertainty. Specify formula = y ~ x to make the intent explicit and silence the message about the default. Because the color mapping was set in the main ggplot call, the line would be drawn separately for each cylinder group, so move color into geom_point if you want a single overall line.
ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point(aes(color = factor(cyl)), size = 3) +
geom_smooth(method = "lm", formula = y ~ x)Step 4: Choose the chart for your question
Match the geom to the question. Use geom_histogram to see how one numeric variable is distributed, and experiment with binwidth. Use geom_boxplot to compare a distribution across groups. Use geom_col for bars when you have already computed the heights, such as averages from summarise, and geom_bar when you want ggplot to count rows. Bar charts of totals or averages must start at zero to be honest, while scatter plots do not need to.
ggplot(mtcars, aes(x = mpg)) +
geom_histogram(binwidth = 2, fill = "steelblue", color = "white")
ggplot(mtcars, aes(x = factor(cyl), y = mpg)) +
geom_boxplot()
avg <- mtcars |> group_by(cyl) |> summarise(mpg = mean(mpg))
ggplot(avg, aes(x = factor(cyl), y = mpg)) +
geom_col()Step 5: Split into small multiples with facets
When one chart gets crowded, facets draw the same plot once for each value of a category, arranged in a grid with shared scales so panels can be compared at a glance. Use facet_wrap with a tilde and a column name for a ribbon of panels, or facet_grid to lay out two categories in rows and columns. Small multiples are often clearer than packing every group into one plot with a dozen colors.
ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point() +
facet_wrap(~ cyl)Step 6: Label, theme, and save
A chart someone else will read needs a title that states the point, axis labels with units, and a legend title that means something. Add them with labs. A theme such as theme_minimal removes the heavy gray background. When the plot looks right, ggsave writes it to a file, picking the format from the extension. Set width, height, and dpi explicitly so the image looks the same every time and prints sharply in a report.
p <- ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) +
geom_point(size = 3) +
labs(
title = "Heavier cars use more fuel",
x = "Weight (1000 lb)",
y = "Miles per gallon",
color = "Cylinders"
) +
theme_minimal()
ggsave("output/mpg-vs-weight.png", p, width = 7, height = 5, dpi = 300)FAQs
- Why does my plot show nothing or only a blank panel?
- You need at least one geom layer, because ggplot and aes alone only set up the axes. Also check that the plus sign is at the end of the previous line and that the column names in aes match your data exactly.
- What is the difference between geom_bar and geom_col?
- geom_bar counts the rows in each category for you. geom_col draws bars at heights that are already in a column, such as a mean you computed. Using the wrong one is a common cause of an error about a missing y aesthetic.
- How do I reorder bars or categories?
- Categories follow the order of the factor levels, which is alphabetical by default. Use fct_reorder from forcats to sort by a value, or set the levels yourself when creating the factor.
Summary
ggplot2 charts are built from data, aesthetic mappings, and geom layers, refined with facets, labels, and themes, and exported with ggsave. When a chart looks wrong, check whether a property belongs inside aes because it comes from data, or outside because it is a fixed setting. Next you will use R for what it was designed for: testing hypotheses and fitting statistical models.
Related tutorials
- Schedule and Debug Shell Scripts: cron, bash -x, and Portability
- Shell Scripting Project: A Backup Script with Rotation and a Log Report
- Writing Safe Bash Scripts: set -euo pipefail, getopts, and trap
- Shell Pipes, Redirection, grep, sed, and awk for Text Processing
- Bash Loops, Arrays, and Functions Explained
Comments
Log in or create a free account to join the discussion.