Wählen Sie die erste und letzte Zeile aus den gruppierten Daten aus


137

Frage

Verwendung dplyr, wie wähle ich die obere und untere Beobachtungen / Reihen gruppierten Daten in einer Aussage?

Daten & Beispiel

Gegeben ein Datenrahmen

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))

Ich kann die oberen und unteren Beobachtungen von jeder Gruppe erhalten slice, indem ich zwei separate Aussagen verwende:

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

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

Kann ich diese beiden Statmenets zu einem kombinieren, das sowohl obere als auch untere Beobachtungen auswählt ?


Antworten:


232

Es gibt wahrscheinlich einen schnelleren Weg:

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

66
rownumber() %in% c(1, n())würde die Notwendigkeit vermeiden, Vektorscan zweimal
auszuführen

13
@ MichaelChirico Ich vermute du hast ein weggelassen _? dhfilter(row_number() %in% c(1, n()))
Eric Fail

106

Der Vollständigkeit halber: Sie können sliceeinen Indexvektor übergeben:

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

was gibt

  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

könnte sogar schneller sein als filter - habe dies nicht getestet, aber siehe hier
Tjebo

1
@Tjebo Im Gegensatz zum Filter kann Slice dieselbe Zeile mehrmals zurückgeben. mtcars[1, ] %>% slice(c(1, n()))In diesem Sinne hängt die Auswahl zwischen ihnen davon ab, was zurückgegeben werden soll. Ich würde erwarten, dass die Timings nahe beieinander liegen, es nsei denn, sie sind sehr groß (wo Slice bevorzugt wird), aber ich habe sie auch nicht getestet.
Frank

15

Nicht dplyr, aber es ist viel direkter mit 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

Detailliertere Erklärung:

# 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]

Achten Sie darauf , die auschecken Erste Schritte die Wiki für das Erhalten data.tableGrundlagen abgedeckt


1
Oder df[ df[order(stopSequence), .I[c(1,.N)], keyby=id]$V1 ]. idZweimal erscheinen zu sehen , ist komisch für mich.
Frank

Sie können im setDTAnruf Schlüssel festlegen . Also ein orderAnruf hier nicht nötig.
Artem Klevtsov

1
@ArtemKlevtsov - Möglicherweise möchten Sie die Schlüssel jedoch nicht immer festlegen.
SymbolixAU

2
Oder df[order(stopSequence), .SD[c(1L,.N)], by = id]. Siehe hier
JWilliman

@JWilliman das wird nicht unbedingt genau das gleiche sein, da es nicht nachbestellt wird id. Ich denke , df[order(stopSequence), .SD[c(1L, .N)], keyby = id]soll über dem Ergebnis den Trick (mit dem kleinen Unterschied zu der Lösung tun wird keyed
MichaelChirico

8

Etwas wie:

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

Mit können doSie so ziemlich eine beliebige Anzahl von Operationen an der Gruppe ausführen, aber die Antwort von @ jeremycg ist viel besser für genau diese Aufgabe geeignet.


1
Ich hatte nicht daran gedacht, eine Funktion zu schreiben - sicherlich eine gute Möglichkeit, etwas Komplexeres zu tun.
tospig

1
Dies scheint zu kompliziert im Vergleich zu nur verwenden slice, wiedf %>% arrange(stopSequence) %>% group_by(id) %>% slice(c(1,n()))
Frank

4
Nicht anderer Meinung zu sein (und ich habe auf Jeremyyg's als bessere Antwort in der Post hingewiesen ), aber ein doBeispiel hier zu haben, könnte anderen helfen, wenn slicees nicht funktioniert (dh komplexere Operationen in einer Gruppe). Und du solltest deinen Kommentar als Antwort posten (es ist der beste).
hrbrmstr

6

Ich kenne die angegebene Frage dplyr . Da andere bereits Lösungen mit anderen Paketen veröffentlicht haben, habe ich mich entschlossen, auch andere Pakete zu verwenden:

Basispaket:

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

Datentabelle:

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")

In einer Abfrage:

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)")

Ausgabe:

  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

mit which.minund 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

Benchmark

Es ist auch viel schneller als die aktuell akzeptierte Antwort, da wir den Min- und Max-Wert nach Gruppe finden, anstatt die gesamte stopSequence-Spalte zu sortieren.

# 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

Verwenden von 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

Ein anderer Ansatz mit lapply und einer dplyr-Anweisung. Wir können eine beliebige Anzahl beliebiger Zusammenfassungsfunktionen auf dieselbe Anweisung anwenden:

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

Sie könnten beispielsweise auch an Zeilen mit dem maximalen Wert stopSequence interessiert sein und Folgendes tun:

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

0

Eine andere Basis-R-Alternative wäre, zuerst ordernach idund stopSequence, splitbasierend auf idund für jeden id, wählen wir nur den ersten und letzten Index aus und unterteilen den Datenrahmen unter Verwendung dieser Indizes.

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

Oder ähnlich mit by

df[unlist(with(df, by(order(id, stopSequence), id, function(x) 
                   c(x[1], x[length(x)])))), ]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.