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 的形式时,...

如果所有 n..m 均为负数或零,则删除指定的列 如果所有 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)

R 中 data.table 的 colnames() 行为

R data.table:如何使用包含列名的 R 变量?

如何使用 R 交换 data.table 中的列值