【问题标题】:Vectorise find closest date functionVectorise查找最近的日期函数
【发布时间】:2012-12-19 06:20:02
【问题描述】:

我想传入一个日期向量,并从第二个(部分匹配的)日期向量返回最接近的日期。

以下函数可以满足我对单个日期的要求,但是我无法弄清楚如何将其推广到 searchDate 是日期向量的情况。

closestDate <- function(searchDate, dateList, roundDown=FALSE){
  if (roundDown) {
    dist2date <- as.Date(dateList) - as.Date(searchDate)
    closest <- which(max(dist2date[dist2date<=0]) == dist2date)
  } else {
    dist2date <- as.Date(dateList) - as.Date(searchDate)
    closest <- which(min(dist2date[dist2date>=0]) == dist2date)
  }
  return(dateList[closest])
}

dateSeq <- seq(as.Date("2011-01-01"), as.Date("2012-12-19"), by='day')
oddDates <- dateSeq[as.logical(1:length(dateSeq) %%2)]

closestDate('2012-12-14', oddDates)
[1] "2012-12-15"

miscDatesLong <- rep(c('2012-12-14', '2012-12-16', '2012-12-18'), 100 )
closestDate(miscDatesLong, oddDates)

closestDate(miscDatesLong, oddDates)
[1] "2012-12-15" "2012-12-17" "2012-12-19"
Warning message:
In unclass(time1) - unclass(time2) :
  longer object length is not a multiple of shorter object length

有人可以帮忙吗?

【问题讨论】:

    标签: r date


    【解决方案1】:

    findInterval 函数可以快速做到这一点:

    dateSeq <- seq(as.Date("2011-01-01"), as.Date("2012-12-19"), by='day')
    oddDates <- dateSeq[as.logical(1:length(dateSeq) %%2)]
    
    oddDates[ findInterval(as.Date('2012-12-14'), oddDates)+1 ]
    
    miscDatesLong <- rep(c('2012-12-14', '2012-12-16', '2012-12-18'), 100)
    
    oddDates[ findInterval(as.Date(miscDatesLong), oddDates) + 1 ]
    

    要向下舍入而不是向上舍入,请删除 +1。如果您真的想找到最接近的日期,而不是之前或之后的日期,您可以创建一个新的日期列表,这些日期是间隔的中点 (as.Date(rowMeans(embed(as.numeric(oddDates),2)), '1960-01-01')) 并在这些日期上使用 findInterval。有关其他选项,请参阅 findInterval 的参数。

    【讨论】:

    • 我以为我真的理解findInterval,但你刚刚证明了它比我想象的更强大。
    • @DWin,有一段时间我想根据日期合并 2 个数据框,但有时日期不完全匹配,所以我想根据最近或最近的日期进行合并。就在最近它点击使用findInterval 像上面一样(虽然可以简化合并)会给我我需要做的合并。真是一个很棒的功能。
    • +1,谢谢,这是一个非常简洁的功能。我一直在测试它,它似乎返回了一个完全匹配的结果,通过调整+1 来控制上下舍入——所以我不明白你关于创建中间间隔的建议。我错过了什么?
    • @ricardo,原始间隔是等距的,所以这并不重要,但想象一下,如果您的日期范围是每个月的第一天,并且您希望 12 月 10 日舍入到 12 月 1 日,并且12 月 25 日舍入到 1 月 1 日(使用 +1 将允许两者都舍入到 12 月 1 日或 1 月 1 日,但与原始间隔没有什么不同),然后您可以使用中点创建间隔(例如 11 月 15 日、12 月 15 日、1 月 15 日等),那么 12 月 10 日和 12 月 25 日将处于不同的间隔,并映射到 12 月 1 日和 1 月 1 日。如果您的日期间隔非常不均匀,则中点将给出最接近的日期。
    【解决方案2】:

    ?Vectorize

    > closestDateV = Vectorize(closestDate,"searchDate")
    > closestDateV(c('2012-12-15','2012-12-14'), oddDates)
    2012-12-15 2012-12-14 
         15689      15689 
    

    返回值的日期已被删除。所以加回来:

    > as.Date(closestDateV(c('2012-12-15','2012-12-14'), oddDates),origin="1970-01-01")
      2012-12-15   2012-12-14 
    "2012-12-15" "2012-12-15" 
    

    您可能希望将所有这些都包含在一个新函数中。

    函数式编程很有趣!

    【讨论】:

      【解决方案3】:

      现在,通过示例,只需处理小于一种情况或大于另一种情况的日期子集,即当时正在检查的特定目标。

      closestDt <- function(searchDate, dateList, roundDown=FALSE) 
           as.Date( sapply( searchDate , function (x) if( roundDown ){ 
                      max( dateList[ dateList <= x ] ) } else {
                      min( dateList[ dateList >= x])  } 
                 ), "1970-01-01")
      

      【讨论】:

      • +1 谢谢。我无法使用您建议的日期功能,因为我需要能够在日期前后四舍五入之间移动。 sapply 使用我的函数的一个版本返回命名数字,这些数字可以抵抗强制回溯 - 我已经尝试过 melt(lapply(searchDate, FUN = function(x) matchDate(x, dateList)))[,1] 这似乎工作,但想了解我如何使 sapply 解决方案工作。
      • as.Date 包装版本返回日期分类值。我不理解“在日期前后四舍五入之间移动”的请求。请发布一个示例,说明这在实践中的含义。
      • @DWin 只是打破了不同方向的联系;)
      • @AnthonyDamico:我没有注意到“关系”被排除在外的事实。相应修改。
      • 不排除关系:两条closest 行使用或等于测试:例如closest &lt;- which(max(dist2date[dist2date&lt;=0]) == dist2date)
      【解决方案4】:
      # initiate a tie-breaking function
      tie.breaker <-
          function( x , y , la = look.after ){
      
              # if look.after is TRUE, eliminate all values below x
              # otherwise, eliminate all values above x
              if ( la ) y[ y < x ] <- NA else y[ y > x ] <- NA
      
              # then among the remaining values, figure out the date the shortest distance away
              z <- which.min( abs( x - y ) )[1]
              # use [1] to just take the first result, in case y contains duplicate dates
      
              # return z
              return( z )
          }
      
      # initiate your main function
      closestDate <- 
          function( searchDate , dateList , look.after = FALSE ){
      
              # apply a which.min( abs( ) ) command to each of the dates given, 
              # across every date in the larger list
              dist2date <- 
                  sapply( 
      
                      # on every element of searchDate..
                      as.Date( searchDate ) ,
      
                      # ..run the tie.breaker() function
                      tie.breaker , 
      
                      # and each time, pass in the dateList
                      as.Date( dateList ) ,
      
                      # and also the look.after TRUE/FALSE flag
                      look.after
                  )
      
              # return the matching dates in the same order as passed in
              dateList[ dist2date ]
          }
      
      # try with two input dates
      searchDate <- c( '2012-12-14' , '2012-11-18' )
      
      # create a few dates to test against..
      someDates <- c( '2012-11-12' ,  '2012-11-17' , '2012-12-15' , '2012-12-13' , '2012-12-15' , '2012-11-17' , '2012-11-20' )
      
      # return the two dates closests to the inputted dates
      
      # the first result gives 12/13, because look.after = FALSE
      closestDate( searchDate , someDates )
      
      # the first result gives 12/15, because look.after = TRUE
      closestDate( searchDate , someDates , look.after = TRUE )
      
      # reverse the order to prove it still works
      someDates <- c( '2012-11-12' , '2012-11-17' , '2012-12-13' , '2012-12-15' , '2012-12-13' , '2012-12-15' , '2012-11-17' )
      
      # the first result gives 12/13, because look.after = FALSE
      closestDate( searchDate , someDates )
      
      # the first result gives 12/15, because look.after = TRUE
      closestDate( searchDate , someDates , look.after = TRUE )
      

      【讨论】:

      • +1 谢谢。这很有帮助。我编写函数的方式是它前进或后退,即使它不是平局。我认为你唯一的打破平局?
      【解决方案5】:

      你可以使用cut:

      nearestDate <- function(dates,datesToMatch)
      {
              dtm <- sort(datesToMatch)
              dtmMid <- dtm[-length(dtm)]+diff(dtm)/2
              as.Date(cut(dates,
              breaks=c(as.Date("1970-01-01"),
              dtmMid,as.Date("2100-01-01")),labels=dtm))
      }
      
      dates1 <- as.Date(c("2012-02-14","2012-06-23","2012-08-27","2012-12-01"))
      dates2 <- as.Date(c("2012-04-01","2012-10-31","2012-12-25"))
      nearestDate(dates1,dates2)
      [1] "2012-04-01" "2012-04-01" "2012-10-31" "2012-12-25"
      

      请注意,由于 cut 函数不接受 +/-Inf,因此我必须为结束点选择一些魔术日期。根据您的使用情况进行修改。

      【讨论】:

        【解决方案6】:

        我想这就是你想要的:

        closestDate <- function(searchDate, dateList, roundDown=FALSE) {
          as.Date(sapply(as.Date(searchDate), function(x){
            dist <- abs(x - as.Date(dateList))
            closest <- dateList[which(min(dist) == dist)]
            return(ifelse(roundDown, min(closest), max(closest)))
          }), origin="1970-1-1")
        }
        

        sapply 是你的朋友。您只需确保返回的是日期而不是整数。

        【讨论】:

          猜你喜欢
          • 2019-06-30
          • 1970-01-01
          • 1970-01-01
          • 2019-07-09
          • 1970-01-01
          • 1970-01-01
          • 2018-06-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多