【问题标题】:Want to mutate columns that average columns together based on column names, but also excludes certain columns from the calculation?想要改变基于列名对列进行平均的列,但还要从计算中排除某些列?
【发布时间】:2020-10-07 03:20:42
【问题描述】:

在数据框中工作,我想使用 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() 一起使用,但我在为如何实现它而苦苦挣扎,同时每次还要排除某个列。

【问题讨论】:

    标签: r dataframe dplyr


    【解决方案1】:

    在基础 R 中,您可以找到其中包含 'stat' 的列,然后将其从 lapply 中一一删除,并对其进行逐行平均。

    cols <- grep('stat', names(df))
    new_cols <- paste0('remove_', names(df)[cols])
    df[new_cols] <- lapply(cols, function(x) rowMeans(df[, -c(1, x)], na.rm = TRUE))
    df
    
    #  Team stat1 stat2 stat3 stat4 remove_stat1 remove_stat2 remove_stat3 remove_stat4
    #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
    

    【讨论】:

    • 感谢您的回答。我收到此错误:rowMeans 中的错误(df[, -c(1, x)], na.rm = TRUE) : 'x' must be numeric - 有什么建议吗?
    • @ChazC Team 列是您数据框中的第一个列吗?除了Team,您还有其他非数字的列吗?
    • 是的,并通过将代码更改为 rowMeans(df[, -c(1:5, x)], na.rm = TRUE) 来修复它 - 再次感谢!
    【解决方案2】:

    我们可以在select取出相关列之后使用rowMeans

    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"))
    

    【讨论】:

    • 这非常有用;谢谢你!让其他示例正常工作,但我尝试使用您的示例自动执行此操作(第 3 和第 4 个示例),但出现 x 必须是数字的错误 - 'x' 是否应该替换为此代码示例中的某些内容?
    • @ChazC 这一定是您的数据列类型的问题。
    猜你喜欢
    • 1970-01-01
    • 2016-03-20
    • 2019-08-28
    • 1970-01-01
    • 1970-01-01
    • 2018-10-25
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多