【问题标题】:How to coalesce chunks of columns based on a prefix pattern如何基于前缀模式合并列块
【发布时间】:2021-03-06 07:29:13
【问题描述】:

我的数据由多个“块”列组成。在每个块中,列名称相同但由前缀区分:A_colorB_colorC_colorXX_heightYY_heightZZ_height 等。有时,可能只有词干没有任何前缀的名称。此外,在每个块中只有一列包含数据,其他列是 NA

######### CHUNK OF COLOR ########  ## ###### CHUNK OF HEIGHT #############
##   A_color B_color C_color color ## XX_height height YY_height ZZ_height
##   <lgl>   <lgl>     <dbl> <lgl> ## <lgl>     <lgl>  <lgl>         <dbl>
## 1 NA      NA            1 NA    ## NA        NA     NA               15

因此,我想合并块以获得:

##   color height
##   <dbl>  <dbl>
## 1     1     15

示例

我构建了一个哑函数来模拟数据的结构。它的内部无关紧要,只是输出。

library(stringi)
library(dplyr)
library(purrr)

generate_data <-
  function(n_of_chunks, n_variants_in_each_chunk, nrows) {
    
    # helper func for later
    interweave_vecs <- function(x, y, n){
      c(x, y)[order(c(ceiling(seq_along(x) / n), seq_along(y)))]}
    
    # generate col names
    names_core           <- stri_rand_strings(n = n_of_chunks, length = 10)
    names_expanded       <- rep(names_core, each = n_variants_in_each_chunk)
    prefixes             <- replicate(expr = stri_rand_strings(n = 1, length = sample(1:3, 1)), n = length(names_expanded))
    names_with_prefixes  <- paste(prefixes, names_expanded, sep = "__")
    names_final          <- interweave_vecs(names_with_prefixes, names_core, n_variants_in_each_chunk)
    
    # generate data
    dat <-
      rerun(
      replicate(expr = sample(c(rep(NA, n_variants_in_each_chunk), runif(1))), n = n_of_chunks) %>% 
        as.vector() %>% 
        setNames(., names_final) %>% 
        bind_rows(), 
      .n = nrows
    ) %>%
      bind_rows() %>%
      cbind(., col_blah = rnorm(nrows), col_foo = letters[1:nrows])
    
    # shuffle columns
    dat[,sample(ncol(dat))] %>%
      as_tibble()
    
}

现在模拟数据:

set.seed(2021)
my_df <- generate_data(n_of_chunks = 3, n_variants_in_each_chunk = 3, nrows = 10)
 
> my_df
## # A tibble: 10 x 14
##    v__RmiNdhdGoy s13__1pbZoFVrxX Qx8__1pbZoFVrxX RmiNdhdGoy col_blah Xe__8xOGZuvswo `8xOGZuvswo` A__1pbZoFVrxX w__RmiNdhdGoy col_foo WM__8xOGZuvswo `1pbZoFVrxX` SNr__8xOGZuvswo tYD__RmiNdhdGoy
##            <dbl>           <dbl>           <dbl>      <dbl>    <dbl>          <dbl>        <dbl>         <dbl>         <dbl> <chr>            <dbl>        <dbl>           <dbl>           <dbl>
##  1       NA              NA               NA        NA        -1.50          NA           NA             0.846         0.763 a               NA           NA              0.0479          NA    
##  2        0.0729         NA               NA        NA         2.20           0.365       NA            NA            NA     b               NA            0.995         NA               NA    
##  3       NA              NA               NA         0.398     0.939         NA           NA            NA            NA     c                0.695        0.234         NA               NA    
##  4       NA               0.541           NA        NA         0.713         NA           NA            NA             0.235 d               NA           NA              0.470           NA    
##  5       NA               0.317           NA        NA        -0.805         NA            0.163        NA            NA     e               NA           NA             NA                0.506
##  6       NA              NA                0.991     0.0519    0.791         NA            0.888        NA            NA     f               NA           NA             NA               NA    
##  7        0.159          NA               NA        NA         0.116         NA           NA             0.223        NA     g               NA           NA              0.260           NA    
##  8        0.442           0.0561          NA        NA        -1.33          NA           NA            NA            NA     h                0.663       NA             NA               NA    
##  9       NA              NA                0.346     0.479    -1.82          NA            0.193        NA            NA     i               NA           NA             NA               NA    
## 10       NA              NA                0.443     0.469    -0.403         NA           NA            NA            NA     j               NA           NA              0.450           NA    

my_df 中,列围绕 3 个“词干”进行组织:

  • RmiNdhdGoy
  • 1pbZoFVrxX
  • 8xOGZuvswo

每个版本都有 3 个前缀版本,一个版本只是词干,例如,RmiNdhdGoy 块:

my_df %>% 
  select(ends_with("RmiNdhdGoy"))

## # A tibble: 10 x 4
##    v__RmiNdhdGoy RmiNdhdGoy w__RmiNdhdGoy tYD__RmiNdhdGoy
##            <dbl>      <dbl>         <dbl>           <dbl>
##  1       NA         NA              0.763          NA    
##  2        0.0729    NA             NA              NA    
##  3       NA          0.398         NA              NA    
##  4       NA         NA              0.235          NA    
##  5       NA         NA             NA               0.506
##  6       NA          0.0519        NA              NA    
##  7        0.159     NA             NA              NA    
##  8        0.442     NA             NA              NA    
##  9       NA          0.479         NA              NA    
## 10       NA          0.469         NA              NA 
  • 前缀本身是长度在 1-3 个字符之间的字母数字。
  • 前缀和词干之间的分隔符是两个下划线 (__)

我试图想出一种方法来遍历列名,对于那些有两个下划线的,取子字符串 after 那个分隔符,并寻找其他列名以该子字符串结尾,然后将所有这些视为一大块列并将它们合并。

期望的输出

我需要合并每个块以获得:

## # A tibble: 10 x 5
##    RmiNdhdGoy `1pbZoFVrxX` `8xOGZuvswo` col_blah col_foo
##         <dbl>        <dbl>        <dbl>    <dbl> <chr>  
##  1     0.763        0.846        0.0479   -1.50  a      
##  2     0.0729       0.995        0.365     2.20  b      
##  3     0.398        0.234        0.695     0.939 c      
##  4     0.235        0.541        0.470     0.713 d      
##  5     0.506        0.317        0.163    -0.805 e      
##  6     0.0519       0.991        0.888     0.791 f      
##  7     0.159        0.223        0.260     0.116 g      
##  8     0.442        0.0561       0.663    -1.33  h      
##  9     0.479        0.346        0.193    -1.82  i      
## 10     0.469        0.443        0.450    -0.403 j   

我看过一堆关于这个主题的 SO 帖子(例如,thisthisthis),但我不明白这些解决方案如何适合我的情况。我希望有人会有一个想法。谢谢!


编辑


在下面@tamtam 的回答之后,我想强调我正在寻找的解决方案是针对generate_data()任何 输出,而不是专门针对my_df。我展示了my_df 只是为了说明一个要处理的示例数据。

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    您可以尝试coalesce 中的contains 函数,因此您不需要将列名拆分为before _after_。相反,您只是在寻找名称中包含例如“RmiNdhdGoy”一词的列。

    简而言之,您告诉 R 基于已包含某个表达式的所有列创建一个新列。

    df2 <- my_df %>% 
      mutate(RmiNdhdGoy = coalesce(!!!select(., contains("RmiNdhdGoy"))),
             `1pbZoFVrxX` = coalesce(!!!select(., contains("1pbZoFVrxX"))),
              `8xOGZuvswo` = coalesce(!!!select(., contains("8xOGZuvswo")))) %>%
      select(RmiNdhdGoy, `1pbZoFVrxX`, `8xOGZuvswo`, col_blah, col_foo)
    

    输出

    # A tibble: 10 x 5
       RmiNdhdGoy `1pbZoFVrxX` `8xOGZuvswo` col_blah col_foo
            <dbl>        <dbl>        <dbl>    <dbl> <fct>  
     1     0.763        0.846        0.0479   -1.16  a      
     2     0.0729       0.995        0.365    -0.594 b      
     3     0.398        0.234        0.695    -0.583 c      
     4     0.517        0.632        0.432    -0.819 d      
     5     0.0462       0.652        0.213    -1.50  e      
     6     0.947        0.408        0.0811    2.20  f      
     7     0.495        0.0819       0.713     0.939 g      
     8     0.0151       0.459        0.0561    0.713 h      
     9     0.663        0.479        0.346    -0.805 i      
    10     0.193        0.469        0.443     0.791 j     
    

    【讨论】:

    • 谢谢。抱歉,如果我没有说清楚,但我正在寻找一个不受特定名称约束的解决方案。每次运行 generate_data() 时,都会得到不同的列名和前缀。所以我需要一个灵活的解决方案来处理generate_data()任何输出。
    【解决方案2】:

    只是扩展上一个答案以删除硬编码的列名并将解决方案包装在一个函数中:

    transform_df <- function(start_df_, sep = '__'){
      
      # identify columns of interest
      spread_names <- grep(sep, colnames(start_df_), value = T)
      other_names <- setdiff(colnames(start_df_), spread_names)
      
      # grab the `key` part from their names
      spread_keys <- unique(
        sapply(
          strsplit(spread_names, sep)
          , `[[`
          , 2
        )
      )
    
      # apply the describe operation
      for(key in spread_keys){
        start_df_ <- start_df_ %>% 
          dplyr::mutate(!!key := coalesce(!!!select(., contains(key))))
      }
      
      # format and return
      start_df_ %>%
        dplyr::select(c(spread_keys, other_names))
    }
    

    然后你得到输出

    set.seed(2022)
    my_df <- generate_data(n_of_chunks = 4, n_variants_in_each_chunk = 4, nrows = 10)
    my_df %>% transform_df()
    # A tibble: 10 x 6
       `098Wb7lVaq` oe7XBd42Mk TdFPA4qYnl qZQ8AvXzTl col_foo col_blah
              <dbl>      <dbl>      <dbl>      <dbl> <chr>      <dbl>
     1       0.0846      0.746      0.381     0.182  a         -0.583
     2       0.509       0.492      0.962     0.702  b         -0.198
     3       0.155       0.912      0.917     0.0141 c         -0.603
     4       0.998       0.603      0.121     0.943  d         -1.09 
     5       0.300       0.853      0.317     0.740  e          0.184
     6       0.0486      0.598      0.330     0.122  f          1.31 
     7       0.0583      0.757      0.650     0.465  g         -0.168
     8       0.819       0.461      0.165     0.597  h          0.344
     9       0.541       0.280      0.978     0.793  i          0.376
    10       0.850       0.147      0.865     0.426  j         -0.195   
    
    

    HTH

    【讨论】:

    • 谢谢,太好了!不过小事:我们如何确保grep(sep, colnames(start_df_), value = T) 匹配完全 sep == "__" 而不是sep == "___" 或包含更多下划线的列名?
    • 我尝试在sep 中添加修复程序,然后再添加到grep():sep &lt;- paste0("\\b", sep, "\\b"),但没有成功...
    • 嗨,grep 使用正则表达式,假设您在上面描述的列结构sep = '[a-zA-Z0-9]+_{2}[a-zA-Z0-9]+',其中[a-zA-Z0-9] 将匹配字母/数字,'+' 确保其中一些字母/数字至少出现一次,_{2} 正好匹配 2 个下划线。查看?regex 了解更多详情。 HTH
    猜你喜欢
    • 2015-08-05
    • 2020-07-23
    • 2019-11-08
    • 1970-01-01
    • 1970-01-01
    • 2018-03-28
    • 2016-09-20
    • 2020-01-07
    相关资源
    最近更新 更多