使用带有 ggplotly 的 facet_wrap 的第一个和最后一个面大于中间面

Posted

技术标签:

【中文标题】使用带有 ggplotly 的 facet_wrap 的第一个和最后一个面大于中间面【英文标题】:First and last facets using facet_wrap with ggplotly are larger than middle facets 【发布时间】:2020-08-18 05:09:30 【问题描述】:

使用样本数据:

library(tidyverse)
library(plotly)

myplot <- diamonds %>% ggplot(aes(clarity, price)) +
  geom_boxplot() +
  facet_wrap(~ clarity, ncol = 8, scales = "free", strip.position = "bottom") +
  theme(axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank())

ggplotly(myplot)

返回类似:

与第一个和最后一个相比,内部刻面的缩放比例非常惊人,并且有很多额外的填充。我试图从这些问题中找到解决方案:

ggplotly not working properly when number are facets are more

R: facet_wrap does not render correctly with ggplotly in Shiny app

经过反复试验,我在theme() 中使用了panel.spacing.x = unit(-0.5, "line"),它看起来好多了,很多额外的填充消失了,但内部刻面仍然明显更小。

还有一个额外的问题,但不那么重要,条形标签在ggplotly() 调用中位于顶部,而我将它们设置在底部。似乎是一个持续存在的问题here,有没有人有一个hacky解决方法?

编辑:在我的真实数据集中,我需要每个方面的 y 轴标签,因为它们的比例非常不同,所以我将它们保留在示例中,这就是我需要 facet_wrap 的原因。我的真实数据集的屏幕截图以供解释:

【问题讨论】:

【参考方案1】:

更新答案(2):只需使用fixfacets()

我已经组合了一个函数 fixfacets(fig, facets, domain_offset) 来实现:

...通过使用这个:

f &lt;- fixfacets(figure = fig, facets &lt;- unique(df$clarity), domain_offset &lt;- 0.06)

...进入这个:

这个函数现在在构面数量方面应该非常灵活。

完整代码:

library(tidyverse)
library(plotly)

# YOUR SETUP:

df <- data.frame(diamonds)

df['price'][df$clarity == 'VS1', ] <- filter(df['price'], df['clarity']=='VS1')*2

myplot <- df %>% ggplot(aes(clarity, price)) +
  geom_boxplot() +
  facet_wrap(~ clarity, scales = 'free', shrink = FALSE, ncol = 8, strip.position = "bottom", dir='h') +
  theme(axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank())
fig <- ggplotly(myplot)

# Custom function that takes a ggplotly figure and its facets as arguments.
# The upper x-values for each domain is set programmatically, but you can adjust
# the look of the figure by adjusting the width of the facet domain and the 
# corresponding annotations labels through the domain_offset variable
fixfacets <- function(figure, facets, domain_offset)

  # split x ranges from 0 to 1 into
  # intervals corresponding to number of facets
  # xHi = highest x for shape
  xHi <- seq(0, 1, len = n_facets+1)
  xHi <- xHi[2:length(xHi)]

  xOs <- domain_offset

  # Shape manipulations, identified by dark grey backround: "rgba(217,217,217,1)"
  # structure: p$x$layout$shapes[[2]]$
  shp <- fig$x$layout$shapes
  j <- 1
  for (i in seq_along(shp))
    if (shp[[i]]$fillcolor=="rgba(217,217,217,1)" & (!is.na(shp[[i]]$fillcolor)))
       #$x$layout$shapes[[i]]$fillcolor <- 'rgba(0,0,255,0.5)' # optionally change color for each label shape
       fig$x$layout$shapes[[i]]$x1 <- xHi[j]
       fig$x$layout$shapes[[i]]$x0 <- (xHi[j] - xOs)
       #fig$x$layout$shapes[[i]]$y <- -0.05
       j<-j+1
    
  

  # annotation manipulations, identified by label name
  # structure: p$x$layout$annotations[[2]]
  ann <- fig$x$layout$annotations
  annos <- facets
  j <- 1
  for (i in seq_along(ann))
    if (ann[[i]]$text %in% annos)
       # but each annotation between high and low x,
       # and set adjustment to center
       fig$x$layout$annotations[[i]]$x <- (((xHi[j]-xOs)+xHi[j])/2)
       fig$x$layout$annotations[[i]]$xanchor <- 'center'
       #print(fig$x$layout$annotations[[i]]$y)
       #fig$x$layout$annotations[[i]]$y <- -0.05
       j<-j+1
    
  

  # domain manipulations
  # set high and low x for each facet domain
  xax <- names(fig$x$layout)
  j <- 1
  for (i in seq_along(xax))
    if (!is.na(pmatch('xaxis', lot[i])))
      #print(p[['x']][['layout']][[lot[i]]][['domain']][2])
      fig[['x']][['layout']][[xax[i]]][['domain']][2] <- xHi[j]
      fig[['x']][['layout']][[xax[i]]][['domain']][1] <- xHi[j] - xOs
      j<-j+1
    
  

  return(fig)


f <- fixfacets(figure = fig, facets <- unique(df$clarity), domain_offset <- 0.06)
f

更新答案(1):如何以编程方式处理每个元素!

您需要进行一些编辑以满足您在保持每个方面的缩放比例和修复奇怪的布局方面的需要的图形元素是:

    x标签注释通过fig$x$layout$annotations, x 标签形状通过fig$x$layout$shapes,和 每个刻面沿 x 轴通过fig$x$layout$xaxis$domain 开始和停止的位置

唯一真正的挑战是在许多其他形状和注释中引用正确的形状和注释。下面的代码 sn-p 将执行此操作以生成以下图:

代码 sn-p 可能需要对每个案例在方面名称和名称数量方面进行一些仔细的调整,但代码本身是非常基本的,所以你不应该对此有任何问题。有时间我会自己打磨一下。

完整代码:

ibrary(tidyverse)
library(plotly)

# YOUR SETUP:

df <- data.frame(diamonds)

df['price'][df$clarity == 'VS1', ] <- filter(df['price'], df['clarity']=='VS1')*2

myplot <- df %>% ggplot(aes(clarity, price)) +
  geom_boxplot() +
  facet_wrap(~ clarity, scales = 'free', shrink = FALSE, ncol = 8, strip.position = "bottom", dir='h') +
  theme(axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank())
#fig <- ggplotly(myplot)

# MY SUGGESTED SOLUTION:

# get info about facets
# through unique levels of clarity
facets <- unique(df$clarity)
n_facets <- length(facets)

# split x ranges from 0 to 1 into
# intervals corresponding to number of facets
# xHi = highest x for shape
xHi <- seq(0, 1, len = n_facets+1)
xHi <- xHi[2:length(xHi)]

# specify an offset from highest to lowest x for shapes
xOs <- 0.06

# Shape manipulations, identified by dark grey backround: "rgba(217,217,217,1)"
# structure: p$x$layout$shapes[[2]]$
shp <- fig$x$layout$shapes
j <- 1
for (i in seq_along(shp))
  if (shp[[i]]$fillcolor=="rgba(217,217,217,1)" & (!is.na(shp[[i]]$fillcolor)))
     #fig$x$layout$shapes[[i]]$fillcolor <- 'rgba(0,0,255,0.5)' # optionally change color for each label shape
     fig$x$layout$shapes[[i]]$x1 <- xHi[j]
     fig$x$layout$shapes[[i]]$x0 <- (xHi[j] - xOs)
     j<-j+1
  


# annotation manipulations, identified by label name
# structure: p$x$layout$annotations[[2]]
ann <- fig$x$layout$annotations
annos <- facets
j <- 1
for (i in seq_along(ann))
  if (ann[[i]]$text %in% annos)
     # but each annotation between high and low x,
     # and set adjustment to center
     fig$x$layout$annotations[[i]]$x <- (((xHi[j]-xOs)+xHi[j])/2)
     fig$x$layout$annotations[[i]]$xanchor <- 'center'

     j<-j+1
  


# domain manipulations
# set high and low x for each facet domain
lot <- names(fig$x$layout)
j <- 1
for (i in seq_along(lot))
  if (!is.na(pmatch('xaxis', lot[i])))
    #print(p[['x']][['layout']][[lot[i]]][['domain']][2])
    fig[['x']][['layout']][[lot[i]]][['domain']][2] <- xHi[j]
    fig[['x']][['layout']][[lot[i]]][['domain']][1] <- xHi[j] - xOs
    j<-j+1
  


fig

基于内置功能的初步答案


由于许多变量的值非常不同,似乎无论如何你都会得到一个具有挑战性的格式,这意味着要么

    面将具有不同的宽度,或者 标签会覆盖各个方面或太小而无法阅读,或者 如果没有滚动条,该图将太宽而无法显示。

所以我的建议是重新调整您的 price 列,以获得每个独特的清晰度并设置 scale='free_x。我仍然希望有人能提出更好的答案。但我会这样做:

绘图 1: 重新调整后的值和scale='free_x

代码 1:

#install.packages("scales")
library(tidyverse)
library(plotly)
library(scales)

library(data.table)
setDT(df)

df <- data.frame(diamonds)

df['price'][df$clarity == 'VS1', ] <- filter(df['price'], df['clarity']=='VS1')*2

# rescale price for each clarity
setDT(df)
clarities <- unique(df$clarity)
for (c in clarities)
  df[clarity == c, price := rescale(price)]


df$price <- rescale(df$price)

myplot <- df %>% ggplot(aes(clarity, price)) +
  geom_boxplot() +
  facet_wrap(~ clarity, scales = 'free_x', shrink = FALSE, ncol = 8, strip.position = "bottom") +
  theme(axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank())

p <- ggplotly(myplot)
p

这当然只能深入了解每个类别的内部分布,因为值已重新调整。如果您想显示原始价格数据并保持可读性,我建议通过将width 设置得足够大来为滚动条腾出空间。

情节 2: scales='free' 和足够大的宽度:

代码 2:

library(tidyverse)
library(plotly)

df <- data.frame(diamonds)

df['price'][df$clarity == 'VS1', ] <- filter(df['price'], df['clarity']=='VS1')*2

myplot <- df %>% ggplot(aes(clarity, price)) +
  geom_boxplot() +
  facet_wrap(~ clarity, scales = 'free', shrink = FALSE, ncol = 8, strip.position = "bottom") +
  theme(axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank())

p <- ggplotly(myplot, width = 1400)
p

当然,如果您的值在各个类别中变化不大,scales='free_x' 就可以正常工作。

情节3: scales='free_x

代码 3:

library(tidyverse)
library(plotly)

df <- data.frame(diamonds)

df['price'][df$clarity == 'VS1', ] <- filter(df['price'], df['clarity']=='VS1')*2

myplot <- df %>% ggplot(aes(clarity, price)) +
  geom_boxplot() +
  facet_wrap(~ clarity, scales = 'free_x', shrink = FALSE, ncol = 8, strip.position = "bottom") +
  theme(axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank())

p <- ggplotly(myplot)
p

【讨论】:

【参考方案2】:

有时,如果您对选定的情节感到困惑,那么完全考虑不同的情节会很有帮助。这一切都取决于您希望可视化的是什么。有时箱形图有效,有时直方图有效,有时密度有效。 以下是密度图如何让您快速了解许多参数的数据分布的示例。

library(tidyverse)
library(plotly)
myplot <- diamonds %>% ggplot(aes(price, colour = clarity)) +
  geom_density(aes(fill = clarity), alpha = 0.25) +
  theme(axis.ticks.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank())

【讨论】:

以上是关于使用带有 ggplotly 的 facet_wrap 的第一个和最后一个面大于中间面的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ggplot 缩短带有箭头的长误差线?

R语言使用ggplot2绘制带有边缘直方图的散点图实战

带有子集的 ggplot 图例

在 ggplot2 标题表达式中使用带有变量的 italic()

使用 ggplot2 创建带有形状的图例

使用带有 ggplot 的两个不同向量创建重叠直方图