在R中将日期时间格式化为季节?

7
在R中,可以将POSIXlt日期时间对象格式化为月份:
format(Sys.time(), format='%Y-%m')

有没有办法用季节或3个月的组(DJF,MAM,JJA,SON)完成相同的操作?这些分类在气候学和生态科学中非常常见,能够快速格式化它们将非常有用。显然DJF跨越2年,但出于此问题的目的,这并不重要-只需始终将它们推入任一年份即可(或者最好能够指定它们应放在哪一年)。
我正在将输出作为“by()”的索引使用,因此输出格式并不重要,只要每年/季节是唯一的即可。
编辑:数据示例:
dates <- Sys.Date()+seq(1,380, by=35)
dates <- structure(c(16277, 16312, 16347, 16382, 16417, 16452, 16487, 
                     16522, 16557, 16592, 16627), class = "Date")
dates
#[1] "2014-07-26" "2014-08-30" "2014-10-04" "2014-11-08" "2014-12-13"
# "2015-01-17" "2015-02-21" "2015-03-28" "2015-05-02" "2015-06-06" "2015-07-11"

应该得到的结果是:
c("2014-JJA", "2014-JJA", "2014-SON", "2014-SON", "2015-DJF", "2015-DJF", 
  "2015-DJF", "2015-MAM", "2015-MAM", "2015-JJA", "2015-JJA")

但是“2015-DJF”也可能是“2014-DJF”。另外,输出的形式并不重要 - “2104q4”或“201404”也可以。


1
这里是一个相关的问题和答案:链接 - Josh O'Brien
2
如果 Q1 是 DJF 等,则将其转换为 "yearmon" 类,添加一个月并转换为 "yearqtr" 类:library(zoo); format(as.yearqtr(as.yearmon(Sys.time())+1/12))。如果您想要不同的格式,可以使用格式字符串,例如 format(as.yearqtr(as.yearmon(Sys.time())+1/12), "%Y-%q") - G. Grothendieck
4个回答

6

as.POSIXlt返回一个具有名称的列表(这使其不适用于data.frame列)。可以单独访问列表列,包括“year”(基于1900,而不是默认使用的1970)和“mon”(基于0)。查看此列表的最佳位置在帮助系统中的?DateTimeClasses

首先只是一个季节的计算,然后是年份-季节的计算。

 c('DJF', 'MAM', 'JJA', 'SON')[ # select from character vector with numeric vector
          1+((as.POSIXlt(dates)$mon+1) %/% 3)%%4]

 [1] "JJA" "JJA" "SON" "SON" "DJF" "DJF" "DJF" "MAM" "MAM" "JJA"
[11] "JJA"



   paste( 1900 + # this is the base year for POSIXlt year numbering 
             as.POSIXlt( dates )$year + 
             1*(as.POSIXlt( dates )$year==12) ,   # offset needed for December
          c('DJF', 'MAM', 'JJA', 'SON')[          # indexing from 0-based-mon
                             1+((as.POSIXlt(dates)$mon+1) %/% 3)%%4] 
          , sep="-")
 [1] "2014-JJA" "2014-JJA" "2014-SON" "2014-SON" "2014-DJF"
 [6] "2015-DJF" "2015-DJF" "2015-MAM" "2015-MAM" "2015-JJA"
[11] "2015-JJA"

构建你期望的格式化函数不应该太难。这只是针对月份和年份的 POSIXlt 值的模算术运算。


很酷,不错。我开始做这个题目时,看到了你发布然后又删除的答案,所以在此期间我自己写了一个。你的答案更好,因为使用了更好听的季节名称,但我猜它们在功能上差不多。我会让大家投票决定 :) - naught101
我认为我的问题不是很明显。我在模数除法和余数方面遇到了困难,结果总是出错。 - IRTFM
我也是这样,因为 POSIXlt 中月份的内部表示是从零开始索引的,而外部表示是从一开始索引的,真是太烦人了。无论如何,我决定采用自己的方法,只是因为它允许你以日期的形式保留结果,这对我的目的很有用。 - naught101
有人能够对模数算术进行解释吗?我对于 %/%%% 感到很疑惑,希望可以了解更多。 - boshek
你应该使用 %% 和 %/% 来“玩耍”1:12。 - IRTFM
1
@KingJulien:请不要在评论区发垃圾信息。你的问题最终会得到回答。 - naught101

4

我喜欢使用查找向量来解决这类问题,例如:

x <- as.POSIXlt(
  seq.Date(as.Date("2000-01-01"),as.Date("2002-01-01"),by="2 months")
)

例如,如果您想指定南半球的季节,可以这样做:
src <- rep(c("su","au","wi","sp"),each=3)[c(2:12,1)]

paste(format(x,"%Y-%m"),src[x$mon+1])
# [1] "2000-01 su" "2000-03 au" "2000-05 au" "2000-07 wi" "2000-09 sp"
# [6] "2000-11 sp" "2001-01 su" "2001-03 au" "2001-05 au" "2001-07 wi"
#[11] "2001-09 sp" "2001-11 sp" "2002-01 su"

根据您的需要更改 src 名称以重新标记类别。


2

假设Q1代表冬季,Q2代表春季,依次类推,则:

seasonal.quarters <- function(x) {
    x <- as.POSIXlt(x)
    x$mon <- (x$mon + 1) %% 12
    quarters(x)
}

options(stringsAsFactors=FALSE)

nonleap.year <- seq(from=as.POSIXct('2013-1-1'), to=as.POSIXct('2014-1-1'), by='day')
d <- data.frame(ms=months(nonleap.year), qs=seasonal.quarters(nonleap.year))
by(d, INDICES=list(d$qs), FUN=function(x) unique(x$ms))
# : Q1
# [1] "January"  "February" "December"
# -------------------------------------
# : Q2
# [1] "March" "April" "May"  
# -------------------------------------
# : Q3
# [1] "June"   "July"   "August"
# -------------------------------------
# : Q4
# [1] "September" "October"   "November" 

leap.year <- seq(from=as.POSIXct('2016-1-1'), to=as.POSIXct('2017-1-1'), by='day')
d <- data.frame(ms=months(leap.year), qs=seasonal.quarters(leap.year))
by(d, INDICES=list(d$qs), FUN=function(x) unique(x$ms))
# : Q1
# [1] "January"  "February" "December"
# -------------------------------------
# : Q2
# [1] "March" "April" "May"  
# -------------------------------------
# : Q3
# [1] "June"   "July"   "August"
# -------------------------------------
# : Q4
# [1] "September" "October"   "November" 

difftime基本上是把十二月变成了一月,对吧?但这样做不准确,因为每个季度的长度都不同,在闰年中第一季度的长度也不同。 - naught101
是的,我刚意识到这一点。这不可靠。很快就会删除。 - Matthew Plourde
不确定这是否实现了我的要求-请参见问题中编辑的示例。 - naught101

2
这是对上面42的回答的另一种选择。请看我在那里发表的评论,了解我发布它的原因。
dates_orig <- as.POSIXlt(c("2013-01-01", "2013-02-01", "2013-03-01", "2013-04-01", "2013-05-01", "2013-06-01", "2013-07-01", "2013-08-01", "2013-09-01", "2013-10-01", "2013-11-01", "2013-12-01", "2014-01-01", "2014-02-01", "2014-03-01", "2014-04-01", "2014-05-01", "2014-06-01", "2014-07-01", "2014-08-01", "2014-09-01", "2014-10-01", "2014-11-01", "2014-12-01"))

format(dates_orig, format='%Y%b')
 [1] "2013Jan" "2013Feb" "2013Mar" "2013Apr" "2013May" "2013Jun" "2013Jul" "2013Aug" "2013Sep" "2013Oct" "2013Nov" "2013Dec" "2014Jan" "2014Feb" "2014Mar"
[16] "2014Apr" "2014May" "2014Jun" "2014Jul" "2014Aug" "2014Sep" "2014Oct" "2014Nov" "2014Dec"

dates <- as.POSIXlt(dates_orig)
# shift Jan and Feb to the previous year
dates$year[dates$mon < 2] <- dates$year[dates$mon < 2] - 1
# convert months to seasons (named by first month of season)
dates$mon <- (((dates$mon - 2) %/% 3) %% 4) * 3 + 2

format(dates, format='%Y%b')
[1] "2012Dec" "2012Dec" "2013Mar" "2013Mar" "2013Mar" "2013Jun" "2013Jun" "2013Jun" "2013Sep" "2013Sep" "2013Sep" "2013Dec" "2013Dec" "2013Dec" "2014Mar"
[16] "2014Mar" "2014Mar" "2014Jun" "2014Jun" "2014Jun" "2014Sep" "2014Sep" "2014Sep" "2014Dec"

1
它使用了破坏性修改来处理原始数据,但实际上并没有回答问题,因为你仍然得到的是月份。 - IRTFM
@42-:它会得出季节的第一个月 - 如果需要,将月份替换为“DJF”,“MMA”等并不难。对我来说也不是。至于数据丢失,将日期转换为季节始终会有损失。如果您在意,那么请勿丢弃原始数据。您的解决方案同样会有损失。 - naught101
我认为你应该将那个解释编辑到答案中,而不是让人们去看评论。 - Frank
我的评论中有一部分是由于误读而来的,但你回答了另一部分。我建议你继续采纳Frank的建议,我会删除我所有的评论。 - IRTFM

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