在情节子图中合并图例

Posted

技术标签:

【中文标题】在情节子图中合并图例【英文标题】:Merging legends in plotly subplot 【发布时间】:2020-12-17 00:29:39 【问题描述】:

我有几个组,每个组都有几个类,我测量了连续值:

set.seed(1)

df <- data.frame(value = c(rnorm(100,1,1), rnorm(100,2,1), rnorm(100,3,1),
                           rnorm(100,3,1), rnorm(100,1,1), rnorm(100,2,1),
                           rnorm(100,2,1), rnorm(100,3,1), rnorm(100,1,1)),
                 class = c(rep("c1",100), rep("c2",100), rep("c3",100),
                           rep("c2",100), rep("c4",100), rep("c1",100),
                           rep("c4",100), rep("c3",100), rep("c2",100)),
                 group = c(rep("g1",300), rep("g2",300), rep("g3",300)))

df$class <- factor(df$class, levels =c("c1","c2","c3","c4"))
df$group <- factor(df$group, levels =c("g1","g2","g3"))

并非数据中的每个组都具有相同的类,或者换句话说,每个组都具有所有类的子集。

我正在尝试为每个组生成Rplotly 密度曲线,按类别进行颜色编码,然后使用plotlysubplot 函数将它们全部组合到一个图上。

这就是我正在做的:

library(dplyr)
library(ggplot2)
library(plotly)


set.seed(1)

df <- data.frame(value = c(rnorm(100,1,1), rnorm(100,2,1), rnorm(100,3,1),
                           rnorm(100,3,1), rnorm(100,1,1), rnorm(100,2,1),
                           rnorm(100,2,1), rnorm(100,3,1), rnorm(100,1,1)),
                 class = c(rep("c1",100), rep("c2",100), rep("c3",100),
                           rep("c2",100), rep("c4",100), rep("c1",100),
                           rep("c4",100), rep("c3",100), rep("c2",100)),
                 group = c(rep("g1",300), rep("g2",300), rep("g3",300)))

df$class <- factor(df$class, levels =c("c1","c2","c3","c4"))
df$group <- factor(df$group, levels =c("g1","g2","g3"))

plot.list <- lapply(c("g1","g2","g3"), function(g)
  density.df <- do.call(rbind,lapply(unique(dplyr::filter(df, group == g)$class),function(l)
    ggplot_build(ggplot(dplyr::filter(df, group == g & class == l),aes(x=value))+geom_density(adjust=1,colour="#A9A9A9"))$data[[1]] %>%
      dplyr::select(x,y) %>% dplyr::mutate(class = l)))
  plot_ly(x = density.df$x, y = density.df$y, type = 'scatter', mode = 'lines',color = density.df$class) %>%
    layout(title=g,xaxis = list(zeroline = F), yaxis = list(zeroline = F))
)
subplot(plot.list,nrows=length(plot.list),shareX=T)

这给出了:

我想解决的问题是:

    让图例只出现一次(现在它对每个组重复)合并所有类 让标题出现在每个子图中,而不是只出现在最后一个图上。 (我知道我可以简单地将组名作为 x 轴标题,但我宁愿节省空间,因为实际上我有超过 3 个组)

【问题讨论】:

Here plotly.js 的相关 FR 和 here r-plotly 的相关问题可以找到。 【参考方案1】:

使用plot_ly() 有点棘手,至少如果您想坚持使用color 参数从数据中生成多个跟踪。

您需要定义一个legendgroup 并考虑您的类变量。 这 legendgroup 但是不会将图例项合并为一个(它只是将它们分组)。

因此,为避免图例中出现重复条目​​,您需要为要隐藏的有关图例的那些痕迹设置 showlegend = FALSE

由于您不使用循环(或 lapply)来创建子图的单个轨迹,因此在生成图时我们无法控制每个轨迹的可见性(通过上面提到的 color参数 - 我们可以隐藏或显示 plot_ly 调用的所有痕迹 - 通过 add_trace 我们可以单独控制每个痕迹)。因此我只为第一个图设置了showlegend = TRUE,并强制它通过虚拟数据显示所有可用的类。请参阅以下内容:

set.seed(1)

df <- data.frame(value = c(rnorm(100,1,1), rnorm(100,2,1), rnorm(100,3,1),
                           rnorm(100,3,1), rnorm(100,1,1), rnorm(100,2,1),
                           rnorm(100,2,1), rnorm(100,3,1), rnorm(100,1,1)),
                 class = c(rep("c1",100), rep("c2",100), rep("c3",100),
                           rep("c2",100), rep("c4",100), rep("c1",100),
                           rep("c4",100), rep("c3",100), rep("c2",100)),
                 group = c(rep("g1",300), rep("g2",300), rep("g3",300)))

df$class <- factor(df$class, levels =c("c1","c2","c3","c4"))
df$group <- factor(df$group, levels =c("g1","g2","g3"))

library(dplyr)
library(ggplot2)
library(plotly)

plot.list <- lapply(c("g1","g2","g3"), function(g)
  density.df <- do.call(rbind,lapply(unique(dplyr::filter(df, group == g)$class),function(l)
    ggplot_build(ggplot(dplyr::filter(df, group == g & class == l),aes(x=value))+geom_density(adjust=1,colour="#A9A9A9"))$data[[1]] %>%
      dplyr::select(x,y) %>% dplyr::mutate(class = l)))
  
  p <- plot_ly(data = density.df, x = ~x, y = ~y, type = 'scatter', mode = 'lines', color = ~class, legendgroup = ~class, showlegend = FALSE) %>%
    layout(xaxis = list(zeroline = F), yaxis = list(zeroline = FALSE)) %>%
    add_annotations(
      text = g,
      x = 0.5,
      y = 1.1,
      yref = "paper",
      xref = "paper",
      xanchor = "middle",
      yanchor = "top",
      showarrow = FALSE,
      font = list(size = 15)
    )
  if(g == "g1")
    dummy_df <- data.frame(class = unique(df$class))
    dummy_df$x <- density.df$x[1]
    dummy_df$y <- density.df$y[1]
    p <- add_trace(p, data = dummy_df, x = ~x, y = ~y, color = ~class, type = "scatter", mode = "lines", showlegend = TRUE, legendgroup = ~class, hoverinfo = 'none')
  
  p
)

subplot(plot.list, nrows = length(plot.list), shareX = TRUE)

另一种方法(避免使用虚拟数据解决方法)是在循环中(或通过 lapply)创建每个跟踪,并根据项目的第一次出现来控制它的图例可见性。

此外,我认为应该可以使用?plotly::style 控制图例项目的可见性。但是,我目前无法控制单个跟踪。我提交了一个问题here。

关于子图的标题,请参阅this。

【讨论】:

【参考方案2】:

您可以使用以下代码

library(tidyverse)
library(plotly)

ggplotly(
  ggplot(df, aes(x=value, col = class)) + 
  geom_density(adjust=1) + 
  facet_wrap(~group, ncol = 1) +
    theme_minimal() + 
    theme(legend.position = 'top')
)

这给了我以下情节

【讨论】:

以上是关于在情节子图中合并图例的主要内容,如果未能解决你的问题,请参考以下文章

如何摆脱情节线图中的潦草线条?

如何在情节中摆脱 scatter_matrix 的所有子图中的网格线?

如何在 Matplotlib 的子图中独立绘制相同的图形? [复制]

次要情节之外的主要人物图例

如何在R子图中添加茎叶图

在情节图中标记特殊日子