通过R中的dplyr将特定的列对收集到行中

Hid*_*o.S 5 r multiple-columns dplyr tidyverse

我试图通过收集特定的列对将数据框从宽格式转换为长格式,其示例如下所示:

数据框的一个例子

df <- data.frame(id=c(1,2,3,4,5), var=c("a","d","g","f","i"),a1=c(3,5,1,2,2), b1=c(2,4,1,2,3), a2=c(8,1,2,5,1), b2=c(1,6,4,7,2), a3=c(7,7,2,3,1), b3=c(1,1,4,9,6))
Run Code Online (Sandbox Code Playgroud)

初始表:

  id var a1 b1 a2 b2 a3 b3
1  1   a  3  2  8  1  7  1
2  2   d  5  4  1  6  7  1
3  3   g  1  1  2  4  2  4
4  4   f  2  2  5  7  3  9
5  5   i  2  3  1  2  1  6
Run Code Online (Sandbox Code Playgroud)

期望的结果:

   id  var a  b
 1  1   a  3  2
 2  1   a  8  1
 3  1   a  7  1
 4  2   d  5  4
 5  2   d  1  6
 6  2   d  7  1
 7  3   g  1  1
 8  3   g  2  4
 9  3   g  2  4
10  4   f  2  2
11  4   f  5  7
12  4   f  3  9
13  5   i  2  3
14  5   i  1  2
15  5   i  1  6
Run Code Online (Sandbox Code Playgroud)

条件:

  • 应该收集ai和bi的对:由于有3对a和b,"a1和b1","a2和b2"以及"a3和b3",这些对中的值应该移动到一对"a"和b"通过三次复制每条记录
  • 第一个和第二个字段(每个样本的id及其公共变量)应保存在每个复制的行中

我认为可以通过gather()tidyverse 来实现它,但是,据我所知,我认为聚集函数可能不适合将这些特定的字段对收集到特定的多列中(在这种情况下为两列).

可以使它分别准备三个数据框并将其绑定为一个(示例脚本如下所示),但我更喜欢在tidyverse的一个连续管道操作中使它不停止操作.

df1 <- df %>% dplyr::select(id,var,a1,b1)
df2 <- df %>% dplyr::select(id,var,a2,b2)
df3 <- df %>% dplyr::select(id,var,a3,b3)
df.fin <- bind_rows(df1,df2,df3)
Run Code Online (Sandbox Code Playgroud)

我很欣赏你使用tidyverse的优雅建议.

=================其他问题==================

@Akrun&Camille感谢您的建议,对不起我迟到的回复.我现在正试图将您的想法应用到实际的数据框架中,但仍然在努力解决另一个问题.

以下是实际数据框中的列名称(抱歉,我没有设置每列的任何值,因为它可能不是问题).

colnames(df) <- c("hid","mid","rel","age","gen","mlic","vlic",
                  "wtaz","staz","ocp","ocpot","emp","empot","expm",
                  "minc","otaz1","op1","dtime1","atime1","dp1","dtaz1",
                  "pur1", "repm1","lg1t1","lg2t1","lg3t1","lg4t1","expt1",
                  "otaz2","op2","dtime2","atime2","dp2","dtaz2","pur2",
                  "repm2","lg1t2","lg2t2","lg3t2","lg4t2","expt2",
                  "otaz3","op3","dtime3","atime3","dp3","dtaz3","pur3",
                  "repm3","lg1t3","lg2t3","lg3t3","lg4t3","expt3",
                  "otaz4","op4","dtime4","atime4","dp4","dtaz4","pur4",
                  "repm4","lg1t4","lg2t4","lg3t4","lg4t4","expt4",
                  "otaz5","op5","dtime5","atime5","dp5","dtaz5","pur5",
                  "repm5","lg1t5","lg2t5","lg3t5","lg4t5","expt5"
                  )
Run Code Online (Sandbox Code Playgroud)

然后,我试图应用您的建议如下:在数据框中,列1:15是公共变量,其他是重复变量,重复5次(1到5位于每个变量的末尾).我可以使用脚本,但仍有问题:

#### Convert member table into activity table
## Common variables
hm.com <- names(hm)[c(1:15)]
## Repeating variables
hm.rep <- names(hm)[c(-1:-15)]
hm.rename <- unique(sub("\\d+$","",hm.rep))
## Extract members with trips
hm.trip <- hm %>% filter(otaz!=0) %>% data.frame()
## Convert from member into trip table
test <- split(hm.rep, sub(".*[^1-9$]", "", hm.rep)) %>%
    map_df(~ hm.trip %>% dplyr::select(hm.com, .x)) %>% 
    rename_at(16:28, ~ hm.rename) %>%
    arrange(hid,mid,dtime,atime) %>%
    data.frame()
Run Code Online (Sandbox Code Playgroud)

结果仍有问题:

我可以重命名第一组重复变量,但是仍然保留2到5的剩余字段,并且记录未适当地存储在数据框中.我的意思是,一组重复变量,例如,从otaz2到expt2,不存储在otaz~expt的第二行,而是存储在其原始位置(从otaz2到expt2).我想map_df在我的情况下不能正常工作.

==========解决的问题==========上面的脚本包含不正确的操作:

错误:

map_df(~ hm.trip %>% dplyr::select(hm.com, .x)) %>% 
        rename_at(16:28, ~ hm.rename)
Run Code Online (Sandbox Code Playgroud)

正确:

map_df(~ hm.trip %>% dplyr::select(hm.com, .x) %>% 
        rename_at(16:28, ~ hm.rename))
Run Code Online (Sandbox Code Playgroud)

谢谢,我可以进入下一步.

akr*_*run 12

我们能做到这一点与melt来自data.table可采取多种patterns的measure说法重塑成"长"格式.在这种情况下,我们使用的列名称^以"a" 开头()后跟数字作为一个模式,以"b"开头,后跟数字作为其他

library(data.table)  
melt(setDT(df), measure = patterns("^a\\d+", "^b\\d+"), 
       value.name = c("a", "b"))[order(id)][, variable := NULL][]
#    id var a b
# 1:  1   a 3 2
# 2:  1   a 8 1
# 3:  1   a 7 1
# 4:  2   d 5 4
# 5:  2   d 1 6
# 6:  2   d 7 1
# 7:  3   g 1 1
# 8:  3   g 2 4
# 9:  3   g 2 4
#10:  4   f 2 2
#11:  4   f 5 7
#12:  4   f 3 9
#13:  5   i 2 3
#14:  5   i 1 2
#15:  5   i 1 6
Run Code Online (Sandbox Code Playgroud)

或者使用tidyverse,我们gather感兴趣的列为'long'格式(但在处理具有不同类的列组时应该谨慎 - 哪里melt更有用),然后separate将'key'列分成两个,并且spread'wide'格式

library(tidyverse)
df %>% 
  gather(key, val, a1:b3) %>%
  separate(key, into = c("key1", "key2"), sep=1) %>%
  spread(key1, val) %>%
  select(-key2)
#   id var a b
#1   1   a 3 2
#2   1   a 8 1
#3   1   a 7 1
#4   2   d 5 4
#5   2   d 1 6
#6   2   d 7 1
#7   3   g 1 1
#8   3   g 2 4
#9   3   g 2 4
#10  4   f 2 2
#11  4   f 5 7
#12  4   f 3 9
#13  5   i 2 3
#14  5   i 1 2
#15  5   i 1 6
Run Code Online (Sandbox Code Playgroud)


cam*_*lle 2

这可扩展性不太好,所以如果您最终需要的列不止这 3 对,请采用 @akrun 的答案。我只是想指出,bind_rows您包含的代码片段实际上可以在一个管道中完成:

library(tidyverse)


bind_rows(
        df %>% select(id, var, a = a1, b = b1),
        df %>% select(id, var, a = a2, b = b2),
        df %>% select(id, var, a = a3, b = b3)
    ) %>%
    arrange(id, var)
#>    id var a b
#> 1   1   a 3 2
#> 2   1   a 8 1
#> 3   1   a 7 1
#> 4   2   d 5 4
#> 5   2   d 1 6
#> 6   2   d 7 1
#> 7   3   g 1 1
#> 8   3   g 2 4
#> 9   3   g 2 4
#> 10  4   f 2 2
#> 11  4   f 5 7
#> 12  4   f 3 9
#> 13  5   i 2 3
#> 14  5   i 1 2
#> 15  5   i 1 6
Run Code Online (Sandbox Code Playgroud)

由reprex 包(v0.2.0)于 2018-05-07 创建。

如果您想要可扩展的东西并且喜欢map_*函数(来自purrr)tidyverse,您可以抽象上面的管道:

1:3 %>%
    map_df(~select(df, id, var, ends_with(as.character(.))) %>% 
                    setNames(c("id", "var", "a", "b"))) %>%
    arrange(id, var)
Run Code Online (Sandbox Code Playgroud)

其中1:3仅代表您拥有的对的数量。