自动化分面 ggplot 图中的刻度最大值和最小值

6
我试图在分面的 ggplot 图表中标记每个 x 轴的最大值和最小值。我有几个不同 x 轴比例尺但相同 y 轴比例尺的分面,而且 x 轴刻度标签会重叠。为了避免手动确定每个分面 x 轴的限制和间隔,我正在寻找一种只标记每个最小值和最大值的方法。
使用 CO2 数据集的示例数据的代码(请参阅 ?CO2):
CO2$num <- 1:nrow(CO2)
library(reshape2)
CO2.melt <- melt(CO2,
                 id.var=c("Type",
                          "Plant",
                          "Treatment",
                          "num"))
CO2.melt <- CO2.melt[order(CO2.melt$num),]

library(ggplot2)
ggplot(CO2.melt, 
       aes(x = value, 
           y = num)) +
  geom_path(aes(color = Treatment)) +
  facet_wrap( ~ variable, scales = "free_x",nrow=1)

enter image description here

目的是复制类似于这个的测井显示。


注:测井是一种用于确定地下地质结构和矿物资源的方法。

1
我不确定我理解了。您是想在现有标签的顶部添加更大的最小和最大标签吗?还是您想删除其他标签,只保留最小和最大标签?标签应该着色吗? - Stibu
@Stibu,我只是想标记最小值和最大值,而不使用现有的标签。我匆忙制作了这个图。 - Matt
3个回答

9

当你想对刻度标签进行此实现时,在面板绘图中使用scales="free_x"使得自动化变得困难。但是,通过一些调整和其他几个软件包的帮助,你也可以采用以下方法:

1) 总结数据,以便获得在x轴上需要哪些刻度标签/间断点的想法:

library(data.table)
minmax <- melt(setDT(CO2.melt)[, .(min.val = min(value), max.val = max(value),
                                   floor.end = 10*ceiling(min(value)/10),
                                   ceil.end = 10*floor((max(value)-1)/10)),
                               variable][],
               measure.vars = patterns('.val','.end'),
               variable.name = 'var',
               value.name = c('minmax','ends'))

这将会得到:

> minmax
   variable var minmax ends
1:     conc   1   95.0  100
2:   uptake   1    7.7   10
3:     conc   2 1000.0  990
4:   uptake   2   45.5   40

2) 为每个面创建断点向量:

brks1 <- c(95,250,500,750,1000)
brks2 <- c(7.7,10,20,30,40,45.5)

3) 创建分类:

p1 <- ggplot(CO2.melt[CO2.melt$variable=="conc",], 
             aes(x = value, y = num, colour = Treatment)) +
  geom_path() +
  scale_x_continuous(breaks = brks1) +
  theme_minimal(base_size = 14) +
  theme(axis.text.x = element_text(colour = c('red','black')[c(1,2,2,2,1)],
                                   face = c('bold','plain')[c(1,2,2,2,1)]),
        axis.title = element_blank(),
        panel.grid.major = element_line(colour = "grey60"),
        panel.grid.minor = element_blank())

p2 <- ggplot(CO2.melt[CO2.melt$variable=="uptake",], 
             aes(x = value, y = num, colour = Treatment)) +
  geom_path() +
  scale_x_continuous(breaks = brks2) +
  theme_minimal(base_size = 14) +
  theme(axis.text.x = element_text(colour = c('red','black')[c(1,2,2,2,2,1)],
                                   face = c('bold','plain')[c(1,2,2,2,2,1)]),
        axis.title = element_blank(),
        panel.grid.major = element_line(colour = "grey60"),
        panel.grid.minor = element_blank())

4) 将图例提取到一个单独的对象中:

library(grid)
library(gtable)
fill.legend <- gtable_filter(ggplot_gtable(ggplot_build(p2)), "guide-box")
legGrob <- grobTree(fill.legend)

5) 创建最终的图表:

library(gridExtra)
grid.arrange(p1 + theme(legend.position="none"), 
             p2 + theme(legend.position="none"), 
             legGrob, ncol=3, widths = c(4,4,1))

这导致结果如下所示:

enter image description here


自动实现此操作的可能替代方案是使用geom_textgeom_label。以下是示例,展示了如何实现此功能:
# create a summary
library(dplyr)
library(tidyr)
minmax <- CO2.melt %>% 
  group_by(variable) %>% 
  summarise(minx = min(value), maxx = max(value)) %>%
  gather(lbl, val, -1)

# create the plot
ggplot(CO2.melt, aes(x = value, y = num, color = Treatment)) +
  geom_path() +
  geom_text(data = minmax, 
            aes(x = val, y = -3, label = val), 
            colour = "red", fontface = "bold", size = 5) +
  facet_wrap( ~ variable, scales = "free_x", nrow=1) +
  theme_minimal()

这将会得到:

enter image description here

你也可以在 ggplot 中实时获取最小值和最大值(感谢 @eipi10)。以下是使用 geom_label 的另一个示例:

ggplot(CO2.melt, aes(x = value, y = num, color = Treatment)) +
  geom_path() +
  geom_label(data = CO2.melt %>% 
               group_by(variable) %>% 
               summarise(minx = min(value), maxx = max(value)) %>%
               gather(lbl, val, -1), 
             aes(x = val, y = -3, label = val), 
             colour = "red", fontface = "bold", size = 5) +
  facet_wrap( ~ variable, scales = "free_x", nrow=1) +
  theme_minimal()

这将会提供以下内容:

在此输入图片描述


谢谢!我正在使用你第二个解决方案的变体。随着面板数量的增加,将物品放置得更加困难,但这是一个单独的问题。 - Matt

8

编辑 更新到ggplot2版本3.0.0

这种方法修改了ggplot生成数据中的标签(即ggplot_build(plot))。我已经删除了x轴扩展,使得最大和最小值落在面板边界上。

# Packages
library(grid)
library(ggplot2)
library(reshape2)

# Data
CO2$num <- 1:nrow(CO2)
library(reshape2)
CO2.melt <- melt(CO2,
                 id.var=c("Type",
                          "Plant",
                          "Treatment",
                          "num"))
CO2.melt <- CO2.melt[order(CO2.melt$num),]

# Plot
(p <- ggplot(CO2.melt, 
       aes(x = value, 
           y = num)) +
  scale_x_continuous(expand = c(0, 0)) +
  geom_path(aes(color = Treatment)) +
  facet_wrap( ~ variable, scales = "free_x", nrow=1)) 

# Get the build data
gb <- ggplot_build(p)

# Get number of panels
panels = length(gb$layout$panel_params)

# Get x tick mark labels
x.labels = lapply(1:panels, function(N)   gb$layout$panel_params[[N]]$x.labels)

# Get range of x values
x.range = lapply(1:panels, function(N) gb$layout$panel_params[[N]]$x.range)

# Get position of x tick mark labels
x.pos = lapply(1:panels, function(N) gb$layout$panel_params[[N]]$x.major)

# Get new x tick mark labels - includes max and min
new.labels = lapply(1:panels, function(N) as.character(sort(unique(c(as.numeric(x.labels[[N]]), x.range[[N]])))))

# Tag min and max values with "min" and "max"
new.labelsC = new.labels
minmax = c("min", "max")
new.labelsC = lapply(1:panels, function(N) {
   x = c(new.labelsC[[N]][1], new.labelsC[[N]][length(new.labels[[N]])])
   x = paste0(x, "\n", minmax)
   c(x[1], new.labelsC[[N]][2:(length(new.labels[[N]])-1)], x[2])
} )

# # Get position of new labels
new.pos = lapply(1:panels, function(N) (as.numeric(new.labels[[N]]) - x.range[[N]][1])/(x.range[[N]][2] - x.range[[N]][1]))

# Put them back into the build data
for(i in 1:panels) {
   gb$layout$panel_params[[i]]$x.labels = new.labelsC[[i]]
   gb$layout$panel_params[[i]]$x.major_source = as.numeric(new.labels[[i]])
   gb$layout$panel_params[[i]]$x.major = new.pos[[i]]
}

# Get the ggplot grob
gp = ggplot_gtable(gb)

# Add some additional space between the panels
pos = gp$layout$l[grep("panel", gp$layout$name)] # Positions of the panels
for(i in 1:(panels-1)) gp$widths[[pos[i]+1]] = unit(1, "cm")

# Colour the min and max labels using `grid` editing functions
for(i in 1:panels) {
   gp = editGrob(grid.force(gp), gPath(paste0("axis-b-", i), "axis", "axis", "GRID.text"), 
         grep = TRUE, gp = gpar(col = c("red", rep("black", length(new.labels[[i]])-2), "red")))
}

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

enter image description here


1
非常好。我真的需要更多地了解ggplot_build的东西 :-) - Jaap

1
想分享一下,因为我需要类似的东西。这是一种更简单的方法,可以打印出每个面的最小值和最大值。然而,我不确定如何更改最小/最大轴文本值的颜色。也许在所有应用程序中都不需要这样做。
library(ggplot2); library(reshape2)

data(CO2)
CO2$num <- 1:nrow(CO2)

CO2.melt <- reshape2::melt(CO2,
                 id.var=c("Type",
                          "Plant",
                          "Treatment",
                          "num"))

CO2.melt <- CO2.melt[order(CO2.melt$num),]

ggplot(CO2.melt, 
       aes(x = value, 
           y = num)) +
  geom_path(aes(color = Treatment)) +
  scale_x_continuous(breaks = function(k) {
    sort(unique(c(
      pretty(range(k)),
      round(unname(quantile(k, c(0,1))))
    )))
    }
    ) +
  facet_wrap( ~ variable, scales = "free_x",nrow=1) +
  theme(panel.spacing = unit(2, "lines"))

2023-10-31创建,使用reprex v2.0.2生成


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接