【问题标题】:Get all combinations of a variable and their corresponding values in a grouped data set获取分组数据集中变量及其对应值的所有组合
【发布时间】:2018-03-24 20:12:58
【问题描述】:

我的数据如下所示:

mydata <- data.frame(id = c(1,1,1,2,2,3,3,3,3), 
               subid = c(1,2,3,1,2,1,2,3,4),
               time = c(16, 18, 20, 10, 11, 7, 9, 10, 11))

  id subid time
1  1     1   16
2  1     2   18
3  1     3   20
4  2     1   10
5  2     2   11
6  3     1    7
7  3     2    9
8  3     3   10
9  3     4   11

我的目标是将数据转换为:

newdata <- data.frame(id = c(1,1,1,2,3,3,3,3,3,3), 
                  subid.1 = c(1,1,2,1,1,1,1,2,2,3), 
                  subid.2 = c(2,3,3,2,2,3,4,3,4,4), 
                  time.1 = c(16,16,18,10,7,7,7,9,9,10),
                  time.2 = c(18,20,20,11,9,10,11,10,11,11))

   id subid.1 subid.2 time.1 time.2
1   1       1       2     16     18
2   1       1       3     16     20
3   1       2       3     18     20
4   2       1       2     10     11
5   3       1       2      7      9
6   3       1       3      7     10
7   3       1       4      7     11
8   3       2       3      9     10
9   3       2       4      9     11
10  3       3       4     10     11

所以这不是从长到宽过程的简单重塑:这个想法是,在由 id 定义的组内,采用所有可能的组合 subid 及其对应的时间值,并将它们转换为宽格式。

我知道我可以使用例如gtools::combinations 获得所有可能的组合。第一组由 3 行组成,所以

gtools::combinations(n=3, r=2) 

为我提供组 id==1 的新 subid.1 和 subid.2 对的矩阵:

      [,1] [,2]
[1,]    1    2
[2,]    1    3
[3,]    2    3

但是我不知道如何继续(既不将id==1 的组重塑为这种格式,也不知道如何为每个组单独执行此操作)。谢谢!

【问题讨论】:

    标签: r reshape reshape2


    【解决方案1】:

    使用data.table-package:

    library(data.table)
    setDT(mydata)[, .(subid = c(t(combn(subid, 2)))), by = id
                  ][, grp := rep(1:2, each = .N/2), by = id
                    ][mydata, on = .(id, subid), time := time
                      ][, dcast(.SD, id + rowid(grp) ~ grp, value.var = list('subid','time'), sep = '.')]
    

    给你:

        id grp subid.1 subid.2 time.1 time.2
     1:  1   1       1       2     16     18
     2:  1   2       1       3     16     20
     3:  1   3       2       3     18     20
     4:  2   4       1       2     10     11
     5:  3   5       1       2      7      9
     6:  3   6       1       3      7     10
     7:  3   7       1       4      7     11
     8:  3   8       2       3      9     10
     9:  3   9       2       4      9     11
    10:  3  10       3       4     10     11
    

    【讨论】:

    • 谢谢@Jaap!我总是很难理解 data.table 函数(dplyr 和 reshape 有我的偏好,或者只是应用函数的简单链接)。我想知道的是:这是否适用于成千上万行数据,或者它可能会占用所有 RAM?如果是这样,我更喜欢真正迭代每一行数据的for循环或应用函数(和/或我可以并行化以使用所有内核)并逐行构建生成的data.frame。但我听说 data.table 非常高效,所以也许这正是它在下面的作用?
    • @graham 我认为这是一种非常有效的方法。 data.table 最有效的数据处理包之一。 for 循环或链接 *apply 函数的效率肯定会降低。
    • 我会在我庞大的数据集上尝试一下,看看它是否有效。谢谢你的好回答
    • @Henrik Thx 发现错误,现在更新了答案。顺便说一句:你为​​什么删除你的答案?我觉得没问题。
    【解决方案2】:

    忘了说我想出了这个相当蹩脚的四步解决方案:

    step1 <- lapply(unique(mydata$id), function(x) {
      nrows <- nrow(mydata[which(mydata$id == x), ])
      combos <- gtools::combinations(n=nrows, r=2)
      return(as.data.frame(cbind(x, combos)))
    })
    
    step2 <- dplyr::bind_rows(step1)
    
    step3a <- merge(step2, mydata, by.x = c("x", "V2"), by.y = c("id", "subid"))
    step3b <- merge(step3a, mydata, by.x = c("x", "V3"), by.y = c("id", "subid"))
    
    step4 <- step3b[, c(1, 3, 2, 4, 5)]
    names(step4) <- c("id", "subid.1", "subid.2", "time.1", "time.2")
    

    虽然很丑,但是很管用。

    【讨论】:

      【解决方案3】:

      以 R 为基数:

      subset(merge(mydata, mydata, by="id", suffix=c(".1",".2")), subid.1 < subid.2)
      #    id subid.1 time.1 subid.2 time.2
      # 1   1       1     16       2     18
      # 2   1       1     16       3     20
      # 3   1       2     18       3     20
      # 4   2       1     10       2     11
      # 5   3       1      7       2      9
      # 6   3       1      7       3     10
      # 7   3       1      7       4     11
      # 8   3       2      9       3     10
      # 9   3       2      9       4     11
      # 10  3       3     10       4     11
      

      dplyr版本:

      mydata %>% inner_join(.,.,by="id",suffix=c(".1",".2")) %>% filter(subid.1 < subid.2)
      

      data.table 版本:

      setDT(mydata)
      mydata[mydata, on="id", allow.cartesian=TRUE][subid < i.subid]
      #     id subid time i.subid i.time
      #  1:  1     1   16       2     18
      #  2:  1     1   16       3     20
      #  3:  1     2   18       3     20
      #  4:  2     1   10       2     11
      #  5:  3     1    7       2      9
      #  6:  3     1    7       3     10
      #  7:  3     2    9       3     10
      #  8:  3     1    7       4     11
      #  9:  3     2    9       4     11
      # 10:  3     3   10       4     11
      

      或者让你的列名正确,但这会扼杀简短解决方案的乐趣:)。

      merge(mydata, mydata, by="id", suffix=c(".1",".2"), allow.cartesian=TRUE)[subid.1 < subid.2]
      

      【讨论】:

      • 我发现您的解决方案是最容易理解的,并称赞您意识到“subid.1
      • 不一定,您“仅”创建了两倍的数据,并且您可能通过使用简单有效的矢量化操作来减少其他开销,您必须进行测试:)。如果你这样做了,请测试我的data.table 解决方案,我相信data.table 的合并效率要高得多,至少几年前它在一个案例中救了我。
      • 我已经使用 microbenchmark 和 50 次运行在更大的数据集(大约 500k 行)上对其进行了测试。我自己的解决方案(我同时改进了一点)大约需要 2 分钟。我不记得你的基本 R 解决方案,但我认为大约是 15 秒。您的 dplyr 解决方案平均耗时 790 毫秒。两种 data.table 解决方案都花费了大约相同的时间(可以理解),大约 900 毫秒。所以对于我的数据,dplyr 解决方案实际上更快!我想您需要更大的数据集才能看到 data.table 的强大功能。感谢您的意见!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-04-15
      • 2015-03-24
      • 2019-11-14
      • 1970-01-01
      • 2017-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多