按行平均超过增加号。在 mutate 中使用 for 循环的列数:dplyr R

Rowwise average over increasing no. of columns using for loop inside mutate : dplyr R

我想执行这样的操作。

a <- data.frame(A=c(1,5,9),
                B=c(2,6,10),
                C=c(3,7,11),
                D=c(4,8,12))

a <- a %>% rowwise()
a <- a %>% mutate(mean(c_across(1:2)))
a <- a %>% mutate(mean(c_across(1:3)))
a <- a %>% mutate(mean(c_across(1:4)))

这给出:

A   B   C   D  mean(c_across(1:2)) mean(c_across(1:3)) mean(c_across(1:4))
1   2   3   4                  1.5                   2                 2.5
5   6   7   8                  5.5                   6                 6.5
9   10  11  12                 9.5                  10                10.5

我想使用 for 循环获得相同的结果。我试过这个:

a <- data.frame(A=c(1,5,9),
                B=c(2,6,10),
                C=c(3,7,11),
                D=c(4,8,12))

a <- a %>% rowwise()
for(i in 2:4){
  a <- a %>% mutate(mean(c_across(1:i)))
}

但它只显示最后一个值 i=4 的结果

A   B   C   D  mean(c_across(1:i))
1   2   3   4                  2.5
5   6   7   8                  6.5
9   10  11  12                10.5

谁能解释一下发生了什么?每当我在使用 dplyr 时使用 for 循环,我立即觉得我做错了什么。还有其他更好的方法吗?

您可以使用purrr::reduce(或base::Reduce)进行迭代。

library(tidyverse)

reduce(2:4, ~ mutate(.x, !!paste0("col1to", .y) := mean(c_across(1:.y))), .init = rowwise(a))

# A tibble: 3 x 7
# Rowwise: 
      A     B     C     D col1to2 col1to3 col1to4
  <dbl> <dbl> <dbl> <dbl>   <dbl>   <dbl>   <dbl>
1     1     2     3     4     1.5       2     2.5
2     5     6     7     8     5.5       6     6.5
3     9    10    11    12     9.5      10    10.5
  • base::Reduce版本:
Reduce(\(x, y) mutate(x, !!paste0("col1to", y) := mean(c_across(1:y))), 2:4, init = rowwise(a))

要修复 for 循环,您需要为每个新列设置不同的列名。否则,每个新列都将具有相同的名称,即 "mean(c_across(1:i))",并覆盖之前的列。

b <- rowwise(a)
for(i in 2:4) {
  b <- b %>% mutate(!!paste0("col1to", i) := mean(c_across(1:i)))
}

b

另一种选择 tidyr::unnest_wider():

a %>%
  rowwise() %>%
  mutate(mean = list(cummean(c_across(1:4))[-1])) %>%
  unnest_wider(mean, names_sep = "_")

使用data.table:

setDT(a)[
  , 
  paste0("col", seq_len(ncol(a)-1)) :=  
    transpose(lapply(transpose(.SD), function(x) cummean(x)[-1]))
]

使用 base R 你可以做类似的事情:

cbind(a, t(apply(a, 1, function(x) cummean(x)[-1])))

这是另一个 tidyverse 选项,它也使用 purrr。我们可以使用 map 遍历列名,以便 select 列的范围并获得列的平均值 selected。然后,我们可以更改新列的名称并将输出绑定回原始数据框。在这里,我使用 names(a)[-1] 以便代码更灵活并且适用于任何其他数据帧。

library(tidyverse)

names(a)[-1] %>% 
  map(~ a %>% 
        select(names(a)[1]:.x) %>% 
        rowMeans(.)) %>%
  set_names(paste0("mean_", names(a)[1], "_", names(a)[-1])) %>%
  bind_cols(a, .)

输出

  A  B  C  D mean_A_B mean_A_C mean_A_D
1 1  2  3  4      1.5        2      2.5
2 5  6  7  8      5.5        6      6.5
3 9 10 11 12      9.5       10     10.5