找出特定日期属于哪个季节

51

我有一个日期向量,对于每个条目,我想分配一个季节。例如,如果一个日期在 12 月 21 日至 3 月 21 日之间,我会说这是 winter (冬季)。到目前为止,我尝试了以下代码,但我无法使它更加通用,不考虑年份

my.dates <- as.Date("2011-12-01", format = "%Y-%m-%d") + 0:60
low.date <- as.Date("2011-12-15", format = "%Y-%m-%d")
high.date <- as.Date("2012-01-15", format = "%Y-%m-%d")

my.dates[my.dates <= high.date & my.dates >= low.date] 
 [1] "2011-12-15" "2011-12-16" "2011-12-17" "2011-12-18" "2011-12-19" "2011-12-20" "2011-12-21" "2011-12-22" "2011-12-23" "2011-12-24" "2011-12-25"
[12] "2011-12-26" "2011-12-27" "2011-12-28" "2011-12-29" "2011-12-30" "2011-12-31" "2012-01-01" "2012-01-02" "2012-01-03" "2012-01-04" "2012-01-05"
[23] "2012-01-06" "2012-01-07" "2012-01-08" "2012-01-09" "2012-01-10" "2012-01-11" "2012-01-12" "2012-01-13" "2012-01-14" "2012-01-15"

我已经尝试过去掉年份进行日期格式化,但是没有成功。

ld <- as.Date("12-15", format = "%m-%d")
hd <- as.Date("01-15", format = "%m-%d")
my.dates[my.dates <= hd & my.dates >= ld] 

4
别忘了为我们在澳大利亚的朋友加上一个季节调整开关 :-) - Carl Witthoft
1
@CarlWitthoft 以及新西兰!还有巴西...至于洛杉矶人和季节:只能有一个! - Iterator
2
请注意,这里很多答案都关注天文季节(在欧洲通常使用)。在一些地方(比如澳大利亚),以及科学领域中,季节被简单地定义为三个月的时间段(DJF、MAM、JJA、SON),因为这更容易处理(并且巧合地更准确地表示了温度季节,因为从天文季节到温度季节有一个滞后期)。https://dev59.com/lIHba4cB1Zd3GeqPW9sD 是以这种意义提出问题的。 - naught101
11个回答

62

你可以尝试使用这样的代码:

getSeason <- function(DATES) {
    WS <- as.Date("2012-12-15", format = "%Y-%m-%d") # Winter Solstice
    SE <- as.Date("2012-3-15",  format = "%Y-%m-%d") # Spring Equinox
    SS <- as.Date("2012-6-15",  format = "%Y-%m-%d") # Summer Solstice
    FE <- as.Date("2012-9-15",  format = "%Y-%m-%d") # Fall Equinox

    # Convert dates from any year to 2012 dates
    d <- as.Date(strftime(DATES, format="2012-%m-%d"))

    ifelse (d >= WS | d < SE, "Winter",
      ifelse (d >= SE & d < SS, "Spring",
        ifelse (d >= SS & d < FE, "Summer", "Fall")))
}

my.dates <- as.Date("2011-12-01", format = "%Y-%m-%d") + 0:60
head(getSeason(my.dates), 24)
#  [1] "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"  
#  [8] "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"   "Fall"  
# [15] "Winter" "Winter" "Winter" "Winter" "Winter" "Winter"

需要注意的一点: 2012年是一个很好的年份,可以将所有日期转换为该年;由于该年是闰年,数据集中的任何2月29日都能被顺利处理。


第一个ifelse循环中应该是“&”,但实际上是“|”。我无法编辑它,因为它似乎太短了。但我喜欢这段代码:谢谢! - Puddlebunk
1
@Puddlebunk 不是的,这里应该用“|”,因为有两种不同的方式来表示冬季——日历日期可以在冬至之后或春分之前。很高兴你觉得这段代码有帮助。 - Josh O'Brien
@JoshO'Brien。我现在明白了。我使用了你的代码(再次感谢)来设置秋季和春季的燃烧窗口,所以我没有遇到一个类别落在日历不同侧的问题:这就是我的困惑所在。 - Puddlebunk
@Puddlebunk 很酷。曾经,我在印第安那州沙丘国家湖畔工作,帮助管理卡纳蓝蝶栖息地的控制性烧草。那是美好的回忆。 - Josh O'Brien
1
@M.Beausoleil 它只是给你一个长度为61的日期序列,从12月1日开始,我在那里使用它作为一些示例数据。 - Josh O'Brien
显示剩余3条评论

12

我也有和Tim一样丑陋的东西:

R> toSeason <- function(dat) {
+ 
+     stopifnot(class(dat) == "Date")
+ 
+     scalarCheck <- function(dat) {
+         m <- as.POSIXlt(dat)$mon + 1        # correct for 0:11 range
+         d <- as.POSIXlt(dat)$mday           # correct for 0:11 range
+         if ((m == 3 & d >= 21) | (m == 4) | (m == 5) | (m == 6 & d < 21)) {
+             r <- 1
+         } else if ((m == 6 & d >= 21) | (m == 7) | (m == 8) | (m == 9 & d < 21)) {
+             r <- 2
+         } else if ((m == 9 & d >= 21) | (m == 10) | (m == 11) | (m == 12 & d < 21)) {
+             r <- 3
+         } else {
+             r <- 4
+         }
+         r
+     }
+ 
+     res <- sapply(dat, scalarCheck)
+     res <- ordered(res, labels=c("Spring", "Summer", "Fall", "Winter"))
+     invisible(res)
+ }
R> 

这里有一个测试:

R> date <- Sys.Date() + (0:11)*30
R> DF <- data.frame(Date=date, Season=toSeason(date))
R> DF
         Date Season
1  2012-02-29 Winter
2  2012-03-30 Spring
3  2012-04-29 Spring
4  2012-05-29 Spring
5  2012-06-28 Summer
6  2012-07-28 Summer
7  2012-08-27 Summer
8  2012-09-26   Fall
9  2012-10-26   Fall
10 2012-11-25   Fall
11 2012-12-25 Winter
12 2013-01-24 Winter
R> summary(DF)
      Date               Season 
 Min.   :2012-02-29   Spring:3  
 1st Qu.:2012-05-21   Summer:3  
 Median :2012-08-12   Fall  :3  
 Mean   :2012-08-12   Winter:3  
 3rd Qu.:2012-11-02             
 Max.   :2013-01-24             
R> 

7

只需使用time2season函数。它获取日期并生成季节:

time2season(x, out.fmt = "months", type="default")

您可以在这里找到更多相关信息。该信息与IT技术有关,请注意。

这里的链接对我来说无法使用,因此我添加了一个有效的链接:https://www.rdocumentation.org/packages/hydroTSM/versions/0.6-0/topics/time2season - adl

7
我会创建一个查找表,并从那里开始。以下是一个示例(注意使用d()函数来混淆代码和实用的方法来填充查找表):
# Making lookup table (lut), only needed once. You can save
# it using save() for later use. Note I take a leap year.
d = function(month_day) which(lut$month_day == month_day)
lut = data.frame(all_dates = as.POSIXct("2012-1-1") + ((0:365) * 3600 * 24),
                 season = NA)
lut = within(lut, { month_day = strftime(all_dates, "%b-%d") })
lut[c(d("Jan-01"):d("Mar-20"), d("Dec-21"):d("Dec-31")), "season"] = "winter"
lut[c(d("Mar-21"):d("Jun-20")), "season"] = "spring"
lut[c(d("Jun-21"):d("Sep-20")), "season"] = "summer"
lut[c(d("Sep-21"):d("Dec-20")), "season"] = "autumn"
rownames(lut) = lut$month_day

创建查找表后,您可以轻松地从中提取一个月/日组合属于哪个季节。
dat = data.frame(dates = Sys.Date() + (0:11)*30)
dat = within(dat, { 
  season =  lut[strftime(dates, "%b-%d"), "season"] 
 })
> dat
        dates season
1  2012-02-29 winter
2  2012-03-30 spring
3  2012-04-29 spring
4  2012-05-29 spring
5  2012-06-28 summer
6  2012-07-28 summer
7  2012-08-27 summer
8  2012-09-26 autumn
9  2012-10-26 autumn
10 2012-11-25 autumn
11 2012-12-25 winter
12 2013-01-24 winter

所有的内容都已经向量化:)。我认为一旦表格创建完成,这个过程非常快速。


4

我认为这个方法可以解决问题,但它不够优雅:

    my.dates <- as.Date("2011-12-01", format = "%Y-%m-%d") + 0:60
    ld <- as.Date("12-15", format = "%m-%d")
    hd <- as.Date("01-15", format = "%m-%d")
    my.dates2 <- as.Date(unlist(lapply(strsplit(as.character(my.dates),split=""),function(x)   paste(x[6:10],collapse=""))),format="%m-%d")
    my.dates[my.dates2 <= hd | my.dates2 >= ld] 
    [1] "2011-12-15" "2011-12-16" "2011-12-17" "2011-12-18" "2011-12-19"
    [6] "2011-12-20" "2011-12-21" "2011-12-22" "2011-12-23" "2011-12-24"
    [11] "2011-12-25" "2011-12-26" "2011-12-27" "2011-12-28" "2011-12-29"
    [16] "2011-12-30" "2011-12-31" "2012-01-01" "2012-01-02" "2012-01-03"
    [21] "2012-01-04" "2012-01-05" "2012-01-06" "2012-01-07" "2012-01-08"
    [26] "2012-01-09" "2012-01-10" "2012-01-11" "2012-01-12" "2012-01-13"
    [31] "2012-01-14" "2012-01-15"

2

这里有一个更通用的解决方案,但需要使用3个库...它考虑了所有年份和半球:

library(data.table)
library(zoo)
library(dplyr)

get.seasons <- function(dates, hemisphere = "N"){
  years <- unique(year(dates))
  years <- c(min(years - 1), max(years + 1), years) %>% sort

  if(hemisphere == "N"){
    seasons <- c("winter", "spring", "summer", "fall")}else{
      seasons <- c("summer", "fall", "winter", "spring")}

  dt.dates <- bind_rows(
    data.table(date = as.Date(paste0(years, "-12-21")), init = seasons[1], type = "B"),# Summer in south hemisphere
    data.table(date = as.Date(paste0(years, "-3-21")), init = seasons[2], type = "B"), # Fall in south hemisphere
    data.table(date = as.Date(paste0(years, "-6-21")), init = seasons[3], type = "B"), # Winter in south hemisphere
    data.table(date = as.Date(paste0(years, "-9-23")), init = seasons[4], type = "B"), # Winter in south hemisphere
    data.table(date = dates, i = 1:(length(dates)), type = "A") # dates to compute
  )[order(date)] 

  dt.dates[, init := zoo::na.locf(init)] 

  return(dt.dates[type == "A"][order(i)]$init)
}

2

我的解决方案虽然不快,但对于季节的开始非常灵活,只要它们在函数assignSeason中首先被定义为数据框。它需要使用magrittr进行管道函数,使用lubridate进行year函数,并使用dplyr进行mutate

seasons <- data.frame(
   SE = as.POSIXct(c("2009-3-20", "2010-3-20", "2011-3-20", "2012-3-20", 
        "2013-3-20", "2014-3-20"), format="%Y-%m-%d"),
   SS = as.POSIXct(c("2009-6-21", "2010-6-21", "2011-6-21", "2012-6-20",
        "2013-6-21", "2014-6-21"), format="%Y-%m-%d"),
   FE = as.POSIXct(c("2009-9-22", "2010-9-23", "2011-9-23", "2012-9-22",
        "2013-9-22", "2014-9-23"), format="%Y-%m-%d"),
   WS = as.POSIXct(c("2009-12-21", "2010-12-21", "2011-12-22", "2012-12-21", 
        "2013-12-21", "2014-12-21"), format="%Y-%m-%d")
)

assignSeason <- function(dat, SeasonStarts=seasons) {
    dat %<>% mutate(
        Season = lapply(Date,
            function(x) {
                findInterval(
                    x, 
                    SeasonStarts[which(year(x)==year(SeasonStarts$WS)), ]
                )
            }
        ) %>% unlist    
    )
    dat[which(dat$Season==0 | dat$Season==4), ]$Season   <- "Winter"
    dat[which(dat$Season==1), ]$Season                  <- "Spring"
    dat[which(dat$Season==2), ]$Season                  <- "Summer"
    dat[which(dat$Season==3), ]$Season                  <- "Fall"
    return(dat)
}

示例数据:

dat = data.frame(
    Date = as.POSIXct(strptime(as.Date("2011-12-01", format = "%Y-%m-%d") + 
        (0:10)*30, format="%Y-%m-%d"))
)
dat %>% assignSeason

结果:

         Date Season
1  2011-12-01   Fall
2  2011-12-31 Winter
3  2012-01-30 Winter
4  2012-02-29 Winter
5  2012-03-30 Spring
6  2012-04-29 Spring
7  2012-05-29 Spring
8  2012-06-28 Summer
9  2012-07-28 Summer
10 2012-08-27 Summer
11 2012-09-26   Fall

1

我认为图书馆动物园会很容易

   library(zoo)
      yq <- as.yearqtr(as.yearmon(DF$dates, "%m/%d/%Y") + 1/12)
      DF$Season <- factor(format(yq, "%q"), levels = 1:4, 
      labels = c("winter", "spring", "summer", "fall"))

0

虽然有点晚了,但是我提供了一个额外的基于R语言的解决方案(我借鉴了@Josh O'Brien在天文季节部分的精彩逻辑),更新了2016年至2026年十年间春分、秋分和夏至、冬至的UTC日期(我将努力添加一个查找表,以获取过去和未来春分、秋分和夏至、冬至的UTC日期)。

# Function to take a date vector and return the season
# season_stamper => function
season_stamper <- function(
  date_vec, 
  date_fmt = "%Y-%m-%d", 
  hemisphere = c("north", "south"),
  season_type = c(
    ifelse(hemisphere == "south", 
           "monthly periods", "astronomical"),
           ifelse(hemisphere == "south", 
            "astronomical", "monthly periods")
  )){
  # Resolve which hemisphere was selected: 
  # hemisphere_selected => string scalar
  hemisphere_selected <- match.arg(hemisphere)
  # Extract the month number from the dates: 
  # mon_nos => integer vector
  mon_nos <- (as.POSIXlt(strptime(date_vec, date_fmt))$mon + 1)
  # Resolve the type of season: season_type_selected => character scalar
  season_type_selected <- match.arg(season_type)
  # If the season type is a 3-month period: 
  if(season_type_selected == "monthly periods"){
    # Resolve the seasons based on the hemisphere:
    # seasons => string vector
    seasons <- switch(
      hemisphere_selected,
      "north"=c("Winter", "Spring", "Summer", "Fall"),
      c("Summer", "Autumn", "Winter", "Spring")
    )
    # Stamp the date vector: season_stamps => string vector
    season_stamps <- seasons[((mon_nos %/% (12 / 4)) %% 4 + 1)]
  # Otherwise: 
  }else{
    # Convert dates from any year to 2020: d=> Date Scalar
    d <- as.Date(strftime(date_vec, format="2020-%m-%d"))
    
    # If the dates are from the northern hemisphere:
    if(hemisphere_selected == "north"){
      # Store as a variable Date of the Winter Solstice for a leap year: 
      # WS => date scalar 
      WS <- as.Date("2020-12-21", format = "%Y-%m-%d")
      # Store as a variable Date of the Spring Equinox for a leap year: 
      # SE => date scalar 
      SE <- as.Date("2020-3-20",  format = "%Y-%m-%d")
      # Store as a variable Date of the Summer Solstice for a leap year: 
      # SS => date scalar 
      SS <- as.Date("2020-6-21",  format = "%Y-%m-%d")
      # Store as a variable Date of the Fall Equinox for a leap year: 
      # SS => date scalar 
      FE <- as.Date("2020-9-22",  format = "%Y-%m-%d")
      # Resolve the season: season_stamps => character vector
      season_stamps <- ifelse(d >= WS | d < SE, "Winter",
              ifelse(d >= SE & d < SS, "Spring",
                      ifelse(d >= SS & d < FE, "Summer", "Fall")))
    # Otherwise: 
    }else{
      # Store as a variable Date of the Summer Solstice for a leap year: 
      # WS => date scalar 
      SS <- as.Date("2020-12-21", format = "%Y-%m-%d")
      # Store as a variable the Date of the Autumn Equinox:
      # AE => date scalar
      AE <- as.Date("2020-3-20",  format = "%Y-%m-%d")
      # Store as a variable the Date of the Winter Solstice: 
      # WS => date scalar
      WS <- as.Date("2020-6-21",  format = "%Y-%m-%d")
      # Store as a variable the DAte of the Spring Equinox: 
      # SE => date scalar
      SE <- as.Date("2020-9-22",  format = "%Y-%m-%d")
      # Resolve the season: season_stamps => character vector
      season_stamps <- ifelse(d >= SS | d < AE, "Summer", 
             ifelse(d >= SE & d < SS, "Spring",
                    ifelse(d >= WS & d < SE, "Winter", "Autumn")))
    }
  }
  # Explicitly define the returned object: 
  # string vecctor => Global Env
  return(season_stamps)
}

# Data: 
my.dates <- as.Date("2019-12-01", format = "%Y-%m-%d") + 0:60
low.date <- as.Date("2019-12-15", format = "%Y-%m-%d")
high.date <- as.Date("2020-01-15", format = "%Y-%m-%d")

date_vec <- my.dates[my.dates <= high.date & my.dates >= low.date] 

0

解决这个问题最准确的方法是将跨越新年的季节分割。

现在我是一个 c# 程序员,但是季节检查背后的思想对于所有编程语言都是相同的。 我在这里创建了一个 jsfiddle:https://jsfiddle.net/pieterjandc/L3prwqmh/1/

以下是核心代码,它分割跨越新年的季节,并执行比较:

const seasons = [{
    name: 'Spring',
    start: new Date(2000, 2, 21),
    end: new Date(2000, 5, 20)
},{
    name: 'Summer',
    start: new Date(2000, 5, 21),
    end: new Date(2000, 8, 20)
},{
    name: 'Autumn/Fall',
    start: new Date(2000, 8, 21),
    end: new Date(2000, 11, 20)
},{
    name: 'Winter',
    start: new Date(2000, 11, 21),
    end: new Date(2001, 2, 20)
}];

/** Checks if a date is within a specified season */
function checkSeason(season, date) {
    let remappedStart = new Date(2000, season.start.getMonth(), season.start.getDate());
    let remappedDate = new Date(2000, date.getMonth(), date.getDate());
    let remappedEnd = new Date(2000, season.end.getMonth(), season.end.getDate());
    
    // Check if the season crosses newyear
    if (season.start.getFullYear() === season.end.getFullYear()) {
        // Simple comparison
        return (remappedStart <= remappedDate) && (remappedDate <= remappedEnd);
    } else {
        // Split the season, remap all to year 2000, and perform a simple comparison
        return (remappedStart <= remappedDate) && (remappedDate <= new Date(2000, 11, 31))
        || (new Date(2000, 0, 1) <= remappedDate) && (remappedDate <= remappedEnd);
    }
}

function findSeason(seasons, date) {
    for (let i = 0; i < seasons.length; i++) {
        let isInSeason = checkSeason(seasons[i], date);
        if (isInSeason === true) {
            return seasons[i];
        }
    }
    return null;
}

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