首页 > 解决方案 > 基于在没有 for() 循环的情况下递归调用列的先前元素,在 R 中生成一个新的变量/列?

问题描述

我在 R 中有一些按以下方式组织的数据(简化示例):

x <- as.data.frame(cbind(c(1,2,3,4,5),c(6,7,8,9,10)))

这将创建一个两列数据框(“V1”和“V2”)。我想创建第三和第四列(“V3”和“V4”),它们都根据这两列中的每一个中的先前条目调用不同的函数。V3 基于不断附加向量的函数,而 V4 是简单的数学运算(几何平均)。到目前为止,我可以通过指定新列和使用for()循环来获得我需要的东西(使用简化函数的示例):

x <- x %>% mutate(V3 = 0, V4_0 = 0, V4 = 0)
for(i in 2:nrow(x)){x$V3[i] <- sum(c(x$V1[1:i - 1], x$V1[i] + x$V2[i]))}
for(i in 2:nrow(x)){x$V4_0[i] <- ((x$V2[i] - x$V2[i - 1]) / x$V2[i - 1])}
for(i in 2:nrow(x)){x$V4[i] <- prod(x$V4_0[1:i - 1] + 1, na.rm = TRUE)^(1/i) - 1}  # essentially, a geometric mean

输出:

  V1 V2 V3      V4_0         V4
1  1  6  0 0.0000000 0.00000000
2  2  7 10 0.1666667 0.00000000
3  3  8 14 0.1428571 0.05272660
4  4  9 19 0.1250000 0.07456993
5  5 10 25 0.1111111 0.08447177

我很好奇:使用 tidyverse 函数(例如mutate())或基本 R 是否有更清洁或更简单的方法?还是for()循环是我最好的选择?在小范围内一切正常,但我担心更大的数据集,因为for()循环据说效率较低。我认为这cumsum()接近于此,但具体到总和。任何帮助将不胜感激!

标签: rfor-looptidyversedplyr

解决方案


x$V3 <- c(0, cumsum(x$V1)[-1] + x$V2[-1])
x$V4_0 <- c(0, diff(x$V2) / x$V2[-nrow(x)])
x$V4 <- c(1, cumprod(x$V4_0 + 1)[-nrow(x)])^(1/seq_len(nrow(x))) - 1
x
#   V1 V2 V3      V4_0         V4
# 1  1  6  0 0.0000000 0.00000000
# 2  2  7 10 0.1666667 0.00000000
# 3  3  8 14 0.1428571 0.05272660
# 4  4  9 19 0.1250000 0.07456993
# 5  5 10 25 0.1111111 0.08447177

替代#1

如果在某些情况下您不能像这样进行矢量化处理(例如,如果jrvFinance::irr需要对每个新值的长度为 2 的向量进行操作),那么我建议zoo::rollapply

zoo::rollapply(seq_along(x$V1), 2, FUN = function(vec) {
  # ...
})

内部函数以向量长度 2 调用;第一次是c(1,2),第二c(2,3)次等。通常,rollapply对数据本身进行操作(即,x$V1),但由于您想同时使用V1V2,我将滚动向量的索引而不是向量本身。

这改变了你的

sum(c(x$V1[1:i - 1], x$V1[i] + x$V2[i]))

sum(c(x$V1[1:vec[1]], x$V1[vec[2]] + x$V2[vec[2]]))

关于这一点的更多说明:

zoo::rollapply(seq_along(x$V1), 2,
               FUN = function(vec) sum(c(x$V1[1:vec[1]], x$V1[vec[2]] + x$V2[vec[2]])))
# [1] 10 14 19 25
  • 这将返回2-1=1比输入短的元素。(也就是说,如果rollapply(vec, 5, ...),那么它将返回比输入向量少 4 个元素。)为了解决这个问题,我们可以手动填充它c(0, rollapply(...)),或者我们可以fill=使用值。

  • 滚动函数有alignment 的概念。从帮助文档:

       align: specifyies whether the index of the result should be left- or
              right-aligned or centered (default) compared to the rolling
              window of observations. This argument is only used if 'width'
              represents widths.
    

    以及它们的演示:

    zoo::rollapply(1:5, 3, sum, align = "left", fill = NA)
    # [1]  6  9 12 NA NA
    zoo::rollapply(1:5, 3, sum, align = "center", fill = NA)
    # [1] NA  6  9 12 NA
    zoo::rollapply(1:5, 3, sum, align = "right", fill = NA)
    # [1] NA NA  6  9 12
    

    我的解释align="left"是输出进入用于生成总和的三个值(此处)的最左边的位置。那是,

    1  2  3  4  5
    `-----' sum these
    ^       align="left", output goes here
       ^    align="center", output goes here
          ^ align="right", output goes here
    

    (意识到align=没有fill=something.)

所以使用align=and fill=,我们现在可以这样做:

x$V3_again <- zoo::rollapply(seq_along(x$V1), 2,
                             FUN = function(vec) sum(c(x$V1[1:vec[1]], x$V1[vec[2]] + x$V2[vec[2]])),
                             align = "right", fill = 0)
x
#   V1 V2 V3      V4_0         V4 V3_again
# 1  1  6  0 0.0000000 0.00000000        0
# 2  2  7 10 0.1666667 0.00000000       10
# 3  3  8 14 0.1428571 0.05272660       14
# 4  4  9 19 0.1250000 0.07456993       19
# 5  5 10 25 0.1111111 0.08447177       25

替代#2

inds <- seq_len(nrow(x))
c(0, mapply(function(a,b) sum(c(x$V1[1:a], x$V1[b] + x$V2[b])), 
            inds[-length(inds)], inds[-1]))
# [1]  0 10 14 19 25

(这c(0, ...)是在模仿rollapplyalign="right",fill=0论点。)

mapply类似于lapply/ sapply/ vapply,但是虽然这三个函数一次对单个向量进行操作,但mapply对一个或多个向量起作用,有效地将它们“压缩”在一起。例如,

mapply(FUN, 1:5, 6:10)

展开有效地调用

FUN(1, 6)
FUN(2, 7)
FUN(3, 8)
FUN(4, 9)
FUN(5, 10)

推荐阅读