Introduction to ggplot2

When I first started out with R, I only ever used the base plotting system. This was primarily because once I saw an example of a plot made with ggplot I was a little bit scared.

After awhile I realized that ggplot was the way to go when it came to graphics. The goal of this is to hopefully assuage you to do the same!

First, we will generate some data. I teach so we will use students and scores on a test and then look at how the students performed.

Some of my colleagues use a 4 test grading scheme where each test is worth 25%. Let’s create a sample dataset with a class of 40 students and varying scores for each student as well as an average for each student.

library(ggplot2)
library(forcats)
library(huxtable)

Attaching package: 'huxtable'
The following object is masked from 'package:ggplot2':

    theme_grey
library(stringr)
library(dplyr,warn.conflicts = F)

test_data <- 
  data.frame(
    Student = paste0("Student",1:40),
    Exam_1= runif(40,80,85) |> round(0),
    Exam_2= runif(40,75,100) |> round(0),
    Exam_3= runif(40,70,90) |> round(0),
    Exam_4= runif(40,60,100) |> round(0)
  )

test_data |> 
  select(!Student) |> 
  summarize_all(mean) |> 
  hux() |> 
  theme_article()
Exam_1 Exam_2 Exam_3 Exam_4
82.5 86.8 79.8 79.5

Let’s make a plot of the average score on each exam using the base plotting system.

test_means <- 
  test_data |> 
  select(!Student) |> 
  summarize_all(mean) |> 
  tidyr::pivot_longer(cols = everything(),
                      names_to = "Exam",
                      values_to = "Average")

barplot(
  test_means$Average,
    names.arg = 
      test_means$Exam,
    col=c("darkblue","darkgreen","darkorange"
    ,"darkred"),
        density = 40,
        ylim = c(0,100),
        xlab="Exam Number",
        ylab="Score",
        main="Exam Score Averages")

This does not look bad, but we could probably do better with ggplot.

The first thing to note is the syntax that ggplot uses.

ggplot(data,aes(x,y)) + geom_() + labs() + theme_classic() + theme()

You will see here that each line is followed by a + which indicates that a new layer is being added. Let’s try this out using our dataframe. I will do it the way you will see it most places, and then afterwards only do it the shortened way:

# Use the means we created before and add two line numbers
# The x-axis will be the test number, y will be the average 
# The color will change based on value of the average

ggplot(data = test_means,
       aes(x = Exam, y = Average,fill = Exam)) +
  # Tells R to plot the value associated with the x-axis
  geom_bar(stat="identity",
           show.legend = FALSE,
           aes(fill = 
                 ifelse(Average == max(Average),
                  "darkgreen","grey"))) +
  # A nice theme 
  theme_minimal() +
  # Specifies x and y axes labels and title for plot
  labs(x = "\nExam Number",
       y = "Score\n",
       title = "Exam Score Averages") +
  ylim(0,100) + 
  scale_fill_identity()

I know that there is a lot going on in the example, but it kind of looks nice, right?

Let’s go back to the example and add some filtering. Let’s make a new column that determines whether or not a student passed or failed the test.

When we first made the dataset we included a fifth column, FinalGrade that represents the students overall grade. We will then make a series of statements that will determine what the letter grade is, and then plot the result!

We will be using the |> or pipe operator to pass the contents of test_data into our arranging.

We will first use the mutate function which creates a new column based on the arguments you pass to it.

We will also be using case_when which acts as an ifelse statement but with multiple conditionals.

# Create a new column for final grade

final_grade <- 
  test_data |> 
  group_by(Student) |> 
  rowwise() |> 
  mutate(
    FinalGrade = mean(c_across(Exam_1:Exam_4))
  ) |> 
  ungroup() |> 
  mutate(
    LetterGrade=
      case_when(
      FinalGrade>=95~"A",
      FinalGrade>=90~"A-",
      FinalGrade>=87~"B+",
      FinalGrade>=84~"B",
      FinalGrade>=80~"B-",
      FinalGrade>=74~"C+",
      FinalGrade>=70~"C",
      FinalGrade>=67~"D+",
      FinalGrade>=64~"D",
      FinalGrade>=60~"D-",
      FinalGrade<60~"F")
    ) |> 
  group_by(LetterGrade) |> 
  count()

Our students didn’t do particularly well, but that is to be expected when we control the range their grades can be in!

Next, we will graph the data using ggplot, but this time we will pass the dataframe directly into the plot and remove the  x= and y= declarations

When we graph the results the letter grades will be backwards so we will need to use fct_rev to reverse them.

final_grade %>% 
  # Place Grades on X axis and count on Y axis
  # Color based on Letter Grade
  ggplot(aes(fct_rev(LetterGrade),n,fill=LetterGrade)) +
    geom_bar(stat="identity") +
    theme_minimal(base_size=12) +
    labs(x="",y="Count",
         title="Letter Grade Frequency") +
    # Flip the axes for better readability
    coord_flip() +
    # Remove the legend and the axis ticks
    theme(legend.position ="none",
          axis.ticks = element_blank()) +
    # Add the count to the bars
    geom_text(aes(label=n,
                  hjust=-.08)) +
  theme(
    axis.text.x = element_blank()
  )

Scatterplots

When I was first learning R in graduate school the class was given a task to plot points but in different colors depending on what gender the points came from. In base plotting this requires the use of not only the plot function but also the points function. It is entirely doable, but ggplot just handles it better!

Let’s generate some data where we have hypothetical participants take an experiment where their reaction time is measured as well as their mood is measured.

scatter <- 
  data.frame(
    Group = sample(c("G1","G2"),100,replace = T),
    RT = rnorm(100,800,150) |> round(2),
    TestScore = c(runif(50,60,100) |> round(2),
                runif(50,40,90) |> round(2)
                )
  )
scatter |> 
  head() |> 
  hux() |> 
  theme_article()
Group RT TestScore
G1 746 86.1
G1 685 97.4
G2 895 82.4
G2 663 65.6
G2 916 75.1
G2 913 77.8

Plot Sex against RT and have the colors be linked to the Group of the participant (point).

scatter %>% 
  ggplot(aes(TestScore,RT,color = Group)) +
    geom_point(aes(shape = Group)) +
    theme_minimal() +
    labs(x="\nTest Score",
         y="RT(ms)\n",
         title="RT and Test Scores",
         subtitle = "Participants had quicker reaction time in Group 2 compared to Group 1\n") + 
  theme(
    plot.title = element_text(size = 20, 
                              face = "bold"),
    plot.subtitle = element_text(face = "italic")
  )

No pattern is emerging from the (fake) data, but we can see a distinction between the different points.

Heatmaps

One of my favorite geom’s to use is geom_tile which creates a heatmap style plot.

Let’s create a fake TV show with fake episode titles and fake episode ratings.

Dave_TV <- 
  data.frame(
    Season = rep(paste0("S",1:6),each=12),
    Episode = as.factor(rep(1:12,6)),
    Title = str_to_title(paste0("The ",
                              sample(words,72)," ",
                              sample(words,72))),
    Ratings = c(runif(60,5.5,9),runif(12,7,9)) |> round(0),
    stringsAsFactors = F)

Dave_TV |> head() |> hux() |> theme_article()
Season Episode Title Ratings
S1 1 The East Lot 7
S1 2 The Family Support 6
S1 3 The Normal Cup 7
S1 4 The Luck Four 9
S1 5 The Perfect Section 6
S1 6 The Film Cook 6

Firstly, I want to take a moment to see how truly excellent some of the episode titles are:

title_samp <- sample(Dave_TV$Title,10)
title_samp
 [1] "The Prepare Strike"  "The Create Where"    "The Enjoy Laugh"    
 [4] "The Picture Figure"  "The Charge Now"      "The Serious Wife"   
 [7] "The Expense Mention" "The Shop Either"     "The Country Turn"   
[10] "The Luck Four"      

Anyways, now that our data is in the correct format we can plot it.

Dave_TV %>% 
  ggplot(aes(Season,Episode,fill = Ratings)) +
    geom_tile() +
    theme_minimal() +
    labs(x = "\n") +
    geom_text(aes(label = Ratings)) +
    theme(axis.ticks = element_blank())

There could be an entirely separate tutorial on just color choices in ggplot2.

Here is one last iteration that uses conditional values to color each episode or tile.

cols=c("Bad"="red2",
       "Garbage" ="darkblue",
       "Great" ="darkgreen",
       "Regular"="darkorange", 
       "Good" ="gold")

Dave_TV %>% 
  mutate(Quality=case_when(
      Ratings<5.0 ~ "Garbage",
      Ratings<6.5 ~ "Bad",
      Ratings<7.5 ~ "Regular",
      Ratings<8.5 ~ "Good",
      Ratings<10 ~ "Great")) %>% 
  ggplot(aes(Season,Episode,fill=Quality)) +
    geom_tile(color="black",size=.085) +
    theme_minimal() +
    geom_text(aes(label=Ratings)) +
    theme(axis.ticks = element_blank()) +
    scale_fill_manual(values=cols) +
    labs(x="",y="",
        title="Dave TV Episode Ratings",
        caption="Data from IAmDb)") +
  theme(
    plot.caption = element_text(face = "italic")
  )
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.

So there you have it, a very brief introduction to ggplot. As my own projects expand, I will include new sections or add more examples here.