当变量由多个部分组成时,将 tibble 转换为长格式

Convert tibble to long form when the variables consist of several parts

我有这样的数据

library(tidyverse)

df = tribble(
  ~id, ~a1, ~a2, ~a3, ~b1, ~b2, ~b3, ~c1, ~c2, ~c3,
    1,   1,   4,   7,  11,  14,  17,  21,  24,  27,
    2,   2,   5,   8,  12,  15,  18,  22,  25,  28,
    3,   3,   6,   8,  13,  16,  19,  23,  26,  29,
)

我想将其转换为长格式,其中变量名包含名称 (a, b, c) 和数字 (1, 2, 3) 两部分,这应该成为长版本中的新变量table 如下。

   id name nr data
1   1    a  1    1
2   2    a  1    2
3   3    a  1    3
4   1    a  2    4
5   2    a  2    5
6   3    a  2    6
7   1    a  3    7
8   2    a  3    8
9   3    a  3    8
10  1    b  1   11
11  2    b  1   12
12  3    b  1   13
13  1    b  2   14
14  2    b  2   15
15  3    b  2   16
16  1    b  3   17
17  2    b  3   18
18  3    b  3   19
19  1    C  1   21
20  2    C  1   22
21  3    C  1   23
22  1    C  2   24
23  2    C  2   25
24  3    C  2   26
25  1    C  3   27
26  2    C  3   28
27  3    C  3   29

仅使用dplyr包中的函数就可以做到吗?我试了pivot_longer效果很失望

欢迎任何提示。

我们可以通过几种方式做到这一点 - 即首先重塑为 'long' 格式 pivot_longer 排除 'id' 列,然后仅 separate [=通过将 sep 指定为正则表达式环视,即(因为只有一个小写字母),将 27=] 列分成两列,在第一次出现该字母后拆分 ((?<=[a-z]))

library(dplyr)
library(tidyr)
df %>% 
   pivot_longer(cols = -id, names_to = 'name', values_to = 'data') %>%
   separate(name, into = c("name", 'nr'), sep = "(?<=[a-z])")

-输出

 A tibble: 27 × 4
      id name  nr     data
   <dbl> <chr> <chr> <dbl>
 1     1 a     1         1
 2     1 a     2         4
 3     1 a     3         7
 4     1 b     1        11
 5     1 b     2        14
 6     1 b     3        17
 7     1 c     1        21
 8     1 c     2        24
 9     1 c     3        27
10     2 a     1         2
# … with 17 more rows

或者另一种选择是在列名中附加一个后缀,然后使用 pivot_longer

library(stringr)
df %>% 
   rename_with(~ str_c(., "_data"), -id) %>% 
   pivot_longer(cols = -id, names_to = c("name", "nr", ".value"), 
       names_pattern = "^(.)(.)_(.*)")

我知道以前有人问过这个问题,但我找不到好的重复目标。同时,如果您指定正则表达式来区分列名的 name 部分和 nr 部分,您可以在一个函数调用中完成:


df %>%
  pivot_longer(-id, names_to = c("name", "nr"), 
               values_to = "data", 
               names_pattern = "(^[a-z])(\d$)")
#> # A tibble: 27 × 4
#>       id name  nr     data
#>    <dbl> <chr> <chr> <dbl>
#>  1     1 a     1         1
#>  2     1 a     2         4
#>  3     1 a     3         7
#>  4     1 b     1        11
#>  5     1 b     2        14
#>  6     1 b     3        17
#>  7     1 c     1        21
#>  8     1 c     2        24
#>  9     1 c     3        27
#> 10     2 a     1         2
#> # … with 17 more rows

如果您在实践中有不同的列名,请根据需要调整正则表达式,但这会将它们分开,以便第一部分来自字符串开头的单个小写字母,第二部分来自单个数字在字符串的末尾。