为 Excel 替换 for-loops-like 公式填充 dataframe/matrix

Replacing for-loops for Excel-like formula filling in dataframe/matrix

我正在尝试在 R 中执行类似 excel 的基本公式填充。我想根据同一矩阵中其他单元格的值填充 "cell" 的值,或者data.frame。该函数对于单个单元格非常简单,但似乎更难以跨行和列缩放。

假设我有一个简单的矩阵:

simple <- matrix(c(0,1,2,3,0,4,5,6,7,NA,NA,NA,8,NA,NA,NA), nrow = 4, ncol = 4)

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   NA   NA
[3,]    2    5   NA   NA
[4,]    3    6   NA   NA

我想用同一行中第 1 列和第 2 列以及同一列中第 1 行的总和填充 NA。在 Excel 中,对于单元格 C2,它将是

=$A2 + $B2 + C

在 R

simple[2,3] <- simple[2,1] + simple[2,2] + simple[1,3]

在 Excel 中,您只需将公式拖到剩余的单元格上,瞧。在 R 中,没那么容易。

由于 r 是矢量化的,我可以通过给出范围而不是单个单元格来很容易地填充整列,如下所示:

simple[2:4,3] <- simple[2:4,1] + simple[2:4,2] + simple[1,3]

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   12   NA
[3,]    2    5   14   NA
[4,]    3    6   16   NA

但是当我尝试对行和列进行矢量化时,它不起作用,因为它将最后一个值解释为矢量 c(7,8),并尝试以行方式添加它,而不是逐列添加。

simple[2:4,3:4] <- simple[2:4,1] + simple[2:4,2] + simple[1,3:4]

Warning message:
In simple[2:4, 1] + simple[2:4, 2] + simple[1, 3:4] :
  longer object length is not a multiple of shorter object length

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   12   12
[3,]    2    5   15   15
[4,]    3    6   16   16

作为另一种解决方案,可以使用嵌套 for 循环,如下所示:

for (i in 2:4){
  for (j in 3:4){
    simple[i,j] <- simple[i,1] + simple[i,2] + simple[1,j]
  }
}

     [,1] [,2] [,3] [,4]
[1,]    0    0    7    8
[2,]    1    4   12   13
[3,]    2    5   14   15
[4,]    3    6   16   17

这确实有效并且非常简单,但是它涉及嵌套的 for 循环,所以,说得够多了。

我觉得 "right" 解决方案应该是使用正确的向量化、apply() 或 dplyr 的解决方案,但我似乎无法弄清楚如何使它们工作,除非重新排列来自将交叉表格式转换为平面格式,但这会使您的文件大小迅速增加。

关于如何以更 R-ish 的方式使这项工作有任何想法吗?

这里有一个更像 R 的方法,让我们先将 simple 转换为 data.frame。

library(tidyverse)

df1 <- as.data.frame(simple)

df1 %>% mutate(V3 = V1 + V2 + first(V3), V4 = V1 + V2 + first(V4))

  V1 V2 V3 V4
1  0  0  7  8
2  1  4 12 13
3  2  5 14 15
4  3  6 16 17

first from dplyr 很方便,因为它可以让您锁定列中的第一个值,就像在 Excel 中使用 C

我可能来晚了,但这里有一个 data.table 和基础 R 解决方案,对于大型数据集,它比 tidyverse 快得多。语法一开始可能看起来更混乱,但是一旦你很好地掌握了 lapply.

,就可以将其分解为非常合乎逻辑且直接的语法

为了使单元格和您添加的向量兼容,您应该将单元格转换为向量,方法是简单地复制该值的次数与数据帧的观察数或行数一样多。因此,在您的示例中,V3 = rep(7,4) 将生成一个全为 7 的向量。然后 R 会让你做 V3=V1+V2+V3,其中右侧的 V3 是 rep(7,4)。

data.table 有一些方便的内置特殊只读符号,它们还使您能够将解决方案扩展到示例中提供的两列之外。我最常使用的两个是 .SD.N。在此示例中,您可以将 .SD 视为一种引用除前两列之外的所有列的方式,而 .N 始终是等于 data.table 中行数的常数。这些符号可以用在 data.table 的 j 槽中,相当于矩阵的列或 data.frame 对象。所以你的代码看起来像这样:

    simple <- data.table(simple)

    NAcols <- colnames(simple)[-c(1,2)] ##Can modify this to get names of columns you wish to change if its not the first two using match or grep. I can add that if you want?

    simple[,NAcols:=lapply(.SD,function(i) V1+V2+rep(i[1],.N)),.SDcols=NAcols]

请注意,lapply 循环中的每次迭代只是第 i 列,i[1] 仅选择该列的第一个元素并将其复制与行数 (.N) 一样多的次数在将三个向量加在一起之前。 .SDcols 用于防止将此功能应用于前两列。虽然在这个问题中不需要分组,但如果你想在应用功能。最后请注意,我不需要将最后一行代码分配给另一个 R 对象,因为 data.table 使用指针更新 'simple' 的旧列,这就是为什么它比基本 R 和 tidyverse 数据快得多框架对象。但是,如果您出于某种原因希望保存原始 data.table,则可以像这样使用 data.table 的复制功能:

    final_result <- copy(simple)[,NAcols:=lapply(.SD,function(i) V1+V2+rep(i[1],.N)),.SDcols=NAcols]

无论如何,我希望解释对您有所帮助,如果您需要我澄清任何事情,请告诉我!祝你好运!

在矩阵运算中,每个分量必须是相同的维度或者任何一个都是单项向量。因此,考虑通过为每个需要的行 2-4(即 3 次)复制 78 来对齐。然后转置2 X 3维:

simple[2:4,3:4] <- simple[2:4,1] + simple[2:4,2] + t(replicate(length(2:4), simple[1,3:4]))

或者,考虑 sapply 分别迭代 78 值:

simple[2:4,3:4] <- sapply(3:4, function(i) simple[2:4,1] + simple[2:4,2] + simple[1,i])

使用 rowSums 并省略行索引稍微更简洁:

simple[,3:4] <- sapply(3:4, function(i) rowSums(simple[,1:2]) + simple[1,i])