【问题标题】:Coalesce pairs of variables within a dataframe based on a regular expression基于正则表达式合并数据框中的变量对
【发布时间】:2022-01-23 06:40:51
【问题描述】:

我想使用dplyr::coalesce 在包含多对变量的数据框中查找变量对之间的第一个非缺失值。目标是创建一个新的数据框,现在每对变量只有一个副本(一个没有 NA 值的合并变量)。

这是一个例子:

df <- data.frame(
      A_1=c(NA, NA, 3, 4, 5),
      A_2=c(1, 2, NA, NA, NA),
      B_1=c(NA, NA, 13, 14, 15),
      B_2=c(11, 12, NA, NA, NA))


Expected output: 

A  B
1  11
2  12
3  13
4  14
5  15

我猜测可以使用基于正则表达式的 dplyr::coalescedplyr::mutate_at 的组合,但我不知道该怎么做。有没有办法用tidyverse语法完成这个任务?

谢谢!

编辑:感谢大家的回答!但是,我应该包含变量的命名约定,以方便您将答案转移到我的实际问题中。对此我很抱歉。我的变量是地球化学变量,分为两部分(化学元素名称加上核心名称)。

示例:Al_TAC4.25.275 其中Al 是元素,TAC4.25.275 是核心。我想为每个元素(名称的第一部分)合并来自 3 个不同核心(名称的第二部分)的数据。我有 25 对元素要合并。

【问题讨论】:

    标签: r dplyr tidyverse purrr coalesce


    【解决方案1】:

    你可以使用transmute,例如

    library(dplyr)
    
    df <- data.frame(
      A_1 = c(NA, NA, 3, 4, 5),
      A_2 = c(1, 2, NA, NA, NA),
      B_1 = c(NA, NA, 13, 14, 15),
      B_2 = c(11, 12, NA, NA, NA)
      )
    
    df %>%
      transmute(A = coalesce(A_1, A_2),
                B = coalesce(B_1, B_2))
    #>   A  B
    #> 1 1 11
    #> 2 2 12
    #> 3 3 13
    #> 4 4 14
    #> 5 5 15
    

    reprex package (v2.0.1) 于 2021 年 12 月 22 日创建

    另一种选择,如果您有很多“A_*”和“B_*”列(来源:Romain François,用户:@Romain Francois):

    library(dplyr)
    
    df <- data.frame(
      A_1 = c(NA, NA, 3, 4, 5),
      A_2 = c(1, 2, NA, NA, NA),
      B_1 = c(NA, NA, 13, 14, 15),
      B_2 = c(11, 12, NA, NA, NA)
      )
    
    coacross <- function(...) {
      coalesce(!!!across(...))
    }
    
    df %>%
      transmute(A = coacross(starts_with("A_")),
                B = coacross(starts_with("B_")))
    #>   A  B
    #> 1 1 11
    #> 2 2 12
    #> 3 3 13
    #> 4 4 14
    #> 5 5 15
    

    reprex package (v2.0.1) 于 2021 年 12 月 22 日创建

    编辑

    根据您更新的问题,您没有很多“A_*”或“B_*”列,而是有很多“*_1”、“*_2”和“*_3”列。我认为这是您的用例最直接的解决方案:

    library(dplyr)
    
    df <- data.frame(Al_TAC4.25.275 = c(1, 1, 1, NA, NA, NA),
                     Al_TAC4.25.276 = c(NA, NA, 2, 2, 2, NA),
                     Al_TAC4.25.277 = c(NA, NA, 3, NA, NA, 3),
                     Au_TAC4.25.275 = c(1, 1, 1, NA, NA, NA),
                     Au_TAC4.25.276 = c(NA, NA, 2, 2, 2, NA),
                     Au_TAC4.25.277 = c(NA, NA, 3, NA, NA, NA),
                     Ar_TAC4.25.275 = c(1, 1, 1, NA, NA, 1),
                     Ar_TAC4.25.276 = c(NA, NA, 2, 2, 2, 2),
                     Ar_TAC4.25.277 = c(NA, NA, 3, NA, NA, 3))
    
    df
    #>   Al_TAC4.25.275 Al_TAC4.25.276 Al_TAC4.25.277 Au_TAC4.25.275 Au_TAC4.25.276
    #> 1              1             NA             NA              1             NA
    #> 2              1             NA             NA              1             NA
    #> 3              1              2              3              1              2
    #> 4             NA              2             NA             NA              2
    #> 5             NA              2             NA             NA              2
    #> 6             NA             NA              3             NA             NA
    #>   Au_TAC4.25.277 Ar_TAC4.25.275 Ar_TAC4.25.276 Ar_TAC4.25.277
    #> 1             NA              1             NA             NA
    #> 2             NA              1             NA             NA
    #> 3              3              1              2              3
    #> 4             NA             NA              2             NA
    #> 5             NA             NA              2             NA
    #> 6             NA              1              2              3
    
    names(df) %>% 
      split(str_extract(., '[:alpha:]+')) %>%
      map_dfc(~ coalesce(!!!df[.x][c(1,2,3)]))
    #> # A tibble: 6 × 3
    #>      Al    Ar    Au
    #>   <dbl> <dbl> <dbl>
    #> 1     1     1     1
    #> 2     1     1     1
    #> 3     1     1     1
    #> 4     2     2     2
    #> 5     2     2     2
    #> 6     3     1    NA
    
    # change the order of the list to change the 'priority'
    names(df) %>% 
      split(str_extract(., '[:alpha:]+')) %>%
      map_dfc(~ coalesce(!!!df[.x][c(3,2,1)]))
    #> # A tibble: 6 × 3
    #>      Al    Ar    Au
    #>   <dbl> <dbl> <dbl>
    #> 1     1     1     1
    #> 2     1     1     1
    #> 3     3     3     3
    #> 4     2     2     2
    #> 5     2     2     2
    #> 6     3     3    NA
    
    names(df) %>% 
      split(str_extract(., '[:alpha:]+')) %>%
      map_dfc(~ coalesce(!!!df[.x][c(2,1,3)]))
    #> # A tibble: 6 × 3
    #>      Al    Ar    Au
    #>   <dbl> <dbl> <dbl>
    #> 1     1     1     1
    #> 2     1     1     1
    #> 3     2     2     2
    #> 4     2     2     2
    #> 5     2     2     2
    #> 6     3     2    NA
    

    reprex package (v2.0.1) 于 2021 年 12 月 22 日创建

    【讨论】:

    • 您可以:df %&gt;% transmute(across(ends_with("_1"), ~coalesce(., get(str_replace(cur_column(), '1$', '2'))), .names = "{.col}_coalesce")) %&gt;% rename_at(vars(ends_with('coalesce')), ~ str_remove(., "\\_1")) 避免该功能?!或避免coalesce 两次。
    • 是的!我尝试了这种方法,但无法弄清楚细节 - 感谢@TarJae!我认为值得将其添加到您的答案中:)
    • 这很好用,谢谢 :) 但如果我可能要求对我的问题进行更多说明:我有大约 25 对或变量(实际上,每 25 个变量重复 3 次;例如:那里是变量A_1A_2A_3...Z_1Z_2Z_3,我想合并到A,...Z以减少变量的数量.有没有办法概括你的代码以避免指定A = ...,B = ....?其次,有没有办法在选择变量以填充合并变量时内置优先级?例如,如果我想优先拥有_2 超过 _1 作为填充?
    • 我已经用一个潜在的解决方案 @AntoineLachance 更新了我的答案
    【解决方案2】:

    我在这里提出了要求:https://github.com/tidyverse/dplyr/issues/6109 那里有一些可能的解决方案。例如

    library(dplyr)
    library(purrr)
    df %>% 
        transmute(map2_dfc(.x = across(ends_with("_1"), .names = '{sub("_1","",.col)}'), 
                        .y = across(ends_with("_2")), 
                        .f = coalesce))
      A  B
    1 1 11
    2 2 12
    3 3 13
    4 4 14
    5 5 15
    

    或者也使用函数

    coalesce_prefix <- function(prefix) {
      exprs <- map(prefix, function(p) {
        expr(coalesce(
          !!sym(paste0(p, ".x")),
          !!sym(paste0(p, ".y"))
        ))
      })
      names(exprs) <- prefix
      exprs
    }
    

    【讨论】:

      【解决方案3】:

      基本 R 选项

      list2DF(
        lapply(
          split.default(df, gsub("_.*", "", names(df))),
          rowSums,
          na.rm = TRUE
        )
      )
      

      给予

        A  B
      1 1 11
      2 2 12
      3 3 13
      4 4 14
      5 5 15
      

      【讨论】:

      • 基础R兄弟!你知道我迷恋igraph 和网络分析。它完全改变了我对事物的看法:)
      • @AnoushiravanR 很高兴听到这个消息。享受您的igraph 之旅:)
      【解决方案4】:

      编辑:我相信即使在您进行了编辑之后,此解决方案仍然有效。无论元素数量或每个元素的核心数量如何,它都可以工作。您只需要确保名称一致,格式为"{element}_{core}"

      library(tidyverse)
      df %>% 
        mutate(id = 1:n()) %>% 
        pivot_longer(-id) %>% 
        filter(!is.na(value)) %>% 
        mutate(variable = str_extract(name, "^[^_]+")) %>% 
        group_by(id, variable) %>% 
        # Arrange by name (e.g. A_1) so that we could select the first non-NA
        arrange(name) %>% 
        summarise(value = value[1]) %>% 
        pivot_wider(names_from = "variable")
      

      输出

      # A tibble: 5 x 3
           id     A     B
        <int> <dbl> <dbl>
      1     1     1    11
      2     2     2    12
      3     3     3    13
      4     4     4    14
      5     5     5    15
      

      【讨论】:

        【解决方案5】:

        这里是旋转的替代方案:

        library(dplyr)
        library(tidyr)
        
        df %>%
          pivot_longer(
            everything()
          ) %>% 
          mutate(name = substr(name, 1, 1)) %>% 
          na.omit %>% 
          pivot_wider(
            names_from = name,
            values_from = value,
            values_fn = list
          ) %>% 
          unnest(cols = c(A, B))
        
              A     B
          <dbl> <dbl>
        1     1    11
        2     2    12
        3     3    13
        4     4    14
        5     5    15
        

        【讨论】:

          【解决方案6】:

          与我的另一个相比,这是另一个更简洁的解决方案。我认为在这里使用cur_data() 函数很有帮助,但您也可以使用across(everything()) 代替它:

          library(dplyr)
          library(purrr)
          
          unique(sub("(\\D)_\\d+", "\\1", names(df))) %>%
            map_dfc(~ df %>%
                      select(starts_with(.x)) %>%
                       summarise(!!.x := do.call(coalesce, cur_data())))
          
            A  B
          1 1 11
          2 2 12
          3 3 13
          4 4 14
          5 5 15
          

          这是针对尽可能多的配对的另一种解决方案。请注意,我使用 bang bang 运算符 !!! 将数据框的元素折叠成独立的单个参数,以便我可以在它们上应用 coalesce

          library(dplyr)
          library(rlang)
          
          as.data.frame(do.call(cbind, lapply(split.default(df, sub("(\\D)_\\d+", "\\1", names(df))), function(x) {
            coalesce(!!!x)
          })))
          
            A  B
          1 1 11
          2 2 12
          3 3 13
          4 4 14
          5 5 15
          

          【讨论】:

          • 很高兴在这里见到你!
          • 兄弟永远是我的荣幸 :)
          猜你喜欢
          • 2014-04-12
          • 2016-01-21
          • 2020-11-13
          • 1970-01-01
          • 1970-01-01
          • 2011-05-14
          • 1970-01-01
          • 2021-08-14
          • 1970-01-01
          相关资源
          最近更新 更多