ggplot2双方相同的尺度(非连续)

时间:2017-07-28 05:56:29

标签: r ggplot2

目标

使用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轴,其中刻度为任何,而不仅仅是离散(因子变量)。为简单起见,我在最小的代表中使用了一个离散变量。

例如,此问题出现在非连续缩放为datetimetime格式的上下文中。

2 个答案:

答案 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 =是一个函数,它采用我们刚刚生成的整数序列,并将它们转换回可显示的日期。

enter image description here

这也适用于时代而不是日期。虽然日期是整数天,但是时间是整数秒(从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(...)(...),它可以创建一个很好的格式来显示。

enter image description here

最后一次。

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)。

enter image description here

所有这一切都值得吗?可能不会,除非你真的想要一个重复的时间轴。我可能会在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]])

enter image description here

选项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()函数已被弃用。