【问题标题】:How to combine tidyr::pivot_longer with stringr::separate_rows in one shot如何一次性将 tidyr::pivot_longer 与 stringr::separate_rows 结合起来
【发布时间】:2021-01-18 10:53:15
【问题描述】:

我正在尝试根据列名将数据从宽格式转换为长格式,这可以通过tidyr::pivot_longer() 轻松完成。但是,我还需要以宽格式解构特定单元格的内容——即解析其中的字符串——并沿着旋转(更长)行分离解析的组件。虽然使用stringr::separate_rows 可以轻松完成解析和分离任务,但我不知道如何在同一个镜头中结合旋转和分离过程。

数据

df <- data.frame(
  id = 1:3,
  blue_type1 = 110:112,
  purple_type5 = 5:7,
  black_type1 = 28:30,
  batch_number = c("bgd | ddg | qwe",
                   "afp | qqw | edt",
                   "pqr | khp | rty")
  )

df

##   id blue_type1 purple_type5 black_type1    batch_number
## 1  1        110            5          28 bgd | ddg | qwe
## 2  2        111            6          29 afp | qqw | edt
## 3  3        112            7          30 pqr | khp | rty

我想要什么

转换为长格式并解压batch_number,这样第一个子字符串将分配给长格式的第一行,第二个子字符串分配给第二行,第三个子字符串分配给第三行。

期望的输出

## # A tibble: 9 x 5
##      id batch_number color   type  vals
##   <dbl> <chr>        <chr>  <dbl> <dbl>
## 1     1 bgd          blue       1   110
## 2     1 ddg          purple     5     5
## 3     1 qwe          black      1    28
## 4     2 afp          blue       1   111
## 5     2 qqw          purple     5     6
## 6     2 edt          black      1    29
## 7     3 pqr          blue       1   112
## 8     3 khp          purple     5     7
## 9     3 rty          black      1    30

我的尝试

如果我只是tidyr::pivot_longer,我就成功了一半:

df %>% 
  pivot_longer(., 
               -c(id, batch_number), 
               names_to = c("color", "type"), 
               names_pattern = "(.*)_type(.)", 
               values_to = "vals")

## # A tibble: 9 x 5
##      id batch_number    color  type   vals
##   <int> <chr>           <chr>  <chr> <int>
## 1     1 bgd | ddg | qwe blue   1       110
## 2     1 bgd | ddg | qwe purple 5         5
## 3     1 bgd | ddg | qwe black  1        28
## 4     2 afp | qqw | edt blue   1       111
## 5     2 afp | qqw | edt purple 5         6
## 6     2 afp | qqw | edt black  1        29
## 7     3 pqr | khp | rty blue   1       112
## 8     3 pqr | khp | rty purple 5         7
## 9     3 pqr | khp | rty black  1        30

如果我尝试 stringr::separate_rows 在此之上,我会得到不想要的输出:

## # A tibble: 27 x 5
## # Groups:   id [3]
##       id batch_number color  type   vals
##    <int> <chr>        <chr>  <chr> <int>
##  1     1 bgd          blue   1       110
##  2     1 ddg          blue   1       110
##  3     1 qwe          blue   1       110
##  4     1 bgd          purple 5         5
##  5     1 ddg          purple 5         5
##  6     1 qwe          purple 5         5
##  7     1 bgd          black  1        28
##  8     1 ddg          black  1        28
##  9     1 qwe          black  1        28
## 10     2 afp          blue   1       111
## 11     2 qqw          blue   1       111
## 12     2 edt          blue   1       111
## 13     2 afp          purple 5         6
## 14     2 qqw          purple 5         6
## 15     2 edt          purple 5         6
## 16     2 afp          black  1        29
## 17     2 qqw          black  1        29
## 18     2 edt          black  1        29
## 19     3 pqr          blue   1       112
## 20     3 khp          blue   1       112
## 21     3 rty          blue   1       112
## 22     3 pqr          purple 5         7
## 23     3 khp          purple 5         7
## 24     3 rty          purple 5         7
## 25     3 pqr          black  1        30
## 26     3 khp          black  1        30
## 27     3 rty          black  1        30

如何在运行pivot_longer 的同时合并separate_rows 的操作?有没有一种优雅的方式来完成这样的任务?基本上我正在寻找tidyverse 解决方案,但也会对其他方法感到满意。

【问题讨论】:

    标签: r reshape tidyr stringr


    【解决方案1】:

    也许有一个更短更优雅的解决方案,但同时你可以试试这个。基本思路是

    1. 在调用tidyr::separate_rows之前添加一个批次标识符(batch)(id
    2. tidyr::separate_rows 之后过滤@​​987654325@ 和batch 标识符相等的obs。对于这最后一步,我首先使用forcats::fct_inorderbatch_number 转换为一个因子,然后转换为一个数字,它给出了batch_number 的位置,然后可以与batch 标识符匹配
    set.seed(42)
    
    df <- data.frame(
      id = 1:3,
      blue_type1 = 110:112,
      purple_type5 = 5:7,
      black_type1 = 28:30,
      batch_number = c("bgd | ddg | qwe",
                       "afp | qqw | edt",
                       "pqr | khp | rty")
    )
    library(dplyr)
    library(tidyr)
    library(forcats)
    
    df %>% 
      pivot_longer(-c(id, batch_number)) %>% 
      group_by(id) %>% 
      mutate(batch = row_number()) %>% 
      separate_rows(batch_number) %>% 
      filter(batch == as.numeric(forcats::fct_inorder(batch_number)))
    #> # A tibble: 9 x 5
    #> # Groups:   id [3]
    #>      id batch_number name         value batch
    #>   <int> <chr>        <chr>        <int> <int>
    #> 1     1 bgd          blue_type1     110     1
    #> 2     1 ddg          purple_type5     5     2
    #> 3     1 qwe          black_type1     28     3
    #> 4     2 afp          blue_type1     111     1
    #> 5     2 qqw          purple_type5     6     2
    #> 6     2 edt          black_type1     29     3
    #> 7     3 pqr          blue_type1     112     1
    #> 8     3 khp          purple_type5     7     2
    #> 9     3 rty          black_type1     30     3
    

    【讨论】:

      【解决方案2】:

      您可以在旋转后对 batch_number 中的字符串进行子集化。

      library(dplyr)
      library(tidyr)
      library(stringr)
      
      df %>%
        group_by(id) %>%
        mutate(batch_index = seq.int(1, n()*6, 6)) %>%
        ungroup() %>%
        mutate(batch_number = str_sub(batch_number, batch_index, batch_index+2)) %>%
        select(-batch_index)
      
      #      id batch_number color  type   vals
      #   <int> <chr>        <chr>  <chr> <int>
      # 1     1 bgd          blue   1       110
      # 2     1 ddg          purple 5         5
      # 3     1 qwe          black  1        28
      # 4     2 afp          blue   1       111
      # 5     2 qqw          purple 5         6
      # 6     2 edt          black  1        29
      # 7     3 pqr          blue   1       112
      # 8     3 khp          purple 5         7
      # 9     3 rty          black  1        30
      

      请注意mutate(batch_index = seq.int(1, n()*6, 6)) 在数据框中添加了一个名为 batch_index 的列(按 id 分组)。在每一行中,batch_index 用于对 batch_number 中的字符串进行子集化。 batch_index 由 seq.int(1, n()*6, 6) 生成,由 1 到 n()*6 的整数组成(即当前组中的行数乘以 6 - 请注意,最终值不必这么高)。序列中的数字相隔六: 1 , 7, 13 ...

      mutate(batch_number = str_sub(batch_number, batch_index, batch_index+2) 使用 batch_index 对每行的 batch_number 中的字符串进行子集化。组中的第一行需要来自 batch_number 的第一个子字符串 - 因此函数将 batch_number 从 batch_index(即 1)子集到 batch_index+2(即 3)。组中的第二行需要来自 batch_number 的第二个子字符串 - 该函数因此将 batch_number 从 batch_index(即 7)子集到 batch_index+2(即 9)等。

      前提是 batch_number 中的所有子字符串都由三个字母组成。

      【讨论】:

      • 谢谢@rjen。尽管此解决方案在问题中提供的数据下完成了工作,但它不能扩展到更大的数据集,其中batch_number 可能包含许多要子字符串化的组件。实际上,我在问题中包含的数据只是用于使用简短数据集演示问题的玩具数据。实际上,我的问题与更大的数据集有关。
      • 不客气@Emman。我已经概括了解决方案以适应您评论中的信息。
      • 再次感谢@rjen。关于这个解决方案,我可以再问两件事吗? (1) 在我的真实数据中,batch_number 中的子字符串长度超过 3 个字符。将您的解决方案应用于我的数据会将这些子字符串修剪为 3 个字符长,这与预期不符。这个可以调整吗? (2) 你介意解释一下解决方案吗?例如,mutate(batch_index = seq.int(1, n()*6, 6))。这条线里面发生了什么?
      • 我很高兴,@Emman。我在我的帖子中添加了一些解释。请让我知道 batch_number 中的子字符串在真实数据集中的样子。它们的长度是一样的,还是在一个范围内?
      • 嗨@rjen,谢谢!这是杰出的!在我的真实数据中,子字符串的长度是恒定的,恰好比3长得多。
      【解决方案3】:

      您可以在mutate 中使用separate_rowspull,在pivot_longer 之后:

      df %>% 
        pivot_longer(-c(id, batch_number), 
                     names_to = c("color", "type"), 
                     names_pattern = "(.*)_type(.)", 
                     values_to = "vals") %>%
        mutate(batch_number = 
                 df %>% 
                 separate_rows(batch_number, sep = " \\| ") %>% 
                 pull(batch_number)
               ) 
      
      # A tibble: 9 x 5
           id batch_number color  type   vals
        <int> <chr>        <chr>  <chr> <int>
      1     1 bgd          blue   1       110
      2     1 ddg          purple 5         5
      3     1 qwe          black  1        28
      4     2 afp          blue   1       111
      5     2 qqw          purple 5         6
      6     2 edt          black  1        29
      7     3 pqr          blue   1       112
      8     3 khp          purple 5         7
      9     3 rty          black  1        30
      

      【讨论】:

        【解决方案4】:

        使用tidyr 中的separate_rows() 尝试此选项并使用顺序ID:

        library(tidyverse)
        #Code
        df <- df %>% separate_rows(batch_number,sep='\\|') %>%
          mutate(batch_number=trimws(batch_number)) %>%
          group_by(id) %>% mutate(Val=1:n()) %>%
          pivot_longer(-c(id,batch_number,Val)) %>%
          separate(name,c('color','type'),sep='_') %>%
          mutate(type=gsub('type','',type),Flag=ifelse(id==Val,1,0)) %>%
          filter(Flag==1) %>% select(-c(Flag,Val))
        

        输出:

        # A tibble: 9 x 5
        # Groups:   id [3]
             id batch_number color  type  value
          <int> <chr>        <chr>  <chr> <int>
        1     1 bgd          blue   1       110
        2     1 bgd          purple 5         5
        3     1 bgd          black  1        28
        4     2 qqw          blue   1       111
        5     2 qqw          purple 5         6
        6     2 qqw          black  1        29
        7     3 rty          blue   1       112
        8     3 rty          purple 5         7
        9     3 rty          black  1        30
        

        【讨论】:

        • 不幸的是,这个解决方案只是我想要完成的一半。请参阅上述问题中包含的所需输出。首先,我需要将列名同时转为separate_rows
        • @Emman 抱歉,我没有注意到这个细节。我已经更新了解决方案。希望对你有帮助!
        • 谢谢@Duck!但目前的输出仍然不如预期。将此解决方案输出中的 batch_number 列与所需输出中的 batch_number 列进行比较,看看差异。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-12-08
        • 2012-03-17
        • 2017-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多