Подтвердить что ты не робот

Сохранять только минимальное значение для каждого уровня фактора

У меня возникли проблемы, которые меня иногда беспокоят... надеюсь, кто-нибудь здесь может мне помочь.

Я получил следующий фрейм данных

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... но ничего не работает. По любым предложениям я был бы очень благодарен.

4b9b3361

Ответ 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" или другие, как описано в файле справки.