在R

时间:2018-05-07 16:58:57

标签: r dplyr multiple-columns 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))

初始表:

  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

期望的结果:

   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

条件:

  • 应该收集一对ai和bi:因为有3对a和b,&#34; a1和b1&#34;,&#34; a2和b2&#34;和&#34; a3和b3&#34;,这些对中的值应该移动到一对&#34; a和b&#34;通过三次复制每条记录
  • 第一个和第二个字段(每个样本的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)

我很感激你使用tidyverse的优雅建议。

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

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

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

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"
                  )

然后,我正在尝试将您的建议应用如下: 在数据框中,列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()

结果仍有问题:

我可以重命名第一组重复变量,但是仍然保留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)

正确:

map_df(~ hm.trip %>% dplyr::select(hm.com, .x) %>% 
        rename_at(16:28, ~ hm.rename))

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

3 个答案:

答案 0 :(得分:10)

我们可以使用data.table中的patterns执行此操作,measure可以将^参数中的多个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 重新整形为'long'格式。在这种情况下,我们使用的列名称以“a”开头(tidyverse)后跟数字作为一个模式,以“b”开头,后跟数字作为其他

gather

或者使用melt,我们separate感兴趣的列为'long'格式(但在处理具有不同类的列组时应该谨慎 - spread是更有用),然后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 将'key'列分为两个,并mydomain.com到'wide'格式

localhost

答案 1 :(得分: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

reprex package(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)

其中1:3只表示您拥有的对的数量。

答案 2 :(得分:2)

基础R解决方案:

res <- do.call(rbind,lapply(1:3,function(x) setNames(df[c(1:2,2*x+(1:2))],names(df)[1:4])))
res[order(res$id),]
#    id var a1 b1
# 1   1   a  3  2
# 6   1   a  8  1
# 11  1   a  7  1
# 2   2   d  5  4
# 7   2   d  1  6
# 12  2   d  7  1
# 3   3   g  1  1
# 8   3   g  2  4
# 13  3   g  2  4
# 4   4   f  2  2
# 9   4   f  5  7
# 14  4   f  3  9
# 5   5   i  2  3
# 10  5   i  1  2
# 15  5   i  1  6