简体   繁体   English

如何在双Y轴ggplot中使用构面

[英]How to use facets with a dual y-axis ggplot

I have been trying to extend my scenario from here to make use of facets (specifically facet_grid() ). 我一直在尝试从这里扩展场景以利用构面(特别是facet_grid() )。

I have seen this example , however I can't seem to get it to work for my geom_bar() and geom_point() combo. 我已经看到了这个示例 ,但是我似乎无法使其适用于我的geom_bar()geom_point()组合。 I attempted to use the code from the example just changing from facet_wrap to facet_grid which also seemed to make the first layer not show. 我试图从仅仅改变例子中使用的代码facet_wrapfacet_grid这也似乎让第一层不显示。

I am very much a novice when it comes to grid and grobs so if someone can give some guidance on how to make P1 show up with the left y axis and P2 show up on the right y axis that would be great. 我是网格和杂项方面的新手,因此,如果有人可以给出一些指导,使P1在y轴左方显示,P2在y轴右方显示,那会很棒。

Data 数据

library(ggplot2)
library(gtable)
library(grid)
library(data.table)
library(scales)

grid.newpage()

dt.diamonds <- as.data.table(diamonds)

d1 <- dt.diamonds[,list(revenue = sum(price),
                        stones = length(price)),
                  by=c("clarity","cut")]

setkey(d1, clarity,cut)

p1 & p2 p1和p2

p1 <- ggplot(d1, aes(x=clarity,y=revenue, fill=cut)) +
  geom_bar(stat="identity") +
  labs(x="clarity", y="revenue") +
  facet_grid(. ~ cut) +
  scale_y_continuous(labels=dollar, expand=c(0,0)) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1),
        axis.text.y = element_text(colour="#4B92DB"), 
        legend.position="bottom")

p2 <- ggplot(d1, aes(x=clarity, y=stones, colour="red")) +
  geom_point(size=6) + 
  labs(x="", y="number of stones") + expand_limits(y=0) +
  scale_y_continuous(labels=comma, expand=c(0,0)) +
  scale_colour_manual(name = '',values =c("red","green"), labels = c("Number of Stones"))+
  facet_grid(. ~ cut) +
  theme(axis.text.y = element_text(colour = "red")) +
  theme(panel.background = element_rect(fill = NA),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_rect(fill=NA,colour="grey50"),
        legend.position="bottom")

Attempt to combine (based on example linked above) This fails in the first for loop, I suspect to the hard coding of geom_point.points, however I don't know how to make it suit my charts (or fluid enough to suit a variety of charts) 尝试组合(基于上面链接的示例)这在第一个for循环中失败,我怀疑对geom_point.points进行了硬编码,但是我不知道如何使其适合我的图表(或者足够灵活以适合各种图表)图表)

# extract gtable
g1 <- ggplot_gtable(ggplot_build(p1))
g2 <- ggplot_gtable(ggplot_build(p2))

combo_grob <- g2
pos <- length(combo_grob) - 1
combo_grob$grobs[[pos]] <- cbind(g1$grobs[[pos]],
                                 g2$grobs[[pos]], size = 'first')

panel_num <- length(unique(d1$cut))
for (i in seq(panel_num))
{
   grid.ls(g1$grobs[[i + 1]])
  panel_grob <- getGrob(g1$grobs[[i + 1]], 'geom_point.points',
                        grep = TRUE, global = TRUE)
  combo_grob$grobs[[i + 1]] <- addGrob(combo_grob$grobs[[i + 1]], 
                                       panel_grob)
}       


pos_a <- grep('axis_l', names(g1$grobs))
axis <- g1$grobs[pos_a]
for (i in seq(along = axis))
{
  if (i %in% c(2, 4))
  {
    pp <- c(subset(g1$layout, name == paste0('panel-', i), se = t:r))

    ax <- axis[[1]]$children[[2]]
    ax$widths <- rev(ax$widths)
    ax$grobs <- rev(ax$grobs)
    ax$grobs[[1]]$x <- ax$grobs[[1]]$x - unit(1, "npc") + unit(0.5, "cm")
    ax$grobs[[2]]$x <- ax$grobs[[2]]$x - unit(1, "npc") + unit(0.8, "cm")
    combo_grob <- gtable_add_cols(combo_grob, g2$widths[g2$layout[pos_a[i],]$l], length(combo_grob$widths) - 1)
    combo_grob <- gtable_add_grob(combo_grob, ax,  pp$t, length(combo_grob$widths) - 1, pp$b)
  }
}

pp <- c(subset(g1$layout, name == 'ylab', se = t:r))

ia <- which(g1$layout$name == "ylab")
ga <- g1$grobs[[ia]]
ga$rot <- 270
ga$x <- ga$x - unit(1, "npc") + unit(1.5, "cm")

combo_grob <- gtable_add_cols(combo_grob, g2$widths[g2$layout[ia,]$l], length(combo_grob$widths) - 1)
combo_grob <- gtable_add_grob(combo_grob, ga, pp$t, length(combo_grob$widths) - 1, pp$b)
combo_grob$layout$clip <- "off"

grid.draw(combo_grob)

EDIT to attempt to make workable for facet_wrap 编辑以尝试使其适用于facet_wrap

The following code still works with facet_grid using ggplot2 2.0.0 以下代码仍使用ggplot2 2.0.0facet_grid使用

g1 <- ggplot_gtable(ggplot_build(p1))
g2 <- ggplot_gtable(ggplot_build(p2))

pp <- c(subset(g1$layout, name == "panel", se = t:r))
g <- gtable_add_grob(g1, g2$grobs[which(g2$layout$name == "panel")], pp$t,
                     pp$l, pp$b, pp$l)
# axis tweaks
ia <- which(g2$layout$name == "axis-l")
ga <- g2$grobs[[ia]]
ax <- ga$children[[2]]
ax$widths <- rev(ax$widths)
ax$grobs <- rev(ax$grobs)
ax$grobs[[1]]$x <- ax$grobs[[1]]$x - unit(1, "npc") + unit(0.15, "cm")
g <- gtable_add_cols(g, g2$widths[g2$layout[ia, ]$l], length(g$widths) - 1)
g <- gtable_add_grob(g, ax, unique(pp$t), length(g$widths) - 1)

# Add second y-axis title
ia <- which(g2$layout$name == "ylab")
ax <- g2$grobs[[ia]]
# str(ax) # you can change features (size, colour etc for these -
# change rotation below
ax$rot <- 90
g <- gtable_add_cols(g, g2$widths[g2$layout[ia, ]$l], length(g$widths) - 1)
g <- gtable_add_grob(g, ax, unique(pp$t), length(g$widths) - 1)

# Add legend to the code
leg1 <- g1$grobs[[which(g1$layout$name == "guide-box")]]
leg2 <- g2$grobs[[which(g2$layout$name == "guide-box")]]

g$grobs[[which(g$layout$name == "guide-box")]] <-
  gtable:::cbind_gtable(leg1, leg2, "first")

grid.draw(g)

Now that ggplot2 has secondary axis support this has become much much easier in many (but not all ) cases. 现在ggplot2具有辅助轴支持,这在很多(但不是全部 )情况下变得容易得多。 No grob manipulation needed. 无需杂项操作。

Even though it is supposed to only allow for simple linear transformations of the same data, such as different measurement scales, we can manually rescale one of the variables first to at least get a lot more out of that property. 即使只允许对相同数据进行简单的线性变换(例如,不同的测量范围),我们也可以首先手动重新缩放其中一个变量,至少可以从该属性中获得更多收益。

library(tidyverse)

max_stones <- max(d1$stones)
max_revenue <- max(d1$revenue)

d2 <- gather(d1, 'var', 'val', stones:revenue) %>% 
  mutate(val = if_else(var == 'revenue', as.double(val), val / (max_stones / max_revenue)))

ggplot(mapping = aes(clarity, val)) +
  geom_bar(aes(fill = cut), filter(d2, var == 'revenue'), stat = 'identity') +
  geom_point(data = filter(d2, var == 'stones'), col = 'red') +
  facet_grid(~cut) +
  scale_y_continuous(sec.axis = sec_axis(trans = ~ . * (max_stones / max_revenue),
                                         name = 'number of stones'),
                     labels = dollar) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1),
        axis.text.y = element_text(color = "#4B92DB"),
        axis.text.y.right = element_text(color = "red"),
        legend.position="bottom") +
  ylab('revenue')

在此处输入图片说明

It also works nicely with facet_wrap : 它也可以与facet_wrap配合facet_wrap

在此处输入图片说明

Other complications, such as scales = 'free' and space = 'free' are also done easily. scales = 'free'space = 'free'等其他复杂问题也很容易实现。 The only restriction is that the relationship between the two axes is equal for all facets. 唯一的限制是,对于所有构面,两个轴之间的关系均相等。

EDIT: UPDATED TO GGPLOT 2.2.0 编辑:更新为GGPLOT 2.2.0
But ggplot2 now supports secondary y axes, so there is no need for grob manipulation. 但是ggplot2现在支持次要y轴,因此不需要grob操纵。 See @Axeman's solution. 请参阅@Axeman的解决方案。

facet_grid and facet_wrap plots generate different sets of names for plot panels and left axes. facet_gridfacet_wrap图面板和左轴生成不同的名称集。 You can check the names using g1$layout where g1 <- ggplotGrob(p1) , and p1 is drawn first with facet_grid() , then second with facet_wrap() . 您可以使用g1$layout来检查名称,其中g1 <- ggplotGrob(p1) ,然后首先使用facet_grid()绘制p1,然后使用facet_wrap()绘制第二个。 In particular, with facet_grid() the plot panels are all named "panel", whereas with facet_wrap() they have different names: "panel-1", "panel-2", and so forth. 特别是,使用facet_grid() ,绘图面板都被命名为“ panel”,而使用facet_wrap()它们具有不同的名称:“ panel-1”,“ panel-2”,依此类推。 So commands like these: 所以像这样的命令:

pp <- c(subset(g1$layout, name == "panel", se = t:r))
g <- gtable_add_grob(g1, g2$grobs[which(g2$layout$name == "panel")], pp$t,
                     pp$l, pp$b, pp$l)

will fail with plots generated using facet_wrap . 使用facet_wrap生成的图将失败。 I would use regular expressions to select all names beginning with "panel". 我会使用正则表达式来选择所有以“ panel”开头的名称。 There are similar problems with "axis-l". “ l轴”也有类似的问题。

Also, your axis-tweaking commands worked for older versions of ggplot, but from version 2.1.0, the tick marks don't quite meet the right edge of the plot, and the tick marks and the tick mark labels are too close together. 另外,您的轴扭曲命令适用于旧版本的ggplot,但从2.1.0版开始,刻度线不太完全符合绘图的右边缘,并且刻度线和刻度线标签太靠近了。

Here is what I would do (drawing on code from here , which in turn draws on code from here and from the cowplot package ). 这就是我要做的(从此处借鉴代码,而从此处借鉴代码,又从cowplot软件包借鉴 )。

# Packages
library(ggplot2)
library(gtable)
library(grid)
library(data.table)
library(scales)

# Data 
dt.diamonds <- as.data.table(diamonds)
d1 <- dt.diamonds[,list(revenue = sum(price),
                        stones = length(price)),
                  by=c("clarity", "cut")]
setkey(d1, clarity, cut)

# The facet_wrap plots
p1 <- ggplot(d1, aes(x = clarity, y = revenue, fill = cut)) +
  geom_bar(stat = "identity") +
  labs(x = "clarity", y = "revenue") +
  facet_wrap( ~ cut, nrow = 1) +
  scale_y_continuous(labels = dollar, expand = c(0, 0)) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1),
        axis.text.y = element_text(colour = "#4B92DB"), 
        legend.position = "bottom")

p2 <- ggplot(d1, aes(x = clarity, y = stones, colour = "red")) +
  geom_point(size = 4) + 
  labs(x = "", y = "number of stones") + expand_limits(y = 0) +
  scale_y_continuous(labels = comma, expand = c(0, 0)) +
  scale_colour_manual(name = '', values = c("red", "green"), labels = c("Number of Stones"))+
  facet_wrap( ~ cut, nrow = 1) +
  theme(axis.text.y = element_text(colour = "red")) +
  theme(panel.background = element_rect(fill = NA),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_rect(fill = NA, colour = "grey50"),
        legend.position = "bottom")


# Get the ggplot grobs
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)

# Get the locations of the plot panels in g1.
pp <- c(subset(g1$layout, grepl("panel", g1$layout$name), se = t:r))

# Overlap panels for second plot on those of the first plot
g <- gtable_add_grob(g1, g2$grobs[grepl("panel", g1$layout$name)], 
      pp$t, pp$l, pp$b, pp$l)


# ggplot contains many labels that are themselves complex grob; 
# usually a text grob surrounded by margins.
# When moving the grobs from, say, the left to the right of a plot,
# Make sure the margins and the justifications are swapped around.
# The function below does the swapping.
# Taken from the cowplot package:
# https://github.com/wilkelab/cowplot/blob/master/R/switch_axis.R 

hinvert_title_grob <- function(grob){

  # Swap the widths
  widths <- grob$widths
  grob$widths[1] <- widths[3]
  grob$widths[3] <- widths[1]
  grob$vp[[1]]$layout$widths[1] <- widths[3]
  grob$vp[[1]]$layout$widths[3] <- widths[1]

  # Fix the justification
  grob$children[[1]]$hjust <- 1 - grob$children[[1]]$hjust 
  grob$children[[1]]$vjust <- 1 - grob$children[[1]]$vjust 
  grob$children[[1]]$x <- unit(1, "npc") - grob$children[[1]]$x
  grob
}

# Get the y axis title from g2
index <- which(g2$layout$name == "ylab-l") # Which grob contains the y axis title?   EDIT HERE
ylab <- g2$grobs[[index]]                # Extract that grob
ylab <- hinvert_title_grob(ylab)         # Swap margins and fix justifications

# Put the transformed label on the right side of g1
g <- gtable_add_cols(g, g2$widths[g2$layout[index, ]$l], max(pp$r))
g <- gtable_add_grob(g, ylab, max(pp$t), max(pp$r) + 1, max(pp$b), max(pp$r) + 1, clip = "off", name = "ylab-r")

# Get the y axis from g2 (axis line, tick marks, and tick mark labels)
index <- which(g2$layout$name == "axis-l-1-1")  # Which grob.    EDIT HERE
yaxis <- g2$grobs[[index]]                    # Extract the grob

# yaxis is a complex of grobs containing the axis line, the tick marks, and the tick mark labels.
# The relevant grobs are contained in axis$children:
#   axis$children[[1]] contains the axis line;
#   axis$children[[2]] contains the tick marks and tick mark labels.

# First, move the axis line to the left
# But not needed here
# yaxis$children[[1]]$x <- unit.c(unit(0, "npc"), unit(0, "npc"))

# Second, swap tick marks and tick mark labels
ticks <- yaxis$children[[2]]
ticks$widths <- rev(ticks$widths)
ticks$grobs <- rev(ticks$grobs)

# Third, move the tick marks
# Tick mark lengths can change. 
# A function to get the original tick mark length
# Taken from the cowplot package:
# https://github.com/wilkelab/cowplot/blob/master/R/switch_axis.R 
plot_theme <- function(p) {
  plyr::defaults(p$theme, theme_get())
}

tml <- plot_theme(p1)$axis.ticks.length   # Tick mark length
ticks$grobs[[1]]$x <- ticks$grobs[[1]]$x - unit(1, "npc") + tml

# Fourth, swap margins and fix justifications for the tick mark labels
ticks$grobs[[2]] <- hinvert_title_grob(ticks$grobs[[2]])

# Fifth, put ticks back into yaxis
yaxis$children[[2]] <- ticks

# Put the transformed yaxis on the right side of g1
g <- gtable_add_cols(g, g2$widths[g2$layout[index, ]$l], max(pp$r))
g <- gtable_add_grob(g, yaxis, max(pp$t), max(pp$r) + 1, max(pp$b), max(pp$r) + 1, 
   clip = "off", name = "axis-r")

# Get the legends
leg1 <- g1$grobs[[which(g1$layout$name == "guide-box")]]
leg2 <- g2$grobs[[which(g2$layout$name == "guide-box")]]

# Combine the legends
g$grobs[[which(g$layout$name == "guide-box")]] <-
    gtable:::cbind_gtable(leg1, leg2, "first")

# Draw it
grid.newpage()
grid.draw(g)

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM