【问题标题】:Combine incomplete dataframes in R into matrix将 R 中的不完整数据框组合成矩阵
【发布时间】:2013-06-15 00:14:46
【问题描述】:

我在 R 中有一个数据框列表,每个数据框都是从包含排名的不同文件加载的。例如,这些文件可能包含不同运动员在不同比赛中的完赛位置。

同一个元素(运动员)可以出现在多个数据框(比赛)中,但没有一个数据框一定会包含所有元素。

我想填充一个排名矩阵,其中运动员作为行,种族作为列。如果没有运动员在特定比赛中的排名,则应为 0。

例如,如果我有:

[[1]]
   name rank
1 Alice    1
2   Bob    2
3 Carla    3
4 Diego    4

[[2]]
   name rank
1 Alice    2
2 Carla    1
3  Eric    3
4 Frank    4
5  Gary    5

[[3]]
   name rank
1   Bob    5
2 Carla    4
3 Diego    3
4  Eric    1
5  Gary    2

我想生成一个矩阵:

      1 2 3
Alice 1 2 0
Bob   2 0 5
Carla 3 1 4
Diego 4 0 3
Eric  0 3 1
Frank 0 4 0
Gary  0 5 2

我正在寻找一种有效的方法来做到这一点:我的数据更像是 200 个数据帧和每个数据帧 10000 个排名元素(总共 15000 个唯一元素),因此最终矩阵大约为 15000x200

【问题讨论】:

    标签: r list matrix dataframe


    【解决方案1】:

    这里有一个更简单的reshape2 解决方案:

    library(reshape2)
    
    dcast(melt(ll, id.vars = 'name'), name ~ L1, fill = 0)
    #   name 1 2 3
    #1 Alice 1 2 0
    #2   Bob 2 0 5
    #3 Carla 3 1 4
    #4 Diego 4 0 3
    #5  Eric 0 3 1
    #6 Frank 0 4 0
    #7  Gary 0 5 2
    

    Arun 的基准测试非常有趣,看起来 data.table 做得很好的是融化部分,reshape2 做得很好的是 dcast,所以这是两全其美的:

    library(reshape2)
    library(data.table)
    
    pp = rbindlist(ll)[, id := rep(seq_along(ll), sapply(ll, nrow))]
    dcast(pp, name ~ id, fill = 0, value.var = 'rank')
    

    使用 Arun 的基准数据:

    names <- tapply(sample(letters, 1e4, replace=TRUE), rep(1:(1e4/5), each=5), paste, collapse="")
    names <- unique(names)
    
    dd_create <- function() {
        nrow <- sample(c(100:500), 1)
        ncol <- 3
        data.frame(name = sample(names, nrow, replace=FALSE), rank = sample(nrow))
    }
    
    ll <- replicate(1e3, dd_create(), simplify = FALSE)
    
    Arun_data.table <- function(ll) {
        pp <- rbindlist(ll)[, id := rep(seq_along(ll), sapply(ll, nrow))]
        setkey(pp, "name", "id")
        pp[CJ(unique(name), 1:1000)][is.na(rank), rank := 0L][, as.list(rank), by = name]
    }
    
    mix_of_both = function(ll) {
        pp = rbindlist(ll)[, id := rep(seq_along(ll), sapply(ll, nrow))]
        dcast(pp, name ~ id, fill = 0, value.var = 'rank')
    }
    
    require(microbenchmark)
    microbenchmark(Arun_data.table(ll), mix_of_both(ll), times = 10)
    # Unit: milliseconds
    #                expr      min        lq    median        uq       max neval
    # Arun_data.table(ll) 2568.333 2586.0079 2626.7704 2832.8076 2911.1314    10
    #     mix_of_both(ll)  615.166  739.9383  766.8994  788.5822  821.0478    10
    

    【讨论】:

    • eddi,非常有趣!我刚刚阅读了它的要点。看起来真的很不错! (我已经为你 +1 了)。我会尽快详细检查并回信。
    【解决方案2】:

    这是使用reshape2 包的解决方案:

    require(reshape2)
    dcast(do.call(rbind, lapply(seq_along(ll), function(ix) 
             transform(ll[[ix]], id = ix))), name ~ id, value.var="rank", fill=0)
    
       name 1 2 3
    1 Alice 1 2 0
    2   Bob 2 0 5
    3 Carla 3 1 4
    4 Diego 4 0 3
    5  Eric 0 3 1
    6 Frank 0 4 0
    7  Gary 0 5 2
    

    其中ll 是您的data.frames 列表。


    或等效:

    dcast(transform(do.call(rbind, ll), id = rep(seq_along(ll), sapply(ll, nrow))), 
        name ~ id, value.var = "rank", fill = 0)
    

    data.table 解决方案:

    require(data.table)
    pp <- rbindlist(ll)[, id := rep(seq_along(ll), sapply(ll, nrow))]
    setkey(pp, "name", "id")
    pp[CJ(unique(name), 1:3)][is.na(rank), rank := 0L][, as.list(rank), by = name]
    
        name V1 V2 V3
    1: Alice  1  2  0
    2:   Bob  2  0  5
    3: Carla  3  1  4
    4: Diego  4  0  3
    5:  Eric  0  3  1
    6: Frank  0  4  0
    7:  Gary  0  5  2
    

    一些基准测试(现在我们已经有了一些答案):

    names <- tapply(sample(letters, 1e4, replace=TRUE), rep(1:(1e4/5), each=5), paste, collapse="")
    names <- unique(names)
    
    dd_create <- function() {
        nrow <- sample(c(100:500), 1)
        ncol <- 3
        data.frame(name = sample(names, nrow, replace=FALSE), rank = sample(nrow))
    }
    
    ll <- replicate(1e3, dd_create(), simplify = FALSE)
    
    require(reshape2)
    require(data.table)
    Arun1_reshape2 <- function(ll) {
        # same as @agstudy's 
        dcast(do.call(rbind, lapply(seq_along(ll), function(ix) 
                 transform(ll[[ix]], id = ix))), name ~ id, value.var="rank", fill=0)
    }
    
    Arun2_reshape2 <- function(ll) {
        dcast(transform(do.call(rbind, ll), id = rep(seq_along(ll), sapply(ll, nrow))), 
            name ~ id, value.var = "rank", fill = 0)
    }
    
    eddi_reshape2 <- function(ll) {
        dcast(melt(ll, id.vars = 'name'), name ~ L1, fill = 0)
    }
    
    Arun_data.table <- function(ll) {
        pp <- rbindlist(ll)[, id := rep(seq_along(ll), sapply(ll, nrow))]
        setkey(pp, "name", "id")
        pp[CJ(unique(name), 1:1000)][is.na(rank), rank := 0L][, as.list(rank), by = name]
    }
    
    merge.all <- function(x, y) {
        merge(x, y, all=TRUE, by="name")
    }
    
    Hong_Ooi <- function(ll) {
        for(i in seq_along(ll))
            names(ll[[i]])[2] <- paste0("rank", i)
        out <- Reduce(merge.all, ll)    
    }
    
    require(microbenchmark)
    microbenchmark( arun1 <- Arun1_reshape2(ll), 
                    arun2 <- Arun2_reshape2(ll), 
                    eddi <- eddi_reshape2(ll), 
                    hong <- Hong_Ooi(ll), 
                    arun.dt <- Arun_data.table(ll), times=10)
    
    Unit: seconds
                               expr       min        lq    median         uq        max neval
        arun1 <- Arun1_reshape2(ll)  9.157160  9.177143  9.366775   9.715767  28.043125    10
        arun2 <- Arun2_reshape2(ll)  8.408356  8.437066  8.494233   9.018796  10.075029    10
          eddi <- eddi_reshape2(ll)  8.056605  8.314110  8.402396   8.474129   9.124581    10
               hong <- Hong_Ooi(ll) 82.457432 82.716930 82.908646 108.413217 321.164598    10
     arun.dt <- Arun_data.table(ll)  2.006474  2.123331  2.212783   2.311619   2.738914    10
    

    【讨论】:

      【解决方案3】:

      似乎是另一个Reduce 用例。

      merge.all <- function(x, y)
      merge(x, y, all=TRUE, by="name")
      
      # to avoid problems with merged name clashes
      for(i in seq_along(ll))
          names(ll[[i]])[2] <- paste0("rank", i)
      
      out <- Reduce(merge.all, ll)
      

      您必须稍微修改您的数据框以避免merge 抱怨名称冲突; for 循环可以与任何用于此目的的方法一样有效。

      任何缺少的比赛都会有 NA。您可以将它们替换为 0 by out[is.na(out)] &lt;- 0;你应该问问自己这是否明智。例如,如果您这样做,那么简单的汇总统计数据(如均值、方差等)将产生误导性结果。如果您想进行更复杂的建模,也是如此。相比之下,大多数 R 建模函数都足够聪明,可以排除 NA。

      【讨论】:

      • 我认为Reduce 在很多列表元素上会非常慢。
      • Hong,知道为什么您的答案以该错误结尾(请参阅我在基准测试中的编辑)?
      • 这是因为合并结果中的列名重复。您需要将每个数据框中的名称rank 更改为不重复的名称,例如rank1rank2 等。
      • 谢谢,基准测试结果现在也包含您的函数。
      【解决方案4】:

      这里的数据,因为 OP 没有给出可重现的例子:

      dput(ll)
      list(structure(list(name = structure(1:4, .Label = c("Alice", 
      "Bob", "Carla", "Diego"), class = "factor"), rank = 1:4), .Names = c("name", 
      "rank"), class = "data.frame", row.names = c("1", "2", "3", "4"
      )), structure(list(name = structure(1:5, .Label = c("Alice", 
      "Carla", "Eric", "Frank", "Gary"), class = "factor"), rank = c(2L, 
      1L, 3L, 4L, 5L)), .Names = c("name", "rank"), class = "data.frame", row.names = c("1", 
      "2", "3", "4", "5")), structure(list(name = structure(1:5, .Label = c("Bob", 
      "Carla", "Diego", "Eric", "Gary"), class = "factor"), rank = c(5L, 
      4L, 3L, 1L, 2L)), .Names = c("name", "rank"), class = "data.frame", row.names = c("1", 
      "2", "3", "4", "5")))
      

      与@Arun 几乎相同的解决方案,但分两步:

      ## add race column
      ll <- lapply(seq_along(ll),function(x){
        ll[[x]]$race <- x
        ll[[x]]
        })
      ## create a long data.frame
      dd <- do.call(rbind,ll)
      ## transform to the wide format
      library(reshape2)
      
      dcast(name~race,data=dd,fill=0,value.var='rank')
      
         name 1 2 3
      1 Alice 1 2 0
      2   Bob 2 0 5
      3 Carla 3 1 4
      4 Diego 4 0 3
      5  Eric 0 3 1
      6 Frank 0 4 0
      7  Gary 0 5 2
      

      【讨论】:

      • @Arun 因为我错过了 value.var ='rank' :) 我编辑了答案并添加了数据。
      猜你喜欢
      • 1970-01-01
      • 2013-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-08
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多