繁体   English   中英

使用tidyverse整理多组宽列的表格

[英]Tidying table with multiple groups of wide columns, using tidyverse

我经常遇到这样的情况:我的表包含多组宽列,如下所示:

  replicate groupA        VA1         VA2 groupB         VB1        VB2
1         1      a  0.3429166 -2.30336406      f  0.05363582  1.6454078
2         2      b -1.3183732 -0.13516849      g -0.42586417  0.1541541
3         3      c -0.7908358 -0.10746447      h  1.05134242  1.4297350
4         4      d -0.9963677 -1.82557058      i -1.14532536  1.0815733
5         5      e -1.3634609  0.04385812      j -0.65643595 -0.1452877

我想将这些列变成一个长表,如下所示:

   replicate group key       value
1          1     a  V1  0.34291665
2          2     b  V1 -1.31837322
3          3     c  V1 -0.79083580
4          4     d  V1 -0.99636772
5          5     e  V1 -1.36346088
6          1     a  V2 -2.30336406
7          2     b  V2 -0.13516849
8          3     c  V2 -0.10746447
9          4     d  V2 -1.82557058
10         5     e  V2  0.04385812
11         1     f  V1  0.05363582
12         2     g  V1 -0.42586417
13         3     h  V1  1.05134242
14         4     i  V1 -1.14532536
15         5     j  V1 -0.65643595
16         1     f  V2  1.64540784
17         2     g  V2  0.15415408
18         3     h  V2  1.42973499
19         4     i  V2  1.08157329
20         5     j  V2 -0.14528774

我可以通过分别选择两组列,进行整理然后重新绑定在一起来实现此目的(下面的代码)。 但是,这种方法似乎并不特别优雅,并且如果存在多于两组的列,则将变得很麻烦。 我想知道是否存在使用单个管道数据转换链的更优雅的方法。

这里的基本问题是:我们如何自动化将表分为几列,整理一下然后重新组合在一起的过程。

我当前的代码:

library(dplyr)
library(tidyr)

# generate example code
df_wide <- data.frame(replicate = 1:5,
                      groupA = letters[1:5],
                      VA1 = rnorm(5),
                      VA2 = rnorm(5),
                      groupB = letters[6:10],
                      VB1 = rnorm(5),
                      VB2 = rnorm(5))

# tidy columns with A in the name
dfA <- select(df_wide, replicate, groupA, VA1, VA2) %>%
  gather(key, value, VA1, VA2) %>%
  mutate(key = case_when(key == "VA1" ~ "V1",
                         key == "VA2" ~ "V2")) %>%
  select(replicate, group = groupA, key, value)

# tidy columns with B in the name
dfB <- select(df_wide, replicate, groupB, VB1, VB2) %>%
  gather(key, value, VB1, VB2) %>%
  mutate(key = case_when(key == "VB1" ~ "V1",
                         key == "VB2" ~ "V2")) %>%
  select(replicate, group = groupB, key, value)

# combine
df_long <- rbind(dfA, dfB)

注意: 在这里这里都曾提出类似的问题,但我认为已被接受的答案表明这是一个微妙的问题。

1个

虽然这个问题问了tidyverse的解决方案,有一个方便的选择meltdata.table ,也可以采取多种patternsmeasure参数。

library(data.table)
setnames(melt(melt(setDT(df1), measure = patterns('group', 'VA', 'VB')), 
        id.var = 1:3)[, -4, with = FALSE], 2:3, c('key', 'group'))[]

2.一个

tidyverse我们可以子集的数据集到一个list ,然后通过循环listmap_df将其转换为“长”格式与gather得到一个data.frame

library(tidyverse)
list(df1[1:4], df1[c(1,5:7)]) %>%
      map_df(~gather(., key, value, 3:4) %>%
                   {names(.)[2] <- 'group';.}) %>%
      mutate(key = sub('(.).(.)', '\\1\\2', key))
#   replicate group key       value
#1          1     a  V1  0.34291660
#2          2     b  V1 -1.31837320
#3          3     c  V1 -0.79083580
#4          4     d  V1 -0.99636770
#5          5     e  V1 -1.36346090
#6          1     a  V2 -2.30336406
#7          2     b  V2 -0.13516849
#8          3     c  V2 -0.10746447
#9          4     d  V2 -1.82557058
#10         5     e  V2  0.04385812
#11         1     f  V1  0.05363582
#12         2     g  V1 -0.42586417
#13         3     h  V1  1.05134242
#14         4     i  V1 -1.14532536
#15         5     j  V1 -0.65643595
#16         1     f  V2  1.64540780
#17         2     g  V2  0.15415410
#18         3     h  V2  1.42973500
#19         4     i  V2  1.08157330
#20         5     j  V2 -0.14528770

2.b

如果我们需要根据“组”的出现进行split

split.default(df1[-1], cumsum(grepl('group', names(df1)[-1]))) %>% 
         map(~bind_cols(df1[1], .)) %>% 
         map_df(~gather(., key, value, 3:4) %>% 
               {names(.)[2] <- 'group';.}) %>%
         mutate(key = sub('(.).(.)', '\\1\\2', key))

2.c

包含的rename_at代替了tidyverse选项精神的names分配

df1[-1] %>% 
      split.default(cumsum(grepl('group', names(df1)[-1]))) %>% 
      map_df(~bind_cols(df1[1], .) %>% 
           gather(., key, value, 3:4) %>%
           rename_at(2, funs(substring(.,1, 5))))

注意:

1) 2.a2.b2.c使用了dydyverse函数

2)它不依赖于列名称中的子字符串“ A”或“ B”

3)假设OP数据集中的模式将是“组”,后跟值列

1)该解决方案包括:

  • 收集生成所需的行数
  • 结合了groupA和groupB列并将键列更改为请求的mutate
  • 选择哪个选择所需的列。

首先收集名称以V开头的列,然后从groupA和groupB创建一个新的group列,如果键中包含A,则选择groupA,如果键中包含B,则选择groupB。 (我们在这里使用mapply(switch,...)可以很容易地扩展到3+组的情况,但是我们可以使用ifelse,即ifelse(grepl(“ A”,key)as.character(groupA)as .character(groupB)),因为我们只有两个组。)mutate还将键名从VA1减少到V1,依此类推,最后选择所需的列。

DF %>% 
   gather(key, value, starts_with("V")) %>%
   mutate(group = mapply(switch, gsub("[^AB]", "", key), A = groupA, B = groupB),
          key = sub("[AB]", "", key)) %>%
   select(replicate, group, key, value)

给予:

   replicate group key       value
1          1     a  V1  0.34291660
2          2     b  V1 -1.31837320
3          3     c  V1 -0.79083580
4          4     d  V1 -0.99636770
5          5     e  V1 -1.36346090
6          1     a  V2 -2.30336406
7          2     b  V2 -0.13516849
8          3     c  V2 -0.10746447
9          4     d  V2 -1.82557058
10         5     e  V2  0.04385812
11         1     f  V1  0.05363582
12         2     g  V1 -0.42586417
13         3     h  V1  1.05134242
14         4     i  V1 -1.14532536
15         5     j  V1 -0.65643595
16         1     f  V2  1.64540780
17         2     g  V2  0.15415410
18         3     h  V2  1.42973500
19         4     i  V2  1.08157330
20         5     j  V2 -0.14528770

2)另一种方法是将列分成几组,以便从名称中删除A和B后,组中的所有列都具有相同的名称。 在每个这样的组上执行不列出,以将列表简化为纯矢量列表,并将该列表转换为data.frame。 最后收集V列并重新排列。 请注意,rownames_to_column来自tibble包。

DF %>%
   as.list %>%
   split(sub("[AB]", "", names(.))) %>%
   lapply(unlist) %>%
   as.data.frame %>%
   rownames_to_column %>%
   gather(key, value, starts_with("V")) %>%
   arrange(gsub("[^AB]", "", rowname), key) %>%
   select(replicate, group, key, value)

2a)如果行顺序不重要,则可以省略rownames_to_column,range和select行,将其缩短为:

DF %>%
   as.list %>%
   split(sub("[AB]", "", names(.))) %>%
   lapply(unlist) %>%
   as.data.frame %>%
   gather(key, value, starts_with("V"))

解决方案(2)和(2a)可以很容易地转换为仅基于碱基的解决方案,方法是像第二个变形一样,用适当的从底部进行的变形替换道集,即在(3)中产生d2的那个。

3)尽管这个问题要求一个整洁的解决方案,但是有一个相当方便的基本解决方案,它包含两个重塑调用。 拆分产生的变化是: list(group = c("groupA", "groupB"), V1 = c("VA1", "VB1"), V2 = c("VA2", "VB2")) - -也就是说,它与每组列中的第i列匹配。

varying <- split(names(DF)[-1], gsub("[AB]", "", names(DF))[-1])
d <- reshape(DF, dir = "long", varying = varying, v.names = names(varying))
d <- subset(d, select = -c(time, id))

d2 <- reshape(d, dir = "long", varying = list(grep("V", names(d))), v.names = "value", 
  timevar = "key")
d2 <- subset(d2, select = c(replication, group, key, value))

d2

注意:可复制形式的输入为:

DF <- structure(list(replicate = 1:5, groupA = structure(1:5, .Label = c("a", 
"b", "c", "d", "e"), class = "factor"), VA1 = c(0.3429166, -1.3183732, 
-0.7908358, -0.9963677, -1.3634609), VA2 = c(-2.30336406, -0.13516849, 
-0.10746447, -1.82557058, 0.04385812), groupB = structure(1:5, .Label = c("f", 
"g", "h", "i", "j"), class = "factor"), VB1 = c(0.05363582, -0.42586417, 
1.05134242, -1.14532536, -0.65643595), VB2 = c(1.6454078, 0.1541541, 
1.429735, 1.0815733, -0.1452877)), .Names = c("replicate", "groupA", 
"VA1", "VA2", "groupB", "VB1", "VB2"), class = "data.frame", row.names = c("1", 
"2", "3", "4", "5"))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM