【问题标题】:Creating Multiple Lists Based on Multiple Subsets of a Larger Data.Frame基于较大 Data.Frame 的多个子集创建多个列表
【发布时间】:2013-03-09 00:08:16
【问题描述】:

在 R 中工作,我有与下面类似结构的数据(代码块 1)。我希望创建一个具有以下特征的新 data.frame:

对于每个唯一的 ID_1 值,我希望有两个新列,一个包含 (ID_2s that share ID_1 & Direction==1) 的列表,另一列包含 (ID_2s that share ID_1 & Direction的列表==0), (见下一个代码块 2)

数据集块 1(初始):

ID_1    ID_2    Direction
100001  1           1
100001  11          1
100001  111         1
100001  1111        0
100001  11111       0
100001  111111      0
100002  2           1
100002  22          1
100002  222         0
100002  2222        0
100003  3           1
100003  33          1
100003  333         1
100003  3333        0
100003  33333       0
100003  333333      1
100004  4           1
100004  44          1

转化为:

数据集块 2(所需输出):

ID_1    ID_2_D1             ID_2_D0
100001  1,11,111            1111,11111,111111
100002  2,22                222,222
100003  3,33,333,333333     3333,33333
100004  4,44    

我有执行此操作的代码(获取子集子集的循环),但我在数百万个唯一的“ID_1”上运行它,这使得这非常耗时(我告诉你几个小时!!)。

有什么建议——也许使用 apply() 或 plyr() 包可以让它运行得更快?


参考代码:

DF <- data.frame(ID_1=c(100001,100001,100001,100001,100001,100001,100002,100002,100002,100002,100003,100003,100003,100003,100003,100003,100004,100004)
                   ,ID_2=c(1,11,111,1111,11111,111111,2,22,222,2222,3,33,333,3333,33333,333333,4,44)
                   ,Direction=c(1,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1,1,1)
                   )

我当前(太慢)的代码:

  DF2 <- data.frame( ID_1=DF[!duplicated(DF$ID_1),][,1])

  for (i in 1:length(unique(DF2$ID_1))){
    DF2$ID_2_D1[i] <- list(subset(DF,ID_1==unique(DF2$ID_1)[i] & Direction==1)$ID_2)
    DF2$ID_2_D0[i] <- list(subset(DF,ID_1==unique(DF2$ID_1)[i] & Direction==0)$ID_2)        
  }

【问题讨论】:

    标签: r subset plyr


    【解决方案1】:

    像这样:

    library(reshape2)
    dcast(DF, ID_1 ~ Direction, value.var = "ID_2", list)
    #     ID_1                   0                  1
    # 1 100001 1111, 11111, 111111         1, 11, 111
    # 2 100002           222, 2222              2, 22
    # 3 100003         3333, 33333 3, 33, 333, 333333
    # 4 100004                                  4, 44    
    

    【讨论】:

    • 天哪。对我的完整数据的一小部分进行测试,我的代码(在上面的原始帖子中)被计时以在 20 多个小时内通过完整的数据集(因此,我的帖子)。您的代码在 5 秒内完成。 ——我想我可以区分一个好的程序员和我自己。感谢您为我节省了很多时间!
    • @EconomiCurtis 出于兴趣什么方法最快?我假设是这个,但看起来dcast 无论如何都在使用lapply 和表兄弟,所以我想知道它是否快得多?如果此解决方案适合您,请务必勾选解决方案顶部旁边的绿色箭头,以便将此问题标记为已回答。
    • 对你们来说这是一个愚蠢的问题——但很好奇你能否告诉我(或者,给我一个维基百科文章或 cran 文档的链接)为什么 dcast 比我的快得多环形? --我做了几百万行,dcast 在 17 秒内完成了它(加上一些额外的子集和合并),就像我说的那样,我的方法被计时在一天内完成。 (只是想学习基础理论)
    • 和@SimonO101 -- 在我的数据上(并忽略所有额外需要的额外子集和合并位:)(1)flodel 的 dcast:7.4 秒,(2)Ananda Mahto 的聚合耗时 23 秒同样的任务,(3) Arun 的补充:老实说,我无法运行,抱歉,并且 (4) 我的循环被计时在 190 小时内完成(我在 0.01% 的子集上计时,花了 68 秒) .
    【解决方案2】:

    @flodel 的答案是迄今为止我能想到的最直接的答案,但这里有一个使用 aggregatemerge 的基本 R 选项。它利用aggregate 步骤中的“subset”参数来获取“Direction == 0”和“Direction == 1”时的单独列。

    temp1 <- aggregate(ID_2 ~ ., DF, as.vector, subset = c(Direction == 0))
    temp2 <- aggregate(ID_2 ~ ., DF, as.vector, subset = c(Direction == 1))
    merge(temp1[-2], temp2[-2], by = "ID_1", all = TRUE, suffixes=c("_0", "_1"))
    #     ID_1              ID_2_0             ID_2_1
    # 1 100001 1111, 11111, 111111         1, 11, 111
    # 2 100002           222, 2222              2, 22
    # 3 100003         3333, 33333 3, 33, 333, 333333
    # 4 100004                NULL              4, 44
    

    一种相关的方法(不确定它是否会更快)是使用split 创建子集,lapplyaggregate 在结果列表上,Reduce 促进merge

    Reduce(function(x, y) 
      merge(x, y, by = "ID_1", all = TRUE, suffixes = c("_0", "_1")), 
           lapply(split(DF[1:2], DF$Direction), 
                  function(x) aggregate(ID_2 ~ ID_1, x, as.vector)))
    

    当然,这是使用data.table 的一种方法,您可能需要考虑使用这种方法,因为您已经提到必须工作*超过数百万个唯一的“ID_1”*。您不太可能从这个小示例中看到任何速度优势,但您应该使用实际数据。

    library(data.table)
    DT <- data.table(DF, key = "ID_1")
    DT0 <- DT[Direction == 0, list(D0 = list(ID_2)), by = key(DT)]
    DT1 <- DT[Direction == 1, list(D1 = list(ID_2)), by = key(DT)]
    DT0[DT1]
    #      ID_1                D0              D1
    # 1: 100001 1111,11111,111111        1,11,111
    # 2: 100002          222,2222            2,22
    # 3: 100003        3333,33333 3,33,333,333333
    # 4: 100004                              4,44
    

    更新

    正如@Arun 在 R 公共聊天室中提到的,这是一种简化的 data.table 方法,避免了创建两个单独的对象并将它们合并。

    DT[, list(list(D0 = ID_2[Direction==0]), list(D1 = ID_2[Direction == 1])), by=ID_1]
    

    【讨论】:

    • +1 一组非常好的解决方案。我已收藏此问题,以便查看您的参考解决方案。
    【解决方案3】:

    你当然可以在这里使用 apply 函数。我不确定你是否需要,(即你可以通过 subsetting 变得更快)但我想不出你现在会如何做。你可以像这样实现你想要的:

    # Direction = 1
    d1 <- lapply( unique( DF$ID_1 ) , function(x){ subset( DF , ID_1== x & Direction == 1)$ID_2 } )
    d1 <- sapply( d1 , function(x){ paste0( x , sep = "," , collapse = "" ) } )
    # Direction = 0
    d0 <- lapply( unique( DF$ID_1 ) , function(x){ subset( DF , ID_1== x & Direction == 0)$ID_2 } )
    d0 <- sapply( d0 , function(x){ paste0( x , sep = "," , collapse = "" ) } )
    
    
    # Results dataframe
    resDF <- data.frame(ID_1 = unique(DF$ID_1), d1, d0)
    resDF
                  d1                 d0                  
    [1,] "100001" "1,11,111,"        "1111,11111,111111,"
    [2,] "100002" "2,22,"            "222,2222,"         
    [3,] "100003" "3,33,333,333333," "3333,33333,"       
    [4,] "100004" "4,44,"            "," 
    

    我很想知道这种方式是否/多快。

    【讨论】:

    • 这在小数据集上表现得相当好。我想知道它的扩展性如何?我希望最后一步是 data.frame(ID_1 = unique(DF$ID_1), d1, d0) 而不是 cbind(...)。 +1
    • 按照您的建议编辑!是的,这个解决方案可能不是最好的。事实上,它在弗洛德尔超级优雅的单线车旁边看起来非常邋遢。但是dcastcast 确实在内部使用了很多应用函数系列。我无法估计有多少实际的工作是由dcast 中的apply 函数完成的。
    • 您的代码非常有帮助(但我很快就发现了 flodel 的建议)。谢谢!
    • @SimonO101,我之前忘了提一些事情:我对你的回答的一个批评是,你实际上只是用这种方法结束了一个字符串。其他方法将值保持为listvectors,因此如果需要,可以轻松地对该数据进行进一步的处理。不过,很高兴看到替代方案并获得更多想法!
    猜你喜欢
    • 2015-02-01
    • 2020-12-14
    • 1970-01-01
    • 2014-07-23
    • 2017-08-06
    • 2022-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多