两行带有嵌套 x 变量的轴标签(年份低于月份)
Posted
技术标签:
【中文标题】两行带有嵌套 x 变量的轴标签(年份低于月份)【英文标题】:Axis labels on two lines with nested x variables (year below months) 【发布时间】:2021-12-17 09:52:50 【问题描述】:我想沿水平轴显示月份(以缩写形式),并打印一次相应的年份。我知道如何显示月-年:
一年中不必要的重复使标签变得混乱。相反,我想要这样的东西:
除了年份将打印在月份之后。
我在轴标签上方打印了年份,因为这是我能做的最好的事情。这遵循annotate()
函数的限制,如果它位于绘图区域之外,则会被剪裁。我知道基于annotate_custom()
的可能解决方法,但我无法让它们与日期对象一起使用(我没有尝试将日期转换为数字并再次转换回日期,因为它似乎比希望需要的更复杂)
我想知道是否可以为此目的劫持新的dup_axis()
。如果不是将复制的轴发送到面板的另一侧,它可以将其发送到复制的轴下方几行,那么也许只需设置一个轴并将panel.grid.major
置空并设置标签到%b
,而另一个轴将有panel.grid.minor
空白并且标签设置为%Y
。 (一个额外的挑战是年份标签将被转移到 10 月而不是 1 月)
这些问题是相关的。但是,据我所知,annotate_custom()
函数和 textGrob()
函数不能很好地处理日期。
how-can-i-add-annotations-below-the-x-axis-in-ggplot2
displaying-text-below-the-plot-generated-by-ggplot2
数据和基本代码如下:
library("ggplot2")
library("scales")
ggplot(data = df, aes(x = Date, y = value)) + geom_line() +
scale_x_date(date_breaks = "2 month", date_minor_breaks = "1 month", labels = date_format("%b %Y")) +
xlab(NULL)
ggplot(data = df, aes(x = Date, y = value)) + geom_line() +
scale_x_date(date_minor_breaks = "2 month", labels = date_format("%b")) +
annotate(geom = "text", x = as.Date("1719-10-01"), y = 0, label = "1719") +
annotate(geom = "text", x = as.Date("1720-10-01"), y = 0, label = "1720") +
xlab(NULL)
# data
df <- structure(list(Date = structure(c(-91455, -91454, -91453, -91452,
-91451, -91450, -91448, -91447, -91446, -91445, -91444, -91443,
-91441, -91440, -91439, -91438, -91437, -91436, -91434, -91433,
-91431, -91430, -91429, -91427, -91426, -91425, -91424, -91423,
-91422, -91420, -91419, -91418, -91417, -91416, -91415, -91413,
-91412, -91411, -91410, -91409, -91408, -91406, -91405, -91404,
-91403, -91402, -91401, -91399, -91398, -91397, -91396, -91395,
-91394, -91392, -91391, -91390, -91389, -91388, -91387, -91385,
-91384, -91382, -91381, -91380, -91379, -91377, -91376, -91375,
-91374, -91373, -91372, -91371, -91370, -91369, -91368, -91367,
-91366, -91364, -91363, -91362, -91361, -91360, -91359, -91357,
-91356, -91355, -91354, -91353, -91352, -91350, -91349, -91348,
-91347, -91346, -91345, -91343, -91342, -91341, -91340, -91339,
-91338, -91336, -91335, -91334, -91333, -91332, -91331, -91329,
-91328, -91327, -91326, -91325, -91324, -91322, -91321, -91320,
-91319, -91315, -91314, -91313, -91312, -91311, -91310, -91308,
-91307, -91306, -91305, -91304, -91303, -91301, -91300, -91299,
-91298, -91297, -91296, -91294, -91293, -91292, -91291, -91290,
-91289, -91287, -91286, -91285, -91284, -91283, -91282, -91280,
-91279, -91278, -91277, -91276, -91275, -91273, -91272, -91271,
-91270, -91269, -91268, -91266, -91265, -91264, -91263, -91262,
-91261, -91259, -91258, -91257, -91256, -91255, -91254, -91252,
-91251, -91250, -91249, -91248, -91247, -91245, -91244, -91243,
-91242, -91241, -91240, -91238, -91237, -91236, -91235, -91234,
-91233, -91231, -91230, -91229, -91228, -91227, -91226, -91224,
-91223, -91222, -91221, -91220, -91219, -91217, -91216, -91215,
-91214, -91213, -91212, -91210, -91209, -91208, -91207, -91205,
-91201, -91200, -91199, -91198, -91196, -91195, -91194, -91193,
-91192, -91191, -91189, -91188, -91187, -91186, -91185, -91184,
-91182, -91181, -91180, -91179, -91178, -91177, -91175, -91174,
-91173, -91172, -91171, -91170, -91168, -91167, -91166, -91165,
-91164, -91163, -91161, -91160, -91159, -91158, -91157, -91156,
-91154, -91153, -91152, -91151, -91150, -91149, -91147, -91146,
-91145, -91144, -91143, -91142, -91140, -91139, -91138, -91131,
-91130, -91129, -91128, -91126, -91125, -91124, -91123, -91122,
-91121, -91119, -91118, -91117, -91116, -91115, -91114, -91112,
-91111, -91110, -91109, -91108, -91107, -91104, -91103, -91102,
-91101, -91100, -91099, -91097, -91096, -91095, -91094, -91093,
-91091, -91090, -91089, -91088, -91087, -91086, -91084, -91083,
-91082, -91081, -91080, -91079, -91077, -91076, -91075, -91074,
-91073, -91072, -91070, -91069, -91068, -91065, -91063, -91062,
-91061, -91060, -91059, -91058, -91056, -91055, -91054, -91053,
-91052, -91051, -91049, -91048, -91047, -91046, -91045, -91044,
-91042, -91041, -91040, -91039, -91038, -91037, -91035, -91034,
-91033, -91032, -91031, -91030, -91028, -91027, -91026, -91025,
-91024, -91023, -91021, -91020, -91019, -91018, -91017, -91016,
-91014, -91013, -91012, -91011, -91010, -91009, -91007, -91006,
-91005, -91004, -91003, -91002, -91000, -90999, -90998, -90997,
-90996, -90995, -90993, -90992, -90991, -90990, -90989, -90988,
-90986, -90985, -90984, -90983, -90982), class = "Date"), value = c(113,
113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 116,
117, 117, 117, 117, 116, 117, 116, 116, 116, 117, 117, 117, 117,
117, 117, 117, 116, 117, 116, 116, 116, 117, 117, 117, 117, 117,
117, 117, 116, 116, 117, 117, 117, 117, 117, 117, 117, 117, 117,
117, 117, 118, 118, 118, 118, 117, 118, 117, 117, 117, 117, 117,
117, 118, 116, 116, 116, 116, 116, 116, 116, 117, 117, 118, 118,
118, 118, 118, 119, 120, 120, 119, 119, 120, 120, 121, 121, 122,
124, 124, 122, 123, 124, 123, 123, 123, 123, 123, 124, 124, 126,
126, 126, 126, 126, 125, 125, 126, 127, 126, 126, 125, 126, 126,
126, 128, 128, 128, 130, 133, 131, 133, 134, 134, 134, 136, 136,
136, 135, 135, 135, 136, 136, 136, 136, 135, 135, 135, 135, 130,
129, 129, 130, 131, 136, 138, 155, 157, 161, 170, 174, 168, 165,
169, 171, 181, 184, 182, 179, 181, 179, 175, 177, 177, 174, 170,
174, 173, 178, 173, 178, 179, 182, 184, 184, 180, 181, 182, 182,
184, 184, 188, 195, 198, 220, 255, 275, 350, 310, 315, 320, 320,
316, 300, 310, 310, 320, 317, 313, 312, 310, 297, 285, 285, 286,
288, 315, 328, 338, 344, 345, 352, 352, 342, 335, 343, 340, 342,
339, 337, 336, 336, 342, 347, 352, 352, 351, 352, 352, 351, 352,
352, 355, 375, 400, 452, 487, 476, 475, 473, 485, 500, 530, 595,
720, 720, 770, 750, 770, 750, 735, 740, 745, 735, 700, 700, 750,
760, 755, 755, 760, 760, 765, 950, 950, 950, 875, 875, 875, 880,
880, 880, 900, 900, 900, 880, 880, 890, 895, 890, 880, 870, 870,
870, 870, 870, 860, 860, 860, 860, 850, 840, 810, 820, 810, 810,
805, 810, 805, 820, 815, 820, 805, 790, 800, 780, 760, 765, 750,
740, 820, 810, 800, 800, 775, 750, 810, 750, 740, 700, 705, 660,
630, 640, 595, 590, 570, 565, 535, 440, 400, 410, 400, 405, 390,
370, 300, 300, 180, 200, 310, 290, 260, 260, 275, 260, 270, 265,
255, 250, 210, 210, 200, 195, 210, 215, 240, 240, 220, 220, 220,
220, 210, 212, 208, 220, 210, 212, 208, 220, 215, 220, 214, 214,
213, 212, 210, 210, 195, 195, 160, 160, 175, 205, 210, 208, 197,
181, 185)), .Names = c("Date", "value"), row.names = c(NA, 393L
), class = "data.frame")
【问题讨论】:
为什么不在aes()
中使用colour = format(Date,"%Y")
。它更清洁 imo,而不是尝试将自定义 x 轴组合在一起。
相关***.com/questions/20571306/…
【参考方案1】:
我遇到了这个问题,并想也许我可以添加一个解决方案。我们可以通过使用一个简单的条件,在每年的第一个显示月份中同时显示月份和年份。您可以使用date_breaks
从标签中删除一月,这仍然有效。我正在使用来自lubridate
的month()
和year()
。
library(tidyverse)
library(lubridate)
df %>%
ggplot(aes(Date, value)) +
geom_line() +
scale_x_date(date_breaks = "2 months",
labels = function(x) if_else(is.na(lag(x)) | !year(lag(x)) == year(x),
paste(month(x, label = TRUE), "\n", year(x)),
paste(month(x, label = TRUE))))
【讨论】:
绝招!函数if_else
来自包dplyr
(tidyverse
的一部分)。【参考方案2】:
避免复杂性的一种方法是更改所需的输出,以便将一月替换为年份。
lab
函数返回给定中断的标签。出乎意料的是,ggplot 会将 NA 传递给它,因此在函数体的第一行中,我们将其替换为某个日期——哪个日期无关紧要,因为这些值随后不会被 ggplot 使用。最后,我们将日期格式化为年份或缩写月份,具体取决于月份是否为一月(对应于 POSIXlt 组件 mon
等于 0)。
library(ggplot2)
library(scales)
lab <- function(b)
b[is.na(b)] <- Sys.Date()
format(b, ifelse(as.POSIXlt(b)$mon == 0, "%Y", "%b"))
ggplot(df, aes(Date, value)) +
geom_line() +
scale_x_date(date_breaks = "month", labels = lab)
注意:我已将Issue 2182 添加到有关传递给标签函数的 NA 的 ggplot2 github 问题列表中。如果 ggplot2 的后续版本不再通过 NA,则可以省略 lab
正文的第一行。
更新:已修复。
【讨论】:
【参考方案3】:下面的代码提供了添加年份标签的两个潜在选项。
选项 1a:分面
您可以使用刻面来标记年份。例如:
library(ggplot2)
library(lubridate)
ggplot(df, aes(Date, value)) +
geom_line() +
scale_x_date(date_labels="%b", date_breaks="month", expand=c(0,0)) +
facet_grid(~ year(Date), space="free_x", scales="free_x", switch="x") +
theme_bw() +
theme(strip.placement = "outside",
strip.background = element_rect(fill=NA,colour="grey50"),
panel.spacing=unit(0,"cm"))
请注意,使用这种方法,如果在一年的开始或结束时缺少日期(通过“缺少”,我的意思是这些日期的行甚至不存在于数据中),那么 x 轴将开始/结束于该年数据的第一个/最后一个日期,而不是从 1 月 1 日到 12 月 31 日。在这种情况下,您需要为缺少的日期添加行,并为value
添加NA
或插入value
。此外,这种方法在一年的 12 月 31 日和次年的 1 月 1 日之间没有空格或线,因此每年都有不连续性。
选项 1b:分面 + 居中的月份标签
解决@AF7 的评论。您可以通过在每个标签前添加一些空格来使月份标签居中。但是您必须手动选择空格数,具体取决于打印到设备时绘图的物理大小。 (可能有一种方法可以根据内部 grob 测量以编程方式使标签居中,但我不知道该怎么做。)我还删除了较小的垂直网格线并减轻了年份之间的界限。
ggplot(df, aes(Date, value)) +
geom_line() +
scale_x_date(date_labels=paste(c(rep(" ",11), "%b"), collapse=""),
date_breaks="month", expand=c(0,0)) +
facet_grid(~ year(Date), space="free_x", scales="free_x", switch="x") +
theme_bw() +
theme(strip.placement = "outside",
strip.background = element_blank(),
panel.grid.minor.x = element_blank(),
panel.border = element_rect(colour="grey70"),
panel.spacing=unit(0,"cm"))
选项 2a:编辑 x 轴标签 grob
这是一种更复杂和挑剔的方法(尽管它可能会由比我更了解网格图形的结构和单位间距的人自动化),它避免了上述分面方法的缺陷:
library(grid)
# Fake data with an extra year added for illustration
set.seed(2)
df = data.frame(Date=seq(as.Date("1718-03-01"),as.Date("1721-09-20"), by="1 day"))
df$value = cumsum(rnorm(nrow(df)))
# The plot we'll start with
p = ggplot(df, aes(Date, value)) +
geom_vline(xintercept=as.numeric(df$Date[yday(df$Date)==1]), colour="grey60") +
geom_line() +
scale_x_date(date_labels="%b", date_breaks="month", expand=c(0,0)) +
theme_bw() +
theme(panel.grid.minor.x = element_blank()) +
labs(x="")
现在我们要在下面和每年的 6 月和 7 月之间添加年份值。下面的代码通过修改 x 轴标签 grob 来实现这一点,并改编自 @SandyMuspratt 的 this SO answer。
# Get the grob
g <- ggplotGrob(p)
# Get the y axis
index <- which(g$layout$name == "axis-b") # Which grob
xaxis <- g$grobs[[index]]
# Get the ticks (labels and marks)
ticks <- xaxis$children[[2]]
# Get the labels
ticksB <- ticks$grobs[[2]]
# Edit x-axis label grob
# Find every index of Jun in the x-axis labels and add a newline and
# then a year label
junes = which(ticksB$children[[1]]$label == "Jun")
ticksB$children[[1]]$label[junes] = paste0(ticksB$children[[1]]$label[junes],
"\n ", unique(year(df$Date)))
# Put the edited labels back into the plot
ticks$grobs[[2]] <- ticksB
xaxis$children[[2]] <- ticks
g$grobs[[index]] <- xaxis
# Draw the plot
grid.newpage()
grid.draw(g)
选项 2b:编辑 x 轴标签 grob 并将月份标签居中
下面是唯一需要对选项 2a 进行更改以使月份标签居中的更改,但同样需要手动调整空格数。
# Make the edit
# Center the month labels between ticks
ticksB$children[[1]]$label = paste0(paste(rep(" ",7),collapse=""), ticksB$children[[1]]$label)
# Find every index of Jun in the x-axis labels and a year label
junes = grep("Jun", ticksB$children[[1]]$label)
ticksB$children[[1]]$label[junes] = paste0(ticksB$children[[1]]$label[junes], "\n ", unique(year(df$Date)))
【讨论】:
这比我尝试做的还要好!谢谢! 第一种方法,可能类似于panel.border = element_rect(colour="grey80")
。使用第二种方法,更改geom_vline
颜色以更改线条的突出程度,或者如果您不想划分年份,则完全删除geom_vline
语句。
我觉得这个 x 比例有点误导。我会说最好的方法是让几个月被蜱虫包围,这样,例如“Jan”以记号开头,以记号结尾。目前,一月以“Jan”开始,以“Feb”结束。这通常适用于数字,但对于几个月恕我直言有点误导。因此,我会将刻度标签向右移动少量(刻度宽度的一半)。
每次都变得更好!评论:1xlab("")
对于腾出空间来打印年份很重要。因此,xlab(NULL)
不会提供该空间。 2 为了防止标签发出咔哒声,输出需要足够大,例如ggsave(g,文件 =“a.pdf”,宽度 = 12,高度 = 4)。 3您仍然可以设置date_breaks = "2 month"
,但随后需要调整标签位置。
我的情节从 01/01/2010 到 06/30/2015。我在 x 轴上没有足够的空间来保存每个月的前 3 个字母。我保留了标签 J,F,M,A,M,J,J,.... 您使用“Jun”来设置年份标签的位置,我能想到的唯一解决方法是更改“J”将 June 更改为“J”,并将 junes = grep("June", ticksB$children[[1]]$label)
更改为 junes = grep(" J ", ticksB$children[[1]]$label)
(J 前后的空格代表 June)。这增加了六月标签周围的额外间距。我该如何解决这个问题?这是how my plot looks【参考方案4】:
如果您想尝试将子标签组合在一起,可以将其转换为 grob
。我从原始帖子中对此进行了编辑,以创建一个添加子标签并返回 gtable
对象的函数。请注意,sublabs
输入的长度必须与 x 轴中断的长度相同:
library(grid)
library(gtable)
library(gridExtra)
add_sublabs <- function(plot, sublabs)
gg <- ggplotGrob(plot)
axis_num <- which(gg$layout[,"name"] == "axis-b")
xbreaks <- gg[["grobs"]][[axis_num]][["children"]][[2]][["grobs"]][[2]][["children"]][[1]]$x
if(length(xbreaks) != length(sublabs)) stop("Sub-labels must be the same length as the x-axis breaks")
to_breaks <- c(as.numeric(xbreaks),1)[which(!duplicated(sublabs, fromLast = TRUE))+1]
sublabs_x <- diff(c(0,to_breaks))
sublabs_labels <- sublabs[!duplicated(sublabs, fromLast = TRUE)]
tg <- tableGrob(matrix(sublabs_labels, nrow = 1))
tg$widths = unit(sublabs_x, attr(xbreaks,"unit"))
pos <- gg$layout[axis_num,c("t","l")]
gg2 <- gtable_add_rows(gg, heights = sum(tg$heights)+unit(4,"mm"), pos = pos$t)
gg3 <- gtable_add_grob(gg2, tg, t = pos$t+1, l = pos$l)
return(gg3)
#Plot and sublabels
p <- ggplot(data = df, aes(x = Date, y = value)) + geom_line() +
scale_x_date(date_breaks = "2 month", date_minor_breaks = "1 month", labels = date_format("%b")) +
xlab(NULL)
sublabs <- c(rep("1719",2),rep("1720",6))
#Draw
grid.draw(add_sublabs(p, sublabs))
【讨论】:
那是非常好的迈克。我可以看到这将是一种很好的方式来标记诸如“President X”、“President Y”之类的内容,并使用相应的匹配填充颜色。竖起大拇指! @PatrickT 谢谢!我将其编辑为我认为更好的方法。我写了一个快速函数,它接受一个子标签向量和一个图。我认为它比我原来的方式更直观/简洁。以上是关于两行带有嵌套 x 变量的轴标签(年份低于月份)的主要内容,如果未能解决你的问题,请参考以下文章