【问题标题】:R: sequence of days between datesR:日期之间的天数
【发布时间】:2015-08-21 17:30:45
【问题描述】:

我有以下数据框:

AllDays  
2012-01-01  
2012-01-02  
2012-01-03  
...  
2015-08-18  

Leases 
StartDate  EndDate
2012-01-01 2013-01-01  
2012-05-07 2013-05-06  
2013-09-05 2013-12-01   

我想要做的是,对于 allDays 数据框中的每个日期,计算有效的租约数量。例如如果有 4 个开始日期 = 2015-01-01 的租约,那么我想在该数据框中放置一个 4。

我有以下代码

  for (i in 1:nrow(leases))
  {
    occupied = seq(leases$StartDate[i],leases$EndDate[i],by="days")
    occupied = occupied[occupied < dateOfInt]
    matching = match(occupied,allDays$Date)
    allDays$Occupancy[matching] = allDays$Occupancy[matching] + 1
  }

这可行,但由于我有大约 5000 个租约,因此大约需要 1.1 秒。有没有人有更有效的方法需要更少的计算时间? 感兴趣的日期只是当前日期,仅用于确保它不计算未来的租赁日期。

【问题讨论】:

  • 所有日期是否都存储为Date 变量?似乎是 foverlaps 的东西,但我仍然不知道如何使用该功能:\

标签: r date seq


【解决方案1】:

使用seq 几乎肯定是低效的——假设您的数据租用了长达 10000 年的租约。 seq 将永远返回 10000*365-1 天,这对我们来说并不重要。然后我们必须使用%in%,这也会进行相同数量的不必要比较。

我不确定以下是最好的方法(我确信有一个完全矢量化的解决方案),但它更接近问题的核心。

数据

set.seed(102349)
days<-data.frame(AllDays=seq(as.Date("2012-01-01"),
                             as.Date("2015-08-18"),"day"))

leases<-data.frame(StartDate=sample(days$AllDays,5000L,T))
leases$EndDate<-leases$StartDate+round(rnorm(5000,mean=365,sd=100))

方法

使用data.tablesapply

library(data.table)
setDT(leases); setDT(days)

days[,lease_count:=
       sapply(AllDays,function(x)
         leases[StartDate<=x&EndDate>=x,.N])][]
         AllDays lease_count
   1: 2012-01-01           5
   2: 2012-01-02           8
   3: 2012-01-03          11
   4: 2012-01-04          16
   5: 2012-01-05          18
  ---                       
1322: 2015-08-14        1358
1323: 2015-08-15        1358
1324: 2015-08-16        1360
1325: 2015-08-17        1363
1326: 2015-08-18        1359

【讨论】:

  • 感谢您的回复。我不断收到错误消息,说the longer object length is not a multiple of shorter object length。有任何想法吗?谢谢!
  • 对不起,我的错!有用!只需0.5秒多一点,好多了!非常感谢!
  • 这正是foverlaps 的亮点:setkey(days[, AllDays1:=AllDays,], AllDays, AllDays1);setkey(leases, StartDate, EndDate);foverlaps(leases, days)[, .(lease_count=.N), AllDays]
  • @khashaa 是的!请发布基准以证明它的主导地位,我相信这是更好的答案。
  • @MichaelChirico 既然它基于您的示例,为什么不将其添加到您的答案中。
【解决方案2】:

这正是 foverlaps 的亮点:根据另一个 data.frame 对 data.frame 进行子集化(foverlaps 似乎是为此目的量身定制的)。

基于@MichaelChirico 的数据。

setkey(days[, AllDays1:=AllDays,], AllDays, AllDays1)
setkey(leases, StartDate, EndDate)
foverlaps(leases, days)[, .(lease_count=.N), AllDays]
#   user  system elapsed 
#  0.114   0.018   0.136
# @MichaelChirico's approach
#   user  system elapsed 
#  0.909   0.000   0.907 

Here 是 @Arun 对其工作原理的简要说明,这让我开始使用 data.table

【讨论】:

  • 这应该是公认的答案.. :-(。知道这个答案让你开始使用 dt。
【解决方案3】:

没有您的数据,我无法测试这是否更快,但它可以用更少的代码完成工作:

for (i in 1:nrow(AllDays)) AllDays$tally[i] = sum(AllDays$AllDays[i] >= Leases$Start.Date & AllDays$AllDays[i] <= Leases$End.Date)

我使用以下方法对其进行了测试;请注意,两个数据框中的相关列都被格式化为日期:

AllDays = data.frame(AllDays = seq(from=as.Date("2012-01-01"), to=as.Date("2015-08-18"), by=1))
Leases = data.frame(Start.Date = as.Date(c("2013-01-01", "2012-08-20", "2014-06-01")), End.Date = as.Date(c("2013-12-31", "2014-12-31", "2015-05-31")))

【讨论】:

    【解决方案4】:

    另一种方法,但我不确定它是否更快。

    library(lubridate)
    library(dplyr)
    
    AllDays = data.frame(dates = c("2012-02-01","2012-03-02","2012-04-03"))
    
    Lease = data.frame(start = c("2012-01-03","2012-03-01","2012-04-02"),
                       end = c("2012-02-05","2012-04-15","2012-07-11"))
    
    # transform to dates
    AllDays$dates = ymd(AllDays$dates)
    Lease$start = ymd(Lease$start)
    Lease$end = ymd(Lease$end)
    
    # create the range id
    Lease$id = 1:nrow(Lease)
    
    AllDays
    
    #        dates
    # 1 2012-02-01
    # 2 2012-03-02
    # 3 2012-04-03
    
    Lease
    
    #       start        end id
    # 1 2012-01-03 2012-02-05  1
    # 2 2012-03-01 2012-04-15  2
    # 3 2012-04-02 2012-07-11  3
    
    
    data.frame(expand.grid(AllDays$dates,Lease$id)) %>%      # create combinations of dates and ranges
      select(dates=Var1, id=Var2) %>%
      inner_join(Lease, by="id") %>%                         # join information
      rowwise %>%
      do(data.frame(dates=.$dates,
                    flag = ifelse(.$dates %in% seq(.$start,.$end,by="1 day"),1,0))) %>%     # create ranges and check if the date is in there
      ungroup %>%
      group_by(dates) %>%
      summarise(N=sum(flag))
    
    #        dates N
    # 1 2012-02-01 1
    # 2 2012-03-02 1
    # 3 2012-04-03 2
    

    【讨论】:

      【解决方案5】:

      试试 lubridate 包。为每个租约创建一个间隔。然后计算每个日期所在的租约间隔。

      # make some data
      AllDays <- data.frame("Days" = seq.Date(as.Date("2012-01-01"), as.Date("2012-02-01"), by = 1))
      Leases <- data.frame("StartDate" = as.Date(c("2012-01-01", "2012-01-08")),
                       "EndDate" = as.Date(c("2012-01-10", "2012-01-21")))
      library(lubridate)
      
      x <- new_interval(Leases$StartDate, Leases$EndDate, tzone = "UTC")
      AllDays$NumberInEffect <- sapply(AllDays$Days, function(a){sum(a %within% x)})
      

      输出

      head(AllDays)
              Days NumberInEffect
      1 2012-01-01              1
      2 2012-01-02              1
      3 2012-01-03              1
      4 2012-01-04              1
      5 2012-01-05              1
      6 2012-01-06              1
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-09-22
        • 1970-01-01
        • 2014-06-14
        • 2018-03-16
        • 1970-01-01
        相关资源
        最近更新 更多