Search code examples
rgraphggplot2ggvis

ggvis: Interaction Plot


I used to make interaction plot with ggplot2 and code is given below. Now I want to reproduce the same plot with ggvis as shown below which not the same as ggplto2 output. How can I get the same plot with ggvis?

library(ggplot2)
p <- qplot(as.factor(dose), len, data=ToothGrowth, geom = "boxplot", color = supp) + theme_bw()
p <- p + labs(x="Dose", y="Response")
p <- p + stat_summary(fun.y = mean, geom = "point", color = "blue", aes(group=supp))
p <- p + stat_summary(fun.y = mean, geom = "line", aes(group = supp))
p <- p  + theme(axis.title.x = element_text(size = 12, hjust = 0.54, vjust = 0))
p <- p  + theme(axis.title.y = element_text(size = 12, angle = 90,  vjust = 0.25))
print(p)

enter image description here

library(ggvis)
ggvis(data=ToothGrowth, x= ~as.factor(dose), y= ~len, fill= ~supp, stroke = ~supp) %>% 
  layer_points(shape=~supp) %>% 
  layer_lines(fillOpacity=0)

enter image description here


Solution

  • The basic problem, when trying to implement this in ggvis, is that there is no position = dodge option like in ggplot2, and therefore the boxplots for different supp values cannot be plotted at the same x coordinate. So indexing the x axis by as.factor(dose) doesn't appear to be an option. However, what we can do is use an integer index of length equal to the number of unique dose values, and then manually offset the x position of the data to the left or right, according to the supp value:

    library(ggvis)
    library(dplyr)
    d <- ToothGrowth
    d$xpos <- as.integer(factor(d$dose)) + ifelse(d$supp == "OJ", -.2, .2)
    

    So we can now use x = ~xpos to plot the boxplots at the right positions. The next step is to define the data holding the means used to plot the points that are connected by lines.

    means <- d %>% group_by(dose, supp) %>% summarise(len = mean(len))
    means$xpos <- as.integer(factor(means$dose))
    means <- group_by(means, supp) # The grouping is needed for layer_paths()
    

    The graph can now be obtained as

    ggvis(d, x = ~xpos, y = ~len, stroke = ~supp) %>% 
        layer_boxplots() %>%
        layer_points(data = means, fill := "blue") %>%
        layer_paths(data = means)
    

    Now we have the problem that the x position of the plots will be at 1, 2, 3 rather than the actual dose values. This is not very straightforward to overcome because add_axis() gives no way to re-label the axis ticks (also, we couldn't have used the actual dose values instead of 1, 2, 3 in the first place because that would have placed the boxplots at dose values 0.5 and 1 closer to each other than the ones at dose values 1 and 2). This can be overcome by a not so elegant hack, which is to add an axis for each single dose value. The function add_axis() gives a way to modify the axis properties (which includes the labels) but it will use the same label for the whole axis, since the properties apply to the whole axis. So by adding an axis for each dose value, we can manipulate the labels one by one. This looks like

    ggvis(d, x = ~xpos, y = ~len, stroke = ~supp) %>% 
        layer_boxplots() %>%
        layer_points(data = means, fill := "blue") %>%
        layer_paths(data = means) %>%
        add_axis("x", title = "Dose", 
            values = c(1, 1), # For some reason values of length 1 don't work...
            properties = axis_props(labels = list(text = "0.5"))) %>%
        add_axis("x", title = "", 
            values = c(2, 2), 
            properties = axis_props(labels = list(text = "1"))) %>%
        add_axis("x", title = "", 
            values = c(3, 3), 
            properties = axis_props(labels = list(text = "2"))) %>%     
        add_axis("y", title = "Response")
    

    Alternatively, you can use a loop for these so you don't have to type the same thing over and over

    labs <- data.frame(dose = unique(d$dose))
    labs$xpos <- as.integer(factor(labs$dose))
    
    v <- ggvis(d, x = ~xpos, y = ~len, stroke = ~supp) %>% 
        layer_boxplots() %>%
        layer_points(data = means, fill := "blue") %>%
        layer_paths(data = means) %>%
        add_axis("x", title = "Dose", ticks = 0) %>%
        add_axis("y", title = "Response")
    
    for (i in 1:nrow(labs)) {
        v <- add_axis(v, "x", title = "", values = rep(labs[i, "xpos"], 2),
            properties = axis_props(labels = list(text = as.character(labs[i, "dose"]))))
    }
    

    The final outcome looks like this

    enter image description here