1504 Stimmen

Sortieren Sie die Zeilen des Datenrahmens nach mehreren Spalten.

Ich möchte ein Datenrahmen nach mehreren Spalten sortieren. Zum Beispiel würde ich gerne mit dem unten stehenden Datenrahmen nach der Spalte 'z' (absteigend) und dann nach der Spalte 'b' (aufsteigend) sortieren:

dd <- data.frame(b = factor(c("Hi", "Med", "Hi", "Low"), 
      levels = c("Low", "Med", "Hi"), ordered = TRUE),
      x = c("A", "D", "A", "C"), y = c(8, 3, 9, 9),
      z = c(1, 1, 1, 2))
dd
    b x y z
1  Hi A 8 1
2 Med D 3 1
3  Hi A 9 1
4 Low C 9 2

1793voto

Dirk Eddelbuettel Punkte 345316

Sie können die order() Funktion direkt verwenden, ohne auf zusätzliche Tools zurückgreifen zu müssen - sehen Sie sich diese einfachere Antwort an, die einen Trick direkt aus dem example(order) Code verwendet:

R> dd[with(dd, order(-z, b)), ]
    b x y z
4 Low C 9 2
2 Med D 3 1
1  Hi A 8 1
3  Hi A 9 1

Bearbeiten Sie dies einige Jahre später: Es wurde gerade gefragt, wie dies nach Spaltenindex durchgeführt werden kann. Die Antwort besteht einfach darin, die gewünschte Sortierspalte(n) an die order() Funktion zu übergeben:

R> dd[order(-dd[,4], dd[,1]), ]
    b x y z
4 Low C 9 2
2 Med D 3 1
1  Hi A 8 1
3  Hi A 9 1
R> 

anstatt den Namen der Spalte zu verwenden (und with() für einfachen/direkten Zugriff).

17 Stimmen

Sollte genauso funktionieren, aber du kannst with nicht verwenden. Versuche M <- matrix(c(1,2,2,2,3,6,4,5), 4, 2, byrow=FALSE, dimnames=list(NULL, c("a","b"))), um eine Matrix M zu erstellen, und verwende dann M[order(M[,"a"],-M[,"b"]),], um sie nach zwei Spalten zu ordnen.

8 Stimmen

Leicht genug: dd[ order(-dd[,4], dd[,1]), ], aber kann with nicht für die Namensbasierte Untermenge verwenden.

0 Stimmen

Warum ist dd[ order(-dd[,4],] nicht gültig oder 'dd[ order(-dd[,4], ]' im Grunde genommen, warum wird dd[,1] benötigt? reicht -dd[,4] nicht aus, wenn Sie nur nach einer Spalte sortieren möchten?

594voto

Ari B. Friedman Punkte 69065

Ihre Auswahl

  • order von base
  • arrange von dplyr
  • setorder und setorderv von data.table
  • arrange von plyr
  • sort von taRifx
  • orderBy von doBy
  • sortData von Deducer

Meistens sollten Sie die Lösungen von dplyr oder data.table verwenden, es sei denn, dass keine Abhängigkeiten wichtig sind, in diesem Fall verwenden Sie base::order.


Ich habe kürzlich sort.data.frame zu einem CRAN-Paket hinzugefügt, um es kompatibel zu machen, wie hier diskutiert wurde: Bester Weg, um allgemeine/methodische Konsistenz für sort.data.frame zu erstellen?

Daher können Sie für das data.frame dd wie folgt sortieren:

dd <- data.frame(b = factor(c("Hi", "Med", "Hi", "Low"), 
      levels = c("Low", "Med", "Hi"), ordered = TRUE),
      x = c("A", "D", "A", "C"), y = c(8, 3, 9, 9),
      z = c(1, 1, 1, 2))
library(taRifx)
sort(dd, f= ~ -z + b )

Wenn Sie einer der ursprünglichen Autoren dieser Funktion sind, kontaktieren Sie mich bitte. Die Diskussion zur Public Domain Init ist hier: https://chat.stackoverflow.com/transcript/message/1094290#1094290


Sie können auch die Funktion arrange() von plyr verwenden, wie Hadley in obigem Thread erwähnt hat:

library(plyr)
arrange(dd,desc(z),b)

Benchmarks: Beachten Sie, dass ich jedes Paket in einer neuen R-Sitzung geladen habe, da es viele Konflikte gab. Insbesondere führt das Laden des doBy-Pakets dazu, dass sort "Die folgenden Objekte sind von 'x (Position 17)' verdeckt: b, x, y, z" zurückgibt, und das Laden des Deducer-Pakets überschreibt sort.data.frame von Kevin Wright oder das taRifx-Paket.

#Jedes Mal laden
dd <- data.frame(b = factor(c("Hi", "Med", "Hi", "Low"), 
      levels = c("Low", "Med", "Hi"), ordered = TRUE),
      x = c("A", "D", "A", "C"), y = c(8, 3, 9, 9),
      z = c(1, 1, 1, 2))
library(microbenchmark)

# R zwischen Benchmarks neu laden
microbenchmark(dd[with(dd, order(-z, b)), ] ,
    dd[order(-dd$z, dd$b),],
    times=1000
)

Medianzeiten:

dd[with(dd, order(-z, b)), ] 778

dd[order(-dd$z, dd$b),] 788

library(taRifx)
microbenchmark(sort(dd, f= ~-z+b ),times=1000)

Medianzeit: 1.567

library(plyr)
microbenchmark(arrange(dd,desc(z),b),times=1000)

Medianzeit: 862

library(doBy)
microbenchmark(orderBy(~-z+b, data=dd),times=1000)

Medianzeit: 1.694

Beachten Sie, dass doBy viel Zeit benötigt, um das Paket zu laden.

library(Deducer)
microbenchmark(sortData(dd,c("z","b"),increasing= c(FALSE,TRUE)),times=1000)

Konnte Deducer nicht laden. Erfordert JGR-Konsole.

esort <- function(x, sortvar, ...) {
attach(x)
x <- x[with(x,order(sortvar,...)),]
return(x)
detach(x)
}

microbenchmark(esort(dd, -z, b),times=1000)

Scheint aufgrund von attach/detach nicht kompatibel mit microbenchmark zu sein.


m <- microbenchmark(
  arrange(dd,desc(z),b),
  sort(dd, f= ~-z+b ),
  dd[with(dd, order(-z, b)), ] ,
  dd[order(-dd$z, dd$b),],
  times=1000
  )

uq <- function(x) { fivenum(x)[4]}  
lq <- function(x) { fivenum(x)[2]}

y_min <- 0 # min(by(m$time,m$expr,lq))
y_max <- max(by(m$time,m$expr,uq)) * 1.05

p <- ggplot(m,aes(x=expr,y=time)) + coord_cartesian(ylim = c( y_min , y_max )) 
p + stat_summary(fun.y=median,fun.ymin = lq, fun.ymax = uq, aes(fill=expr))

microbenchmark plot

(Die Linien reichen vom unteren Quartil bis zum oberen Quartil, der Punkt ist der Median)


Anhand dieser Ergebnisse und der Abwägung von Einfachheit gegen Geschwindigkeit muss ich arrange im plyr-Paket den Vorzug geben. Es hat eine einfache Syntax und ist fast so schnell wie die Basis-R-Befehle mit ihren komplizierten Machenschaften. Typische brillante Arbeit von Hadley Wickham. Mein einziger Kritikpunkt ist, dass es mit der Standard-R-Nomenklatur bricht, in der Sortierobjekte mit sort(object) aufgerufen werden, aber ich verstehe, warum Hadley es auf diese Weise gemacht hat, aufgrund der diskutierten Probleme in der oben verlinkten Frage.

5 Stimmen

Die ggplot2 Microbenchmark-Funktion oben ist jetzt als taRifx::autoplot.microbenchmark verfügbar.

0 Stimmen

@AriB.Friedman mit 'arrange', wie sortieren wir aufsteigend? Ich sehe nie Beispiele, die in aufsteigender Reihenfolge sortiert sind. Ich habe versucht, 'asc' anstelle von 'desc' zu verwenden, und es funktioniert nicht. Danke

4 Stimmen

@AME schau dir an, wie b im Beispiel sortiert ist. Die Standardeinstellung ist die Sortierung in aufsteigender Reihenfolge, daher umschließt du sie einfach nicht in desc. Aufsteigend in beiden: arrange(dd,z,b). Absteigend in beiden: arrange(dd,desc(z),desc(b)).

174voto

Matt Dowle Punkte 57472

Dirks Antwort ist großartig. Es hebt auch einen wichtigen Unterschied in der Syntax hervor, die für die Indizierung von data.frames und data.tables verwendet wird:

## Der data.frame Weg
dd[with(dd, order(-z, b)), ]

## Der data.table Weg: (7 Zeichen weniger, aber das ist nicht das Wichtige)
dd[order(-z, b)]

Der Unterschied zwischen den beiden Aufrufen ist klein, aber er kann wichtige Konsequenzen haben. Besonders, wenn Sie Produktionscode schreiben und/oder auf Korrektheit in Ihrer Forschung achten, ist es am besten, unnötige Wiederholungen von Variablennamen zu vermeiden. data.table hilft Ihnen dabei.

Hier ist ein Beispiel dafür, wie die Wiederholung von Variablennamen Ihnen Probleme bereiten könnte:

Lassen Sie uns den Kontext von Dirks Antwort ändern und sagen, dass dies Teil eines größeren Projekts ist, in dem es viele Objektnamen gibt, die lang und aussagekräftig sind; statt dd heißt es quarterlyreport. Es wird dann :

quarterlyreport[with(quarterlyreport,order(-z,b)),]

Ok, gut. Nichts falsch daran. Als nächstes bittet Sie Ihr Chef, den Bericht des letzten Quartals in den Bericht aufzunehmen. Sie gehen Ihren Code durch, fügen an verschiedenen Stellen ein Objekt lastquarterlyreport hinzu und irgendwie (wie zum Teufel?) landen Sie hier :

quarterlyreport[with(lastquarterlyreport,order(-z,b)),]

Das ist nicht das, was Sie gemeint haben, aber Sie haben es nicht bemerkt, weil Sie schnell waren und es sich auf einer Seite mit ähnlichem Code befindet. Der Code stürzt nicht ab (keine Warnung und kein Fehler), weil R denkt, dass es das ist, was Sie gemeint haben. Sie hoffen, dass derjenige, der Ihren Bericht liest, es bemerkt, aber vielleicht tut er es nicht. Wenn Sie viel mit Programmiersprachen arbeiten, dann ist Ihnen diese Situation vielleicht bekannt. Es war ein "Tippfehler", werden Sie sagen. Ich werde den "Tippfehler" beheben, werden Sie zu Ihrem Chef sagen.

Bei data.table kümmern wir uns um winzige Details wie dieses. Daher haben wir etwas Einfaches gemacht, um das zweimalige Eintippen von Variablennamen zu vermeiden. Etwas sehr Einfaches. i wird bereits automatisch im Rahmen von dd ausgewertet. Du brauchst überhaupt kein with().

Anstelle von

dd[with(dd, order(-z, b)), ]

ist es einfach

dd[order(-z, b)]

Und anstelle von

quarterlyreport[with(lastquarterlyreport,order(-z,b)),]

ist es einfach

quarterlyreport[order(-z,b)]

Es ist ein sehr kleiner Unterschied, aber er könnte Ihnen eines Tages den Kopf retten. Beim Abwägen der verschiedenen Antworten auf diese Frage sollten Sie das Zählen der Wiederholungen von Variablennamen als eines Ihrer Kriterien für die Entscheidung in Betracht ziehen. Einige Antworten enthalten ziemlich viele Wiederholungen, andere haben keine.

11 Stimmen

+1 Das ist ein sehr guter Punkt und geht auf ein Detail der R-Syntax ein, das mich oft gereizt hat. Manchmal verwende ich subset() nur, um zu vermeiden, dass ich mich wiederholt auf das gleiche Objekt innerhalb eines einzelnen Aufrufs beziehen muss.

8 Stimmen

Ich denke, du könntest die neue setorder-Funktion auch hier hinzufügen, da dieser Thread der Ort ist, an dem wir alle Duplikate vom Typ order senden.

154voto

Ben Punkte 40207

Es gibt viele ausgezeichnete Antworten hier, aber dplyr bietet die einzige Syntax, die ich schnell und einfach merken kann (und daher jetzt sehr oft verwende):

library(dplyr)
# sortiere mtcars nach mpg aufsteigend ... verwende desc(mpg) für absteigend
arrange(mtcars, mpg)
# sortiere mtcars zuerst nach mpg, dann nach cyl, dann nach wt)
arrange(mtcars , mpg, cyl, wt)

Für das Problem des OP:

arrange(dd, desc(z),  b)

    b x y z
1 Low C 9 2
2 Med D 3 1
3  Hi A 8 1
4  Hi A 9 1

5 Stimmen

Die akzeptierte Antwort funktioniert nicht, wenn meine Spalten oder Typfaktor sind (oder so etwas) und ich möchte in absteigender Reihenfolge für diese Faktorspalte sortieren, gefolgt von einer Ganzzahlen-Spalte in aufsteigender Reihenfolge. Aber das funktioniert einwandfrei! Danke!

14 Stimmen

Warum "nur"? Ich finde data.table's dd[order(-z, b)] ziemlich einfach zu verwenden und zu merken.

3 Stimmen

Vereinbart, es gibt nicht viel Unterschied zwischen diesen beiden Methoden, und data.table ist auch in vielerlei anderer Hinsicht ein großer Beitrag zu R. Ich vermute, dass es für mich sein könnte, dass in diesem Fall mit einem Satz weniger Klammern (oder einem Klammertyp weniger) die kognitive Belastung um eine kaum wahrnehmbare Menge reduziert wird.

103voto

Arun Punkte 112870

Das R-Paket data.table bietet sowohl eine schnelle als auch eine speichereffiziente Sortierung von Datentabellen mit einer einfachen Syntax (ein Teil davon, den Matt recht schön in seiner Antwort hervorgehoben hat). Seitdem hat es eine Menge Verbesserungen gegeben und auch eine neue Funktion setorder(). Ab v1.9.5+ funktioniert setorder() auch mit Datenrahmen.

Zuerst erstellen wir ein ausreichend großes Datenset und führen Benchmarktests durch, um die verschiedenen in anderen Antworten genannten Methoden zu vergleichen, und listen dann die Features von data.table auf.

Daten:

require(plyr)
require(doBy)
require(data.table)
require(dplyr)
require(taRifx)

set.seed(45L)
dat = data.frame(b = as.factor(sample(c("Hi", "Med", "Low"), 1e8, TRUE)),
                 x = sample(c("A", "D", "C"), 1e8, TRUE),
                 y = sample(100, 1e8, TRUE),
                 z = sample(5, 1e8, TRUE), 
                 stringsAsFactors = FALSE)

Benchmarks:

Die berichteten Zeitangaben stammen aus dem Ausführen von system.time(...) bei den folgenden Funktionen. Die Zeiten werden unten tabellarisch aufgeführt (in der Reihenfolge von langsamsten bis schnellsten).

orderBy( ~ -z + b, data = dat)     ## doBy
plyr::arrange(dat, desc(z), b)     ## plyr
arrange(dat, desc(z), b)           ## dplyr
sort(dat, f = ~ -z + b)            ## taRifx
dat[with(dat, order(-z, b)), ]     ## base R

# Konvertierung in data.table durch Verweis
setDT(dat)

dat[order(-z, b)]                  ## data.table, ähnliche Syntax wie base R
setorder(dat, -z, b)               ## data.table, Verwendung von setorder()
                                   ## setorder() funktioniert jetzt auch mit Datenrahmen 

# R-Sitzungsspeicher (VORHER) = ~2GB (Größe von 'dat')
# ------------------------------------------------------------
# Paket      Funktion    Zeit (s)  Spitzenauslastung des Speichers   Verwendeter Speicher
# ------------------------------------------------------------
# doBy          orderBy      409.7        6.7 GB        4.7 GB
# taRifx           sort      400.8        6.7 GB        4.7 GB
# plyr          arrange      318.8        5.6 GB        3.6 GB 
# base R          order      299.0        5.6 GB        3.6 GB
# dplyr         arrange       62.7        4.2 GB        2.2 GB
# ------------------------------------------------------------
# data.table      order        6.2        4.2 GB        2.2 GB
# data.table   setorder        4.5        2.4 GB        0.4 GB
# ------------------------------------------------------------
  • data.table's DT[order(...)] Syntax war etwa 10x schneller als die schnellste andere Methode (dplyr), und verbrauchte dabei die gleiche Menge an Speicher wie dplyr.

  • data.table's setorder() war ungefähr 14x schneller als die schnellste andere Methode (dplyr), und benötigte dabei nur zusätzliche 0,4GB Speicher. dat ist nun in der gewünschten Reihenfolge (da sie durch Verweis aktualisiert wurde).

data.table Features:

Geschwindigkeit:

  • Die Sortierung mit data.table ist extrem schnell, da sie Radix-Sortierung implementiert.

  • Die Syntax DT[order(...)] ist intern optimiert, um data.table's schnelle Sortierung zu verwenden. Sie können weiterhin die vertraute base R Syntax verwenden, aber den Prozess beschleunigen (und dabei weniger Speicher verwenden).

Speicher:

  • Oft benötigen wir das ursprüngliche Datenrahmen oder die ursprüngliche Datentabelle nach der Neuanordnung nicht mehr. Das heißt, wir weisen das Ergebnis normalerweise demselben Objekt zu, zum Beispiel:

    DF <- DF[order(...)]

    Das Problem dabei ist, dass dies mindestens doppelt so viel Speicher wie das ursprüngliche Objekt erfordert. Um speichereffizient zu sein, bietet data.table daher auch eine Funktion setorder().

    setorder() sortiert Datentabellen by reference (vor Ort), ohne zusätzliche Kopien anzufertigen. Es verwendet nur zusätzlichen Speicher, der der Größe einer Spalte entspricht.

Weitere Features:

  1. Es unterstützt die Typen integer, logical, numeric, character und sogar bit64::integer64.

    Beachten Sie, dass auch Klassen wie factor, Date, POSIXct usw. alles integer/numeric-Typen mit zusätzlichen Attributen sind und daher ebenfalls unterstützt werden.

  2. In base R können wir kein - auf einen Zeichenvektor anwenden, um danach aus dieser Spalte in absteigender Reihenfolge zu sortieren. Stattdessen müssen wir -xtfrm(.) verwenden.

    In data.table können wir jedoch einfach, zum Beispiel, dat[order(-x)] oder setorder(dat, -x) verwenden.

0 Stimmen

Vielen Dank für diese sehr aufschlussreiche Antwort zu data.table. Ich verstehe jedoch nicht, was mit "Spitzenleistung" gemeint ist und wie Sie das berechnet haben. Könnten Sie das bitte erklären? Danke!

0 Stimmen

Ich habe Instruments -> Allokationen verwendet und die Größe von "All heap and allocation VM" gemeldet.

3 Stimmen

@Arun der Link zu den Instrumenten in Ihrem Kommentar ist tot. Möchten Sie ein Update veröffentlichen?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X