在 data.table 中使用 "or" 的条件行明智聚合
Conditional row wise aggregation using "or" in data.table
我有一个相当大的(300 万行)data.table 包含许多客户的发票和付款,其中每个客户都有许多(唯一的)文件,每个文件都有一个创建日期,和一个单据付款的日期。如果还没有付款日期,payment_date 列会列出 NA。数据看起来像这样:
dt = data.table(
customer_id = c(rep(1,4), rep(2,4)),
document_id = c(1:8),
creation_date = as.Date(c("2005-03-01", "2005-03-03", "2005-03-10", "2005-03-25", "2006-03-01", "2006-03-04", "2006-03-10", "2006-03-12"), "%Y-%m-%d"),
payment_date = as.Date(c("2005-03-05", "2005-03-07", NA, "2005-03-28", "2006-03-05", NA, "2006-03-15", "2006-03-16"), "%Y-%m-%d"),
open_docs_10 = c(0,1,2,1,0,1,2,3),
percentage_open_10 = c(0.0,0.20,0.70,1.0,0.0,0.3,1.0,1.0)
)
每个文档(即每行),我希望(理想情况下)计算两个特征:
1) Open_docs_10,即当前文档的customer_id在一定时间window(比如10天)内未付款或"open"文档的数量) 在 document_id 的创建日期之前。 "Open" 表示 payment_date 是 NA,落在时间间隔之后或落在时间间隔内,而 creation_date 在时间间隔内或之前。
2) Percentage_open_10,这是 windows 客户打开文档的天数百分比。文档的数量并不重要;该图表示“在创建此新文档时,该客户前 10 天中有 4 天有未结付款”。
对于 1),我尝试了类似的方法:
open_docs_10 = dt[,c("customer_id", "document_id", "creation_date", "payment_date")] %>%
.[, open_docs_10 := .[.(customer_id = customer_id, upper = creation_date, lower = creation_date - days(10)),
on = .(customer_id, payment_date >= lower, creation_date > lower), uniqueN(document_id), by=.EACHI
]$V1
]
但这还没有给出正确的结果,因为 true/correct 连接条件必须类似于
payment_date >= lower OR upper >= creation_date >= lower
看来我不能在 "on" 子句中使用 and/or 语句。但是我该如何实现,使用 data.table?
对于 2),我不知道如何解决这个问题。
我不受任何真正意义上使用 data.table 的约束;也许我正试图以一种困难的方式解决我的问题,而另一个 R 包会提供一种更智能的处理方式?任何帮助将不胜感激!
我认为您在 percentage_open_10
的计算中没有始终如一地包括或排除结束日期。如果我们包括结束日期,您可以使用以下内容:
ndays <- 10L
setnafill(dt, fill=as.IDate("9999-12-31"), cols="payment_date")
dt[, cd10 := creation_date - ndays + 1L]
dt[, c("open_docs_10", "percentage_open_10") :=
.SD[.SD, on=.(customer_id, creation_date<=creation_date, payment_date>=cd10),
allow.cartesian=TRUE, by=.EACHI, {
ix <- x.document_id != i.document_id
p <- 0
if (any(ix)) {
lastd <- min(c(i.creation_date, max(x.payment_date[ix])))
firstd <- if (any(ix)) max(c(i.cd10, min(x.creation_date[ix])))
p <- (lastd - firstd + 1) / 10
}
.(.N - 1L, p)
}][, (1L:3L) := NULL]
]
输出:
customer_id document_id creation_date payment_date cd10 open_docs_10 percentage_open_10
1: 1 1 2005-03-01 2005-03-05 2005-02-20 0 0.0
2: 1 2 2005-03-03 2005-03-07 2005-02-22 1 0.3
3: 1 3 2005-03-10 9999-12-31 2005-03-01 2 0.7
4: 1 4 2005-03-25 2005-03-28 2005-03-16 1 1.0
5: 2 5 2006-03-01 2006-03-05 2006-02-20 0 0.0
6: 2 6 2006-03-04 9999-12-31 2006-02-23 1 0.4
7: 2 7 2006-03-10 2006-03-15 2006-03-01 2 1.0
8: 2 8 2006-03-12 2006-03-16 2006-03-03 3 1.0
但是,有 300 万行,我不希望这能在几秒钟内完成。
我有一个相当大的(300 万行)data.table 包含许多客户的发票和付款,其中每个客户都有许多(唯一的)文件,每个文件都有一个创建日期,和一个单据付款的日期。如果还没有付款日期,payment_date 列会列出 NA。数据看起来像这样:
dt = data.table(
customer_id = c(rep(1,4), rep(2,4)),
document_id = c(1:8),
creation_date = as.Date(c("2005-03-01", "2005-03-03", "2005-03-10", "2005-03-25", "2006-03-01", "2006-03-04", "2006-03-10", "2006-03-12"), "%Y-%m-%d"),
payment_date = as.Date(c("2005-03-05", "2005-03-07", NA, "2005-03-28", "2006-03-05", NA, "2006-03-15", "2006-03-16"), "%Y-%m-%d"),
open_docs_10 = c(0,1,2,1,0,1,2,3),
percentage_open_10 = c(0.0,0.20,0.70,1.0,0.0,0.3,1.0,1.0)
)
每个文档(即每行),我希望(理想情况下)计算两个特征:
1) Open_docs_10,即当前文档的customer_id在一定时间window(比如10天)内未付款或"open"文档的数量) 在 document_id 的创建日期之前。 "Open" 表示 payment_date 是 NA,落在时间间隔之后或落在时间间隔内,而 creation_date 在时间间隔内或之前。
2) Percentage_open_10,这是 windows 客户打开文档的天数百分比。文档的数量并不重要;该图表示“在创建此新文档时,该客户前 10 天中有 4 天有未结付款”。
对于 1),我尝试了类似的方法:
open_docs_10 = dt[,c("customer_id", "document_id", "creation_date", "payment_date")] %>%
.[, open_docs_10 := .[.(customer_id = customer_id, upper = creation_date, lower = creation_date - days(10)),
on = .(customer_id, payment_date >= lower, creation_date > lower), uniqueN(document_id), by=.EACHI
]$V1
]
但这还没有给出正确的结果,因为 true/correct 连接条件必须类似于
payment_date >= lower OR upper >= creation_date >= lower
看来我不能在 "on" 子句中使用 and/or 语句。但是我该如何实现,使用 data.table?
对于 2),我不知道如何解决这个问题。
我不受任何真正意义上使用 data.table 的约束;也许我正试图以一种困难的方式解决我的问题,而另一个 R 包会提供一种更智能的处理方式?任何帮助将不胜感激!
我认为您在 percentage_open_10
的计算中没有始终如一地包括或排除结束日期。如果我们包括结束日期,您可以使用以下内容:
ndays <- 10L
setnafill(dt, fill=as.IDate("9999-12-31"), cols="payment_date")
dt[, cd10 := creation_date - ndays + 1L]
dt[, c("open_docs_10", "percentage_open_10") :=
.SD[.SD, on=.(customer_id, creation_date<=creation_date, payment_date>=cd10),
allow.cartesian=TRUE, by=.EACHI, {
ix <- x.document_id != i.document_id
p <- 0
if (any(ix)) {
lastd <- min(c(i.creation_date, max(x.payment_date[ix])))
firstd <- if (any(ix)) max(c(i.cd10, min(x.creation_date[ix])))
p <- (lastd - firstd + 1) / 10
}
.(.N - 1L, p)
}][, (1L:3L) := NULL]
]
输出:
customer_id document_id creation_date payment_date cd10 open_docs_10 percentage_open_10
1: 1 1 2005-03-01 2005-03-05 2005-02-20 0 0.0
2: 1 2 2005-03-03 2005-03-07 2005-02-22 1 0.3
3: 1 3 2005-03-10 9999-12-31 2005-03-01 2 0.7
4: 1 4 2005-03-25 2005-03-28 2005-03-16 1 1.0
5: 2 5 2006-03-01 2006-03-05 2006-02-20 0 0.0
6: 2 6 2006-03-04 9999-12-31 2006-02-23 1 0.4
7: 2 7 2006-03-10 2006-03-15 2006-03-01 2 1.0
8: 2 8 2006-03-12 2006-03-16 2006-03-03 3 1.0
但是,有 300 万行,我不希望这能在几秒钟内完成。