【问题标题】:Gathering specific pairs of columns into rows by dplyr in R [duplicate]通过R中的dplyr将特定的列对收集到行中[重复]
【发布时间】:2018-10-17 13:12:05
【问题描述】:

我正在尝试通过收集特定的列对来将数据帧从宽格式转换为长格式,示例如下所示:

数据框示例

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 对:由于 a 和 b 有 3 对,“a1 和 b1”、“a2 和 b2”和“a3 和 b3”,这些对中的值应移动到一对通过将每条记录复制 3 次来计算“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)

感谢您使用 tidyverse 提出的优雅建议。

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

@Akrun 和卡米尔 感谢您的建议,并对我迟到的回复感到抱歉。我现在正在尝试将您的想法应用到实际的数据框架中,但仍在努力解决另一个问题。

以下是实际数据框中的列名(对不起,我没有设置每列的任何值,因为这可能无关紧要)。

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

谢谢,我可以进行下一步了。

【问题讨论】:

    标签: r dplyr multiple-columns tidyverse


    【解决方案1】:

    一个基本的 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
    

    【讨论】:

    • 感谢您使用 base 函数的建议,但我在原帖中也提到了另一个问题。
    【解决方案2】:

    这不是很可扩展,所以如果您最终需要超过这 3 对列,请使用 @akrun 的答案。我只是想指出,您包含的bind_rows sn-p 实际上可以在一个管道中完成:

    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 年 5 月 7 日创建。

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

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

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

    【讨论】:

    • 以编程方式,你可以做nm1 &lt;- names(df)[-(1:2)]; split(nm1, sub("\\D+", "", nm1)) %&gt;% map_df(~ df %&gt;% select(id, var, .x) %&gt;% rename_at(3:4, ~ c("a", "b"))) %&gt;% arrange(id, var)
    • @akrun 我在玩类似的东西,认为这只是 3 对的过度杀伤,但我会添加它
    • 最好把上面改一下,让它更有活力; nm2 &lt;- unique(sub("\\d+", "", nm1)) 然后在 rename_at(-(1:2), ~ nm2))
    • @camille 和 akrun 谢谢你们的建议。您能否查看我的其他问题以实施您的建议?
    【解决方案3】:

    我们可以使用 data.table 中的 melt 来做到这一点,它可以将 measure 参数中的多个 patterns 重新整形为“长”格式。在这种情况下,我们使用以“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
    

    或使用tidyverse,我们将gather 感兴趣的列转换为“长”格式(但在处理具有不同类的列组时应谨慎 - 其中melt 更有用),然后@ 987654330@ '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
    

    【讨论】:

    • 感谢您的快速回答,但更喜欢将 dplyr 解决方案用于我的用例。在正则表达式中指定模式对我来说似乎是新想法。谢谢!
    • @Hideo 使用 tidyverses 解决方案更新
    • 感谢您的额外建议。我明白你为什么建议使用data.table。请在@camille 的回答中查看其他问题以实施您的建议。
    • @akrun,有没有办法更通用地做到这一点,但计算给定 df 中的列数然后排列?
    • @Ben 你的问题我不清楚。能否请您发布一个新问题
    猜你喜欢
    • 2018-11-06
    • 1970-01-01
    • 2015-09-01
    • 2016-11-01
    • 2018-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多