【问题标题】:Select rows with min value by group按组选择具有最小值的行
【发布时间】:2010-11-15 23:15:10
【问题描述】:

我遇到了一个困扰我一段时间的问题……希望这里的任何人都可以帮助我。

我得到以下数据框

f <- c('a','a','b','b','b','c','d','d','d','d')
v1 <- c(1.3,10,2,10,10,1.1,10,3.1,10,10)
v2 <- c(1:10)
df <- data.frame(f,v1,v2)

f 是一个因数; v1 和 v2 是值。 对于 f 的每个级别,我只想保留一行:在此因子级别中 v1 值最低的那一行。

f   v1  v2
a   1.3 1
b   2   3
c   1.1 6
d   3.1 8

我尝试了各种聚合、ddply、by、tapply……但似乎没有任何效果。对于任何建议,我将非常感谢。

【问题讨论】:

    标签: r dataframe


    【解决方案1】:

    使用 DWin 的解决方案,tapply 可以避免使用 ave

    df[ df$v1 == ave(df$v1, df$f, FUN=min), ]
    

    这提供了另一个加速,如下所示。请注意,这也取决于级别的数量。我给出这个是因为我注意到 ave 经常被遗忘,尽管它是 R 中更强大的功能之一。

    f <- rep(letters[1:20],10000)
    v1 <- rnorm(20*10000)
    v2 <- 1:(20*10000)
    df <- data.frame(f,v1,v2)
    
    > system.time(df[ df$v1 == ave(df$v1, df$f, FUN=min), ])
       user  system elapsed 
       0.05    0.00    0.05 
    
    > system.time(df[ df$v1 %in% tapply(df$v1, df$f, min), ])
       user  system elapsed 
       0.25    0.03    0.29 
    
    > system.time(lapply(split(df, df$f), FUN = function(x) {
    +             vec <- which(x[3] == min(x[3]))
    +             return(x[vec, ])
    +         })
    +  .... [TRUNCATED] 
       user  system elapsed 
       0.56    0.00    0.58 
    
    > system.time(df[tapply(1:nrow(df),df$f,function(i) i[which.min(df$v1[i])]),]
    + )
       user  system elapsed 
       0.17    0.00    0.19 
    
    > system.time( ddply(df, .var = "f", .fun = function(x) {
    +     return(subset(x, v1 %in% min(v1)))
    +     }
    + )
    + )
       user  system elapsed 
       0.28    0.00    0.28 
    

    【讨论】:

    • 这显然是最好的答案!太好了,你也添加了时间。
    • 感谢您的回答和时间安排。我将如何使其适应多个因素?假设我有 f1 和 f2 作为因子,并且我想要每个组合的最小值...我尝试了 c() 和 list(),但没有成功。
    • @donodarazao : 见?ave : ave(x,factor1,factor2,factor3,factor4,...,FUN=min)
    • @hadley:谢谢,我什至没有意识到我纠正了一个棘手的错误。
    【解决方案2】:

    data.table 解决方案。

    library(data.table)
    DT <- as.data.table(df)
    DT[,.SD[which.min(v1)], by = f]
    
    ##   f  v1 v2
    ## 1: a 1.3  1
    ## 2: b 2.0  3
    ## 3: c 1.1  6
    ## 4: d 3.1  8
    

    或者,更有效

    DT[DT[,.I[which.min(v1)],by=f][['V1']]]
    

    一些基准测试

    f <- rep(letters[1:20],100000)
    v1 <- rnorm(20*100000)
    v2 <- 1:(20*100000)
    df <- data.frame(f,v1,v2)
    DT <- as.data.table(df)
    f1<-function(){df2<-df[order(df$f,df$v1),]
                   df2[!duplicated(df2$f),]}
    
    f2<-function(){df2<-df[order(df$v1),]
                   df2[!duplicated(df2$f),]}
    
    f3<-function(){df[ df$v1 == ave(df$v1, df$f, FUN=min), ]}
    
    
    f4 <- function(){DT[,.SD[which.min(v1)], by = f]}
    
    f5 <- function(){DT[DT[,.I[which.min(v1)],by=f][['V1']]]}
    
    library(microbenchmark)
    microbenchmark(f1(),f2(),f3(),f4(), f5(),times = 5)
    # Unit: milliseconds
    # expr       min        lq    median        uq       max neval
    # f1() 3254.6620 3265.4760 3286.5440 3411.4054 3475.4198     5
    # f2() 1630.8572 1639.3472 1651.5422 1721.4670 1738.6684     5
    # f3()  172.2639  174.0448  177.4985  179.9604  184.7365     5
    # f4()  206.1837  209.8161  209.8584  210.4896  210.7893     5
    # f5()  105.5960  106.5006  107.9486  109.7216  111.1286     5
    

    .I 方法是赢家(FR #2330 有望在实施时同样快速地呈现.SD 方法的优雅)。

    【讨论】:

      【解决方案3】:

      对于plyr,我会使用:

      ddply(df, .var = "f", .fun = function(x) {
          return(subset(x, v1 %in% min(v1)))
          }
      )
      

      试试看它是否返回你想要的。

      【讨论】:

      • 或更简单的:ddply(df, "f", subset, v1 == min(v1))
      • 嗯,解决方案很漂亮,但是有很多级别,每个级别只有很少的值非常慢......
      【解决方案4】:

      另一个tapply 解决方案,无需使用%in% 对矢量进行不必要的扫描:

      df[tapply(1:nrow(df),df$f,function(i) i[which.min(df$v1[i])]),]
      

      编辑:如果出现平局,这将只留下第一行。

      EDIT2:对ave 印象深刻,我做了额外的改进:

      df[sapply(split(1:nrow(df),df$f),function(x) x[which.min(df$v1[x])]),]
      

      在我的机器上(使用 Joris 的基准数据):

      > system.time(df[ df$v1 == ave(df$v1, df$f, FUN=min), ])
         user  system elapsed
        0.022   0.000   0.021
      > system.time(df[sapply(split(1:nrow(df),df$f),function(x) x[which.min(df$v1[x])]),])
         user  system elapsed
        0.006   0.000   0.007
      

      【讨论】:

      • 嗯,mbq,我投了你的票。就像马特和我一样,我必须通过你的工作来“看到”它的内部运作。
      • @DWin 很抱歉这 %in% 的评论,我可能倾向于高估效率 :|我同意所有解决方案都非常复杂。 by 解决方案在 IMO 上可读性很强,但结果很糟糕(-;
      【解决方案5】:

      这是按 f 组过滤最小 v1 值的 dplyr 方式:

      require(dplyr)
      df %>%
        group_by(f) %>%
        filter(v1 == min(v1))
      
      #Source: local data frame [4 x 3]
      #Groups: f
      #
      #  f  v1 v2
      #1 a 1.3  1
      #2 b 2.0  3
      #3 c 1.1  6
      #4 d 3.1  8
      

      如果v1 中存在关联,这将导致f 的每组多行。如果你想避免这种情况,你可以使用:

      df %>% 
        group_by(f) %>% 
        filter(rank(v1, ties.method= "first") == 1)
      

      这样,您只会在出现平局的情况下获得第一行。您也可以使用ties.method = "random" 或帮助文件中描述的其他方式。

      【讨论】:

        【解决方案6】:

        这是一个tapply解决方案;

        > df[ df$v1 %in% tapply(df$v1, df$f, min), ]
        
          f  v1 v2
        1 a 1.3  1
        3 b 2.0  3
        6 c 1.1  6
        8 d 3.1  8
        

        在您的示例中,它只为每组挑选一个,但如果有联系,此方法将全部显示。 (我怀疑 Parker 和 Luštrik 也一样。)

        【讨论】:

        • 关于领带的优点。不错的功能,我读了几遍,看了一眼 ?tapply 就知道发生了什么。
        • 哇,太棒了! Matt 的 ddply 解决方案有效,但在我的真实数据框(约 10,000 行)中花了大约 2 分钟。此解决方案给出了相同的结果,但耗时不到 1 秒。非常优雅,谢谢!
        • 如果 ddply 在 10,000 次观察中花费 2 分钟,就会出现问题。另外,这种方法并不总是能返回正确的结果——想想 2 是一组的最小值而另一组的第二低的情况。很幸运,它适用于这个例子。
        • 可能是因为 10,000 个观测值中有 7,700 个因子水平?我用你的 ddply-solution 再次尝试了它,它真的需要很长时间......
        【解决方案7】:

        很抱歉,我的思维能力已经耗尽,我在凌晨 1 点左右只能想到这个丑陋的解决方案。

        lapply(split(df, df$f), FUN = function(x) {
                    vec <- which(x[3] == min(x[3]))
                    return(x[vec, ])
                })
        

        【讨论】:

          【解决方案8】:

          另一种方法是使用order!duplicated,但你只会在领带上获得第一个。

          df2 <- df[order(df$f,df$v1),]
          df2[!duplicated(df2$f),]
          
            f  v1 v2
          1 a 1.3  1
          3 b 2.0  3
          6 c 1.1  6
          8 d 3.1  8
          

          时间安排

          f1<-function(){df2<-df[order(df$f,df$v1),]
          df2[!duplicated(df2$f),]}
          
          f2<-function(){df2<-df[order(df$v1),]
          df2[!duplicated(df2$f),]}
          
          f3<-function(){df[ df$v1 == ave(df$v1, df$f, FUN=min), ]}
          
          library(rbenchmark)
          > benchmark(f1(),f2(),f3())
            test replications elapsed relative user.self sys.self user.child sys.child
          1 f1()          100   38.16 7.040590     36.66     1.48         NA        NA
          2 f2()          100   20.54 3.789668     19.30     1.23         NA        NA
          3 f3()          100    5.42 1.000000      4.96     0.46         NA        NA
          

          【讨论】:

          • 订购v1 就足够了。不错的解决方案,时间呢?
          • @Marek 谢谢,没想到真的需要在f 上订购。这似乎加快了大约 2 倍,但它仍然比 Joris Meys 的ave 解决方案慢一点。
          【解决方案9】:

          这是by的解决方案

          do.call(rbind, unname(by(df, df$f, function(x) x[x$v1 == min(x$v1),])))
          ##   f  v1 v2
          ## 1 a 1.3  1
          ## 3 b 2.0  3
          ## 6 c 1.1  6
          ## 8 d 3.1  8
          

          【讨论】:

            【解决方案10】:

            使用 tidyverse

            df %>%
              arrange(v1) %>% # You can also do arrange(f, v1)
              distinct(f, .keep_all = TRUE)
            

            我也喜欢@talat 之前的回答

            df %>%
              group_by(f) %>%
              filter(v1 == min(v1))
            

            但第一个避免分组和取消分组。

            【讨论】:

            • 我个人认为它看起来与@talat的答案非常相似。
            猜你喜欢
            • 2021-07-30
            • 2012-09-01
            • 2016-03-18
            • 1970-01-01
            • 2017-12-17
            • 2023-03-08
            • 2012-07-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多