R :: data.table:使用先前的余额和逐行迭代按组生成运行余额
Posted
技术标签:
【中文标题】R :: data.table:使用先前的余额和逐行迭代按组生成运行余额【英文标题】:R :: data.table: Generate a running balance by group using previous balance and row-wise iteration 【发布时间】:2020-01-30 00:13:09 【问题描述】:我在 R 中有以下 DT (data.table)。
dt <- fread("
id| rowids | charge | payment | balance
a | 1 | 7.1 | 0 |
a | 2 | 1.2 | 3 |
a | 3 | 1.7 | 1 |
b | 1 | 8.1 | 0 |
b | 2 | 2.5 | 4 |
b | 3 | 2.3 | 2 |
b | 4 | 3.2 | 1 |
",
sep = "|",
colClasses = c("character", "numeric", "numeric", "numeric",
"numeric"))
“余额”应该在每个 id 组内计算为“余额
我最初低估了计算流动余额的难度。我在想dt[,previous.row.balance := (shift(balance,1),by=id]
。但是 R 进行矢量化计算。 “余额”中没有可供我执行 shift() 的值,因为“余额”将通过逐行迭代计算。
我在 *** 上搜索,发现一个similar question and its first answer 极大地帮助了我思考整个过程。我对我的问题的第一个答案中的代码进行了修改,并让以下代码完美地按组生成运行余额。
dt[rowids == 1, balance := charge, by=.(id)]
dt[rowids != 1, balance :=
dt[,
balance1 <- balance[1L]
.SD[rowids != 1,
balance1 <- balance1 + charge - payment
.(balance1)
,
by=.(rowids)]
,
by=.(id)][, -1L:-2L]
]
这是我的问题。
-
我仍然无法理解
by=.(id)][, -1L:-2L]
,链式括号是如何完成迭代的。由于代码没有使用shift() by = group
,我猜[, -1L:-2L]
在这里执行迭代的技巧。但是怎么做? [, -1L:-2L]
在这里实际上做了什么?
对不起,我不得不在这里问这个问题,而不是在that question 下发表评论或提问。原因是我是 *** 的新手,只有 1 点声誉。我不允许评论该问题的原始答案。我也想投票赞成这个答案。在此之前,我必须获得更多积分。
-
有没有其他方法,使用 data.table 和 R 矢量化计算来实现这个运行平衡目标,而不为行迭代包装任何循环?
感谢任何见解或想法!
【问题讨论】:
【参考方案1】:关于你的问题 #2:
您可以使用cumsum
函数(输出与问题中的代码匹配)。这将采用 charge - payment
的值作为第一行,然后在第二行中添加第二个 charge - payment
,等等。
dt[, balance2 := cumsum(charge - payment), id]
dt
# id rowids charge payment balance balance2
# 1: a 1 7.1 0 7.1 7.1
# 2: a 2 1.2 3 5.3 5.3
# 3: a 3 1.7 1 6.0 6.0
# 4: b 1 8.1 0 8.1 8.1
# 5: b 2 2.5 4 6.6 6.6
# 6: b 3 2.3 2 6.9 6.9
# 7: b 4 3.2 1 9.1 9.1
【讨论】:
非常感谢@IceCreamToucan 提供的一行代码,这是解决我问题的最简单方法!我一直告诉自己,我必须跳出“框框”来思考,即逐行算法。在那里,cumsum()
是完美的矢量化计算。我刚刚从您的代码中了解到,我可以对 cumsum()
内的向量进行操作。
我在我的实际工作数据集上运行了我的代码和@IceCreamToucan cumsum() 代码来比较结果。令我惊讶的是,大约 10% 的条目在这两种算法的结果之间存在浮点错误。虽然没关系,因为我必须将它四舍五入到小数点后 2 位才能以美元为单位,但我很好奇为什么会涉及浮点错误。【参考方案2】:
由于@IceCreamToucan 已经回答了第 2 部分(如何改进代码),我将只介绍第 1 部分(为什么 x[, -1:-2]
有效)。从?data.table
我们知道,一般j
字段可以用来选择列:
当
j
是要选择的列名或位置的向量(如在 data.frame 中)[,那么它的行为与 data.frame 一样]。
(括号中的单词是我为完成句子所做的编辑。)
特别是,当j
采用n:m
的形式时,...
您还会在将j
设置为-c(1,2)
或!c(1,2)
或!(1:2)
或-(1:2)
时看到此行为。
此行为基于对 j
的特殊解析,以检查 :
或 !
或 -
是否为***函数。
接下来,重要的是要知道by=
中的列将作为表中的第一列。
在 OP 的示例中结合这两点,您将 by=id
作为第一列(外部 by),将 by=rowids
作为第二列(内部 by)。在使用 [, -1L:-2L]
删除这些后,您将剩下 .(balance1)
表达式。
【讨论】:
感谢@Frank 的解释!我现在的理解是,dt
中的第一列和第二列被删除了。 -1L
是否会丢弃 balance1
,它是从中间进程 ...
返回的。那么我的示例代码中-2L
所指的另一列是什么?
@JaneLu 啊,我忘了另一点。我现在已将其编辑为答案,希望它有意义以上是关于R :: data.table:使用先前的余额和逐行迭代按组生成运行余额的主要内容,如果未能解决你的问题,请参考以下文章
R语言data.table导入数据实战:data.table使用字符向量创建新的数据列
R语言data.table导入数据实战:data.table中编写函数并使用SD数据对象
R语言data.table导入数据实战:data.table使用by函数进行数据分组(aggregate)