R绘图:指定时间刻度线的数量 - 时间/日期相当于pretty函数

16
我想知道如何在 x 轴绘制时间时绘制更多的刻度线。
基本上,我需要一个与 pretty 函数相当的时间函数。因为 pretty 函数使用 1、2、5 和 10 的因子,所以对于时间来说并不太适用。对于时间,我们可能需要小时、半小时等等。
plot(as.POSIXct(x,origin="1960-01-01"),y,type="l",xlab="Time")

刻度太少且间距过大。

zoox<-zoo(y,as.POSIXct(stats$Time,origin="1960-01-01"))
plot(zoox)

提供相同的结果。

谢谢。

编辑:

只是为了澄清(目前的答案没有解决我的问题):我正在寻找像pretty一样用于日期的函数,例如,一个函数,它接受开始日期、结束日期、刻度数,并输出刻度的位置。也就是说,我非常清楚可以绘制小时、分和其他内容,但是pretty自动确定数字的刻度距离,对于日期,生成的函数应该自行决定使用天、小时、分钟、秒、毫秒、微秒、30分钟、500微秒、5秒等时间间隔。这就是pretty对数字的作用方式。

编辑2:

这是我目前用来确定时间轴格式的函数(请注意,这对日期无效):

mydiff <- end-start
if(mydiff>1800) {
    axis.POSIXct(1,xrange,format="%H:%M")
} else if(mydiff>30) {
    axis.POSIXct(1,xrange,format="%H:%M:%S")
} else if(mydiff>0.5) {
    axis.POSIXct(1,xrange,format="%H:%M:%OS3")
} else
    axis.POSIXct(1,xrange,format="%H:%M:%OS6")
}

我没有一个增加刻度标记的函数,所以我使用默认数量的刻度标记。


R的内部函数不会考虑秒以下的时间单位。POSIXct类可以处理小数秒,但是围绕该类的辅助函数通常只在秒级别工作。因此,微秒间隔实际上将以小数秒形式出现,并且必须手动处理。 - Gavin Simpson
@GavinSimpson:不准确,至少在我的系统上,微秒运行得非常完美。使用axis.POSIXct(1,xrange,format="%H:%M:%OS6")将它们显示在图表中。让我再做一个适当函数的编辑。虽然单位是秒,但它是一个双精度值,并且具有相当高的精度。 - Cookie
library(xts); ?axTicksByTime 或者 quantmod:::axTicksByTime2 能够满足你的需求吗? - GSee
@Cookie 抱歉,我没有表达清楚。我的意思是你不能使用“500微秒”这样的格式;R只能理解十进制秒,并且在seq()axis.POSIXt()等函数中自动处理秒级别的时间。 - Gavin Simpson
4个回答

10

利用可重现的示例

set.seed(1)
x <- as.POSIXct(sort(sample(100000, 100)), origin="1960-01-01")
y <- rpois(100, 5)
plot(x, y, type = "l", xlab = "Time")

我们可以使用axis.POSIXct()函数(也可以使用Axis() S3通用函数)向绘图中添加自定义轴。重点是,您作为用户可以完全控制刻度线的绘制位置及其标签,只需要在默认设置无法满足需求时多花一些功夫。

首先,我们绘制数据,但抑制绘制x轴:

plot(x, y, type = "l", xlab = "Time", xaxt = "n")

接下来我将在每个小时的开始处添加一个主要的刻度标记。因此,我创建了一个日期时间序列,该序列从:

  1. 第一个观测值所在的整点,
  2. 到最后一个观测发生的整点的结束时间(使用ceiling()函数将我们移动到下一个小时),
  3. 以1小时为单位递增by = "1 hour")。

将该序列提供给axis.POSIXct()函数的at参数。如果不理解,请查看?axis.POSIXct?par文档。

## add axis tick at each hour:
axis.POSIXct(side = 1, x = x,
             at = seq(from = round(x[1], "hours"),
                      to = x[1] + ceiling(difftime(tail(x, 1), head(x, 1), 
                                                   units = "hours")),
                      by = "1 hour"),
             las = 2)

生成的图形如下所示:

图片描述

为了显示更精细的控制,我现在在每个半小时位置添加了次刻度线,但抑制了这些刻度线的注释(通过 labels 参数),并且使次刻度线更短(通过图形参数 tcl)。请注意,seq() 方法的 by 参数可以使用指定间隔的数字量。

## add minor ticks at 30 min intervals to above plot
axis.POSIXct(side = 1, x = x,
             at = seq(from = round(x[1], "hours"),
                      to = x[1] + ceiling(difftime(tail(x, 1), head(x, 1), 
                                                   units = "hours")),
                       by = "30 mins"),
             las = 2, tcl = -0.2, labels = FALSE)

现在的图表看起来是这样的:

enter image description here

您可以添加自己的标签,而不是axis.POSIXct函数提供的标签。如果您想这样做,那么我们应该将seq()的输出分配给一个对象,然后可以在该对象上使用format()函数。例如:

plot(x, y, type = "l", xlab = "Time", xaxt = "n")
tseq <- seq(from = round(x[1], "hours"),
            to = x[1] + ceiling(difftime(tail(x, 1), head(x, 1), 
                                         units = "hours")),
            by = "1 hour")
axis.POSIXct(side = 1, x = x, at = tseq,
             labels = format(tseq, format = "%H:%M"), las = 2)
下图显示了生成的绘图: enter image description here format()返回格式化后的日期时间字符串。您可以使用paste()添加任何其他内容,或查看可以用于格式化日期时间对象的其他占位符,请参阅?strftime

抱歉在这个回答中花费了这么多心思,但你仍然需要自行选择小时或半小时。如果开始时间和结束时间之间只有10秒钟会发生什么情况呢?举个例子,我个人的情况是,我有一个交互式绘图程序,使用getGraphicsEvent()函数,我可以通过鼠标放大绘图,随着放大程度的增加,绘图坐标轴会慢慢从小时切换到分钟,再切换到秒,最后切换到毫秒 - 当缩小放大倍数时,又会逆向切换回来。 - Cookie
我只选择了那些时间间隔,因为你明确地提到了它们。据我所知,在R中没有任何预设的功能可以满足你的要求,所以你需要将我展示的自定义轴方法与axis.POSIXct()的修改版本结合起来。请参阅我的其他答案以获取相关指针。(即将推出) - Gavin Simpson
我并不否认在R中编写函数是可能的(毕竟,R只是C和C++,并允许更多的第三方插件,因此几乎任何事情都是可能的),但我想说的是,到目前为止还没有这样的函数存在,而且我也没有时间自己编写一个。 - Cookie
因此,另一种选择是采用您的Edit 2代码位,但生成我上面展示的日期时间序列,并将“by”参数更改为适合您if else子句中的每个选项的值。我认为您展示的代码加上我上面展示的自定义轴代码就可以了。axis.POSIXct()确实可以做到您想要的很多事情,问题在于它使用默认的pretty(),而您似乎想要变化(有更多刻度等)?如果是这样,请尝试像我下面提到的那样修改pretty() - Gavin Simpson
非常感谢这篇文章。我有一些带时间的POSIXct日期时间对象,并尝试使用axis.POSIXct(1,at = seq(min(x),max(x),by =“days”),format =“%b%d”),但是它会生成错误位置的刻度线!(我不明白为什么。似乎第一个刻度线总是靠左对齐,即使第一个数据点不在一天的开始。)我也感谢次要刻度线代码。 - Mark Rajcok

7
axis.POSIXct()已经尽力猜测适合坐标轴的漂亮值,因此我建议从那里开始修改。目前,它在内部依赖于对日期时间应用pretty()函数的某些功能。它使用pretty()的默认值,所以你可以修改该函数并添加一个nmin.n参数来增加选择的漂亮刻度线数量。

axis.POSIXct()复制到自己的函数/文件中(给它一个新名称)。 在定义中添加一个nmin.n参数,可能比pretty()函数使用的默认值大。并将其传递给进行的每个pretty()调用。

试试看。如果表现良好,那么可以使用fixInNamespace(axis.POSIXct)对实际函数进行相同的更改,以便在调用该函数的所有绘图上使用它。

P.S. 这是一种可能的修改方式:

function (side, x, at, format, labels = TRUE, n = 5, ...) {
  mat <- missing(at) || is.null(at)
  if (!mat) 
    x <- as.POSIXct(at)
  else x <- as.POSIXct(x)
  range <- par("usr")[if (side%%2) 
    1L:2L
    else 3L:4L]
  d <- range[2L] - range[1L]
  z <- c(range, x[is.finite(x)])
  attr(z, "tzone") <- attr(x, "tzone")
  if (d < 1.1 ) {
    sc <- 0.001
    if (missing(format)) 
      format <- "%H:%M:%OS6"
  }
  else if (d < 1.1 * 30) {
    sc <- 1
    if (missing(format)) 
      format <- "%H:%M:%OS3"
  }
  else if (d < 1.1 * 60) {
    sc <- 1
    if (missing(format)) 
      format <- "%H:%M:%S"
  }
  else if (d < 1.1 * 30 * 60) {
    sc <- 60
    if (missing(format)) 
      format <- "%H:%M:%S"
  }
  else if (d < 1.1 * 60 * 60) {
    sc <- 60
    if (missing(format)) 
      format <- "%H:%M"
  }
  else if (d < 1.3 * 60 * 60 * 24) {
    sc <- 60 * 60
    if (missing(format)) 
      format <- "%H:%M"
  }
  else if (d < 2 * 60 * 60 * 24) {
    sc <- 60 * 60
    if (missing(format)) 
      format <- "%a %H:%M"
  }
  else if (d < 7 * 60 * 60 * 24) {
    sc <- 60 * 60 * 24
    if (missing(format)) 
      format <- "%a"
  }
  else {
    sc <- 60 * 60 * 24
  }
  if (d < 60 * 60 * 24 * 50) {
    zz <- pretty(z/sc,n=n)
    z <- zz * sc
    z <- .POSIXct(z, attr(x, "tzone"))
    if (sc == 60 * 60 * 24) 
      z <- as.POSIXct(round(z, "days"))
    if (missing(format)) 
      format <- "%b %d"
  }
  else if (d < 1.1 * 60 * 60 * 24 * 365) {
    z <- .POSIXct(z, attr(x, "tzone"))
    zz <- as.POSIXlt(z)
    zz$mday <- zz$wday <- zz$yday <- 1
    zz$isdst <- -1
    zz$hour <- zz$min <- zz$sec <- 0
    zz$mon <- pretty(zz$mon,n=n)
    m <- length(zz$mon)
    M <- 2 * m
    m <- rep.int(zz$year[1L], m)
    zz$year <- c(m, m + 1)
    zz <- lapply(zz, function(x) rep(x, length.out = M))
    zz <- .POSIXlt(zz, attr(x, "tzone"))
    z <- as.POSIXct(zz)
    if (missing(format)) 
      format <- "%b"
  }
  else {
    z <- .POSIXct(z, attr(x, "tzone"))
    zz <- as.POSIXlt(z)
    zz$mday <- zz$wday <- zz$yday <- 1
    zz$isdst <- -1
    zz$mon <- zz$hour <- zz$min <- zz$sec <- 0
    zz$year <- pretty(zz$year,n=n)
    M <- length(zz$year)
    zz <- lapply(zz, function(x) rep(x, length.out = M))
    z <- as.POSIXct(.POSIXlt(zz))
    if (missing(format)) 
      format <- "%Y"
  }
  if (!mat) 
    z <- x[is.finite(x)]
  keep <- z >= range[1L] & z <= range[2L]
  z <- z[keep]
  if (!is.logical(labels)) 
    labels <- labels[keep]
  else if (identical(labels, TRUE)) 
    labels <- format(z, format = format)
  else if (identical(labels, FALSE)) 
    labels <- rep("", length(z))
  axis(side, at = z, labels = labels, ...)
}

与原始函数的区别可以在此处查看。


还不错。最后证明这很简单 - 函数重载甚至可以帮助修复不正确的默认显示格式。 - Cookie
好的,只要你满意 pretty() 函数使用因素1、2、5和10来选择内容的方式就行。如果这接近了,但是你需要比默认值更多的刻度标记,可以尝试修改axis.POSIXct函数。或者将其复制到自己的函数中,进行所需的更改,并在Edit 2中使用该函数代替原有代码中的函数。这取决于你是否只想让缩放函数生成的图表使用新的启发式算法,还是让R生成的任何图表都使用它。 - Gavin Simpson

3

我倾向于使用函数axis.POSIXct和/或函数cut.POSIXt。假设您的日期向量为time,与之相关的值向量为x:

plot(time,x,xaxt="n")
axis.POSIXct(side=1,at=seq(min(time),max(time),by="week"),format="%d-%m") #For instance

并且更进一步,使用 cut.POSIXt

plot(time,x,xaxt="n")
axis.POSIXct(side=1,at=cut(time, breaks="week"),format="%d-%m")

0

抑制绘图中轴线的默认设置(axes = FALSE或单独的轴线),并使用函数axis详细说明您想要的轴线设置(特别是at参数)。请参见?axis

plot(cars, axes = FALSE)
axis( 1, at = c(5,6,7,10,11,12,21,22,23) )

抱歉需要更清楚一些:Pretty根据所需的刻度线数量来计算刻度值。或者用您的例子重新表达问题,那个数值向量从哪里来? - Cookie
@Cookie 在我的例子中,“at”是一个任意向量,以展示您可以将刻度线放置在任何位置。我是否仍然误解了您的问题?例如,您可以为每个季度小时确定不同的刻度线大小和颜色。 - Andre Michaud
是的,你还是误解了。Pretty tickmarks会随机选择起点和终点,并给出一定数量的刻度线,从而生成漂亮的图形。我需要的是一个函数,它接受开始时间、结束时间和刻度数作为参数,并返回上述提到的向量 - 最好还能调整时间格式。 - Cookie

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