【问题标题】:dplyr: group_by and summarize to collapse (via concatenation) columns of strings that contain NAdplyr:group_by 和汇总以折叠(通过连接)包含 NA 的字符串列
【发布时间】:2021-09-28 06:26:48
【问题描述】:

我有一个相对简单的问题,但我一直找不到解决方案。

假设我有以下数据集:

ID dummy_var String1 String2 String3
1 0 Tom NA NA
1 1 NA Jo NA
2 0 Tom NA NA
2 1 NA Jo NA
2 0 NA NA Bob
3 0 Steve NA NA
3 0 NA Timmy NA
4 0 Alex NA NA

我想使用 group by 和 summarise 来获得以下信息:

ID dummy_var String1 String2 String3
1 1 Tom Jo NA
2 1 Tom Jo Bob
3 0 Steve Timmy NA
4 0 Alex NA NA

我对“dummy_var”没有任何问题,在汇总函数中使用 dummy_var = max(dummy_var) 的变体,但我似乎找不到任何关于如何获取所需字符串的信息。

我尝试过以下变体:

group_by(ID) %>%
summarize(
String1 = str_c(String1)
)

group_by(ID) %>%
summarize(
String1 = case_when(
     length(str_c(String1)) > 0 ~ str_c(String1)
     str_c(String1) == rep(NA,length(str_c(String1)) ~ NA
     )
)

第一次尝试时,行实际上并没有改变。例如,尽管诸如 max(dummy var) 之类的数字运算将按预期为组中的每一行产生 0 或 1,但不会汇总字符串变量,并且在取消分组和打印数据帧时,每个 ID 都会得到多行,就好像你一开始从来没有总结过字符串列。

使用第二种方法时,当每个组的所有值都为 NA 的情况下,函数总是会失败,即“String(i) 的长度必须大于 0”或某种变体。

我注意到如果我尝试以下操作

group_by(ID) %>%
summarize(
String1 = str_replace_na(String1)
)

输出和第一个代码块一样,好像什么都没发生一样。

关于我的数据的其他事实:字符串 1 将始终具有每组至少一个没有 NA 的值。对于 String2 和 String 3,每组有许多包含所有 NA,并且我希望折叠的行也读取 NA,根据我的示例。此外,在任何情况下,任何 group_by() 组都不会包含多于一行的列包含 NA 以外的内容;即,在组内,每一行只有三个 String1/2/3 之一作为 NA 以外的东西,或者它们都可能是 NA(例如在我的示例中 ID=2)。所有其他包含 int 或 double 值的列都可以毫无问题地进行汇总。这只是字符串。使用 paste0 代替 str_c() 也没有区别。

谁能给我建议?我在网上找不到任何这样的示例,其中 NA 在组内的列内,而且在组内它们有时包含列内的所有值。

我唯一的选择是在所有 NA 上使用 replace_na(),将它们与一些填充文本连接起来,然后返回并为每个值使用 stringr 或其他东西将它们取出。它有效,但我知道必须有一种优雅的方法!

编辑: 事实证明,如果我使用 str_replace_na() 而不是 str_c(),你最终会得到,例如,

ID dummy_var String1 String2 String3
1 1 Tom "NA" "NA"
1 1 "NA" "Jo" "NA"
2 1 Tom "NA" "NA"
2 1 "NA" "Jo" "NA"
2 1 "NA" "NA" Bob

也就是说,这些值被替换为字符串“NA”而不是 NA。鉴于以下情况属实,这令人惊讶:

str_replace_na("Something",NA)
> "Something"
str_c("Something",NA)
> NA

【问题讨论】:

    标签: r dplyr summarize


    【解决方案1】:

    data.table 选项

    setDT(df)[
        ,
        dummy_var := max(dummy_var), ID
    ][
        ,
        lapply(.SD, function(x) fcoalesce(as.list(x))), .(ID, dummy_var)
    ]
    

    给予

       ID dummy_var String1 String2 String3
    1:  1         1     Tom      Jo    <NA>
    2:  2         1     Tom      Jo     Bob
    3:  3         0   Steve   Timmy    <NA>
    4:  4         0    Alex    <NA>    <NA>
    

    使用 ´aggregate+ave` 的基本 R 选项

    aggregate(
        . ~ ID + dummy_var,
        transform(
            df,
            dummy_var = ave(dummy_var, ID, FUN = max)
        ),
        function(x) ifelse(all(is.na(x)), x, na.omit(x)),
        na.action = na.pass
    )
    

    给予

      ID dummy_var String1 String2 String3
    1  3         0   Steve   Timmy    <NA>
    2  4         0    Alex    <NA>    <NA>
    3  1         1     Tom      Jo    <NA>
    4  2         1     Tom      Jo     Bob
    

    【讨论】:

      【解决方案2】:

      这是另一种方法,dplyr

      # function that will keep one row for each unique ID
      coalesce_all_columns <- function(df) {
        return(coalesce(!!! as.list(df)))
      }
      
      library(dplyr)
      df %>%
        group_by(ID) %>%
        arrange(ID, desc(dummy_var)) %>% 
        summarise_all(coalesce_all_columns)
      

      输出:

           ID dummy_var String1 String2 String3
        <dbl>     <dbl> <chr>   <chr>   <chr>  
      1     1         1 Tom     Jo      NA     
      2     2         1 Tom     Jo      Bob    
      3     3         0 Steve   Timmy   NA     
      4     4         0 Alex    NA      NA     
      

      【讨论】:

        【解决方案3】:

        你可以使用tidyrfill-function:

        library(tidyr)
        library(dplyr)
        
        df %>% 
          group_by(ID) %>% 
          fill(starts_with("String"), .direction="downup") %>% 
          filter(dummy_var == max(dummy_var)) %>% 
          distinct() %>% 
          ungroup()
        

        返回

        # A tibble: 4 x 5
             ID dummy_var String1 String2 String3
          <dbl>     <dbl> <chr>   <chr>   <chr>  
        1     1         1 Tom     Jo      NA     
        2     2         1 Tom     Jo      Bob    
        3     3         0 Steve   Timmy   NA     
        4     4         0 Alex    NA      NA   
        

        ##数据

        df <- structure(list(ID = c(1, 1, 2, 2, 2, 3, 3, 4), dummy_var = c(0, 
        1, 0, 1, 0, 0, 0, 0), String1 = c("Tom", NA, "Tom", NA, NA, "Steve", 
        NA, "Alex"), String2 = c(NA, "Jo", NA, "Jo", NA, NA, "Timmy", 
        NA), String3 = c(NA, NA, NA, NA, "Bob", NA, NA, NA)), class = c("spec_tbl_df", 
        "tbl_df", "tbl", "data.frame"), row.names = c(NA, -8L), spec = structure(list(
            cols = list(ID = structure(list(), class = c("collector_double", 
            "collector")), dummy_var = structure(list(), class = c("collector_double", 
            "collector")), String1 = structure(list(), class = c("collector_character", 
            "collector")), String2 = structure(list(), class = c("collector_character", 
            "collector")), String3 = structure(list(), class = c("collector_character", 
            "collector"))), default = structure(list(), class = c("collector_guess", 
            "collector")), skip = 1L), class = "col_spec"))
        

        【讨论】:

        • 谢谢!这正是我想要的。把我逼疯了。我知道必须有一个优雅的解决方案!
        • tidyr 为您提供了许多有用的工具来清理数据。值得一看。
        • 对于那些需要汇总()其他列而不仅仅是单个 dummy_var 的人,下一步是在对字符串列执行 fill() 之后,将字符串列包含在 group_by( ) 在总结之前。
        猜你喜欢
        • 2021-11-16
        • 2015-06-18
        • 1970-01-01
        • 2021-03-18
        • 2014-04-24
        • 2015-07-26
        • 2022-11-12
        • 2020-01-10
        • 1970-01-01
        相关资源
        最近更新 更多