只保留每个因素水平的最小值

我遇到了一些让我感到困扰的问题…希望这里的任何人都能帮助我。

我得到了以下数据框架

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,tapply …尝试了各种各样的东西,但似乎没有任何工作。 对于任何build议,我会非常感激。

使用迪文的解决scheme,可以避免使用大便。

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

这提供了另一个加速,如下所示。 请注意,这也取决于层面的数量。 我注意到,大家经常被遗忘,尽pipe它是R中更强大的function之一。

 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 

一个data.table解决scheme。

 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方法的优雅)。

plyr ,我会使用:

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

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

另一种解决scheme,不需要使用%in%不必要的vector扫描:

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

编辑:这将只留下第一行的情况下,一条领带。

编辑2:大大印象,我做了进一步的改进:

 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 

这是一个解决scheme。

 > 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 

在你的例子中,每个组只挑选一个,但是如果有联系,这个方法会显示所有的联系。 (就像帕克和卢斯特里克所怀疑的那样)

对不起,我的思维能力已经耗尽了,这个丑陋的解决办法就是我近乎凌晨1点才能想出来的。

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

另一种方法是使用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 

这是一个解决scheme

 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 

这是通过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 

v1v1情况下,这会导致每个f组有多行。 如果你想避免这种情况,你可以使用:

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

这样,你只会得到关系的第一行。 您也可以使用ties.method = "random"或其他帮助文件中描述的方法。