Menggabungkan legenda dalam subplot plotly

Aug 28 2020

Saya memiliki beberapa kelompok di mana untuk masing-masing saya memiliki beberapa kelas yang saya ukur nilai kontinu:

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"))

Tidak setiap grup dalam data memiliki kelas yang sama, atau secara berbeda, setiap grup memiliki subset dari semua kelas.

Saya mencoba untuk menghasilkan R plotlykurva densitas untuk setiap kelompok, warna-kode oleh kelas, dan kemudian menggabungkan mereka semua untuk plot tunggal dengan menggunakan plotly's subplotfungsi.

Inilah yang saya lakukan:

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)

Pemberian yang mana:

Masalah yang ingin saya perbaiki adalah:

  1. Buat legenda muncul hanya sekali (sekarang berulang untuk setiap grup) menggabungkan semua kelas
  2. Buat judul muncul di setiap subplot, bukan hanya untuk plot terakhir di sekarang. (Saya tahu bahwa saya bisa saja memiliki nama grup sebagai judul sumbu x tetapi saya lebih suka menghemat ruang itu karena pada kenyataannya saya memiliki lebih dari 3 grup)

Jawaban

2 BappaDas Aug 28 2020 at 13:45

Anda dapat menggunakan kode berikut

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')
)

yang memberi saya plot berikut

3 ismirsehregal Aug 28 2020 at 15:06

Menggunakannya plot_ly()sedikit rumit, setidaknya jika Anda ingin tetap menggunakan colorargumen untuk menghasilkan banyak jejak dari data.

Anda perlu menentukan legendgroupmemperhitungkan variabel kelas Anda. ini legendgroupnamun tidak menggabungkan item legenda menjadi satu (hanya kelompok mereka).

Karenanya untuk menghindari entri duplikat dalam legenda yang perlu Anda setel showlegend = FALSEuntuk jejak yang ingin Anda sembunyikan terkait legenda.

Karena faktanya, Anda tidak menggunakan loop (atau lapply) untuk membuat satu jejak subplot, kami tidak dapat mengontrol visibilitas setiap jejak saat membuat plot (melalui colorargumen yang disebutkan di atas - kami dapat menyembunyikan atau menunjukkan semua jejak plot_lypanggilan - melalui add_tracekita dapat mengontrolnya untuk setiap jejak secara individual). Oleh karena itu saya menetapkan showlegend = TRUEhanya untuk plot pertama dan memaksanya untuk menampilkan semua kelas yang tersedia melalui data dummy. Silakan lihat yang berikut ini:

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)

Pendekatan lain (menghindari solusi data dummy) adalah membuat setiap jejak dalam satu lingkaran (atau melalui lapply) dan mengontrol visibilitas legenda sesuai dengan kemunculan pertama item.

Selain itu, saya pikir itu harus memungkinkan untuk mengontrol visibilitas item legenda menggunakan ?plotly::style. Namun, saya tidak dapat mengontrolnya untuk satu jejak saat ini. Saya mengajukan masalah di sini .

Mengenai judul untuk subplot, silakan lihat ini .