Сохранять только минимальное значение для каждого уровня фактора
У меня возникли проблемы, которые меня иногда беспокоят... надеюсь, кто-нибудь здесь может мне помочь.
Я получил следующий фрейм данных
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... но ничего не работает. По любым предложениям я был бы очень благодарен.
Ответы
Ответ 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
Ответ 2
A 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)))
}
)
Дайте попробовать и посмотрите, вернет ли он то, что вы хотите.
Ответ 4
Другое решение tapply
без ненужного сканирования вектора с помощью %in%
:
df[tapply(1:nrow(df),df$f,function(i) i[which.min(df$v1[i])]),]
EDIT: в случае галстука это оставит только первую строку.
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
Ответ 5
Здесь вы найдете решение,
> 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
В вашем примере он выбирает только одну группу, но если есть связи, этот метод будет показывать их все. (Как подозревал Паркер и Луштрик.)
Ответ 6
Извините, моя сила мышления истощена, и это уродливое решение - это все, что я могу придумать почти через 1 час.
lapply(split(df, df$f), FUN = function(x) {
vec <- which(x[3] == min(x[3]))
return(x[vec, ])
})
Ответ 7
Другим способом является использование 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
Ответ 8
Вот решение с 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
Ответ 9
Это dplyr-путь для фильтрации минимальных значений v1
группами f
:
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"
или другие, как описано в файле справки.