2017-11-25 103 views
1

我经常发现自己在一个情况下,我有一个包含宽列的多组,像这样的表:整理表列宽多组,使用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 

我可以通过分别选择所述两个组的列,整理,然后rbinding一起(下面的代码)执行此操作。但是,这种方法看起来并不优雅,而且如果有两组以上的列,则会变得很麻烦。我想知道是否有更优雅的方法,使用单个数据转换管道链。

这里最根本的问题是:我们如何自动化将表分成多组,整理这些表,然后再合并到一起的过程。

我当前的代码:

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) 

注:类似的问题已经被问herehere,但我想接受的答案显示,这儿是个微妙的不同问题。

回答

1

1)此解决方案包括一个:

  • 收集其产生的行
  • 所需数量的
  • mutate组合了groupA和groupB列,并将键列更改为请求的键列,并且选择哪个列选出想要的列。

首先收集名称以V开头的列,然后从groupA和groupB中创建一个新的组列,并选择groupA(如果该密钥在其中具有A和groupB,如果该密钥在其中具有B)。 (我们在这里使用了mapply(switch,...)来轻松扩展到3+组案例,但是我们可以使用ifelse,即ifelse(grepl(“A”,键),as.character(groupA)) .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之后的列分成组,使得一个组中的所有列具有相同的名称。 Performi在每个这样的组上取消列表,将列表减少到一个普通向量列表并将该列表转换为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,安排和选择线可以省略它缩短了这一点:

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

解决方案(2)及(2A)可能(3)中的第二个整形,也就是产生d2的那个,就可以很容易地转换成base-only解决方案。

3)虽然这个问题提出了一个tidyverse解决方案,但有一个相当方便的基础解决方案,它由两个重塑调用组成。分割产生的变化是: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")) 
+0

谢谢!我喜欢你的解决方案1,因为它不需要硬编码的列索引,并且适应许多不同的场景应该是相当直接的。 –

+0

已将ifelse更改为用于泛化为> 2组的开关。 –

+0

已添加新的(2)和(2a)并将旧的(2)移至(3)。 –

3

虽然问了tidyverse解决这个问题,有一个与melt一个方便的选择从data.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的发生。Ç

包括rename_at代替names分配在tidyverse选项

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.b,使用tidyverse功能2.c

2)一点也没有不取决于列名中的子串“A”或“B”

3)假定在OP的数据集中的模式将是“组”之后是值列

+0

谢谢您的回答。不过,我正在专门寻找一种全新的方法。 –

+0

应该可以用双'gather'做同样的事情。 – mikeck

+0

@mikeck我不知道如何。如果你能写出来,我会非常感兴趣。 –