Gruplandırılmış verilerden ilk ve son satırı seçme


137

Soru

Kullanma dplyr, nasıl bir açıklamada üst ve gruplandırılmış verilerin alt gözlemler / satırları seçilir?

Veri ve Örnek

Bir veri çerçevesi verildi

df <- data.frame(id=c(1,1,1,2,2,2,3,3,3), 
                 stopId=c("a","b","c","a","b","c","a","b","c"), 
                 stopSequence=c(1,2,3,3,1,4,3,1,2))

Her gruptan üst ve alt gözlemleri kullanarak slice, ancak iki ayrı ifade kullanarak alabilirim:

firstStop <- df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  slice(1) %>%
  ungroup

lastStop <- df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  slice(n()) %>%
  ungroup

Bu iki istatistiki hem üst hem de alt gözlemleri seçecek bir şekilde birleştirebilir miyim ?


Yanıtlar:


232

Muhtemelen daha hızlı bir yol var:

df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  filter(row_number()==1 | row_number()==n())

66
rownumber() %in% c(1, n())vektör taramasını iki kez çalıştırma gereğini ortadan
kaldırır

13
@MichaelChirico Bir atladığınızdan şüpheleniyorum _? iefilter(row_number() %in% c(1, n()))
Eric Fail

107

Sadece bütünlük için: Bir sliceindeks vektörü iletebilirsiniz :

df %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))

hangi verir

  id stopId stopSequence
1  1      a            1
2  1      c            3
3  2      b            1
4  2      c            4
5  3      b            1
6  3      a            3

daha hızlı bile olabilir filter- bunu test
etmedim

1
@Tjebo Filtreden farklı olarak, dilim aynı satırı birden çok kez döndürebilir, örneğin mtcars[1, ] %>% slice(c(1, n()))bu anlamda aralarındaki seçim, döndürülmesini istediğiniz şeye bağlıdır. nÇok büyük olmadığı sürece (dilim tercih edilebilir) zamanlamaları yakın olmasını beklenir , ama ya da test değil.
Frank

15

Hayır dplyr, ancak kullanımı çok daha doğrudan data.table:

library(data.table)
setDT(df)
df[ df[order(id, stopSequence), .I[c(1L,.N)], by=id]$V1 ]
#    id stopId stopSequence
# 1:  1      a            1
# 2:  1      c            3
# 3:  2      b            1
# 4:  2      c            4
# 5:  3      b            1
# 6:  3      a            3

Daha ayrıntılı açıklama:

# 1) get row numbers of first/last observations from each group
#    * basically, we sort the table by id/stopSequence, then,
#      grouping by id, name the row numbers of the first/last
#      observations for each id; since this operation produces
#      a data.table
#    * .I is data.table shorthand for the row number
#    * here, to be maximally explicit, I've named the variable V1
#      as row_num to give other readers of my code a clearer
#      understanding of what operation is producing what variable
first_last = df[order(id, stopSequence), .(row_num = .I[c(1L,.N)]), by=id]
idx = first_last$row_num

# 2) extract rows by number
df[idx]

Temel bilgilerin ele alınması için Başlarken wiki'sini kontrol ettiğinizden emin olun.data.table


1
Veya df[ df[order(stopSequence), .I[c(1,.N)], keyby=id]$V1 ]. idİki kez göründüğünü görmek benim için garip.
Frank

setDTArama sırasında tuşları ayarlayabilirsiniz . Yani orderburada bir çağrıya gerek yok.
Artem Klevtsov

1
@ArtemKlevtsov - tuşları her zaman ayarlamak istemeyebilirsiniz.
SymbolixAU

2
Veya df[order(stopSequence), .SD[c(1L,.N)], by = id]. Buraya
JWilliman

@JWilliman, tamamen aynı olmayacak, çünkü yeniden sıralanmayacak id. Bence df[order(stopSequence), .SD[c(1L, .N)], keyby = id]olacağını sonucu yukarıdaki çözümüne az farkla (hile yapmak gerekir keyed
MichaelChirico

8

Gibi bir şey:

library(dplyr)

df <- data.frame(id=c(1,1,1,2,2,2,3,3,3),
                 stopId=c("a","b","c","a","b","c","a","b","c"),
                 stopSequence=c(1,2,3,3,1,4,3,1,2))

first_last <- function(x) {
  bind_rows(slice(x, 1), slice(x, n()))
}

df %>%
  group_by(id) %>%
  arrange(stopSequence) %>%
  do(first_last(.)) %>%
  ungroup

## Source: local data frame [6 x 3]
## 
##   id stopId stopSequence
## 1  1      a            1
## 2  1      c            3
## 3  2      b            1
## 4  2      c            4
## 5  3      b            1
## 6  3      a            3

İle dohemen hemen grup operasyonların herhangi bir sayıda gerçekleştirebilirsiniz ancak @ jeremycg cevabı sadece bu görev için bir yol daha uygundur.


1
Bir işlev yazmayı düşünmemiştim - kesinlikle daha karmaşık bir şey yapmanın iyi bir yolu.
tospig

1
Bu sadece kullanılarak karşılaştırıldığında overcomplicated görünüyor slicegibidf %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))
Frank

4
Değil belirtmiş (ve daha iyi bir cevap olarak jeremycg en işaret de yazı) ancak sahip doolduğunda başkalarına yardımcı olabilir burada örnek sliceolmaz işi (bir grup yani daha karmaşık operasyonlar). Ve yorumunuzu cevap olarak yayınlayacaksınız (en iyisi).
hrbrmstr

6

Belirtilen soruyu biliyorum dplyr. Ancak, diğerleri zaten diğer paketleri kullanarak çözüm gönderdiğinden, diğer paketleri de kullanmaya karar verdim:

Temel paket:

df <- df[with(df, order(id, stopSequence, stopId)), ]
merge(df[!duplicated(df$id), ], 
      df[!duplicated(df$id, fromLast = TRUE), ], 
      all = TRUE)

veri tablosu:

df <-  setDT(df)
df[order(id, stopSequence)][, .SD[c(1,.N)], by=id]

sqldf:

library(sqldf)
min <- sqldf("SELECT id, stopId, min(stopSequence) AS StopSequence
      FROM df GROUP BY id 
      ORDER BY id, StopSequence, stopId")
max <- sqldf("SELECT id, stopId, max(stopSequence) AS StopSequence
      FROM df GROUP BY id 
      ORDER BY id, StopSequence, stopId")
sqldf("SELECT * FROM min
      UNION
      SELECT * FROM max")

Bir sorguda:

sqldf("SELECT * 
        FROM (SELECT id, stopId, min(stopSequence) AS StopSequence
              FROM df GROUP BY id 
              ORDER BY id, StopSequence, stopId)
        UNION
        SELECT *
        FROM (SELECT id, stopId, max(stopSequence) AS StopSequence
              FROM df GROUP BY id 
              ORDER BY id, StopSequence, stopId)")

Çıktı:

  id stopId StopSequence
1  1      a            1
2  1      c            3
3  2      b            1
4  2      c            4
5  3      a            3
6  3      b            1

3

which.minve kullanarak which.max:

library(dplyr, warn.conflicts = F)
df %>% 
  group_by(id) %>% 
  slice(c(which.min(stopSequence), which.max(stopSequence)))

#> # A tibble: 6 x 3
#> # Groups:   id [3]
#>      id stopId stopSequence
#>   <dbl> <fct>         <dbl>
#> 1     1 a                 1
#> 2     1 c                 3
#> 3     2 b                 1
#> 4     2 c                 4
#> 5     3 b                 1
#> 6     3 a                 3

kıyaslama

Mevcut stop yanıtı çok daha hızlıdır, çünkü tüm stopSequence sütununu sıralamak yerine min ve maks değerini gruplara göre buluruz.

# create a 100k times longer data frame
df2 <- bind_rows(replicate(1e5, df, F)) 
bench::mark(
  mm =df2 %>% 
    group_by(id) %>% 
    slice(c(which.min(stopSequence), which.max(stopSequence))),
  jeremy = df2 %>%
    group_by(id) %>%
    arrange(stopSequence) %>%
    filter(row_number()==1 | row_number()==n()))
#> Warning: Some expressions had a GC in every iteration; so filtering is disabled.
#> # A tibble: 2 x 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 mm           22.6ms     27ms     34.9     14.2MB     21.3
#> 2 jeremy      254.3ms    273ms      3.66    58.4MB     11.0

2

Kullanma data.table:

# convert to data.table
setDT(df) 
# order, group, filter
df[order(stopSequence)][, .SD[c(1, .N)], by = id]

   id stopId stopSequence
1:  1      a            1
2:  1      c            3
3:  2      b            1
4:  2      c            4
5:  3      b            1
6:  3      a            3

1

Laponly ve dplyr ifadesi ile başka bir yaklaşım. Aynı ifadeye herhangi bir özet fonksiyonun keyfi sayıda uygulayabiliriz:

lapply(c(first, last), 
       function(x) df %>% group_by(id) %>% summarize_all(funs(x))) %>% 
bind_rows()

Örneğin, maksimum stopSequence değerine sahip satırlarla da ilgilenebilir ve şunları yapabilirsiniz:

lapply(c(first, last, max("stopSequence")), 
       function(x) df %>% group_by(id) %>% summarize_all(funs(x))) %>%
bind_rows()

0

Farklı bir baz R alternatif birinci olurdu ordertarafından idve stopSequence, splitonları dayanan idve her için idbiz sadece ilk ve son endeksi seçin ve bu endeksleri kullanılarak dataframe subsetine.

df[sapply(with(df, split(order(id, stopSequence), id)), function(x) 
                   c(x[1], x[length(x)])), ]


#  id stopId stopSequence
#1  1      a            1
#3  1      c            3
#5  2      b            1
#6  2      c            4
#8  3      b            1
#7  3      a            3

Veya benzeri kullanarak by

df[unlist(with(df, by(order(id, stopSequence), id, function(x) 
                   c(x[1], x[length(x)])))), ]
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.