R:迭代 data.table,并进行替换

R: Iterating over a data.table, with replacements

这几天一直在苦思冥想。我有一个如下所示的数据集:

V1 <- c("A", "B", "C", "D", "B", "A", "A", "D")
V2 <- c("B", "E", "A", "G", "C", "G", "E", "B")

R1 <- c(120, 195, 135, 30, 195, 120, 120, 30)
G1 <- c(0, 195, 0, 195, 195, 0, 0, 195)
B1 <- c(240, 195, 0, 135, 195, 240, 240, 135)

R2 <- c(195, 60, 120, 75, 135, 75, 60, 195)
G2 <- c(195, 15, 0, 15, 0, 15, 15, 195)
B2 <- c(195, 150, 240, 150, 0, 150, 150, 195)

cross <- data.frame(V1,V2,R1,G1,B1,R2,G2,B2)

这是我的实际数据集的粗略简化版本,该数据集要大得多,有超过 60,000 个观测值。在前两列中,V1V2 表示 ID 变量。然后 R1B1G1 对应于与 V1 关联的属性,同样,R2B2G2V2 的属性(这是一个网络模型,映射哪些节点链接到哪些节点以及这些节点的关联属性,但这不是重点)。这给出了一个 data.frame 看起来像:

  V1 V2  R1  G1  B1  R2  G2  B2
1  A  B 120   0 240 195 195 195
2  B  E 195 195 195  60  15 150
3  C  A 135   0   0 120   0 240
4  D  G  30 195 135  75  15 150
5  B  C 195 195 195 135   0   0
6  A  G 120   0 240  75  15 150
7  A  E 120   0 240  60  15 150
8  D  B  30 195 135 195 195 195

请注意,出现在 V1 中的 ID 也可以出现在 V2 中。另请注意,每个 ID 的关联属性在整个数据集中都是唯一且统一的。

现在,我要做的是遍历每一行并计算一系列均值。在这个过程的第一次迭代之后,我的数据应该是这样的:

  V1 V2    R1     G1      B1     R2    G2      B2
1  A  B 157.5   97.5   217.5  157.5  97.5   217.5
2  B  E   195    195     195     60    15     150
3  C  A   135      0       0    120     0     240
4  D  G    30    195     135     75    15     150
5  B  C   195    195     195    135     0       0
6  A  G   120      0     240     75    15     150
7  A  E   120      0     240     60    15     150
8  D  B    30    195     135    195   195     195

即,R1R2 是第一行的前两个相应值 120195 的平均值。等等。

Then,该平均值被重新插入为新属性(与 ID 匹配),并且该过程再次从下一行开始。完成此步骤后,我的数据集应如下所示:

  V1 V2    R1     G1      B1     R2    G2      B2
1  A  B 157.5   97.5   217.5  157.5  97.5   217.5
2  B  E 157.5   97.5   217.5     60    15     150
3  C  A   135      0       0  157.5  97.5   217.5
4  D  G    30    195     135     75    15     150
5  B  C 157.5   97.5   217.5    135     0       0
6  A  G 157.5   97.5   217.5     75    15     150
7  A  E 157.5   97.5   217.5     60    15     150
8  D  B    30    195     135  157.5  97.5   217.5

因此,AB 的所有属性都已更改。然后该过程将继续第二行(对于 BE),依此类推。该过程一直持续到到达最后一行。

到目前为止,这是我的代码。我没有使用 data.table 因为那是我想要弄清楚的。但它就在这里。它可以工作,但是非常慢,让我很难探索系统中正在发生的事情。

for(i in 1:nrow(cross)){
  Rc <- (cross[i,3] + cross[i,6]) / 2 
  Gc <- (cross[i,4] + cross[i,7]) / 2 
  Bc <- (cross[i,5] + cross[i,8]) / 2 
  V1c <- cross[i,"V1"]
  V2c <- cross[i,"V2"]
  
  cross$R1 <- with(cross, replace(R1, V1 == V1c, Rc)) 
  cross$G1 <- with(cross, replace(G1, V1 == V1c, Gc))  
  cross$B1 <- with(cross, replace(B1, V1 == V1c, Bc)) 
  
  cross$R1 <- with(cross, replace(R1, V1 == V2c, Rc)) 
  cross$G1 <- with(cross, replace(G1, V1 == V2c, Gc))  
  cross$B1 <- with(cross, replace(B1, V1 == V2c, Bc)) 
  
  cross$R2 <- with(cross, replace(R2, V2 == V2c, Rc)) 
  cross$G2 <- with(cross, replace(G2, V2 == V2c, Gc))  
  cross$B2 <- with(cross, replace(B2, V2 == V2c, Bc)) 
  
  cross$R2 <- with(cross, replace(R2, V2 == V1c, Rc)) 
  cross$G2 <- with(cross, replace(G2, V2 == V1c, Gc))  
  cross$B2 <- with(cross, replace(B2, V2 == V1c, Bc)) 
}

考虑到我的数据量,此过程需要一个多小时。据我所知,data.table 应该快得多。我已经尝试了几乎所有的东西,从 tidyverse 的东西到转换成矩阵。我什至对需要替换的数据进行了子集化。但是我在尝试为此使用 data.table 时遇到了很多困难,这显然会跳过使用 for 循环的需要。

如果有任何帮助的话,循环的缓慢部分似乎是变量被替换的部分,而不是在生成它们时。

提前致谢!

我是 data.table 的忠实粉丝,但我认为这里不需要它。无需在每次迭代中更新整个 data.frame 中的所有匹配条目,只需按索引更新参考矩阵即可。

V1 <- c("A", "B", "C", "D", "B", "A", "A", "D")
V2 <- c("B", "E", "A", "G", "C", "G", "E", "B")

R1 <- c(120, 195, 135, 30, 195, 120, 120, 30)
G1 <- c(0, 195, 0, 195, 195, 0, 0, 195)
B1 <- c(240, 195, 0, 135, 195, 240, 240, 135)

R2 <- c(195, 60, 120, 75, 135, 75, 60, 195)
G2 <- c(195, 15, 0, 15, 0, 15, 15, 195)
B2 <- c(195, 150, 240, 150, 0, 150, 150, 195)

V12 <- c(V1, V2)
uids <- unique(V12)
idx1 <- match(V1, uids)
idx2 <- match(V2, uids)
mRef <- matrix(c(R1, R2, G1, G2, B1, B2), ncol = 3)[match(uids, V12),]

mRefRGB 值的矩阵,用于 V1V2 中的唯一 ID。 idx 向量指向对应于 V1V2.

中 ID 的 mRef

这是一个快速的小 Rcpp 函数,用于遍历 idx1idx2 以更新 mRef。

Rcpp::cppFunction('NumericMatrix updatecross(const IntegerVector& id1, const IntegerVector& id2, NumericMatrix attr) {
  const int idrows = id1.length();
  const int attrcols = attr.ncol();
  double newval = 0;

  for (int col = 0; col < attrcols; col++) {
    for (int row = 0; row < idrows; row++) {
      newval = (attr(id1(row), col) + attr(id2(row), col))/2;
      attr(id1(row), col) = newval;
      attr(id2(row), col) = newval;
    }
  }

  return attr;
}')

更新 mRef 并使用 idx 向量构建最终的 data.frame

mRef <- updatecross(idx1 - 1L, idx2 - 1L, mRef)
cross <- cbind(data.frame(V1, V2),
               setNames(cbind(as.data.frame(mRef[idx1,]),
                              as.data.frame(mRef[idx2,])),
                        c("R1", "G1", "B1", "R2", "G2", "B2")))

cross
#>   V1 V2       R1      G1       B1       R2      G2       B2
#> 1  A  B 104.0625 66.5625 154.6875  90.0000 78.7500 144.3750
#> 2  B  E  90.0000 78.7500 144.3750 104.0625 66.5625 154.6875
#> 3  C  A 127.5000 52.5000 146.2500 104.0625 66.5625 154.6875
#> 4  D  G  90.0000 78.7500 144.3750  99.3750 76.8750 125.6250
#> 5  B  C  90.0000 78.7500 144.3750 127.5000 52.5000 146.2500
#> 6  A  G 104.0625 66.5625 154.6875  99.3750 76.8750 125.6250
#> 7  A  E 104.0625 66.5625 154.6875 104.0625 66.5625 154.6875
#> 8  D  B  90.0000 78.7500 144.3750  90.0000 78.7500 144.3750

D 和 B 到处都是一样的,因为它们是最后更新的。类似地,A 和 E 在任何地方都是相同的,因为在第 7 行之后都没有更新。

使用igraph(不确定这是否会更快):

library(igraph)
library(purrr)

vertices <- tibble(
  V = c("A", "B", "C", "D", "G", "E"),
  R = c(120, 195, 135, 30, 75, 60),
  G = c(0, 195, 0, 195, 15, 15),
  B = c(240, 195, 0, 195, 150, 150)
)

edges <- tibble(
  from = c("A", "B", "C", "D", "B",  "A", "A", "D"),
  to = c("B", "E", "A", "G", "C", "G", "E", "B")
)

g <- graph_from_data_frame(edges, vertices = vertices, directed = FALSE)

for(iRow in seq_len(nrow(edges))){
  
  v <- as.character(edges[iRow,])
  
  values <- igraph::vertex.attributes(g, v) %>%
    .[-1] %>%
    map_dbl(mean) 
  
  for(iAttr in names(values))
    vertex_attr(g, iAttr, v) <- values[[iAttr]]
}

as_tibble(vertex.attributes(g))

结果:

  name      R     G     B
  <chr> <dbl> <dbl> <dbl>
1 A     104.   66.6  162.
2 B      90    78.8  159.
3 C     128.   52.5  146.
4 D      90    78.8  159.
5 G      99.4  76.9  141.
6 E     104.   66.6  162.

我不确定这个过程代表什么,最终结果取决于您的数据描述的合并顺序。你能分享一下你的原始问题吗?