EthicsComputerCourses
Join training +91 90532 10052

ggplot2 Basics

R Programming · Lesson 26 of 29 Advanced 8 min read Updated

ggplot2 is the most popular charting package in R and part of the tidyverse. Instead of one function per chart type, it gives you a small grammar of graphics: you say which data to use, which columns map to which visual properties (position, colour, size), and which shapes to draw. The same few pieces combine into almost any chart, and the defaults already look clean.

In this lesson you will build scatter plots, bar charts, histograms and box plots, add titles and themes, split a chart into panels and save it. The current version is ggplot2 4.0.3. ggplot2 cannot be installed inside this site's browser editor, so the examples are plain code; run them in RStudio or Positron after install.packages("ggplot2"), and read the description of each chart here.

The grammar: data, aesthetics, geoms

Every ggplot has three essential parts:

  1. Data: a data frame, given to ggplot().
  2. Aesthetic mappings with aes(): which column goes on the x-axis, which on the y-axis, which controls colour, and so on.
  3. Geoms (geometric objects): the shapes that draw the data, added with +, such as geom_point() for points.
R
library(ggplot2)
ggplot(mtcars, aes(x = wt, y = mpg)) +
  geom_point()

This draws 32 grey-black points on a light grey panel with white grid lines: weight on the x-axis, miles per gallon on the y-axis, sloping downwards. The axes are labelled with the column names automatically.

Notice the + at the end of the first line. ggplot2 builds a chart in layers joined by +, not by the pipe. If you start a new line with +, R treats the first line as complete and the second line fails.

Mapping more variables and adding layers

Map a column to colour, size or shape inside aes() and ggplot2 adds a legend for you. Set a fixed value outside aes(). Add layers with more geoms and polish with labs() and a theme:

R
ggplot(mtcars, aes(x = wt, y = mpg, colour = factor(cyl))) +
  geom_point(size = 3) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Heavier cars use more fuel",
       x = "Weight (1000 lbs)", y = "Miles per gallon",
       colour = "Cylinders") +
  theme_minimal()
Output
`geom_smooth()` using formula = 'y ~ x'

The chart now shows large points in three colours (by default a red, a green and a blue for 4, 6 and 8 cylinders), each group with its own straight trend line, a legend titled "Cylinders" on the right, a title at the top and a clean white background from theme_minimal(). The message is only information: geom_smooth() tells you which formula it fitted.

  • factor(cyl) makes colour discrete (three separate colours). A numeric column such as hp would give a continuous colour gradient instead.
  • size = 3 sits outside aes(), so every point gets size 3. Writing aes(size = 3) by mistake would create a pointless legend.
  • geom_smooth(method = "lm") adds regression lines; se = FALSE hides the shaded confidence band.
  • Other built-in themes include theme_bw(), theme_classic() and theme_light().

Bar charts, histograms and box plots

R
ggplot(mtcars, aes(x = factor(cyl))) +
  geom_bar(fill = "#276dc3") +
  labs(x = "Cylinders", y = "Number of cars")

sales <- data.frame(day = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"),
                    amount = c(4200, 3900, 4600, 5100, 6800, 9200, 8700))
sales$day <- factor(sales$day, levels = sales$day)
ggplot(sales, aes(x = day, y = amount)) +
  geom_col(fill = "darkorange") +
  labs(title = "Weekly sales (practice data)", x = NULL, y = "Sales (Rs)")
  • geom_bar() counts rows for you: three blue bars of 11, 7 and 14 cars.
  • geom_col() draws bars whose heights are values already in the data: seven orange bars rising to a Saturday peak. Making day a factor with the levels in week order stops ggplot2 from sorting the days alphabetically, the same idea you met in Factors.
R
ggplot(airquality, aes(x = Temp)) +
  geom_histogram(binwidth = 5, fill = "grey70", colour = "white")

ggplot(airquality, aes(x = factor(Month, labels = month.abb[5:9]), y = Temp)) +
  geom_boxplot(fill = "lightgreen") +
  labs(x = "Month", y = "Temperature (F)")

The histogram shows grey bars 5 degrees wide with white outlines, peaking around 80 °F. The box plot shows one green box per month from May to September, with July and August highest. binwidth controls the bin size; try a few values, because the shape of a histogram can change with it.

Facets: one panel per group

Facets split a chart into small panels, one for each value of a variable, all with the same axes. They make comparisons easy:

R
ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
  geom_point(colour = "steelblue") +
  facet_wrap(~ Species) +
  labs(x = "Petal length (cm)", y = "Petal width (cm)")

Three panels appear side by side, labelled setosa, versicolor and virginica, each with its own small cloud of blue points. Because the axes are shared, you can see at a glance that setosa petals are much smaller. facet_grid(rows ~ cols) arranges panels by two variables.

Saving a chart

Store a chart in a variable, then save it with ggsave(). The file type comes from the extension:

R
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
ggsave("mpg-weight.png", p, width = 7, height = 4, dpi = 300)

width and height are in inches by default. Use .pdf or .svg for sharp charts in reports, .png for slides and web pages.

ggplot2 or base graphics?

To see what ggplot2 automates, here is the coloured scatter plot from earlier drawn with base graphics, which does run in the browser. You choose the colours, look them up for every point and build the legend yourself:

RTry it yourself
cols <- c("4" = "#F8766D", "6" = "#00BA38", "8" = "#619CFF")
plot(mtcars$wt, mtcars$mpg, pch = 19, cex = 1.4,
     col = cols[as.character(mtcars$cyl)],
     main = "Heavier cars use more fuel",
     xlab = "Weight (1000 lbs)", ylab = "Miles per gallon")
legend("topright", legend = names(cols), col = cols, pch = 19, title = "Cylinders")
table(mtcars$cyl)
Output
 4  6  8 
11  7 14 

The chart shows the same red, green and blue points with a hand-made legend in the top right corner; the trend lines would need three more abline() calls. cols[as.character(mtcars$cyl)] looks up one colour per car by name. In ggplot2, colour = factor(cyl) inside aes() does all of this in one step.

Base graphicsggplot2
Needs a packageNoYes
LegendsDrawn by hand with legend()Automatic from aes()
Groups and panelsManual (col =, par(mfrow))colour =, facet_wrap()
StylePlain by defaultConsistent themes
Best forQuick looks while exploringPolished, layered charts for reports

Many analysts explore with base graphics and present with ggplot2. Both are in Base Graphics and this lesson.

Common mistakes

  • Starting a line with +. Put the + at the end of the previous line.
  • Setting a fixed colour inside aes(). aes(colour = "blue") makes a legend entry called "blue" and a red-ish default colour. Put fixed values outside: geom_point(colour = "blue").
  • Using the pipe |> between layers. Layers join with +; the pipe can only pass data into ggplot().
  • Numeric codes as continuous colours. Wrap coded categories in factor().
  • Confusing geom_bar() and geom_col(). geom_bar() counts rows; geom_col() uses a y value.

Key points

  • A ggplot is data + aesthetic mappings (aes()) + geoms, joined with +.
  • geom_point(), geom_line(), geom_col(), geom_bar(), geom_histogram() and geom_boxplot() cover everyday charts.
  • Map variables inside aes(); set fixed values outside it.
  • labs() sets titles and labels; themes such as theme_minimal() change the look; facet_wrap() makes panels.
  • ggsave() writes a chart to PNG, PDF or SVG.

Exercise

Using airquality, draw a scatter plot of Temp (x) against Ozone (y), coloured by month (as a factor), with a single straight trend line for all the data, a title, axis labels with units, and theme_bw(). ggplot2 drops the rows with missing ozone and warns you; explain what the warning means.

Show solution
R
library(ggplot2)
ggplot(airquality, aes(x = Temp, y = Ozone)) +
  geom_point(aes(colour = factor(Month))) +
  geom_smooth(method = "lm", se = FALSE, colour = "black") +
  labs(title = "Hotter days have more ozone",
       x = "Temperature (F)", y = "Ozone (ppb)", colour = "Month") +
  theme_bw()

Putting colour in the aes() of geom_point() only, not in ggplot(), colours the points by month while geom_smooth() fits one line through all of them. ggplot2 warns that it removed 37 rows containing missing values: those are the days with no ozone reading, which cannot be placed on the y-axis.

Quick Quiz

1. Which part of a ggplot says that wt goes on the x-axis?

2. How are layers joined in ggplot2?

3. You want bars whose heights are the values in a column. Which geom?

4. What does facet_wrap(~ Species) do?

Learn R Programming with a mentor

Free lessons get you started. Our Data Science & Machine Learning training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call