How to add marginal totals to a ggplot2 facet_grid plot using a different geom

Viewed 169

Say we have a simple facet_grid plot:

library(ggplot2)

ggplot(mtcars, aes(hp, mpg)) +
  geom_point() +
  facet_grid(cyl ~ am, switch = "y")

I would like to add marginal totals (i.e., a count of the records for each row and column) for both cyl and am to the plot. However, I would like to change the underlying geom. Ideally, I would like these to be text. This might look similar to this:

enter image description here

Is this possible? I have tried a few things with facet_grid(..., margins = TRUE), but not yet successful. Maybe this would be easier with pre-computed totals and then combining graphics with grid?

1 Answers

I don't know of an easy way to do this. There is a difficult way, which includes a lot of data reshaping.

Essentially, you create separate data frames for your right margin, bottom margin, and total, then bind them onto your main data frame, row-wise. En route, you also have to add an indicator column to say whether the row is a margin, and another to provide a label with the counts. Finally, the faceting variables have to be converted to factors:

library(ggplot2)
library(dplyr)

data <- mtcars %>% 
          mutate(label = "",
                 plot = TRUE,
                 cyl   = as.character(cyl), 
                 am    = as.character(am)) %>% 
          select(cyl, am, hp, mpg, label, plot)

mar1 <- data %>% 
  group_by(cyl) %>% 
  summarize(am = "(All)", hp = mean(range(data$hp)),
            mpg = mean(range(data$mpg)), 
            label = as.character(n()), plot = FALSE)

mar2 <- data %>% 
  group_by(am) %>% 
  summarize(cyl = "(All)", hp = mean(range(data$hp)), 
            mpg = mean(range(data$mpg)), 
            label = as.character(n()), plot = FALSE)

mar3 <- data %>% 
  summarize(cyl = "(All)", am = "(All)", 
            hp = mean(range(data$hp)), 
            mpg = mean(range(data$mpg)),
            label = as.character(n()), plot = FALSE)


big_data <- bind_rows(data, mar1, mar2, mar3) %>%
  mutate(cyl = factor(cyl, levels = c("4", "6", "8", "(All)")),
         am = factor(am, levels = c("0", "1", "(All)")))

With that done, you can plot the result using big geom_labels (with effectively infinite padding) for your margins.

ggplot(big_data[big_data$plot,], aes(hp, mpg, label = label)) +
  geom_point() +
  geom_label(data = big_data[!big_data$plot,], size = 15, 
             label.padding = unit(1, "npc")) +
  facet_grid(cyl ~ am, switch = "y", drop = FALSE)

enter image description here

Related