首页 > 解决方案 > 想要根据列名改变平均列的列,但还要从计算中排除某些列?

问题描述

在数据框中工作,我想使用 mutate 创建一个新列,该列根据列名将每一行中的所有列平均在一起,除了一个。我需要能够在每次使用 mutate 时排除某个列,并且我希望计算也能跳过 NA 值。

我的 DF 的简单版本:

   Team stat1 stat2 stat3 stat4
1  ARI     3    NA     4     6
2  BAL    NA     2    NA     1
3  CAR     5     4     6     2

NewCol1 通过计算 stat 列的平均值创建,不包括“stat 1”列和 NA 值。对 NewCol2 做了同样的事情,计算的平均值不包括“stat2”列:

  Team stat1 stat2 stat3 stat4 NewCol1 NewCol2
1  ARI     3    NA     4     6     5.0    4.33
2  BAL    NA     2    NA     1     1.5    1.00
3  CAR     5     4     6     2     4.0    4.33

如果我想创建对每个统计数据执行相同操作的新列,那么最有效的方法是什么?DF 有 10 个统计列,每个列都有相同的名称,每个名称后面都有一个数字。我在想starts_with() 函数可能在这里与rowMeans() 一起使用,但是我在为如何实现它而苦苦挣扎,同时也每次都排除某个列。

标签: rdataframedplyr

解决方案


我们可以rowMeansselect取出相关列之后使用

library(dplyr)
df1 %>%
      mutate(NewCol1 = rowMeans(select(., -Team, -stat1), na.rm = TRUE),
        NewCol2 = rowMeans(select(., -Team, -stat2), na.rm = TRUE))

-输出

#  Team stat1 stat2 stat3 stat4 NewCol1  NewCol2
#1  ARI     3    NA     4     6     5.0 4.333333
#2  BAL    NA     2    NA     1     1.5 1.000000
#3  CAR     5     4     6     2     4.0 4.333333

或另一种选择c_across

df1 %>% 
   rowwise %>%
   mutate(NewCol1 = mean(c_across(c(where(is.numeric), -stat1)), na.rm = TRUE), 
   NewCol2 = mean(c_across(c(starts_with('stat'), -stat2)), na.rm = TRUE), 
   NewCol3 = mean(c_across(c(starts_with('stat'), -stat3)), na.rm = TRUE), 
   NewCol4 = mean(c_across(c(starts_with('stat'), -stat4)), na.rm = TRUE)) %>%
   ungroup

-输出

# A tibble: 3 x 9
#  Team  stat1 stat2 stat3 stat4 NewCol1 NewCol2 NewCol3 NewCol4
#  <chr> <int> <int> <int> <int>   <dbl>   <dbl>   <dbl>   <dbl>
#1 ARI       3    NA     4     6     5      4.33    4.5      3.5
#2 BAL      NA     2    NA     1     1.5    1       1.5      2  
#3 CAR       5     4     6     2     4      4.33    3.67     5  

如果我们想自动执行此操作,可以选择

library(purrr)
df1[paste0("NewCol", 1:2)] <-  map(c('stat1', 'stat2'),
                       ~ df1 %>%
                             select(starts_with('stat'), -.x) %>%
                             rowMeans(., na.rm = TRUE))

或创建第 1 到 4 列

nm1 <- names(df1)[startsWith(names(df1), 'stat')]
df1[paste0("NewCol", seq_along(nm1))] <-  map(nm1,
                       ~ df1 %>%
                             select(starts_with('stat'), -.x) %>%
                             rowMeans(., na.rm = TRUE))

-输出

df1
#   Team stat1 stat2 stat3 stat4 NewCol1  NewCol2  NewCol3 NewCol4
#1  ARI     3    NA     4     6     5.0 4.333333 4.500000     3.5
#2  BAL    NA     2    NA     1     1.5 1.000000 1.500000     2.0
#3  CAR     5     4     6     2     4.0 4.333333 3.666667     5.0

或者在 tidyverse 中完全做到这一点

library(stringr)
map_dfc(nm1,  ~
    df1 %>% 
       select(starts_with('stat'), -.x) %>% 
       transmute(!! str_c('NewCol', readr::parse_number(.x)) := 
              rowMeans(., na.rm = TRUE))) %>% 
       bind_cols(df1, .)
#  Team stat1 stat2 stat3 stat4 NewCol1  NewCol2  NewCol3 NewCol4
#1  ARI     3    NA     4     6     5.0 4.333333 4.500000     3.5
#2  BAL    NA     2    NA     1     1.5 1.000000 1.500000     2.0
#3  CAR     5     4     6     2     4.0 4.333333 3.666667     5.0

或使用rowwise/c_across

map_dfc(nm1,  ~
     df1 %>% 
        select(starts_with('stat'), -.x) %>% rowwise %>%
        transmute(!! str_c('NewCol', readr::parse_number(.x)) :=   mean(c_across(everything()), na.rm = TRUE))) %>%
        ungroup %>%
    bind_cols(df1, .)

-输出

#  Team stat1 stat2 stat3 stat4 NewCol1  NewCol2  NewCol3 NewCol4
#1  ARI     3    NA     4     6     5.0 4.333333 4.500000     3.5
#2  BAL    NA     2    NA     1     1.5 1.000000 1.500000     2.0
#3  CAR     5     4     6     2     4.0 4.333333 3.666667     5.0

或使用base R

df1[paste0("NewCol", seq_along(nm1))] <- lapply(nm1,
            function(x) rowMeans(df1[setdiff(names(df1)[-1], x)],  na.rm = TRUE))

数据

df1 <- structure(list(Team = c("ARI", "BAL", "CAR"), stat1 = c(3L, NA, 
5L), stat2 = c(NA, 2L, 4L), stat3 = c(4L, NA, 6L), stat4 = c(6L, 
1L, 2L)), class = "data.frame", row.names = c("1", "2", "3"))

推荐阅读