How to make bar graphs using ggplot2 in R
By: Ishita Batra
Tags: Ggplot2, Bar Graph, Data Visualization, Commentary, R

Motivation

There is a wealth of information on the philosophy of ggplot2 , how to get started with ggplot2 , and how to customize the smallest elements of a graphic using ggplot2 — but it's all in different corners of the Internet. It can be difficult for a beginner to tie all this information together.

Prerequisites

This post assumes basic familiarity with the following R concepts:

• vectors

• data frames

• factors

I also use the dplyr package to clean data. All code is commented so this should be straightforward to follow even if you have not used dplyr before.

Data

We will be using the GapMinder dataset that comes pre-packaged with R. This dataset is an excerpt from the GapMinder data, and it shows the life expectancy, population and GDP per capita of various countries over 12 years between 1952 to 2007.

## # A tibble: 6 x 6

## country continent year lifeExp pop gdpPercap

## <fct> <fct> <int> <dbl> <int> <dbl>

## 1 Afghanistan Asia 1952 28.8 8425333 779.

## 2 Afghanistan Asia 1957 30.3 9240934 821.

## 3 Afghanistan Asia 1962 32.0 10267083 853.

## 4 Afghanistan Asia 1967 34.0 11537966 836.

## 5 Afghanistan Asia 1972 36.1 13079460 740.

## 6 Afghanistan Asia 1977 38.4 14880372 786.

Visualization task

We would like to show the change in life expectancy from 1952 to 2007 for 11 (arbitrarily-selected) countries: Bolivia, China, Ethiopia, Guatemala, Haiti, India, Kenya, Pakistan, Sri Lanka, Tanzania, Uganda.

Specifically, we want to see the life expectancy in each of these countries in 1952 and 2007. We also want to group the countries by continent.

We will use a bar plot to communicate this information graphically because we can easily see the levels of the life expectancy variable, and compare values over time and across countries. Here is a rough sketch to get us started on what we can do:

Note that we want two bars per country — one of these should be the life expectancy in 1952 and the other in 2007. We also want to colour the bars differently based on the continent.

Components of the graph

ggplot2 is based on the "grammar of graphics", which provides a standard way to describe the components of a graph (the "gg" in ggplot2 refers to the grammar of graphics). It has specialized terminology to refer to the elements of a graph, and I'll introduce and explain new terms as we encounter them. For now, what we need to understand is that we will build a graphic by adding components one after the other, like layers.

The first step to building the graphic is to identify the components. Using our rough sketch as a guide, we know that our components are:

Dataset — for us, this is a subset of the gapminder data that includes only the countries and years in question Coordinate system — Cartesian Axes — we want country name on the x-axis and life expectancy on the y-axis Type of visualization — we want one bar per country per year e.g. for India, we want one bar for the life expectancy in 1952 and another bar for 2007 Groups on the x-axis — we want to group countries by continent

Now that we know what we need to include in the graph, let’s move on to writing code.

Making the chart

Setup

We need to install the following packages:

ggplot2

dplyr — to manipulate data

— to manipulate data gapminder — data source

We can use the following code to install and load packages.

# create list of packages we need

packages <- c("ggplot2", "dplyr", "gapminder")



# Install packages

lapply(packages, install.packages, character.only = TRUE)



# Load packages

lapply(packages, library, character.only = TRUE) # In case you are unfamiliar with lapply() - it has been used to apply the install.packages() and library() functions over a list of package names. More information here: https://www.r-bloggers.com/using-apply-sapply-lapply-in-r/

Manipulating the data

Let’s have a look at the data again. It’s saved under gapminder :

head(gapminder) ## # A tibble: 6 x 6

## country continent year lifeExp pop gdpPercap

## <fct> <fct> <int> <dbl> <int> <dbl>

## 1 Afghanistan Asia 1952 28.8 8425333 779.

## 2 Afghanistan Asia 1957 30.3 9240934 821.

## 3 Afghanistan Asia 1962 32.0 10267083 853.

## 4 Afghanistan Asia 1967 34.0 11537966 836.

## 5 Afghanistan Asia 1972 36.1 13079460 740.

## 6 Afghanistan Asia 1977 38.4 14880372 786.

Let’s restrict the data to the countries and years we are interested in, and save this new dataset as data_graph .

# create vectors with country names and years

country_list <- c("Bolivia", "China", "Ethiopia", "Guatemala", "Haiti", "India", "Kenya", "Pakistan", "Sri Lanka", "Tanzania", "Uganda")

year_list <- c(1952, 2007) # save subset of gapminder as data_graph

data_graph <- gapminder %>%

filter(country %in% country_list) %>%

filter(year %in% year_list)



# have a look at the data

head(data_graph) ## # A tibble: 6 x 6

## country continent year lifeExp pop gdpPercap

## <fct> <fct> <int> <dbl> <int> <dbl>

## 1 Bolivia Americas 1952 40.4 2883315 2677.

## 2 Bolivia Americas 2007 65.6 9119152 3822.

## 3 China Asia 1952 44 556263527 400.

## 4 China Asia 2007 73.0 1318683096 4959.

## 5 Ethiopia Africa 1952 34.1 20860941 362.

## 6 Ethiopia Africa 2007 52.9 76511887 691.

Let’s also make “year” a factor, since it is a discrete variable:

data_graph <- data_graph %>%

mutate(year = factor(year))

Base plot

To build a ggplot, we first use the ggplot() function to specify the default data source and aesthetic mappings:

# make the base plot and save it in the object "plot_base"

plot_base <- ggplot(data = data_graph, mapping = aes(x = country, y = lifeExp)) # display the plot object

plot_base

Let’s break this down a little:

data source: “data_graph” in our case

aesthetic mappings: The aes() function maps variables in our data frame to aesthetic attributes. An "aesthetic attribute"" is a visual element of the graph, such as the shape of a point or the colour of a line. In our case, we are specifying that the axes (which are aesthetic attributes) should correspond to the variables "country" and "lifeExp".

Note that there is no bar graph because we haven’t specified one yet. We have just specified which dataset and axes to use, not the type of graphic to display.

Change the appearance

Let’s make the graph look a bit nicer. My preference is to make the following adjustments:

Simple, black-and-white layout

No background colour

No gridlines

The chart area shouldn’t be in a box; we should have only the x and y axis

We will use the theme() function to make these changes. theme() allows us to modify the display of non-data elements of the graph.

# save a better-formatted version of the base plot in "plot_base_clean"

plot_base_clean <- plot_base +

# apply basic black and white theme - this theme removes the background colour by default

theme_bw() +

# remove gridlines. panel.grid.major is for vertical lines, panel.grid.minor is for horizontal lines

theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(),

# remove borders

panel.border = element_blank(),

# removing borders also removes x and y axes. Add them back

axis.line = element_line())



# display the plot object

plot_base_clean

Note that we did not have to re-write the code to make the base plot or modify it in any way. Instead, we kept the base plot object as-is and “added” themes to it using the + operator. This is how we build a ggplot — we add components together to build a graphic.

Add bars

In order to add bars to our ggplot, we need to understand geometric objects (“geoms”). A “geom” is a mark we add to the plot to represent data. For example, we can use the geom “point” to display our data using points, in which case the resulting graphic would be a scatterplot. The ggplot2 cheatsheet has a list of all the geoms we can add to a plot.

We will be adding bars to our graph using geom_bar() :

plot_base_clean + geom_bar(stat = "identity")

We now have a bar graph. The numbers don’t seem to be right since the life expectancy is close to 100 for all countries — we will fix this later.

It may seem strange that we didn’t specify the x and y values for the bars, but the bars displayed life expectancy by country anyway. This is because of ggplot’s “hierarchy of defaults”. Since we add the call to geom_bar() to an existing call to ggplot(data = data_graph, aes(x = country, y = lifeExp)) , ggplot2 assumes that the x and y variables for geom_bar() are the same as those for ggplot() i.e. the x and y variables are "country" and "lifeExp", respectively.

We also specified stat in the call to geom_bar . stat is used when we want to apply a statistical function to the data and show the results graphically. When we use geom_bar() , by default, stat assumes that we want each bar to show the count of y-variables per x-variable. Since we want ggplot to plot the values as-is, we specify stat = "identity" .

Change the bar colours

Now, let’s change the colour of the bars. We ultimately want the colour of the bars to vary by continent, but let’s start with something simpler — let’s change the colour of the bars to light blue. To do this, we will specify fill = "lightblue" inside the call to geom_bar() .

plot_base_clean + geom_bar(stat = "identity", fill = "lightblue")

Now, let’s make the colour of the bars vary by continent. We are saying that we want a mapping from an aesthetic element (the colour inside the bars) to a variable in our data (“continent”). Recall that we use the aes() function to specify a relationship between a visual element and a variable. Within aes() , we will use the fill argument to specify that we are interested in changing the colour of the bars.

plot_base_clean + geom_bar(stat = "identity", aes(fill = continent))

Note that we used fill in both cases, because fill is what controls the colour inside the bars. However, we did not use aes() when we coloured the bars light blue because the colour inside the bars wasn't related to any variables.

Add years

Now, we will address why we aren’t seeing the correct values of life expectancy in the graph. Since each country has two observations for life expectancy (one for 1952 and one for 2007), and we haven’t specified which observation to use, the life expectancy shown by the bars is actually the sum of life expectancy for both years.

Let’s see what happens when we restrict the graph to include only data for 2007.

plot_base_clean +

geom_bar(data = subset(data_graph, year == 2007), stat = "identity", aes(fill = continent))

We now see the correct values of life expectancy. Note that though the plot_base_clean object already had a default value of data (data_graph), we were able to override it in the call to geom_bar() . This again ties back to the hierarchy of defaults - if we don't specify a new dataset or xy-variables for our geoms, we simply use the dataset and xy-variables provided in the call to ggplot() , but since we specified a new value of data within geom_bar() , the bars reflect a new data source.

Next, we are interested in showing two data points per country, one for 1952 and one for 2007. Here is where the alpha aesthetic is useful. It specifies the transparency of the colours we are using. Let's try using alpha with the same subsetted dataset:

plot_base_clean +

geom_bar(data = subset(data_graph, year == 2007), stat = "identity", aes(fill = continent), alpha = .4)

We see that similar to specifying fill = "lightblue" , specifying alpha to be a number changes the transparency levels of each bar. alpha values range from 0 to 1, with higher values being more opaque.

Like fill , alpha can also be used as an aesthetic. Let's establish a relationship between the transparency of a bar and the year. Since we are interested in both years, we won't restrict graph_data in geom_bar() .

plot_base_clean +

geom_bar(stat = "identity", aes(fill = continent, alpha = year))

We don’t want a stacked bar chart, but alpha does seem to be working - we see that the lighter portions of the bars correspond to the values in 1952, while the darker portions correspond to values in 2007.

Now, let’s use the position argument to make the bars appear side-by-side, instead of being stacked. According to the ggplot2 documentation, bars are stacked by default and we need to specify position = "dodge" to make the bars appear side-by-side.

plot_base_clean +

geom_bar(stat = "identity", aes(fill = continent, alpha = year), position = "dodge")

Note that position = "dodge" is another way of writing position = position_dodge() . position_dodge() can take a width argument, which is discussed in detail in this Stack Overflow post. We are using the default width, which is why we can use the shorter version position = "dodge" .

Finalize colour scheme

The 1952 colours for alpha are very light. Let's modify the transparency provided by alpha using scale_alpha_manual() .

plot_base_clean +

geom_bar(stat = "identity", aes(fill = continent, alpha = year), position = "dodge") +

scale_alpha_manual(values = c(0.6, 1))

Here, we specified a vector for scale_alpha_manual , where each element provides the transparency of the corresponding year. We assigned a transparency of 0.6 to 1952 and 1 to 2007 (we know the first element corresponds to 1952 and the second element to 2007 because that is the order of levels for the "year" factor. You can check this using levels(data_graph$year) ).

Let’s also change the colour scheme for the continent colours using scale_fill_manual() . We provide a vector of colours, where each element provides the colour for the corresponding continent. I have provided the colours in hexadecimal format (e.g. as "#FF0011"), but you can provide colours in any other format you prefer.

# add bars and colour scheme

plot_bar <- plot_base_clean +

geom_bar(stat = "identity", aes(fill = continent, alpha = year), position = "dodge") +

scale_fill_manual(values = c("#F4BE85", "#F1CE75", "#B2D1E8")) +

scale_alpha_manual(values = c(0.6, 1))



# display the plot object

plot_bar

Make the plot horizontal

Let’s turn our plot into a horizontal bar chart using coord_flip() :

# make the plot horizontal

plot_horizontal <- plot_bar + coord_flip()



# display the plot object

plot_horizontal

Note the order of the bars still reflects the levels of the factor i.e. countries coming first alphabetically are closer to the origin, and the bar for 1952 is below the bar for 2007. We are going to go ahead with this order, but if you’d like the countries or years to appear in a different order, all you have to do is modify the factor levels of the corresponding variables.

Add facets (continent groups)

Our graph is already quite informative — we can identify the continent a country belongs to by the colour of the bar. If we want the country bars to appear by continent, we can change the levels of the “country” factor so that the country names are sorted by continent.

However, it would be much more effective if we could group the countries into continents on the x-axis. The reader of the graph wouldn’t need to keep referring to the legend; all the information would be in one place. We can create these groups using facets.

Facets are used to split the ggplot into a matrix of panels. Let’s add a facet for the “continent” variable to understand what “matrix of panels” means:

plot_horizontal +

facet_grid(rows = vars(continent))

We see that our graph is now in 3 horizontal panels, with each panel representing a different continent.

Let’s break the facet_grid() command down a little: we wanted horizontal panels, so we specified the rows argument. Each row/panel was on the basis on continent, so we specified rows = vars(continent)) . vars just indicates that the "continent" object exists in the context of the dataset we are using in our ggplot() command. If we don't specify vars , we will get an error saying that the object "continent" was not found.

Now, we will explore some arguments of facet_grid() that can improve the appearance of the graph. All of these are covered in detail in the ggplot2 documentation; in this post, we will use only a few options.

First, we see that the graph is assuming that every x-variable (“country”, in our case) exists for every faceting variable (“continent”) e.g. Haiti is in the Africa and Asia panel as well as the Americas panel. This is because ggplot2 assumes every panel will have the same scale, where "scale" refers to the values the x and y axis take on. Our scale of interest is country names, and currently each continent has exactly the same scale - all of the country names are included for each continent. To remedy this, we specify scales = "free_y" - we say that every faceting variable ("continent") can have its own scale (where a "scale" would be only those country names that are part of the continent).

plot_horizontal +

facet_grid(rows = vars(continent), scales = "free_y")

Now, notice that the bars for the Americas are thicker than the bars for Africa or Asia. This is because by default, ggplot makes all panels (i.e. all continents) occupy the same amount of space. We’d prefer that all our bars be equally thick, rather than our panels be equally tall. Let’s add space = "free_y" .

plot_horizontal +

facet_grid(rows = vars(continent), scales = "free_y", space = "free_y")

It seems a little confusing to have the continent names to the right and the country names to left. We can use the switch option to change where the facet labels (i.e. continent names) are displayed.

# add facets and modify their appearance

plot_facet <- plot_horizontal +

facet_grid(rows = vars(continent), scales = "free_y", space = "free_y", switch = "y")



# display the plot object

plot_facet

This looks quite good! Let’s do the following to modify the appearance of the facet labels i.e. the continent names:

Move the continent names to the left of country names

Remove the gray background and box from the continent labels

Make the continent names horizontal and not vertical

# Modify the appearance of facet labels

plot_facet_clean <- plot_facet +

# move facet label outside the chart area i.e. continent names should be to the left of country names

theme(strip.placement = "outside",

# remove background colour from facet labels

strip.background = element_blank(),

# remove border from facet label

panel.border = element_blank(),

# make continent names horizontal

strip.text.y = element_text(angle = 180))



# display the plot object

plot_facet_clean

Final touches

Our graph is almost ready! Let’s clean up the legend and the axes, and give a title to our graph.

Legend

To reduce chartjunk, let’s suppress the legend for continent because we already have that information in the facets. We will use the guides() function to suppress the legend for the fill aesthetic (recall that we set aes(fill = continent) in geom_bar() ).

# remove the legend for "fill"

plot_nolegend <- plot_facet_clean + guides(fill = FALSE)



# display the plot object

plot_nolegend

DataNovia has an excellent guide for formatting ggplot legends, if you’d like to modify the legend further e.g. change its position, manually change legend colours, etc.

Graph labels

Finally, let’s use the labs function to change the labels for this graph. We want to:

Remove the x-axis label — we don’t need to say “country” since it is apparent

Change the y-axis label to “Life expectancy (years)”

Add a title above the graph explaining what the graph shows

Add the data source below the graph

# change graph labels

plot_final <- plot_nolegend +

# remove x-axis label

xlab("") +

# change the y-axis label

ylab("Life expectancy (years)") +

# add caption and title

labs(title = "Life expectancy in select countries in 1952 and 2007",

caption = "Data source: Gapminder")



# display the plot object

plot_final

And that is our graph!

Final graph code

Here is all the graph code in one place:

## base plot

ggplot(data = data_graph, aes(x = country, y = lifeExp)) +



## change the theme

# apply basic black and white theme - this theme removes the background colour by default

theme_bw() +

# remove gridlines. panel.grid.major is for verical lines, panel.grid.minor is for horizontal lines

theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(),

# remove borders

panel.border = element_blank(),

# removing borders also removes x and y axes. Add them back

axis.line = element_line()) +



## add bars

geom_bar(stat = "identity", aes(fill = continent, alpha = year), position = "dodge") +

scale_fill_manual(values = c("#F4BE85", "#F1CE75", "#B2D1E8")) +

scale_alpha_manual(values = c(0.6, 1)) +



## make the plot horizontal

coord_flip() +



## add facets

facet_grid(rows = vars(continent), scales = "free_y", space = "free_y", switch = "y") +

# move facet label outside the chart area i.e. continent names should be to the left of country names

theme(strip.placement = "outside",

# remove background colour from facet labels

strip.background = element_blank(),

# remove border from facet label

panel.border = element_blank(),

# make continent names horizontal

strip.text.y = element_text(angle = 180)) +



## remove legend

guides(fill = FALSE) +



## modify graph labels

# remove x-axis label

xlab("") +

# change the y-axis label

ylab("Life expectancy (years)") +

# add caption and title

labs(title = "Life expectancy in select countries in 1952 and 2007",

caption = "Data source: Gapminder")

You can save a copy of the graph using the ggsave() command, which allows you to specify the save location, dimensions of the file, image format (.png, .jpg etc.), and more.

Revisiting ggplot() graph components

Now that we understand how to build a ggplot, let’s map the elements of our graph to the components of a plot:

“A default dataset and set of mappings from variables to aesthetics” — we did this in ggplot(data = data_graph, aes(x = country, y = lifeExp)) .

. “One or more layers, with each layer having one geometric object, one statistical transformation, one position adjustment, and optionally, one dataset and set of aesthetic mappings”— we created a layer for bars using geom_bar() , stat = "identity" and " position = "dodge" .

, and " . “One scale for each aesthetic mapping used” — the x and y axes had default scales based on the values of “country” and “lifeExp”. We also created scales for fill and alpha .

and . A coordinate system — Cartesian, in our case, as we specified aesthetics for x and y . We also flipped the axes.

and . We also flipped the axes. The facet specification — we did this using facet_grid() .

The graph components are succinctly expressed in this code template:

ggplot(data = <DATA>,

mapping = aes(<MAPPING>)) +

<GEOM_FUNCTION>(

stat = <STAT>,

position = <POSITION>

) +

<COORDINATE_FUNCTION> +

<FACET_FUNCTION>

Next steps

You can make the following graphs to learn more about ggplot() :