Home > Enterprise >  How can I adjust facet width in plotly in R?
How can I adjust facet width in plotly in R?

Time:03-28

I'm currently in the process of creating a heatmap with plotly. Below is the sample dataset:

library(tidyverse)
library(plotly)
library(hrbrthemes)
set.seed(9999)
df <- data.frame(group.int = rep(c(rep("Prevention", 3), "Diagnosis", rep("Intervention", 2)), 6),
                int = rep(c("Prevention 1", "Prevention 2", "Prevention 3", "Diagnosis 1", "Intervention 1", "Intervention 2"), 6),
                group.outcome = c(rep("Efficacy", 12), rep("Safety", 18), rep("Cost-effectiveness", 6)),
                outcome = c(rep("Efficacy 1", 6), rep("Efficacy 2", 6), rep("Safety 1", 6), rep("Safety 2", 6), rep("Safety 3", 6), rep("Cost-effectiveness 1", 6)),
                n = sample(50:250, 36, rep = TRUE)
            )
df$group.int <- factor(df$group.int, levels = c("Prevention", "Diagnosis", "Intervention"))
df$group.outcome <- factor(df$group.outcome, levels = c("Efficacy", "Safety", "Cost-effectiveness"))

I want to make a heatmap based on the variable outcome against int, with n as the fill of each heatmap cell. Here is the desired plot:

enter image description here

I tried using ggplotly from the created ggplot:

plotly.df <- ggplot(df, 
                aes(x = int, y = outcome, fill= n))   
                geom_tile()  
                scale_fill_gradient(low="white", high="darkred")  
                scale_y_discrete(position = "right")  
                facet_grid(group.outcome ~ group.int,
                    scales = "free", space = "free", switch = "x")  
                theme_bw()  
                theme(axis.ticks = element_blank(),
                    legend.position = "left",
                    strip.placement = "outside", 
                    strip.background = element_blank(),
                    panel.grid = element_blank(),
                    panel.border = element_blank(),
                    legend.key.height = unit(6, "lines"),
                    legend.title = element_blank(),
                    panel.spacing = unit(0, "lines"),
                    axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))
ggplotly(plotly.df)

However, ggplotly seems to ignore space = "free" in facet_grid, so the size of the cells are not proportional:

enter image description here

As an alternative, I tried using subplot with plot_ly:

vals <- unique(scales::rescale(c(df$n)))
o <- order(vals, decreasing = FALSE)
cols <- scales::col_numeric("BuGn", domain = NULL)(vals)
colz <- setNames(data.frame(vals[o], cols[o]), NULL)

plot.prev <- filter(df, group.int == "Prevention") %>%
                plot_ly(x = ~int, y = ~outcome, z = ~n, colorscale = colz, type = "heatmap") %>%
                    layout(xaxis = list(title = 'Prevention', 
                            tickangle=315), 
                            yaxis = list(title = "Outcome"))) %>%
                hide_colorbar()
                            
plot.diag <- filter(df, group.int == "Diagnosis") %>%
                plot_ly(x = ~int, y = ~outcome, z = ~n, colorscale = colz, type = "heatmap") %>%
                    layout(xaxis = list(title = 'Diagnosis', 
                            tickangle=315), 
                            yaxis = list(ticks = "Outcome", showticklabels = FALSE)) %>%
                hide_colorbar()
plot.int <- filter(df, group.int == "Intervention") %>%
                plot_ly(x = ~int, y = ~outcome, z = ~n, colorscale = colz, type = "heatmap") %>%
                    layout(xaxis = list(title = 'Intervention', 
                            tickangle=315), 
                            yaxis = list(ticks = "Outcome", showticklabels = FALSE))
subplot(plot.prev, plot.diag, plot.int, widths = c(3/6, 1/6, 2/6), margin = 0)

enter image description here

However, I find it difficult to change axis labels and to add annotations outside the plot margin with subplot (note that I need to annotate Prevention, Diagnosis, and Treatment for the x-axis facets; and Efficacy, Safety, and Cost-effectiveness for the y-axis facets).

Is there a way to adjust facet widths with ggplotly? Or alternatively, is there an easy way to annotate subplot outside the plot margins and to change axis labels in subplot?

Thank you very much in advance

CodePudding user response:

You don't have to reinvent the wheel. Go back to the first ggplotly object. Domain is what plotly uses to govern the spaces each facet (or as it is in plotly-subplot). You can retrieve this information by assigning the ggplotly graph to an object and calling plotly_json.

However, I've worked around layout shortcuts before. You can retrieve and modify the domains like this:

p = ggplotly(plotly.df)

p$x$layout$xaxis$domain <- c(0, 1/2) # 6 blocks, 3 in this group 1/6 * 3
p$x$layout$xaxis2$domain <- c(1/2, 2/3) # start at previous position, 1 in this group
p$x$layout$xaxis3$domain <- c(2/3, 1) # remaining space

p$x$layout$yaxis3$domain <- c(0, 1/6) # 1 block in bottom chunks
p$x$layout$yaxis2$domain <- c(1/6, 2/3) # 3 in mid group
p$x$layout$yaxis$domain <- c(2/3, 1) # remaining space
p

That got me this far:

enter image description here

Your bottom labels are still aligned, but the top is not. Additionally, the left bottom label is cut off.

To fix the top labels I used plotly_json to figure out where they were at then used the guess-and-check method. To adjust for labels, I modified the margin.

# prevention
p$x$layout$annotations[[3]]$x <- 1/4

# diagnosis
p$x$layout$annotations[[4]]$x <- 7/12

p %>% layout(margin = list(t = 40, r = 50, b = 80, l = 130))

enter image description here

  • Related