【问题标题】:How do I find the closest date to a given date?如何找到与给定日期最接近的日期?
【发布时间】:2019-03-07 16:13:54
【问题描述】:

我试图弄清楚如何在 1 个动物园对象中找到与另一个动物园对象中的给定日期最接近的日期(也可以使用 data.frame)。假设我有:

dates.zoo <- zoo(data.frame(val=seq(1:121)), order.by = seq.Date(as.Date('2018-12-01'), as.Date('2019-03-31'), "days"))
monthly.zoo <- zoo(data.frame(val=c(1,2,4)), order.by = c(as.Date('2018-12-14'), as.Date('2019-1-2'), as.Date('2019-2-3')))

对于dates.zoo 中的每个日期,我想将其与monthly.zoo 中最近的前一个日期对齐。 (NA 如果没有找到每月日期)。所以我期待的 data.frame/zoo 对象是:

...
2018-12-02   2  NA
...
2018-12-14  14  2018-12-14
2018-12-15  15  2018-12-14
2018-12-16  16  2018-12-14
...
2019-01-01  32  2018-12-14
2019-01-02  33  2019-01-02
2019-01-03  34  2019-01-02
...

注意:我更喜欢 Base-R 解决方案,但其他人也会很有趣

【问题讨论】:

  • 如果能看到一个可以轻松配置为能够支持 T-2、T-3、...而不仅仅是 T-1 的通用表单会很有趣。
  • 我相信会有一些聪明的方法来处理数据,链接上的表格答案^^。并且使用基本的 R 答案,我想你可以换掉 which.min 部分来选择第二、第三大
  • 检查findInterval

标签: r dataframe zoo


【解决方案1】:

可以使用使用 data.table 的滚动连接。 另见:https://www.r-bloggers.com/understanding-data-table-rolling-joins/

也是使用base-R的解决方案

data.table 解决方案

library(data.table)
dates.df <- data.table(val=seq(1:121), dates = seq.Date(as.Date('2018-12-01'), as.Date('2019-03-31'), "days"))
monthly.df <- data.table(val=c(1,2,4,5), dates = c(as.Date('2018-12-14'), as.Date('2019-1-2'), as.Date('2019-2-3')))

setkeyv(dates.df,"dates")
setkeyv(monthly.df,"dates")

#monthly.df[,nearest:=(dates)][dates.df,roll = 'nearest'] #closest date
monthly.df[,nearest:=(dates)][dates.df,roll = Inf] #Closest _previous_ date

基础 R 解决方案

dates.df <- zoo(data.frame(val=seq(1:121)), order.by = seq.Date(as.Date('2018-12-01'), as.Date('2019-03-31'), "days"))
monthly.df <- zoo(data.frame(val=c(1,2,4)), order.by = c(as.Date('2018-12-14'), as.Date('2019-1-2'), as.Date('2019-2-3')))

dates.df <- data.frame(val=dates.df$val,dates=attributes(dates.df)$index)
monthly.df <- data.frame(val=monthly.df$val,dates=attributes(monthly.df)$index)

min_distances <- as.numeric(dates.df$dates)- matrix(rep(as.numeric(monthly.df$dates),nrow(dates.df)),ncol=length(monthly.df$dates),byrow=T)
min_distances <- as.data.frame(t(min_distances))

closest <- sapply(min_distances,function(x) 
  { 
    w <- which(x==min(x[x>0])); 
    ifelse(length(w)==0,NA,w) 
  })

dates.df$closest_month <- monthly.df$dates[closest]

结果:data.table

> monthly.df[,nearest:=(dates)][dates.df,roll = Inf]
     val      dates    nearest i.val
  1:  NA 2018-12-01       <NA>     1
  2:  NA 2018-12-02       <NA>     2
  3:  NA 2018-12-03       <NA>     3
  4:  NA 2018-12-04       <NA>     4
  5:  NA 2018-12-05       <NA>     5
 ---                                
118:   4 2019-03-27 2019-02-03   117
119:   4 2019-03-28 2019-02-03   118
120:   4 2019-03-29 2019-02-03   119
121:   4 2019-03-30 2019-02-03   120
122:   4 2019-03-31 2019-02-03   121

结果库 R

> dates.df[64:69,]
           val      dates closest_month
2019-02-02  64 2019-02-02    2019-01-02
2019-02-03  65 2019-02-03    2019-01-02
2019-02-04  66 2019-02-04    2019-02-03
2019-02-05  67 2019-02-05    2019-02-03
2019-02-06  68 2019-02-06    2019-02-03
2019-02-07  69 2019-02-07    2019-02-03

【讨论】:

  • 似乎如此,我应该更仔细地阅读 - 所以 roll=Inf 将适用于此。我会调整的。谢谢!
【解决方案2】:

按照 Henrik 的建议使用 findInterval。我们可以这样做:

interval.idx <- findInterval(index(dates.zoo), index(monthly.zoo))
interval.idx <- ifelse(interval.idx == 0, NA, interval.idx)
dates.zoo$month <- index(monthly.zoo)[interval.idx]

【讨论】:

  • 这个答案确实需要标记为接受的答案。原因:我认为所有其他答案都具有 O(N) 的性能,其中 N 是需要通过搜索的动物园索引的数量。相比之下,findInterval documentation 表示它使用快速 log(N) 算法(二分搜索?)。
【解决方案3】:

如果对于dates.df中的每一个日期,你想得到monthly.df中小于给定日期的最接近的日期,并且monthly.df按日期升序排序,可以使用下面的方法。它计算monthly.df中索引小于给定日期的行数,相当于mothly.df按日期升序排序时的索引。如果有 0 个这样的行,则索引更改为 NA

inds <- rowSums(outer(index(dates.df), index(monthly.df), `>`))
inds[inds == 0] <- NA
dates.df_monthmatch <- index(monthly.df)[inds]


dates.df_monthmatch
#   [1] NA           NA           NA           NA           NA           NA          
#   [7] NA           NA           NA           NA           NA           NA          
#  [13] NA           NA           "2018-12-14" "2018-12-14" "2018-12-14" "2018-12-14"
#  [19] "2018-12-14" "2018-12-14" "2018-12-14" "2018-12-14" "2018-12-14" "2018-12-14"
#  [25] "2018-12-14" "2018-12-14" "2018-12-14" "2018-12-14" "2018-12-14" "2018-12-14"
#  [31] "2018-12-14" "2018-12-14" "2018-12-14" "2019-01-02" "2019-01-02" "2019-01-02"
#  [37] "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02"
#  [43] "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02"
#  [49] "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02"
#  [55] "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02"
#  [61] "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-01-02" "2019-02-03"
#  [67] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
#  [73] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
#  [79] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
#  [85] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
#  [91] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
#  [97] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
# [103] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
# [109] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
# [115] "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03" "2019-02-03"
# [121] "2019-02-03"

【讨论】:

    【解决方案4】:

    这是一种可能性,尽管我确实必须将对象更改为数据框才能分配动物园索引日期。此代码将月份、年份、最后日期与小于或等于要匹配的日期的条件进行比较。如果没有与此标准匹配的日期,则分配 NA。这些比较是通过“lubridate”包检查各个日期元素完成的,然后在逻辑上对最佳匹配进行索引。

    library(zoo)
    library(lubridate)
    
    dates.df <- zoo(data.frame(val=seq(1:121)), order.by = seq.Date(as.Date('2018-12-01'), as.Date('2019-03-31'), "days"))
    monthly.df <- zoo(data.frame(val=c(1,2,4)), order.by = c(as.Date('2018-12-14'), as.Date('2019-1-2'), as.Date('2019-2-3')))
    
    month_m<-month(monthly.df)
    month_d<-month(dates.df)
    
    year_m<-year(monthly.df)
    year_d<-year(dates.df)
    
    day_m<-day(monthly.df)
    day_d<-day(dates.df)
    
    index<-list()
    Index<-list()
    
    for( i in 1:length(monthly.df)){
    
    index[[i]]<-which(month_m[i] == month_d & year_m[i] == year_d
                      & day_d <= day_m[i])
    
    test<-unlist(index[[i]])
    
       #Assigns NA if no suitable match is found
       if(length(test)==0){
        print("NA")
        Index[[i]]=NA
        }else {
        Index[[i]]<-tail(test, n=1)
        }                      
    }
    
    Test<-unlist(Index)
    monthly.df_Fin<-as.data.frame(monthly.df)
    dates.df_Fin<-as.data.frame(dates.df)
    monthly.df_Fin$match<-as.character(row.names(dates.df_Fin)[Test])
    monthly.df_Fin$value<-dates.df_Fin[Test,]
    
    > monthly.df_Fin
               val      match value
    2018-12-14   1 2018-12-14    14
    2019-01-02   2 2019-01-02    33
    2019-02-03   4 2019-02-03    65
    

    假设我们更改了标准范围之外的值:

    monthly.df <- zoo(data.frame(val=c(1,2,4)), order.by = c(as.Date('2018-12- 
    14'), as.Date('2019-1-2'), as.Date('2017-2-3')))
    
    ....
    
    #Result
    > monthly.df_Fin
               val      match value
    2017-02-03   4       <NA>    NA
    2018-12-14   1 2018-12-14    14
    2019-01-02   2 2019-01-02    33
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-24
      • 1970-01-01
      • 2018-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多