Question Comment trier un dataframe par plusieurs colonnes?


Je veux trier un data.frame par plusieurs colonnes. Par exemple, avec le data.frame ci-dessous je voudrais trier par colonne z (décroissant) puis par colonne b (Ascendant):

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

1126
2017-08-18 21:33


origine


Réponses:


Vous pouvez utiliser le order() fonctionner directement sans avoir recours à des outils supplémentaires - voir cette réponse plus simple qui utilise un truc du haut de la example(order) code:

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

Modifier quelques années plus tard:  On a juste demandé comment faire ceci par index de colonne. La réponse est simplement de passer la (les) colonne (s) de tri souhaitée (s) au order() fonction:

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> 

plutôt que d'utiliser le nom de la colonne (et with() pour un accès plus facile / plus direct).


1416
2017-08-18 21:51



Vos choix

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

La plupart du temps, vous devriez utiliser le dplyr ou data.table solutions, à moins d'avoir aucune dépendance est important, dans ce cas utiliser base::order.


J'ai récemment ajouté sort.data.frame à un paquet CRAN, ce qui le rend compatible avec la classe comme discuté ici: La meilleure façon de créer une cohérence générique / méthode pour sort.data.frame?

Par conséquent, étant donné le fichier data.frame dd, vous pouvez trier comme suit:

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 )

Si vous êtes l'un des auteurs originaux de cette fonction, contactez-moi. La discussion sur le domaine public est ici: http://chat.stackoverflow.com/transcript/message/1094290#1094290


Vous pouvez également utiliser le arrange() fonction de plyr comme Hadley a souligné dans le fil ci-dessus:

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

Benchmarks: Notez que j'ai chargé chaque paquet dans une nouvelle session R car il y avait beaucoup de conflits. En particulier le chargement du paquet doBy provoque sort retourner "Les objets suivants sont masqués à partir de 'x (position 17)': b, x, y, z", et le chargement du paquet Deducer est écrasé sort.data.frame de Kevin Wright ou du paquet taRifx.

#Load each time
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)

# Reload R between benchmarks
microbenchmark(dd[with(dd, order(-z, b)), ] ,
    dd[order(-dd$z, dd$b),],
    times=1000
)

Les temps médians:

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

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

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

Temps médian: 1 567

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

Temps médian: 862

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

Temps médian: 1,694

Notez que doBy prend beaucoup de temps pour charger le paquet.

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

Impossible de charger Deducer. Nécessite une console JGR.

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

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

Ne semble pas être compatible avec Microbenchmark en raison de l'attachement / détachement.


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

(les lignes s'étendent du quartile inférieur au quartile supérieur, le point est la médiane)


Compte tenu de ces résultats et en pesant la simplicité contre la vitesse, je dois donner le clin d'œil à arrange dans le plyr paquet. Il a une syntaxe simple et est presque aussi rapide que les commandes de base R avec leurs machinations compliquées. Typiquement brillant travail Hadley Wickham. Mon seul reproche est que ça casse la nomenclature R standard où les objets de tri sont appelés par sort(object), mais je comprends pourquoi Hadley l'a fait de cette façon en raison des questions discutées dans la question ci-dessus.


382
2017-07-29 10:48



La réponse de Dirk est géniale. Il met également en évidence une différence clé dans la syntaxe utilisée pour l'indexation data.framele sable data.tables:

## The data.frame way
dd[with(dd, order(-z, b)), ]

## The data.table way: (7 fewer characters, but that's not the important bit)
dd[order(-z, b)]

La différence entre les deux appels est faible, mais cela peut avoir des conséquences importantes. Surtout si vous écrivez du code de production et / ou êtes préoccupé par l'exactitude de vos recherches, il est préférable d'éviter la répétition inutile de noms de variables. data.tablevous aide à faire cela.

Voici un exemple de la façon dont la répétition de noms de variables peut vous causer des problèmes:

Changeons le contexte de la réponse de Dirk, et disons que cela fait partie d'un plus grand projet où il y a beaucoup de noms d'objets et ils sont longs et significatifs; au lieu de dd c'est appelé quarterlyreport. Il devient :

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

OK bien. Aucun problème avec cela. Ensuite, votre patron vous demande d'inclure le rapport du dernier trimestre dans le rapport. Vous parcourez votre code, en ajoutant un objet lastquarterlyreport dans divers endroits et en quelque sorte (comment diable?) vous vous retrouvez avec ceci:

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

Ce n'est pas ce que vous vouliez dire, mais vous ne l'avez pas repéré parce que vous l'avez fait rapidement et qu'il est niché sur une page de code similaire. Le code ne tombe pas (pas d'avertissement et pas d'erreur) car R pense que c'est ce que vous vouliez dire. Vous espérez que celui qui lit votre rapport le remarque, mais peut-être pas. Si vous travaillez beaucoup avec les langages de programmation alors cette situation peut être familière. C'était une "faute de frappe" que vous direz. Je vais corriger la "faute de frappe" que vous allez dire à votre patron.

Dans data.table nous sommes préoccupés par de petits détails comme celui-ci. Nous avons donc fait quelque chose de simple pour éviter de taper des noms de variables deux fois. Quelque chose de très simple. i est évalué dans le cadre de dd déjà, automatiquement. Vous n'avez pas besoin with() du tout.

Au lieu de

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

c'est juste

dd[order(-z, b)]

Et au lieu de

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

c'est juste

quarterlyreport[order(-z,b)]

C'est une très petite différence, mais ça pourrait sauver votre cou un jour. Lorsque vous évaluez les différentes réponses à cette question, envisagez de compter les répétitions de noms de variables parmi vos critères de décision. Certaines réponses ont quelques répétitions, d'autres n'en ont pas.


128
2018-05-25 16:25



Il y a beaucoup d'excellentes réponses ici, mais dplyr donne la seule syntaxe que je peux rapidement et facilement mémoriser (et donc maintenant utiliser très souvent):

library(dplyr)
# sort mtcars by mpg, ascending... use desc(mpg) for descending
arrange(mtcars, mpg)
# sort mtcars first by mpg, then by cyl, then by wt)
arrange(mtcars , mpg, cyl, wt)

Pour le problème de l'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

105
2018-02-18 21:29



Le paquet R data.table fournit à la fois vite et mémoire efficace commande de data.tables avec une syntaxe directe (une partie de laquelle Matt a mis en évidence assez bien dans sa réponse). Il y a eu beaucoup d'améliorations et aussi une nouvelle fonction setorder() depuis. De v1.9.5+, setorder() travaille également avec data.frames.

Tout d'abord, nous allons créer un ensemble de données suffisamment grand et comparer les différentes méthodes mentionnées dans d'autres réponses, puis répertorier les caractéristiques de data.table.

Les données:

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)

Repères

Les horaires indiqués sont de courir system.time(...) sur ces fonctions montrées ci-dessous. Les timings sont tabulés ci-dessous (dans l'ordre du plus lent au plus rapide).

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

# convert to data.table, by reference
setDT(dat)

dat[order(-z, b)]                  ## data.table, base R like syntax
setorder(dat, -z, b)               ## data.table, using setorder()
                                   ## setorder() now also works with data.frames 

# R-session memory usage (BEFORE) = ~2GB (size of 'dat')
# ------------------------------------------------------------
# Package      function    Time (s)  Peak memory   Memory used
# ------------------------------------------------------------
# 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.tablede DT[order(...)] la syntaxe était ~ 10x plus rapide que le plus rapide des autres méthodes (dplyr), tout en consommant la même quantité de mémoire dplyr.

  • data.tablede setorder() était ~ 14x plus rapide que le plus rapide des autres méthodes (dplyr), en prenant seulement 0,4 Go de mémoire supplémentaire. dat est maintenant dans l'ordre dont nous avons besoin (car il est mis à jour par référence).

caractéristiques de data.table:

La vitesse:

  • data.tableLa commande est extrêmement rapide car elle implémente commande de radix.

  • La syntaxe DT[order(...)] est optimisé en interne pour utiliser data.table's commande rapide aussi bien. Vous pouvez continuer à utiliser la syntaxe de base R familière mais accélérer le processus (et utiliser moins de mémoire).

Mémoire:

  • La plupart du temps, nous n'avons pas besoin de l'original trame de données ou data.table après réorganisation. Autrement dit, nous attribuons généralement le résultat au même objet, par exemple:

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

    Le problème est que cela nécessite au moins deux fois (2x) la mémoire de l'objet original. Être mémoire efficace, data.table donc fournit également une fonction setorder().

    setorder() réorganiser data.tables  by reference (en place), sans faire de copies supplémentaires. Il utilise uniquement de la mémoire supplémentaire égale à la taille d'une colonne.

Autres caractéristiques:

  1. Elle supporte integer, logical, numeric, character et même bit64::integer64 les types.

    Notez que factor, Date, POSIXct etc .. classes sont tous integer/numeric types en dessous avec des attributs supplémentaires et sont donc également pris en charge.

  2. En base R, nous ne pouvons pas utiliser - sur un vecteur de caractères à trier par cette colonne dans l'ordre décroissant. Au lieu de cela, nous devons utiliser -xtfrm(.).

    Cependant, dans data.table, on peut juste faire, par exemple, dat[order(-x)] ou setorder(dat, -x).


69
2018-03-29 15:52



Avec cette fonction (très utile) de Kevin Wright, posté dans la section des astuces du wiki R, ceci est facilement réalisable.

sort(dd,by = ~ -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

57
2017-08-18 21:37



ou vous pouvez utiliser le paquet doBy

library(doBy)
dd <- orderBy(~-z+b, data=dd)

33
2018-01-19 20:44



Supposons que vous avez un data.frame  A et vous voulez le trier en utilisant la colonne appelée x Ordre décroissant. Appelez le trié data.frame  newdata

newdata <- A[order(-A$x),]

Si vous voulez un ordre ascendant, remplacez "-" avec rien. Vous pouvez avoir quelque chose comme

newdata <- A[order(-A$x, A$y, -A$z),]

x et z sont quelques colonnes dans data.frame  A. Cela signifie trier data.frame  A par x descendant, y ascendant et z descendant.


31
2018-01-25 13:10



Alternativement, en utilisant le paquet Deducer

library(Deducer)
dd<- sortData(dd,c("z","b"),increasing= c(FALSE,TRUE))

24
2017-08-20 19:43



Si SQL vous vient naturellement, sqldf gère ORDER BY comme Codd destiné.


24
2018-03-08 23:30



J'ai appris que order avec l'exemple suivant qui m'a alors confus pendant longtemps:

set.seed(1234)

ID        = 1:10
Age       = round(rnorm(10, 50, 1))
diag      = c("Depression", "Bipolar")
Diagnosis = sample(diag, 10, replace=TRUE)

data = data.frame(ID, Age, Diagnosis)

databyAge = data[order(Age),]
databyAge

La seule raison pour laquelle cet exemple fonctionne est parce que order est le tri par le vector Age, pas par la colonne nommée Age dans le data frame data.

Pour voir cela créer une trame de données identique en utilisant read.table avec des noms de colonne légèrement différents et sans utiliser l'un des vecteurs ci-dessus:

my.data <- read.table(text = '

  id age  diagnosis
   1  49 Depression
   2  50 Depression
   3  51 Depression
   4  48 Depression
   5  50 Depression
   6  51    Bipolar
   7  49    Bipolar
   8  49    Bipolar
   9  49    Bipolar
  10  49 Depression

', header = TRUE)

La structure de ligne ci-dessus pour order ne fonctionne plus car il n'y a pas de vecteur nommé age:

databyage = my.data[order(age),]

La ligne suivante fonctionne parce que order trie sur la colonne age dans my.data.

databyage = my.data[order(my.data$age),]

Je pensais que cela valait la peine d'être publié étant donné que j'étais si confuse à cause de cet exemple depuis si longtemps. Si ce post n'est pas jugé approprié pour le fil, je peux le supprimer.

EDIT: 13 mai 2014

Voici une façon généralisée de trier une trame de données par chaque colonne sans spécifier de noms de colonnes. Le code ci-dessous montre comment trier de gauche à droite ou de droite à gauche. Cela fonctionne si chaque colonne est numérique. Je n'ai pas essayé avec une colonne de caractères ajoutée.

J'ai trouvé le do.call code un mois ou deux dans un ancien poste sur un site différent, mais seulement après une recherche approfondie et difficile. Je ne suis pas sûr de pouvoir déplacer ce poste maintenant. Le fil présent est le premier coup pour commander un data.frame dans R. Donc, je pensais que ma version élargie de cet original do.call le code pourrait être utile.

set.seed(1234)

v1  <- c(0,0,0,0, 0,0,0,0, 1,1,1,1, 1,1,1,1)
v2  <- c(0,0,0,0, 1,1,1,1, 0,0,0,0, 1,1,1,1)
v3  <- c(0,0,1,1, 0,0,1,1, 0,0,1,1, 0,0,1,1)
v4  <- c(0,1,0,1, 0,1,0,1, 0,1,0,1, 0,1,0,1)

df.1 <- data.frame(v1, v2, v3, v4) 
df.1

rdf.1 <- df.1[sample(nrow(df.1), nrow(df.1), replace = FALSE),]
rdf.1

order.rdf.1 <- rdf.1[do.call(order, as.list(rdf.1)),]
order.rdf.1

order.rdf.2 <- rdf.1[do.call(order, rev(as.list(rdf.1))),]
order.rdf.2

rdf.3 <- data.frame(rdf.1$v2, rdf.1$v4, rdf.1$v3, rdf.1$v1) 
rdf.3

order.rdf.3 <- rdf.1[do.call(order, as.list(rdf.3)),]
order.rdf.3

14
2017-09-02 19:28