【问题标题】:R: Aggregating between dates without for loopR:在没有 for 循环的日期之间聚合
【发布时间】:2015-07-07 10:14:30
【问题描述】:

我希望在不使用 for 循环的情况下汇总两个日期之间有效的租约赚取的所有租金。

这是租赁数据的示例
数据框1

StartDate     EndDate       MonthlyRental  
2015-07-01    2015-09-30    500
2015-06-01    2015-10-31    600
2015-07-15    2016-01-31    400
2015-08-01    2015-12-31    800

我想计算每个月我能得到的租金金额,如果可能的话,按比例计算(如果太难,不要NB)。例如:
数据帧2

Month        RentalIncome
2015-07-31   500+600+(400*15/31)
2015-08-31   500+600+400+800
2015-09-30   500+600+400+800
2015-10-31   600+400+800
2015-11-30   600+400+800
etc.

有没有人知道比简单地遍历 Dataframe2 更好的方法?

谢谢,

迈克

【问题讨论】:

  • 您当前正在循环通过 Dataframe1,而不是 Dataframe 2(如您所写)。正确的?请发布您当前的代码以将 Dataframe1 转换为 Dataframe2。

标签: r aggregate


【解决方案1】:

这是一个可能的data.table 解决方案(在Hmisc 软件包的帮助下)。如果没有半个月的租金,这可能是一个非常简单的问题,但由于这种限制,它变得虽然困难。

作为旁注,根据您的示例,我只假设 StartDate 有半个月

library(data.table)
require(Hmisc)

# Converting to valid date classes
Dates <- names(df)[1:2]
setDT(df)[, (Dates) := lapply(.SD, as.Date), .SDcols = Dates]

# Handling half months
df[mday(StartDate) != 1, `:=`(GRP = seq_len(.N), 
                              mDays = mday(StartDate), 
                              StartDate = StartDate - mday(StartDate) + 1L)]

## Converting to long format
res <- df[, .(Month = seq(StartDate, EndDate, by = "month")), 
              by = .(MonthlyRental, GRP, mDays)]

## Dividing not full months by the number of days (that could be modified as per other post)
res[match(na.omit(df$GRP), GRP), MonthlyRental := MonthlyRental*mDays/monthDays(Month)]
res[, .(RentalIncome = sum(MonthlyRental)), keyby = .(year(Month), month(Month))]

#    year month RentalIncome
# 1: 2015     6          600
# 2: 2015     7         1293
# 3: 2015     8         2300
# 4: 2015     9         2300
# 5: 2015    10         1800
# 6: 2015    11         1200
# 7: 2015    12         1200
# 8: 2016     1          400

【讨论】:

    【解决方案2】:

    我稍微修改了我之前的答案。矩阵“RentPerDay”不是必需的。 “colSums(t(countDays)*RentPerDay)”可以替换为矩阵向量积。此解决方案计算的租金​​收入与之前的解决方案相同。

    library(lubridate)
    
    ultimo_day <- function( start, end )
    {
      N <- 12*(year(end) - year(start)) + month(end) - month(start) + 1
      d <- start
      day(d) <- 1
      month(d) <- month(d) + (1:N)
      return( d - as.difftime(1,units="days"))
    }
    
    countDays <- function( data, d )
    {
      return( pmin( pmax( outer( d, data$"StartDate", "-") + 1, 0 ), day(d) ) -
              pmin( pmax( outer( d, data$"EndDate"  , "-"), 0 ), day(d) ) )
    }
    
    rentalIncome <- function( data,
                              d = ultimo_day( min(data$StartDate), max(data$EndDate) ) )
    {
      return ( data.frame( date   = d,
                           income = ( countDays(data,d) / days_in_month(d) ) %*% data$"MonthlyRental" ) )
    }
    
    # -------- Example Data: --------
    
    df1 <- data.frame(
      StartDate     = as.Date(c("2015-07-01", "2015-06-01", "2015-07-15", "2015-08-01", "2014-06-20")),
      EndDate       = as.Date(c("2015-09-30", "2015-10-31", "2016-01-31", "2015-12-31", "2015-07-31")),
      MonthlyRental = c(500, 600, 400, 800, 300)
    )
    

    在示例中,我又添加了一个租约,该租约有效期超过一年:

    > df1
       StartDate    EndDate MonthlyRental
    1 2015-07-01 2015-09-30           500
    2 2015-06-01 2015-10-31           600
    3 2015-07-15 2016-01-31           400
    4 2015-08-01 2015-12-31           800
    5 2014-06-20 2015-07-31           300    
    

    “ultimo_day(start,end)”是“开始”和“结束”之间支付租金的天数的向量:

    > d <- ultimo_day( min(df1$StartDate), max(df1$EndDate))
    > d
     [1] "2014-06-30" "2014-07-31" "2014-08-31" "2014-09-30" "2014-10-31" "2014-11-30" "2014-12-31" "2015-01-31" "2015-02-28" "2015-03-31" "2015-04-30"
    [12] "2015-05-31" "2015-06-30" "2015-07-31" "2015-08-31" "2015-09-30" "2015-10-31" "2015-11-30" "2015-12-31" "2016-01-31"
    

    矩阵“countDays”的行对应于这些最后几天,因此对应于月份:

    > countDays(df1,d)
    Time differences in days
          [,1] [,2] [,3] [,4] [,5]
     [1,]    0    0    0    0   11
     [2,]    0    0    0    0   31
     [3,]    0    0    0    0   31
     [4,]    0    0    0    0   30
     [5,]    0    0    0    0   31
     [6,]    0    0    0    0   30
     [7,]    0    0    0    0   31
     [8,]    0    0    0    0   31
     [9,]    0    0    0    0   28
    [10,]    0    0    0    0   31
    [11,]    0    0    0    0   30
    [12,]    0    0    0    0   31
    [13,]    0   30    0    0   30
    [14,]   31   31   17    0   31
    [15,]   31   31   31   31    0
    [16,]   30   30   30   30    0
    [17,]    0   31   31   31    0
    [18,]    0    0   30   30    0
    [19,]    0    0   31   31    0
    [20,]    0    0   31    0    0
    

    第 1 行属于 2014 年 6 月,第 2 行属于 2014 年 7 月,...,第 20 行属于 2016 年 1 月。

    "countDays(df1,d) / days_in_month(d)" 又是一个矩阵。 该矩阵的 (i,j)-分量不是天数 第 j 个租约在第 i 个月是活跃的,但是这个数字的一​​部分 第 i 个月的长度:

    > countDays(df1,d) / days_in_month(d)
    Time differences in days
          [,1] [,2]      [,3] [,4]      [,5]
     [1,]    0    0 0.0000000    0 0.3666667
     [2,]    0    0 0.0000000    0 1.0000000
     [3,]    0    0 0.0000000    0 1.0000000
     [4,]    0    0 0.0000000    0 1.0000000
     [5,]    0    0 0.0000000    0 1.0000000
     [6,]    0    0 0.0000000    0 1.0000000
     [7,]    0    0 0.0000000    0 1.0000000
     [8,]    0    0 0.0000000    0 1.0000000
     [9,]    0    0 0.0000000    0 1.0000000
    [10,]    0    0 0.0000000    0 1.0000000
    [11,]    0    0 0.0000000    0 1.0000000
    [12,]    0    0 0.0000000    0 1.0000000
    [13,]    0    1 0.0000000    0 1.0000000
    [14,]    1    1 0.5483871    0 1.0000000
    [15,]    1    1 1.0000000    1 0.0000000
    [16,]    1    1 1.0000000    1 0.0000000
    [17,]    0    1 1.0000000    1 0.0000000
    [18,]    0    0 1.0000000    1 0.0000000
    [19,]    0    0 1.0000000    1 0.0000000
    [20,]    0    0 1.0000000    0 0.0000000
    

    这个矩阵乘以向量“df1$MonthlyRental”,得到的向量作为“income”存储在租金收入的data.frame中:

    > rentalIncome(df1)
             date   income
    1  2014-06-30  110.000
    2  2014-07-31  300.000
    3  2014-08-31  300.000
    4  2014-09-30  300.000
    5  2014-10-31  300.000
    6  2014-11-30  300.000
    7  2014-12-31  300.000
    8  2015-01-31  300.000
    9  2015-02-28  300.000
    10 2015-03-31  300.000
    11 2015-04-30  300.000
    12 2015-05-31  300.000
    13 2015-06-30  900.000
    14 2015-07-31 1619.355
    15 2015-08-31 2300.000
    16 2015-09-30 2300.000
    17 2015-10-31 1800.000
    18 2015-11-30 1200.000
    19 2015-12-31 1200.000
    20 2016-01-31  400.000
    

    【讨论】:

      【解决方案3】:

      我不确定这是否比“简单地循环遍历数据框”更好——因为我实际上是循环遍历它——但这是一种产生所需输出的方法。

      (输出偏离了 2015 年 7 月的问题,因为要在 7 月支付 17 天的租金,而不是 15 天。)

      给定的间隔转换为天,计算每天的租金,然后按月求和每天的租金:

      library(zoo)
      
      df1 <- data.frame(
        StartDate = as.Date(c("2015-07-01", "2015-06-01", "2015-07-15", "2015-08-01")),
        EndDate = as.Date(c("2015-09-30", "2015-10-31", "2016-01-31", "2015-12-31")),
        MonthlyRental = c(500, 600, 400, 800)
      )
      
      df1LongList <- apply(df1, MARGIN = 1, FUN = function(row) {
        return(data.frame(
          date = seq(from = as.Date(row["StartDate"]), to = as.Date(row["EndDate"]), by = "day"),
          MonthlyRental = as.numeric(row["MonthlyRental"])))
      })
      
      df1Long <- do.call("rbind", df1LongList)
      df1Long$yearMon <- as.yearmon(df1Long$date)
      df1Long$maxDays <- as.numeric(as.Date(df1Long$yearMon, frac = 1) - as.Date(df1Long$yearMon) + 1) # Thanks: http://stackoverflow.com/a/6244503/2706569
      
      df1Long$rental <- df1Long$MonthlyRental / df1Long$maxDays
      
      tapply(X = df1Long$rental, INDEX = df1Long$yearMon, FUN = sum)
      
      # Jun 2015 Jul 2015 Aug 2015 Sep 2015 Okt 2015 Nov 2015 Dez 2015 Jan 2016 
      # 600.000 1319.355 2300.000 2300.000 1800.000 1200.000 1200.000  400.000 
      

      【讨论】:

      • 确实如此(我认为)。你看到我在代码上方的评论了吗?换句话说:我不考虑“半”个月,而是按确切的天数计算。
      • 当合同在第15天开始时,你必须支付那一天的费用,对吧? 31 - 15 + 1 = 17。
      【解决方案4】:

      我使用了外部产品“pmin”和“pmax”来避免循环。部分涵盖的月份很困难,因此很有趣:

      library(lubridate)
      
      df1 <- data.frame(
        StartDate = as.Date(c("2015-07-01", "2015-06-01", "2015-07-15", "2015-08-01")),
        EndDate = as.Date(c("2015-09-30", "2015-10-31", "2016-01-31", "2015-12-31")),
        MonthlyRental = c(500, 600, 400, 800)
      )
      
      d <- c( as.Date("2015-07-31"),
              as.Date("2015-08-31"),
              as.Date("2015-09-30"),
              as.Date("2015-10-31"),
              as.Date("2015-11-30"),
              as.Date("2015-12-31"),
              as.Date("2016-01-31"),
              as.Date("2016-02-29")  )
      
      RentPerDay <- outer( df1$"MonthlyRental", days_in_month(d), "/" )
      
      countDays <- pmin( pmax( outer( d, df1$"StartDate", "-") + 1, 0 ), days_in_month(d) ) -
                   pmin( pmax( outer( d, df1$"EndDate"  , "-"), 0 ), days_in_month(d) )
      
      rentalIncome <- colSums( t(countDays) * RentPerDay )
      

      矩阵 't(countDays)' 的列对应于 'DataFrame_2' 的行,即月份。这些行对应于“DataFrame_1”的行,即租金收入的来源。 (i,j) 处的条目是第 j 个月中第 i 个来源贡献租金收入的天数。矩阵“RentPerDay”具有相同的结构。 (i,j) 处的条目是在第 j 个月的一天内来自第 i 个来源的金额。那么这两个矩阵的元素乘积的第j列的和就是第j个月的总租金收入。

      > t(countDays)
      Time differences in days
           [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
      [1,]   31   31   30    0    0    0    0    0
      [2,]   31   31   30   31    0    0    0    0
      [3,]   17   31   30   31   30   31   31    0
      [4,]    0   31   30   31   30   31    0    0
      > RentPerDay
                Jul      Aug      Sep      Oct      Nov      Dec      Jan      Feb
      [1,] 16.12903 16.12903 16.66667 16.12903 16.66667 16.12903 16.12903 17.24138
      [2,] 19.35484 19.35484 20.00000 19.35484 20.00000 19.35484 19.35484 20.68966
      [3,] 12.90323 12.90323 13.33333 12.90323 13.33333 12.90323 12.90323 13.79310
      [4,] 25.80645 25.80645 26.66667 25.80645 26.66667 25.80645 25.80645 27.58621
      > rentalIncome
           Jul      Aug      Sep      Oct      Nov      Dec      Jan      Feb 
      1319.355 2300.000 2300.000 1800.000 1200.000 1200.000  400.000    0.000 
      > 
      

      【讨论】:

      • 感谢解决方案,抱歉超级耽误,我没在办公室。该解决方案效果很好,只有一个问题:它不需要考虑数年(我意识到我的示例并未说明对此的必要性)。目前,如果有一个开始日期,比如 20-06-2014,相应的结束日期是 30-07-2015,则租金金额按比例分配 2015 年 6 月和 2014 年 6 月。解决这个问题?谢谢,感谢帮助!迈克
      • “RentPerDay”列所对应的月份不仅是一月、二月、...、十二月,而是 2015 年 7 月、2015 年 8 月、...、2016 年 2 月。如果有是另一个租约,从 2014 年 6 月 20 日开始,到 2015 年 7 月 31 日结束,月份是 2014 年 6 月、2014 年 7 月、...、2016 年 2 月。想想螺旋而不是圆圈。也许在这一点上对我的解决方案的解释是模棱两可的。在我的第二个解决方案的示例中,额外的租约贡献了 110 到 2014 年 6 月和 300 到 2015 年 6 月。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-13
      • 1970-01-01
      • 2014-12-18
      • 2011-12-17
      • 2020-09-20
      • 2018-06-11
      相关资源
      最近更新 更多