
Data Visualization with ggplot2
In the prior chapters we have worked on obtaining an open-source data object, tidying it, and constructing variables. Now, we want to use those data to visualize the information. Data visualization transforms raw numbers into meaningful visual patterns that humans can interpret quickly.
Visualization helps analysts:
- Detect trends over time
- Compare categories
- Examine distributions
- Explore relationships between variables
- Identify outliers and anomalies
- Communicate findings effectively
Without visualization, important insights can remain hidden inside tables of data. Furthermore, the visual display of information reaches a broader audience than tabular display.
For example, consider the following table:
| Month | Property Crimes |
|---|---|
| January | 120 |
| February | 150 |
| March | 170 |
| April | 220 |
| May | 245 |
| June | 260 |
| July | 290 |
| August | 310 |
| September | 295 |
| October | 340 |
| November | 360 |
| December | 410 |
The table provides information (property crime each month), but a visualization makes patterns much easier to recognize immediately.
For example, a line graph would quickly reveal:
- overall growth throughout the year
- periods of rapid increase
- slight slowdowns or dips
- seasonal patterns
- long-term trends
You can see these if you look through the table, but the visualization of the data makes them easier to see:
Visualization improves comprehension by allowing analysts to identify relationships and patterns that may not be obvious in raw numerical tables alone.
Principles of Effective Visualization
Before we get started with creating visual displays, we should think about what properties are desirable in a visualization. In general, a good visualization is: clear, accurate, simple, and focused on insight. The reader (viewer?) should be able to look at the visualization and make sense of it without being led astray by too many colors, misleading axes, cluttered labels, unnecessary 3D effects, or overcomplicated graphics. (you can find a good rant about this here)
Design Principles
Here are several design principles to keep in mind when creating visualizations:
- Simplicity - Remove unnecessary elements
- Readability - Labels and titles should be easy to understand
- Appropriate Visual Display Selection - Use the correct type of visualization for the question being asked
- Color with Purpose - Colors should highlight meaning, not distract
For an excellent review of visualization (with some really great examples), check out Kieran Healy’s Look at Data chapter of his Data Visualization: A Practical Introduction book.
What is ggplot2?
ggplot2 is a data visualization package in R based on the “Grammar of Graphics”, a framework for building data visualizations by combining independent visual components together in layers. Rather than treating each chart type as a completely separate object, the Grammar of Graphics views all graphics as combinations of:
- data
- mappings
- geometric shapes
- scales
- coordinate systems
- visual themes
This framework was originally developed by statistician Leland Wilkinson and later implemented in R through the ggplot2 package by Hadley Wickham. The name ggplot2 literally means: - gg = Grammar of Graphics - plot2 = second version of the plotting system.
Traditional graphing systems often require users to memorize separate commands for each chart type. For example, plot() or hist(). In contrast, ggplot2 uses a consistent structure for nearly every visualization. This means that once you learn the grammar, you can build many different types of graphics while only changing specific layers.
The utility might not be immediately obvious, but as we start building visualizations, it will become more clear (hang with me on this one!) This layered structure makes visualization more logical, flexible, easier to customize, and easier to interpret.
Key Components
Every ggplot2 plot has three key components:
- data
- A set of aesthetic mappings between variables in the data and visual properties
- At least one layer which describes how to render each observation, created with a geom function
Here’s a simple example:
# define the data
crime_dat <- data.frame(
month = c("January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"),
crimes = c(120, 150, 170, 220,
245, 260, 290, 310,
295, 340, 360, 410)
)
# plot it
ggplot( data = crime_dat,
mapping = aes( x = crimes, y = month ) ) +
geom_point()
This produces a scatterplot defined by:
- Data:
crime_dat - Aesthetic mapping: number of crimes,
crimes, mapped to the x position, and time,month, mapped to the y position - Layer: points
The dataset contains the variables that will be visualized, the aesthetic mapping determines how variables are connected to visual properties, and layer is how the information is displayed.
Let’s start working through this together. Start by installing ggplot2 with install.packages( "ggplot2" ) and then use the library( ggplot2 ) command to load the functions.
Now, run this code and think about what is happening:
ggplot( data = crime_dat,
mapping = aes( x = crimes, y = month ) )
Why are there no dots?
Let’s think about what we did. We have the data and we have the aesthetic mapping, but we do not have the layer.
Compare the two sets of code:
# what we just did:
ggplot( data = crime_dat,
mapping = aes( x = crimes, y = month ) )
# what we did before:
ggplot( data = crime_dat,
mapping = aes( x = crimes, y = month ) ) +
geom_point()See the difference?
In the second block, we add the layer with the + geom_point() line (hence the inclusion of the + operator).
This basic point is key to understating the power of ggplot2 for visualization. Instead of creating an entire chart with one complicated command, we start by stipulating our data and mapping the variables to their positions, then we add layers, then we customize the appearance. This is the basic setup that allows us to develop very sophisticated visual displays of information.
This is why we call it the “grammar of graphics”: a plot is built by combining parts that each have a specific role, much like a sentence is built from parts of speech. In 1, the data, mappings, and geoms (layers) work together to define what is being plotted, how variables are represented, and what visual form the plot takes.
To help this sink in, let’s work through a real example.
Crimes in Phoenix
Recall from the prior chapters that we spent some time tidying the phx_crime object in DWVpack and that data object is called tidy_phx_crime. We can now use those tidied data (tidier data?) to create a visualization.
Specifically, we want to recreate this plot:

Think for a bit about what we need. Look at the plot and try to identify all of the “parts of speech” that make up the visualization.
First, we need the data in a proper shape. We want a data object to plot that has a column for months and number of crimes. We can use dplyr to create the data object. Let’s start with using the arrange() and group_by() functions to sort the data by “month” and then we will group them to create a summary measure that is the count of crimes:
You might have noticed something new: .groups = "drop" in the summarize() function. What is that?
Ok we have our object, take a look at the data using View( crime_monthly ) (it will pop up a separate window pane). Let’s walk through this to see what we are doing:
-
crime_monthly <- tidy_phx_crimesays to create a new object calledcrime_monthlyfrom thetidy_phx_crimeobject. We create a new object since that will be the data just for this plot. -
select( month )takes just themonthvariable. -
arrange( month )sorts the data bymonth. Again, look at how the data are sorted using theView( crime_monthly )command. -
group_by( month )groups the data bymonth. -
summarize( crimes = n() , .groups = "drop" )creates a new column calledcrimeswhich is a count for each month (because they data are grouped that way). The.groups = "drop"command just tells R to remove the grouping from the object after it is done creating the new column.
In this data recipe, .groups = "drop" tells summarize() what to do with the grouping structure after the summary is created. Because the data are grouped by month, summarize() counts the number of rows in each month. Then, .groups = "drop" removes the grouping so that crime_monthly object is returned as a regular, ungrouped data frame rather than staying grouped by month. Including .groups = "drop" makes the instructions clear: after counting crimes by month, we want the resulting object to no longer be treated as grouped data.
Now that we have our data object, we can start building our plot. As we stated above, every ggplot2 plot has three key components:
- data (which we just created)
- a set of aesthetic mappings between variables in the data and visual properties
- at least one layer which describes how to render each observation, created with a geom function
Let’s set up our mapping by making the x-axis the month and the y-axis the count of crimes:
ggplot( data = crime_monthly,
mapping = aes( x = month, y = crimes )
)
Note that the plot is blank because we have not set up the third piece, the layer.
To set up a layer of points, we can use the geom_point() function:
ggplot( data = crime_monthly,
mapping = aes( x = month, y = crimes )
) +
geom_point()
Note that we are just adding a layer (hence the + operator). The code basically says: here is a ggplot with these data and this mapping, now add some points.
The points are helpful, but adding a line here would help a great deal. We can also add a line using the geom_line() function. Note that we are just adding another layer (hence the + operator, again):
ggplot( data = crime_monthly,
mapping = aes( x = month, y = crimes )
) +
geom_point() +
geom_line()
Ok, that didn’t work and we got a warning. :(
We got the warning because geom_line() connects observations together in order. That is, we have a line that connects January to February and so on. To do that, ggplot must know which observations belong to the same line.
This is determined by the group = aesthetic. The problem is ggplot is assuming that each month is its own group, which prevents a continuous line from being drawn. We need to tell ggplot that there is only a single group and that the line should connect the dots.
This is done using group = 1 inside ggplot:
ggplot( data = crime_monthly,
mapping = aes( x = month, y = crimes, group = 1 )
) +
geom_point() +
geom_line()
Think about what this plot shows. It is the count of crimes per month in Phoenix across all years. What are some issues with the plot? Well to name a few: there is no title, the axes are not well labeled, we might want to separate crimes by years…
A Better Plot: Crimes by Year
Ok, let’s now build a plot for crimes by year.
Remember the first step: think about how the data should be structured. Effectively, we want a data object to plot that has a column for months and number of crimes (like before) but we also want a column for years. We can use dplyr to create the data object. Let’s start with using the arrange() and group_by() functions to sort the data by “year” and then “month” and we will group them to create a summary measure that is the count of crimes:
Now, take a look at the data using View( crime_monthly ) (it will pop up a separate window pane). Let’s (again) walk through the data recipe to see what we are doing:
-
crime_monthly <- tidy_phx_crimesays to create a new object calledcrime_monthlyfrom thetidy_phx_crimeobject. We create a new object since that will be the data just for this plot. -
select( year, month )takes just theyearandmonthvariables. -
arrange( year, month )sorts the data first byyearand then bymonth. Again, look at how the data are sorted using theView( crime_monthly )command. -
group_by( year, month )groups the data first byyearand then bymonth. -
summarize( crimes = n() , .groups = "drop" )creates a new column calledcrimeswhich is a count for each month of each year (because they data are grouped that way). The.groups = "drop"command just tells R to remove the grouping from the object after it is done creating the new column.
We can use the ggplot code from above, except that we have to update our group= argument. Since we want ggplot to separate the lines and dots for each year, we add that to the group= argument:
ggplot( data = crime_monthly,
mapping = aes(
x = month,
y = crimes,
group = year ) ) +
geom_point() +
geom_line()
Think about what the aesthetic (i.e. aes) function is doing. It reads: for each year, draw a dot for the month and then connect the months with a line. The aes argument is the instructions for what the geom functions should do.
Although we now have separate lines by year, it is hard to see which year is which. To do this, we can add color using the color = argument. The color= aesthetic maps a variable to different colors in the plot:
ggplot( data = crime_monthly,
mapping = aes(
x = month,
y = crimes,
group = year,
color = factor( year ) ) ) +
geom_point() +
geom_line()
Here, we included color = factor( year ) to indicate that we want year to be converted from numeric years into categories. This is so that ggplot treats them as separate groups (rather than a continuous scale of values). To see what happens when we don’t treat year as a category, but as a continuous variable, just remove the factor() function:
ggplot( data = crime_monthly,
mapping = aes(
x = month,
y = crimes,
group = year,
color = year ) ) +
geom_point() +
geom_line() 
This might be helpful in some cases, but treating it as a category works better for our case.
Now, we need to update our labels. To do so, we can use the labs function which adds a new layer of labels. Let’s do it first and then walk through it:
ggplot( data = crime_monthly,
mapping = aes(
x = month,
y = crimes,
group = year,
color = factor( year ) ) ) +
geom_point() +
geom_line() + # note that we add a plus here to tell it to add a new layer after
labs(
title = "Crime in Phoenix",
subtitle = "Monthly counts of crime by year",
x = "Month",
y = "Counts of Crime",
color = "Year"
)
The new chunk simply adds a layer of labels for the title, subtitle, axis names, and name for the legend. To better see the change, just remove the code for labs and look and how the plot changes when the layers are removed:

Summary
In this chapter, you learned how to move from wrangling data to visualizing it. Tables and summary statistics are useful, but visualizations allow us to see patterns that may otherwise be difficult to identify. For crime analysts, visualizations can help reveal trends over time, compare categories, identify unusual observations, and communicate findings to audiences who may never examine the underlying data themselves. Because visualizations can shape how people interpret information, we also need to design them carefully, with an emphasis on clarity, accuracy, simplicity, and purpose.
We introduced ggplot2, a visualization package built around the Grammar of Graphics. Rather than thinking of a graph as a single finished object, ggplot2 allows us to construct visualizations from individual components. We begin with a dataset, use aes() to map variables to visual properties, and then add one or more geoms that determine how those observations are displayed. Additional layers can then be added to modify or improve the visualization. Much like the data recipes we developed in previous chapters, this gives us a consistent framework for building increasingly sophisticated plots.
Using the Phoenix crime data, we also saw that creating a visualization often begins before we write any ggplot2 code. We first need to think about what we want to visualize and structure our data accordingly. Using tools from dplyr, we transformed individual crime records into monthly counts and then used those summarized data as the input for our plots. This connection between data wrangling and visualization is important: the variables and observations available in our plotting dataset determine what we are able to visualize.
Finally, we began building plots layer by layer. We used geom_point() and geom_line() to determine how observations were represented, the group aesthetic to tell ggplot2 which observations belonged together, and the color aesthetic to visually distinguish years. We also used labs() to provide meaningful titles, axis labels, and legend labels. Each addition had a specific purpose, demonstrating one of the central advantages of the Grammar of Graphics: instead of memorizing a different procedure for every type of graph, we can build visualizations by combining components that each have a specific role.
As we continue working with ggplot2, the plots will become more sophisticated, but the basic approach will remain the same: prepare the data, define the mappings, choose how the data should be displayed, and then add layers that make the visualization clearer and more informative. The goal is not simply to make attractive graphs, but to create visualizations that help us discover, understand, and communicate meaningful patterns in crime data.
Test Your Knowledge
Before moving on, use the prompts below to test your understanding of the material in this chapter. Try to answer each question or write the R code needed to complete the task without looking back at the examples:
In your own words, explain why visualization is useful for crime analysis. What kinds of patterns might be easier to identify in a visualization than in a table?
What are the three key components of a
ggplot2visualization introduced in this chapter? Explain the role that each component plays in constructing a plot.Consider the following code and explain why are there no points, lines, or bars displayed yet:
ggplot(
data = crime_monthly,
mapping = aes(x = month, y = crimes)
)Add a layer to the previous code that displays each observation as a point. Then add a second layer that connects those observations with a line.
In your own words, explain what
aes()does. In the following code, identify which variables are mapped to the x-axis and y-axis:
ggplot(
data = crime_monthly,
mapping = aes(x = month, y = crimes)
)Suppose
crime_monthlycontains separate observations for several years. Explain whygroup = yearwould be necessary when usinggeom_line(). What isggplot2being told to do when you include this aesthetic?Modify the plot so that each year is represented by a different color. Why might you use
color = factor(year)rather than simplycolor = year?Use
labs()to add the following information to a visualization: a title of"Here is my Fake Plot", a subtitle of"This is a Subtitle", an x-axis label of"Our Pal X-axis", a y-axis label of"And his Friend Y-Axis", and a legend title of"Myth".