2017-11-25 145 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我不知道如何。如果你能寫出來,我會非常感興趣。 –