temp
temp

Reputation: 82

Convert two ggplots into one

I am facing some problem to have one plot instead of two from separate data frames. I explained the situation a bit below. The data frames look like:

df1 <- structure(list(value = c(9921L, 21583L, 11822L, 1054L, 13832L, 
16238L, 13838L, 20801L, 20204L, 13881L, 19935L, 13829L, 14012L, 
20654L, 13862L, 21191L, 3777L, 15552L, 13817L, 20428L, 16850L, 
21003L, 11072L, 22477L, 12321L, 12856L, 16295L, 11431L, 13469L, 
14680L, 10552L, 15272L, 9132L, 9374L, 15123L, 22754L, 10363L, 
12160L, 13729L, 11151L, 11451L, 11272L, 14900L, 14688L, 17133L, 
7315L, 7268L, 6262L, 72769L, 7650L, 16389L, 13027L, 7134L, 6465L, 
6490L, 15183L, 7201L, 14070L, 11210L, 10146L), limit = structure(c(1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L), .Label = c("1Mbit", 
"5Mbit", "10Mbit"), class = "factor")), class = "data.frame", row.names = c(NA, 
-60L))

df2 <- structure(list(value = c(37262L, 39881L, 30914L, 32976L, 28657L, 
39364L, 39915L, 30115L, 29326L, 36199L, 37976L, 36694L, 33718L, 
36945L, 33182L, 35866L, 34188L, 33426L, 32804L, 34986L, 29355L, 
30470L, 37420L, 26465L, 28975L, 29144L, 27491L, 30507L, 27146L, 
26257L, 31231L, 30521L, 30370L, 31683L, 33774L, 35654L, 34172L, 
38554L, 38030L, 33439L, 34817L, 31278L, 33579L, 31175L, 31001L, 
29908L, 31658L, 33381L, 28709L, 34794L, 34154L, 30157L, 33362L, 
30363L, 31097L, 29116L, 27703L, 31229L, 30196L, 30077L), limit = structure(c(3L, 
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 
3L, 3L, 3L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = c("180ms", 
"190ms", "200ms"), class = "factor")), class = "data.frame", row.names = c(NA, 
-60L))

from the data frames above, I have these plots:

limit_bw <- factor(df1$limit, levels = c("1Mbit", "5Mbit", "10Mbit"))
limit_lt <- factor(df2$limit, levels = c("200ms", "190ms", "180ms"))

(to use them sequentially)

bw_line <- ggplot(df1, aes(x = limit_bw, y = value, group=1)) + geom_quantile(method = "loess")
lt_line <- ggplot(df2, aes(x = limit_lt, y = value, group=1)) + geom_quantile(method = "loess")

(I actually have many data so I used geom_quantile())

And also two plots in a grid using rbind/cbind (which is not I want now):

grid.draw(rbind(ggplotGrob(ggplot(df1, aes(limit_bw,value,group=1)) + geom_quantile(method = "loess") + labs(title = "value vs bw",x="bandwidth",y="value")),
ggplotGrob(ggplot(df2, aes(limit_lt, value, group = 1)) + geom_quantile(method="loess") + labs(title="value vs latency", x="latency", y="value")), size = "last"))

I am seeking your help to merge them together into one plot (putting bw_line and lt_line together in the same graph) showing two x-axes either at the top and bottom or two axes in the bottom mentioning their title. Please note, the value has different range for each of the data set. However I need to show two y-axes for separate ranges for each data frame or may be one y-axis showing all the values (min to max) from the both data frame.

I actually seen one very close solution here from @RichieCotton but could not figure out for my data since I have some factors instead of integer values.

I really appreciate your help. Thank you.

Upvotes: 3

Views: 258

Answers (2)

Uwe
Uwe

Reputation: 42544

Here is a different approach to create a single plot from the two datasets which avoids to combine both datasets into one and deal with the factors of limit. df1, df2, limit_bw, and limit_lt are used as given by the OP.

The plot is refined in three steps.

1. Common x axis, common y scale

library(ggplot2)
ggplot() + aes(y = value) +
  geom_quantile(aes(x = as.integer(limit_bw), colour = "bw"), df1, method = "loess") + 
  geom_quantile(aes(x = as.integer(limit_lt), colour = "lt"), df2, method = "loess") +
  scale_x_continuous("limit",
    breaks = 1:nlevels(limit_bw), 
    labels = paste(levels(limit_bw), levels(limit_lt), sep = "\n")) +
  scale_colour_discrete(NULL)

enter image description here

2. Separate x axes, common y scale

library(ggplot2)
ggplot() + aes(y = value) +
  geom_quantile(aes(x = as.integer(limit_bw), colour = "bw"), df1, method = "loess") + 
  geom_quantile(aes(x = as.integer(limit_lt), colour = "lt"), df2, method = "loess") +
  scale_x_continuous("limit",
                     breaks = 1:nlevels(limit_bw), 
                     labels = levels(limit_bw), 
                     sec.axis = dup_axis(labels = levels(limit_lt))) +
  scale_colour_manual(NULL, values = c(bw = "blue", lt = "red")) +
  theme(axis.text.x.bottom = element_text(color = "blue"),
        axis.text.x.top    = element_text(color = "red"))

enter image description here

3. Separate x axes, separate y axes

Here, the y-values of the second dataset are scaled such that the min and max values of the two datasets will coincide.

# compute scaling factor and offset
library(magrittr)   # used to improve readability
bw_rng <- loess(df1$value ~ as.integer(limit_bw)) %>% fitted() %>% range()
lt_rng <- loess(df2$value ~ as.integer(limit_lt)) %>% fitted() %>% range()
scl <- diff(bw_rng) / diff(lt_rng)
ofs <- bw_rng[1] - scl * lt_rng[1]
library(ggplot2)
ggplot() +
  geom_quantile(aes(x = as.integer(limit_bw), y = value, colour = "bw"), 
                df1, method = "loess") + 
  geom_quantile(aes(x = as.integer(limit_lt), y = scl * value + ofs, colour = "lt"), 
                df2, method = "loess") +
  scale_x_continuous("limit",
                     breaks = 1:nlevels(limit_bw), 
                     labels = levels(limit_bw), 
                     sec.axis = dup_axis(labels = levels(limit_lt))) +
  scale_y_continuous(sec.axis = sec_axis(~ (. - ofs) / scl)) +
  scale_colour_manual(NULL, values = c(bw = "blue", lt = "red")) +
  theme(axis.text.x.bottom = element_text(color = "blue"),
        axis.text.x.top    = element_text(color = "red"),
        axis.text.y.left   = element_text(color = "blue"),
        axis.text.y.right  = element_text(color = "red"))

enter image description here

Upvotes: 5

Jon Spring
Jon Spring

Reputation: 66540

I think it's probably easiest to approach this by combining the data into one data frame first. Here I make combined x-values and map your data to those. Then we map as usual, with the addition of a secondary y axis.

library(tidyverse); library(forcats)

# Create shared x axis and combine data frames
limit_combo <- data.frame(level_num = 1:3, 
                          level = as_factor(c("1Mbit\n200ms",
                                              "5Mbit\n190ms",
                                              "10Mbit\n180ms"))) 
df1b <- df1 %>%
  mutate(level_num = limit %>% as.numeric) %>%
  left_join(limit_combo)
df2b <- df2 %>%
  mutate(level_num = 4 - (limit %>% as.numeric)) %>%
  left_join(limit_combo)
df3 <- bind_rows(df1b, df2b, .id = "plot") %>%
  mutate(plot = if_else(plot == "1", "bw", "lt"))

# plot with adjusted y values and second axis for reference
ggplot(df3, aes(x = level, 
                y = value * if_else(plot == "lt", 0.44, 1), 
                group=plot, color = plot)) + 
  geom_quantile(method = "loess") +
  scale_y_continuous("value", sec.axis = sec_axis(~./0.44)) +
  theme(axis.text.y.left  = element_text(color = "#F8766D"),
        axis.text.y.right = element_text(color = "#00BFC4"))

enter image description here

Upvotes: 7

Related Questions