我写这个作为一个问题(阅读本简介并跳到最后一节),并作为一个练习,以帮助我更清楚地思考。如果这被认为不适合SO,请指出并将其移至其他地方。
我有一份付款记录清单,即现金流量,具有以下结构:
ID
,开始日期,(预计)结束日期和多个付款记录。NA
,收到的金额为0。出于本练习的目的,我构建了一个data.table
,其中包含以下模拟数据:
require(data.table)
require(lubridate)
dA <- as.Date('2016-08-02')
dB <- as.Date('2017-01-25')
dC <- as.Date('2017-05-10')
ws <- data.table(
ID = c(rep('A', 12L), rep('B', 12L), rep('C', 12L)),
pmt_no = rep(1L:12L, 3),
start = c(
rep(dA, 12L),
rep(dB, 12L),
rep(dC, 12L)
),
end = c(
rep(dA %m+% days(360L), 12L),
rep(dB %m+% days(360L), 12L),
rep(dC %m+% days(360L), 12L)
),
due_date = c(
dA %m+% days(seq(30L, 360L, by = 30L)),
dB %m+% days(seq(30L, 360L, by = 30L)),
dC %m+% days(seq(30L, 360L, by = 30L))
),
rec_date = c(
dA %m+% days(seq(30L, 360L, by = 30L)),
dB %m+% days(seq(30L, 360L, by = 30L)),
dC %m+% days(seq(30L, 360L, by = 30L))
),
due_amt = c(rep(120, 12L), rep(230, 12L), rep(180, 12L)),
rec_amt = c(rep(120, 12L), rep(230, 12L), rep(180, 12L))
)[
due_date >= '2017-08-01',
`:=`(rec_date = NA, rec_amt = 0)
][
c(2L:5L,14L), rec_date := rec_date %m+% days(13L)
][
c(11L,18L), `:=`(rec_date = NA, rec_amt = 0)
]
一些特征:
这就是它的样子:
ID pmt_no start_date end_date due_date rec_date due_amt rec_amt
1: A 1 2016-08-02 2017-07-28 2016-09-01 2016-09-01 120 120
2: A 2 2016-08-02 2017-07-28 2016-10-01 2016-10-14 120 120
3: A 3 2016-08-02 2017-07-28 2016-10-31 2016-11-13 120 120
sum
和cumsum
考虑以下问题:
我的本能方法是构造一个date
向量,然后循环几次以获取所有数字。但是我认为以下内容更符合data.table
方式:
# amount due and received during each month, and
# total due and total received by (the end of) each month
merge(
ws[
due_date < '2017-08-01',
.(due_amt = sum(due_amt)),
keyby = .(year(due_date), month(due_date))
][
,
due_total := cumsum(due_amt)
],
ws[
rec_date < '2017-08-01',
.(rec_amt = sum(rec_amt)),
keyby = .(year(rec_date), month(rec_date))
][
,
rec_total := cumsum(rec_amt)
],
all = TRUE
)
请注意,我已经省略了计数。结果很愉快:
year month due_amt due_total rec_amt rec_total
1: 2016 9 120 120 120 120
2: 2016 10 240 360 120 240
3: 2016 11 120 480 120 360
<truncated>
.SD
和sum
仍然可以解决,但我不得不操纵收到的日期以便于与截止日期进行比较:
ws[is.na(rec_date), rec_date := as.Date('2099-12-31')]
ws[
due_date < '2017-08-01',
.(
over_total = .SD[rec_date > due_date, sum(due_amt)],
over_outst = .SD[rec_date > '2017-08-01', sum(due_amt)]
),
keyby = .(year(due_date), month(due_date))
]
结果:
year month over_total over_outst
1: 2016 9 0 0
2: 2016 10 240 0
<truncated>
10: 2017 6 120 120
11: 2017 7 230 230
cumsum
?到目前为止,我做得很好。但请考虑一下:
对于单个日期,这不是什么大问题。虽然要注意排除在该日期之前尚未生效的任何合同。此外,如果您跳过中间部分,我已操纵rec_date
以进行比较:
ws[is.na(rec_date), rec_date := as.Date('2099-12-31')]
ws[
rec_date >= '2017-04-01' & start < '2017-04-01',
.(bal = sum(due_amt))
]
结果:
bal
1: 3010
但是,是否有data.table
方法可以为过去一个月找到这个数额?我可以写一个循环,但循环是麻烦,丑陋和缓慢。我想我正在寻找类似转发cumsum
的内容,因此与keyby
相结合,它或多或少等同于以下内容:
# To-be-collected amount by the end of each month, the loop way
result <- data.table(
date = as.Date('2016-09-01') %m+% months(0L:11L)
)
for (i in 1L:result[, .N]) {
temp <- ws[
rec_date >= result[i, date] & start < result[i, date],
.(bal = sum(due_amt))
]
result[i, bal := temp]
}
结果:
date bal
1: 2016-09-01 1440
2: 2016-10-01 1320
3: 2016-11-01 1200
4: 2016-12-01 1080
5: 2017-01-01 960
6: 2017-02-01 3480
7: 2017-03-01 3130
8: 2017-04-01 3010
9: 2017-05-01 2430
10: 2017-06-01 4240
11: 2017-07-01 3830
12: 2017-08-01 3530