Gruba göre en yüksek değerleri elde etmek


93

İşte örnek bir veri çerçevesi:

d <- data.frame(
  x   = runif(90),
  grp = gl(3, 30)
) 

Her bir değeri için dilk 5 değerine sahip satırları içeren alt kümeyi istiyorum .xgrp

Base-R'yi kullanarak yaklaşımım şöyle bir şey olurdu:

ordered <- d[order(d$x, decreasing = TRUE), ]    
splits <- split(ordered, ordered$grp)
heads <- lapply(splits, head)
do.call(rbind, heads)
##              x grp
## 1.19 0.8879631   1
## 1.4  0.8844818   1
## 1.12 0.8596197   1
## 1.26 0.8481809   1
## 1.18 0.8461516   1
## 1.29 0.8317092   1
## 2.31 0.9751049   2
## 2.34 0.9269764   2
## 2.57 0.8964114   2
## 2.58 0.8896466   2
## 2.45 0.8888834   2
## 2.35 0.8706823   2
## 3.74 0.9884852   3
## 3.73 0.9837653   3
## 3.83 0.9375398   3
## 3.64 0.9229036   3
## 3.69 0.8021373   3
## 3.86 0.7418946   3

Kullanarak dplyr, bunun işe yaramasını bekledim:

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  head(n = 5)

ancak yalnızca genel olarak ilk 5 satırı döndürür.

Şunun headiçin takas top_netmek d,.

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  top_n(n = 5)

Doğru alt kümeyi nasıl elde ederim?

Yanıtlar:


126

Gönderen 1.0.0 dplyr , " slice_min()ve slice_max()kafa karıştırıcı devralarak bir değişkenin minimum veya maksimum değerler ile satırları seçin top_n()."

d %>% group_by(grp) %>% slice_max(order_by = x, n = 5)
# # A tibble: 15 x 2
# # Groups:   grp [3]
#     x grp  
# <dbl> <fct>
#  1 0.994 1    
#  2 0.957 1    
#  3 0.955 1    
#  4 0.940 1    
#  5 0.900 1    
#  6 0.963 2    
#  7 0.902 2    
#  8 0.895 2    
#  9 0.858 2    
# 10 0.799 2    
# 11 0.985 3    
# 12 0.893 3    
# 13 0.886 3    
# 14 0.815 3    
# 15 0.812 3

Okul Öncesi dplyr 1.0.0kullanarak top_n:

Gönderen ?top_nhakkında, wtargüman:

Sıralamak için kullanılacak değişken [...] varsayılan olarak tbl'deki son değişkendir ".

Veri kümenizdeki son değişken, sıralamak istediğiniz değişken olmayan "grp" dir ve bu nedenle top_ngirişiminiz "d'nin tamamını döndürür". Bu nedenle, veri kümenizde "x" e göre sıralama yapmak isterseniz, belirtmeniz gerekir wt = x.

d %>%
  group_by(grp) %>%
  top_n(n = 5, wt = x)

Veri:

set.seed(123)
d <- data.frame(
  x = runif(90),
  grp = gl(3, 30))

7
bağları görmezden gelmenin bir yolu var mı?
Matías Guzmán Naranjo


41

Çok kolay data.table...

library(data.table)
setorder(setDT(d), -x)[, head(.SD, 5), keyby = grp]

Veya

setorder(setDT(d), grp, -x)[, head(.SD, 5), by = grp]

Veya (Büyük veri kümesi için daha hızlı olmalı çünkü .SDher grup için çağrı yapmaktan kaçınılmalıdır )

setorder(setDT(d), grp, -x)[, indx := seq_len(.N), by = grp][indx <= 5]

Düzenleme: İşte dplyrkarşılaştırmanın nasıl olduğu data.table(ilgilenen varsa)

set.seed(123)
d <- data.frame(
  x   = runif(1e6),
  grp = sample(1e4, 1e6, TRUE))

library(dplyr)
library(microbenchmark)
library(data.table)
dd <- copy(d)

microbenchmark(
  top_n = {d %>%
             group_by(grp) %>%
             top_n(n = 5, wt = x)},
  dohead = {d %>%
              arrange_(~ desc(x)) %>%
              group_by_(~ grp) %>%
              do(head(., n = 5))},
  slice = {d %>%
             arrange_(~ desc(x)) %>%
             group_by_(~ grp) %>%
             slice(1:5)},
  filter = {d %>% 
              arrange(desc(x)) %>%
              group_by(grp) %>%
              filter(row_number() <= 5L)},
  data.table1 = setorder(setDT(dd), -x)[, head(.SD, 5L), keyby = grp],
  data.table2 = setorder(setDT(dd), grp, -x)[, head(.SD, 5L), grp],
  data.table3 = setorder(setDT(dd), grp, -x)[, indx := seq_len(.N), grp][indx <= 5L],
  times = 10,
  unit = "relative"
)


#        expr        min         lq      mean     median        uq       max neval
#       top_n  24.246401  24.492972 16.300391  24.441351 11.749050  7.644748    10
#      dohead 122.891381 120.329722 77.763843 115.621635 54.996588 34.114738    10
#       slice  27.365711  26.839443 17.714303  26.433924 12.628934  7.899619    10
#      filter  27.755171  27.225461 17.936295  26.363739 12.935709  7.969806    10
# data.table1  13.753046  16.631143 10.775278  16.330942  8.359951  5.077140    10
# data.table2  12.047111  11.944557  7.862302  11.653385  5.509432  3.642733    10
# data.table3   1.000000   1.000000  1.000000   1.000000  1.000000  1.000000    10

Marjinal olarak daha hızlı bir data.tableçözüm eklemek :

set.seed(123L)
d <- data.frame(
    x   = runif(1e8),
    grp = sample(1e4, 1e8, TRUE))
setDT(d)
setorder(d, grp, -x)
dd <- copy(d)

library(microbenchmark)
microbenchmark(
    data.table3 = d[, indx := seq_len(.N), grp][indx <= 5L],
    data.table4 = dd[dd[, .I[seq_len(.N) <= 5L], grp]$V1],
    times = 10L
)

zamanlama çıkışı:

Unit: milliseconds
        expr      min       lq     mean   median        uq      max neval
 data.table3 826.2148 865.6334 950.1380 902.1689 1006.1237 1260.129    10
 data.table4 729.3229 783.7000 859.2084 823.1635  966.8239 1014.397    10

data.tableBiraz daha hızlı olması gereken başka bir yöntem eklemek :dt <- setorder(setDT(dd), grp, -x); dt[dt[, .I[seq_len(.N) <= 5L], grp]$V1]
chinsoon12

@ chinsoon12 benim misafirim. Bu çözümleri tekrar karşılaştırmak için zamanım yok.
David Arenburg

Başka bir data.tableyöntemi daha kolay eklemek :setDT(d)[order(-x),x[1:5],keyby = .(grp)]
Tao Hu

@TaoHu, hemen hemen ilk iki çözüm gibi. Sanmıyorum :yenecekhead
David Arenburg

@DavidArenburg Evet , Sana katılıyorum, bence en büyük fark daha setorderhızlıorder
Tao Hu

34

headBir çağrıya bağlamanız gerekir do. Aşağıdaki kodda, .cari grup (tanımına bakınız temsil ...içinde doyardım sayfasında).

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  do(head(., n = 5))

Akrun'un da bahsettiği gibi slice, bir alternatiftir.

d %>%
  arrange_(~ desc(x)) %>%
  group_by_(~ grp) %>%
  slice(1:5)

Bunu sormamış olsam da, eksiksizlik için olası bir data.tablesürüm (düzeltme için @Arun'a teşekkürler):

setDT(d)[order(-x), head(.SD, 5), by = grp]

1
@akrun Teşekkürler. Bu işlevi bilmiyordum.
Richie Cotton

@DavidArenburg Teşekkürler. Aceleyle bir cevap göndermenin yolu budur. Saçmalamayı kaldırdım.
Richie Cotton

2
Richie, FWIW sadece küçük bir ek gerekir:setDT(d)[order(-x), head(.SD, 5L), by=grp]
Arun

Bu cevap biraz modası geçmiş ama bırakın eğer ikinci bölümü idomatic yoldur ~ve kullanımını arrangeve group_byyerine arrange_vegroup_by_
Moody_Mudskipper

15

R tabanındaki yaklaşımım şöyle olurdu:

ordered <- d[order(d$x, decreasing = TRUE), ]
ordered[ave(d$x, d$grp, FUN = seq_along) <= 5L,]

Ve dplyr kullanarak, yaklaşımı slicemuhtemelen en hızlı olanıdır, ancak hangisini kullanmaktan filterdaha hızlı olacağını da kullanabilirsiniz do(head(., 5)):

d %>% 
  arrange(desc(x)) %>%
  group_by(grp) %>%
  filter(row_number() <= 5L)

dplyr karşılaştırması

set.seed(123)
d <- data.frame(
  x   = runif(1e6),
  grp = sample(1e4, 1e6, TRUE))

library(microbenchmark)

microbenchmark(
  top_n = {d %>%
             group_by(grp) %>%
             top_n(n = 5, wt = x)},
  dohead = {d %>%
              arrange_(~ desc(x)) %>%
              group_by_(~ grp) %>%
              do(head(., n = 5))},
  slice = {d %>%
             arrange_(~ desc(x)) %>%
             group_by_(~ grp) %>%
             slice(1:5)},
  filter = {d %>% 
              arrange(desc(x)) %>%
              group_by(grp) %>%
              filter(row_number() <= 5L)},
  times = 10,
  unit = "relative"
)

Unit: relative
   expr       min        lq    median        uq       max neval
  top_n  1.042735  1.075366  1.082113  1.085072  1.000846    10
 dohead 18.663825 19.342854 19.511495 19.840377 17.433518    10
  slice  1.000000  1.000000  1.000000  1.000000  1.000000    10
 filter  1.048556  1.044113  1.042184  1.180474  1.053378    10

@akrun filterek bir işlev gerektirir, slicesürümünüz ise ...
David Arenburg

1
Buraya neden eklemediğinizi biliyorsunuz data.table;)
David Arenburg

5
Biliyorum ve size söyleyebilirim: çünkü soru özellikle bir dplyr çözümü istiyordu.
talat

1
Sadece şaka yapıyordum ... Hiç aynı şeyi yapmamışsın gibi değil (tam tersi yönde).
David Arenburg

@DavidArenburg, "yasadışı" veya data.table cevap vermek gibi bir şey demiyordum .. Tabii ki bunu yapabilir ve istediğiniz herhangi bir ölçüt sunabilirsiniz :) Btw, bağlantı verdiğiniz soru güzel bir örnek dplyr sözdiziminin data.table'dan çok daha uygun olduğu (biliyorum, öznel!).
talat

1

top_n (n = 1), sıralama değişkeni her grup içinde benzersiz değilse, yine de her grup için birden çok satır döndürecektir . Her grup için kesin olarak bir oluşum seçmek için her satıra benzersiz bir değişken ekleyin:

set.seed(123)
d <- data.frame(
  x   = runif(90),
  grp = gl(3, 30))

d %>%
  mutate(rn = row_number()) %>% 
  group_by(grp) %>%
  top_n(n = 1, wt = rn)

0

data.tableKısa sözdizimini vurgulamak için bir çözüm daha :

setDT(d)
d[order(-x), .SD[1:5], grp]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.