R Data.Table:为每一行动态更新不同的列

R Data.Table: Dynamically Update a Different Column for each Row

我正在编写一些代码,我需要在其中找到一组列的最大值,然后更新该最大值。考虑这个玩具示例:

test <- data.table(thing1=c('AAA','BBB','CCC','DDD','EEE'),
                        A=c(9,5,4,2,5),
                        B=c(2,7,2,6,3),
                        C=c(6,2,5,4,1),
                      ttl=c(1,1,3,2,1))

结果 data.table 如下所示:

thing1 A B C ttl
AAA 9 2 6 1
BBB 5 7 2 1
CCC 4 2 5 3
DDD 2 6 4 2
EEE 5 3 1 1

目标是找到具有最大值的列(ABC)并将该值替换为当前值减去0.1倍的值ttl 列(即 new_value=old_value - 0.1*ttl)。其他列(不包含最大值)应保持不变。生成的 DT 应如下所示:

thing1 A B C ttl
AAA 8.9 2 6 1
BBB 5 6.9 2 1
CCC 4 2 4.7 3
DDD 2 5.8 4 2
EEE 4.9 3 1 1

这样做的“明显”方法是编写一个 for 循环并循环遍历 DT 的每一行。这很容易做到,这就是我正在改编的代码所做的。然而,真正的 DT 比我的玩具示例大得多,for 循环需要一些时间 运行,这就是为什么我试图调整代码以利用矢量化并摆脱循环。

这是我目前的情况:

test[,max_position:=names(.SD)[apply(.SD,1,function(x) which.max(x))],.SDcols=(2:4)]
test[,newmax:=get(max_position)-ttl*.1,by=1:nrow(test)]

生成此 DT: |thing1|A|B|C|ttl|max_position|newmax| |------|-|-|-|---|-|-| |AAA|9|2|6|1|A|8.9| |BBB|5|7|2|1|B|6.9| |CCC|4|2|5|3|C|4.7| |DDD|2|6|4|2|B|5.8| |EEE|5|3|1​​|1|A|4.9|

问题在于将 newmax 列的值分配回需要去的地方。我天真地尝试了这个,连同其他一些东西,它告诉我“'max_position' not found”:

test[,(max_position):=newmax,by=1:nrow(test)]

通过重塑 DT 来解决问题很简单,这是我目前的解决方案(见下文),但我担心使用完整的 DT 两次重塑也会很慢(虽然可能更好比 for 循环)。关于如何按预期进行这项工作有什么建议吗?

整形方案,供参考:

test[,max_position:=names(.SD)[apply(.SD,1,function(x) which.max(x))],.SDcols=(2:4)]
test[,newmax:=get(max_position)-ttl*.1,by=1:nrow(test)]
test <- setDT(gather(test,idgroup,val,c(A,B,C)))
test[,maxval:=max(val),by='thing1']
test[val==maxval,val:=newmax][,maxval:=NULL]
test <- setDT(spread(test,idgroup,val))

使用 OP 的代码,replace 可以工作

test[, (2:4) := replace(.SD, which.max(.SD), max(.SD, na.rm = TRUE) - 0.1 * ttl), 
    by = 1:nrow(test),.SDcols = 2:4]

-输出

> test
   thing1   A   B   C ttl
1:    AAA 8.9 2.0 6.0   1
2:    BBB 5.0 6.9 2.0   1
3:    CCC 4.0 2.0 4.7   3
4:    DDD 2.0 5.8 4.0   2
5:    EEE 4.9 3.0 1.0   1

base R 中,使用 row/column 索引

可能会更快
test1 <- as.data.frame(test)
m1 <- cbind(seq_len(nrow(test1)), max.col(test1[2:4], "first"))
test1[2:4][m1] <- test1[2:4][m1] - 0.1 * test1$ttl