使用 data.frame 中的上方行和另一列计算值
Posted
技术标签:
【中文标题】使用 data.frame 中的上方行和另一列计算值【英文标题】:Calculate value using row above in data.frame and another column 【发布时间】:2019-08-22 02:28:25 【问题描述】:您好,我想在 R 中做一些看起来应该很简单的事情,但我似乎大脑衰退了。
对于 data.frame 中的每一行,我想在上面的行中获取 Vol
的值,为该行添加 In
的值并减去取决于此值的值。
这是我的尝试,但延迟只是在开始时回溯一行,而不是在计算下一个值后继续回溯
library(dplyr)
df <- data.frame(In = c(1,4,0,0,1,2,3,0,0), Vol = c(1,rep(NA,8)))
df %>% mutate(Vol = (lag(Vol) + In) - (lag(Vol) + In)*0.01)
想要的输出 =
In Vol
1 1 1.00
2 4 4.95
3 0 4.90
4 0 4.85
5 1 5.79
6 2 7.72
7 3 10.61
8 0 10.50
9 0 10.40
【问题讨论】:
如果您提供test
的样本以及该样本数据的预期输出,将会有所帮助。
哎呀好点@r2evans - 现在已经更新了。
【参考方案1】:
这是使用purrr
包中的accumulate
的解决方案。 accumulate
函数可以将具有两个参数的函数(例如 x
和 y
)应用于向量序列。返回值将成为下一轮的输入值。
在下面的示例中,我要求accumulate
函数从In
列的第二个数字开始到末尾。我还为.init
参数提供了1
,这将是函数的第一个x
。
library(dplyr)
library(purrr)
df <- data.frame(In = c(1,4,0,0,1,2,3,0,0), Vol = c(1,rep(NA,8)))
df %>%
mutate(Vol = accumulate(In[2:n()], function(x, y) (x + y) * 0.99, .init = 1))
# In Vol
# 1 1 1.000000
# 2 4 4.950000
# 3 0 4.900500
# 4 0 4.851495
# 5 1 5.792980
# 6 2 7.715050
# 7 3 10.607900
# 8 0 10.501821
# 9 0 10.396803
此外,Vol
列的第一个值似乎与 In
列的第一个值相同。如果您要做的只是在In
列上进行accumulate
处理,则以下代码会更简洁,您甚至不需要将第一个值复制到Vol
列。
df %>%
mutate(Vol = accumulate(In, function(x, y) (x + y) * 0.99))
# In Vol
# 1 1 1.000000
# 2 4 4.950000
# 3 0 4.900500
# 4 0 4.851495
# 5 1 5.792980
# 6 2 7.715050
# 7 3 10.607900
# 8 0 10.501821
# 9 0 10.396803
【讨论】:
它似乎非常接近stats::filter
函数 - 例如 filter(df$In, c(1,-0.01), sides=1, method="rec")
。
@thelatemail 感谢分享。您的代码输出为1.00000 5.00000 4.99000 4.94000 5.89010 7.84070 10.78180 10.70339 10.59557
,与 OP 的预期输出不同。
我知道,这就是为什么我说“越来越接近...” - 我认为尝试将其转换为矢量化函数可能会有所帮助。
哦,我想这就是我想要的,我会看看它与我的真实数据的关系,看看是否有任何我遗漏的东西。巧合的是,In 和 Vol 的第一个值相同
@www - 当然,您的答案的基本 R 版本(实际上只是一堆隐藏循环)是 Reduce(function(x, y) (x + y) * 0.99, df$In, accumulate=TRUE)
【参考方案2】:
您也可以使用基础R
中的sapply
来替换@Ronak 的for
循环。 invisible
不是必需的函数,只是将 sapply
包裹起来,使其静默工作。
invisible(
sapply(2:nrow(df), function(i)
df$Vol[i] <<- (df$Vol[i-1] + df$In[i]) - (df$Vol[i-1] + df$In[i])*0.01
)
)
> df
In Vol
1 1 1.000000
2 4 4.950000
3 0 4.900500
4 0 4.851495
5 1 5.792980
6 2 7.715050
7 3 10.607900
8 0 10.501821
9 0 10.396803
微基准测试:
Unit: microseconds
expr min lq mean median uq max neval
tidy1 578.614 602.3825 736.8518 647.7345 792.1560 3409.963 100
tidy2 566.256 601.1450 1524.3789 646.5240 801.3490 80219.732 100
for.loop 4936.829 5288.2650 6007.9584 5635.4895 6540.4290 8982.346 100
sapply 198.919 218.8710 305.8182 226.3600 243.1750 4489.870 100
trans.db.reduce 127.456 149.8150 175.4649 172.6280 195.9935 292.835 100
trans.db 217.416 236.1150 328.3348 255.2275 285.5560 5805.963 100
【讨论】:
您应该重复您的基准测试并包含@d.b 的解决方案,这是最快的。 @jay.sf,看来@d.b 的函数加上Reduce
是最快的。【参考方案3】:
从前一行获取值并更新当前行中的值似乎是一项微不足道的任务。但是,mutate
没有它计算的先前 Vol
值的“知识”,因为它一起计算了整个列的值。
在这种情况下,我们可以使用简单的for
循环
for (i in 2:nrow(df))
df$Vol[i] = (df$Vol[i-1] + df$In[i]) - (df$Vol[i-1] + df$In[i])*0.01
df
# In Vol
#1 1 1.000000
#2 4 4.950000
#3 0 4.900500
#4 0 4.851495
#5 1 5.792980
#6 2 7.715050
#7 3 10.607900
#8 0 10.501821
#9 0 10.396803
数据
test = c(1, 4, 0, 0, 1, 2, 3, 0, 0)
df <- data.frame(In = test, Vol = c(1,rep(NA,8)))
【讨论】:
嗯,感谢您的回答,我希望避免 for 循环,因为在现实生活中我有大量数据,它将按其他列分组,我通常尽量避免他们。也许在这种情况下我别无选择 @user2738526 是的,我自己也经历过这样的例子,如果没有for
循环,我就无法进行任何这样的计算。您可以等待一段时间,看看是否有其他人对此有更好/更智能的解决方案。知道会很有趣。【参考方案4】:
在这种特殊情况下,您可以使用一些代数操作将所有Vol
表达为第一个Vol
transform(df, Vol = c(df$Vol[1], sapply(2:NROW(df), function(n)
0.99^(n-1) * df$Vol[1] + sum(0.99^((n-1):1) * df$In[2:n])
)))
# In Vol
#1 1 1.000000
#2 4 4.950000
#3 0 4.900500
#4 0 4.851495
#5 1 5.792980
#6 2 7.715050
#7 3 10.607900
#8 0 10.501821
#9 0 10.396803
【讨论】:
【参考方案5】:Reduce
的另一个选项
transform(df,
Vol = Reduce(function(x, y)
x + y - 0.01 * (x + y)
,
c(df$Vol[1], df$In[-1]),
accumulate = TRUE))
# In Vol
#1 1 1.000000
#2 4 4.950000
#3 0 4.900500
#4 0 4.851495
#5 1 5.792980
#6 2 7.715050
#7 3 10.607900
#8 0 10.501821
#9 0 10.396803
【讨论】:
以上是关于使用 data.frame 中的上方行和另一列计算值的主要内容,如果未能解决你的问题,请参考以下文章
将函数应用于 data.frame 中的每一行并将结果附加到 R 中的 data.frame