目标
使用ggplot2
(最新版本)生成一个图表,该图表复制图表两侧的x轴或y轴,其中比例不连续。
最小代表
# Example data
dat1 <- tibble::tibble(x = c(rep("a", 50), rep("b", 50)),
y = runif(100))
# Standard scatterplot
p1 <- ggplot2::ggplot(dat1) +
ggplot2::geom_boxplot(ggplot2::aes(x = x, y = y))
当比例是连续的时,这很容易通过身份转换(显然是一对一)。
# This works
p1 + ggplot2::scale_y_continuous(sec.axis = ggplot2::sec_axis(~ .))
但是,当比例不连续时,这不起作用,因为其他scale_*
函数没有sec.axis
参数(这是有意义的)。
# This doesn't work
p1 + ggplot2::scale_x_discrete(sec.axis = ggplot2::sec_axis(~ .))
Error in discrete_scale(c("x", "xmin", "xmax", "xend"), "position_d", :
unused argument (sec.axis = <environment>)
我也尝试在position
函数中使用scale_*
参数,但这也不起作用。
# This doesn't work either
p1 + ggplot2::scale_x_discrete(position = c("top", "bottom"))
Error in match.arg(position, c("left", "right", "top", "bottom")) :
'arg' must be of length 1
修改
为了清楚起见,我希望复制x轴或y轴,其中刻度为任何,而不仅仅是离散(因子变量)。为简单起见,我在最小的代表中使用了一个离散变量。
例如,此问题出现在非连续缩放为datetime
或time
格式的上下文中。
答案 0 :(得分:3)
Duplicating (and modifying) discrete axis in ggplot2
您可以通过在两侧放置相同的标签来调整此答案。至于&#34;你可以将任何非连续的东西转换为一个因子,但这更不优雅!&#34;从上面的评论来看,这是一个非连续的轴 ,所以我不确定为什么这对你来说会有问题。
TL:DR 使用as.numeric(...)
进行分类审美,并使用scale_*_continuous(..., sec_axis(~., ...))
手动提供原始数据中的标签。
我碰巧回顾过这个帖子,看到它被要求提供日期和时间。这使问题措辞错误:日期和时间连续而不是离散。离散尺度是因素。日期和时间是有序的连续比例。在引擎盖下,它们只是自&#34; 1970-01-01&#34;以来的日期或秒数。
如果您尝试传递scale_x_date
参数, sec.axis
确实会引发错误,即使它是dup_axis
。要解决此问题,请将日期/时间转换为数字,然后使用标签欺骗您的比例。虽然这需要一些摆弄,但它并不太复杂。
library(lubridate)
library(dplyr)
df <- data_frame(tm = ymd("2017-08-01") + 0:10,
y = cumsum(rnorm(length(tm)))) %>%
mutate(tm_num = as.numeric(tm))
df # A tibble: 11 x 3 tm y tm_num <date> <dbl> <dbl> 1 2017-08-01 -2.0948146 17379 2 2017-08-02 -2.6020691 17380 3 2017-08-03 -3.8940781 17381 4 2017-08-04 -2.7807154 17382 5 2017-08-05 -2.9451685 17383 6 2017-08-06 -3.3355426 17384 7 2017-08-07 -1.9664428 17385 8 2017-08-08 -0.8501699 17386 9 2017-08-09 -1.7481911 17387 10 2017-08-10 -1.3203246 17388 11 2017-08-11 -2.5487692 17389
我刚刚将一个简单的11天(0到10)矢量添加到&#34; 2017-08-01&#34;。如果你在那里运行as.numeric
,你将获得自Unix纪元开始以来的天数。 (见?lubridate::as_date
)。
df %>%
ggplot(aes(tm_num, y)) + geom_line() +
scale_x_continuous(sec.axis = dup_axis(),
breaks = function(limits) {
seq(floor(limits[1]), ceiling(limits[2]),
by = as.numeric(as_date(days(2))))
},
labels = function(breaks) {as_date(breaks)})
当您针对tm_num
绘制y
时,它会像普通数字一样对待,您可以使用scale_x_continuous(sec.axis = dup_axis(), ...)
。然后你必须弄清楚你想要多少休息以及如何标记它们。
breaks =
是一个获取数据限制的函数,并计算漂亮的中断。首先你绕过限制,以确保你得到整数(日期不适合非整数)。然后生成所需宽度的序列(days(2)
)。您可以使用weeks(1)
或months(3)
或其他任何内容,查看?lubridate::days
。在引擎盖下,days(x)
生成若干秒(每天86400,每周604800等),as_date
将其转换为自Unix时代以来的若干天,as.numeric
将其转换回整数。
labels =
是一个函数,它采用我们刚刚生成的整数序列,并将它们转换回可显示的日期。
这也适用于时代而不是日期。虽然日期是整数天,但是时间是整数秒(从Unix时期开始,对于日期时间,或者从午夜开始,有时间)。
让我们说你有一些观察时间,而不是几天。
代码类似,只需进行一些调整:
df <- data_frame(tm = ymd_hms("2017-08-01 23:58:00") + 60*0:10,
y = cumsum(rnorm(length(tm)))) %>%
mutate(tm_num = as.numeric(tm))
df # A tibble: 11 x 3 tm y tm_num <dttm> <dbl> <dbl> 1 2017-08-01 23:58:00 1.375275 1501631880 2 2017-08-01 23:59:00 2.373565 1501631940 3 2017-08-02 00:00:00 3.650167 1501632000 4 2017-08-02 00:01:00 2.578420 1501632060 5 2017-08-02 00:02:00 5.155688 1501632120 6 2017-08-02 00:03:00 4.022228 1501632180 7 2017-08-02 00:04:00 4.776145 1501632240 8 2017-08-02 00:05:00 4.917420 1501632300 9 2017-08-02 00:06:00 4.513710 1501632360 10 2017-08-02 00:07:00 4.134294 1501632420 11 2017-08-02 00:08:00 3.142898 1501632480
df %>%
ggplot(aes(tm_num, y)) + geom_line() +
scale_x_continuous(sec.axis = dup_axis(),
breaks = function(limits) {
seq(floor(limits[1] / 60) * 60, ceiling(limits[2] / 60) * 60,
by = as.numeric(as_datetime(minutes(2))))
},
labels = function(breaks) {
stamp("Jan 1,\n0:00:00", orders = "md hms")(as_datetime(breaks))
})
在这里,我将虚拟数据更新为从午夜到午夜之后的11分钟。在breaks =
我修改了它以确保我有一个整数分钟来创建中断,将as_date
更改为as_datetime
,并使用minutes(2)
每两个中断一次分钟。在labels =
中,我添加了一个功能stamp(...)(...)
,它可以创建一个很好的格式来显示。
最后一次。
df <- data_frame(tm = milliseconds(1234567 + 0:10),
y = cumsum(rnorm(length(tm)))) %>%
mutate(tm_num = as.numeric(tm))
df
# A tibble: 11 x 3 tm y tm_num <S4: Period> <dbl> <dbl> 1 1234.567S 0.2136745 1234.567 2 1234.568S -0.6376908 1234.568 3 1234.569S -1.1080997 1234.569 4 1234.57S -0.4219645 1234.570 5 1234.571S -2.7579118 1234.571 6 1234.572S -1.6626674 1234.572 7 1234.573S -3.2298175 1234.573 8 1234.574S -3.2078864 1234.574 9 1234.575S -3.3982454 1234.575 10 1234.576S -2.1051759 1234.576 11 1234.577S -1.9163266 1234.577
df %>%
ggplot(aes(tm_num, y)) + geom_line() +
scale_x_continuous(sec.axis = dup_axis(),
breaks = function(limits) {
seq(limits[1], limits[2],
by = as.numeric(milliseconds(3)))
},
labels = function(breaks) {format((as_datetime(breaks)),
format = "%H:%M:%OS3")})
这里我们从t = 20min34.567sec开始每毫秒观察11小时。所以在breaks =
中,我们免除了任何舍入,因为我们现在不想要整数。然后我们每隔milliseconds(2)
使用一次休息。然后需要将labels =
格式化为接受小数秒,&#34;%OS3&#34;表示秒位置的小数位数为3位数(最多可接受6位数,请参阅?strptime
)。
所有这一切都值得吗?可能不会,除非你真的想要一个重复的时间轴。我可能会在ggplot2
GitHub上将此问题作为问题发布,因为dup_axis
应该&#34;只是工作&#34;与约会时间。
答案 1 :(得分:0)
选项1 :这不是很优雅,但可以使用cowplot::align_plots
功能:
library(cowplot)
library(ggplot2)
dat1 <- tibble::tibble(x = c(rep("a", 50), rep("b", 50)),
y = runif(100))
p <- ggplot2::ggplot(dat1) +
ggplot2::geom_boxplot(ggplot2::aes(x = x, y = y))
p <- p + ggplot2::scale_y_continuous(sec.axis = ggplot2::sec_axis(~ .))
p1 <- p + scale_x_discrete(position = c( "bottom"))
p2 <- p + scale_x_discrete(position = c( "top"))
plots <- align_plots(p1, p2, align="hv")
ggdraw() + draw_grob(plots[[1]]) + draw_grob(plots[[2]])
选项2:
library(forcats)
dat1$num <- as.numeric(fct_recode(dat1$x, "1" = "a", "2" = "b"))
x11();ggplot2::ggplot(dat1, (aes(x = num, y = y, group = num))) +
geom_boxplot()+
ggplot2::scale_y_continuous(sec.axis = ggplot2::sec_axis(~ .)) +
scale_x_continuous(position = c("top"), breaks = c(1,2), labels = c("a", "b"),
sec.axis = ggplot2::sec_axis(~ .,breaks = c(1,2), labels = c("a", "b")))
注意:使用cowplot包(Duplicating Discrete Axis in ggplot2)发布[此处]类似问题的答案,但它对我不起作用。 cowplot::switch_axis_position()
函数已被弃用。