Comment trier une trame de données par plusieurs colonne (s)
Question
Je veux trier une 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
(croissant):
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
La solution
Vous pouvez utiliser le order()
fonction directement sans avoir recours à outils complémentaires - voir cette réponse plus simple qui utilise un droit de tour du haut du code example(order)
:
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 une 2 + ans plus tard: Il est juste demandé comment faire cela par l'indice de colonne. La réponse est de simplement passer la colonne de tri souhaité (s) à la fonction de order()
:
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 faciliter / accès plus direct).
Autres conseils
Vos choix
-
order
debase
-
arrange
dedplyr
-
setorder
etsetorderv
dedata.table
-
arrange
deplyr
-
sort
detaRifx
-
orderBy
dedoBy
-
sortData
deDeducer
La plupart du temps, vous devez utiliser les solutions de dplyr
ou data.table
, à moins d'avoir pas les dépendances est important, dans ce cas, utilisez base::order
.
J'ai récemment ajouté sort.data.frame à un ensemble CRAN, ce qui rend la classe compatible tel que discuté ici: La meilleure façon de créer de la cohérence générique / méthode pour le tri .data.frame
Par conséquent, compte tenu de la dd data.frame, 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, s'il vous plaît me contacter. Discussion à domaininess public est ici: http://chat.stackoverflow.com/transcript/message/1094290#1094290
Vous pouvez également utiliser la fonction arrange()
de plyr
comme Hadley a souligné dans le fil ci-dessus:
library(plyr)
arrange(dd,desc(z),b)
Repères: Notez que j'ai chargé chaque paquet dans une nouvelle session R car il y avait beaucoup de conflits. En chargement particulier le paquet DOBY provoque sort
retourner "L'objet suivant (s) sont masquées à partir de x (position 17) ': b, x, y, z", et le chargement du paquet Deducer remplace sort.data.frame
de Kevin Wright ou le taRifx package.
#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
)
fois 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: 1567
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: 1694
Notez que Doby prend un bon peu de temps pour charger le paquet.
library(Deducer)
microbenchmark(sortData(dd,c("z","b"),increasing= c(FALSE,TRUE)),times=1000)
Impossible de faire la charge Deducer. Besoins 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)
Il ne semble pas être compatible avec microbenchmark en raison de l'attachement / Détacher.
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))
(lignes étendent depuis quartile inférieur au quartile supérieur, dot est la médiane)
Compte tenu de ces résultats et la simplicité de pesée par rapport à la vitesse, je dois donner le feu vert à arrange
dans le paquet plyr
. Il a une syntaxe simple mais est presque aussi rapide que la base R commandes avec leurs machinations alambiquées. En général brillant travail Hadley Wickham. Mon seul reproche avec c'est qu'il casse la nomenclature standard R où trier les objets s'appelés par sort(object)
, mais je comprends pourquoi Hadley a fait de cette façon en raison de problèmes abordés dans la question liée ci-dessus.
La réponse de Dirk est grande. Il met également en évidence une différence clé dans la syntaxe utilisée pour data.frame
s d'indexation et data.table
s:
## 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 petit, mais il peut avoir des conséquences importantes. Surtout si vous écrivez le code de production et / ou sont concernés par exactitude dans votre recherche, il est préférable d'éviter la répétition inutile des noms de variables. data.table
vous aide à le faire.
Voici un exemple de la façon dont la répétition des noms de variables pourrait vous causer des problèmes:
Changeons le contexte de la réponse de Dirk et dire que cela fait partie d'un plus grand projet où il y a beaucoup de noms d'objets et ils sont longues et significatives; au lieu de dd
il est appelé quarterlyreport
. Il devient:
quarterlyreport[with(quarterlyreport,order(-z,b)),]
Ok, très bien. Aucun problème avec ça. Suivant votre patron vous demande d'inclure le rapport du dernier trimestre dans le rapport. Vous passez par votre code, l'ajout d'un lastquarterlyreport
d'objets en divers endroits et en quelque sorte vous vous retrouvez avec ce (comment diable?):
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 vite et il est niché sur une page de code similaire. Le code ne tombe pas (aucun avertissement et aucune erreur) parce que R pense qu'il est ce que vous vouliez dire. Vous souhaiteriez que celui qui lit votre rapport, il voit, mais peut-être ils ne sont pas. Si vous travaillez avec des langages de programmation beaucoup alors cette situation peut être tout familier. C'était une « erreur typographique » vous allez dire. Je vais corriger la vous « faute de frappe » allez dire à votre patron.
data.table
nous sommes préoccupés par les 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ée 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)), ]
il est juste
dd[order(-z, b)]
au lieu de
quarterlyreport[with(lastquarterlyreport,order(-z,b)),]
il est juste
quarterlyreport[order(-z,b)]
Il est une différence très petite, mais il pourrait juste sauver votre cou un jour. Lors de la pesée les différentes réponses à cette question, pensez à compter les répétitions de noms de variables comme l'un de vos critères pour décider. Certaines réponses ont assez peu de répétitions, d'autres ont pas.
Il y a beaucoup d'excellentes réponses, mais dplyr donne la seule syntaxe que je peux me rappeler rapidement et facilement (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
Le package R data.table
fournit à la fois rapide et mémoire efficace commande de data.tables avec une syntaxe simple (dont une partie a Matt mis en évidence tout à fait bien dans sa réponse ). Il y a eu beaucoup d'améliorations et aussi une nouvelle setorder()
fonction depuis. De v1.9.5+
, setorder()
travaille également avec data.frames .
Tout d'abord, nous allons créer un ensemble de données assez grand et comparer les différentes méthodes mentionnées d'autres réponses et la liste alors les caractéristiques de data.table .
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 en cours d'exécution de system.time(...)
sur ces fonctions ci-dessous. Les horaires sont présentés ci-dessous (dans l'ordre le 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
# ------------------------------------------------------------
-
syntaxe
data.table
deDT[order(...)]
était ~ 10x plus vite que le plus rapide des autres méthodes (dplyr
), tout en consommant la même quantité de mémoiredplyr
. -
La
data.table
desetorder()
était ~ 14x plus vite que le plus rapide des autres méthodes (dplyr
), tout en prenant juste 0.4GB mémoire supplémentaire .dat
est maintenant dans l'ordre dont nous avons besoin (comme il est mis à jour par référence).
Caractéristiques data.table:
Vitesse:
-
data.table ordre d ' est extrêmement rapide, car il met en œuvre radix commande .
-
La
DT[order(...)]
de syntaxe est optimisée en interne à utiliser data.table de commande rapide aussi bien. Vous pouvez continuer à utiliser la syntaxe de base de R familière, mais accélérer le processus (et utiliser moins de mémoire).
Mémoire:
-
La plupart du temps, nous ne nécessitent pas l'original data.frame ou data.table après remise en ordre. Autrement dit, nous attribuons habituellement 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 d'origine. Pour être mémoire efficace , data.table donc également une
setorder()
fonction.setorder() réassorts> data.tables
by reference
( en place ), sans faire de copies supplémentaires. Il utilise uniquement la mémoire supplémentaire égale à la taille d'une colonne.
Autres caractéristiques:
-
Il prend en charge
integer
,logical
,numeric
,character
et même typesbit64::integer64
.Notez que
factor
,Date
, etc ..POSIXct
classes sont tous les typesinteger
/numeric
sous avec des attributs supplémentaires et sont donc pris en charge. -
Dans la base R, nous ne pouvons pas utiliser
-
sur un vecteur de caractères pour trier par cette colonne dans l'ordre décroissant. nous devons plutôt utiliser-xtfrm(.)
.Cependant, dans data.table , nous pouvons simplement faire, par exemple,
dat[order(-x)]
ousetorder(dat, -x)
.
ce (très utile) fonction de Kevin Wright , posté dans la section des conseils du wiki R, cela est facilement atteint.
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
ou vous pouvez utiliser package Doby
library(doBy)
dd <- orderBy(~-z+b, data=dd)
Supposons que vous ayez un data.frame
de A
et que vous voulez faire le tri en utilisant la colonne appelée x
ordre décroissant. Appelez le data.frame
de newdata
trié
newdata <- A[order(-A$x),]
Si vous voulez remplacer l'ordre croissant, puis "-"
rien. Vous pouvez avoir quelque chose comme
newdata <- A[order(-A$x, A$y, -A$z),]
où x
et z
quelques colonnes dans data.frame
A
. Cela signifie sorte data.frame
A
par x
descendant, y
ascendant et descendant z
.
En variante, en utilisant le paquet Deducer
library(Deducer)
dd<- sortData(dd,c("z","b"),increasing= c(FALSE,TRUE))
si SQL vient à vous naturellement, poignées sqldf ORDER BY comme Codd prévu.
La réponse de Dirk est bonne, mais si vous avez besoin du genre à vous persister aurez envie d'appliquer de nouveau le genre sur le nom de cette trame de données. En utilisant l'exemple de code:
dd <- dd[with(dd, order(-z, b)), ]
J'ai appris order
avec l'exemple suivant qui m'a alors confondu 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 parce que order
est le tri par le vector Age
, non pas par la colonne nommée Age
dans le data frame data
.
Pour voir cela crée une trame de données identiques en utilisant read.table
avec les noms de colonnes légèrement différentes et sans utiliser un quelconque 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 la ligne ci-dessus pour order
ne fonctionne plus parce qu'il n'y a pas de vecteur nommé age
:
databyage = my.data[order(age),]
La ligne suivante fonctionne parce que les sortes de order
sur la age
de colonne dans my.data
.
databyage = my.data[order(my.data$age),]
Je pensais que cela valait l'affichage étant donné la confusion que j'étais par cet exemple depuis si longtemps. Si ce poste ne semble pas approprié pour le fil, je peux le supprimer.
EDIT: 13 mai 2014
Voici une façon générale de trier une trame de données par chaque colonne sans spécifier les 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 ne l'ai pas essayé avec une colonne de caractères ajouté.
J'ai trouvé le code do.call
un mois ou deux il y a dans un ancien poste sur un autre site, mais seulement après une recherche approfondie et difficile. Je ne suis pas sûr que je pourrais déplacer ce poste maintenant. Le fil est présent le premier coup pour commander un data.frame
dans R
. Donc, je pensais que ma version étoffée de ce code de do.call
d'origine 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
En réponse à un commentaire ajouté dans l'OP pour savoir comment trier programme:
Utilisation dplyr
et data.table
library(dplyr)
library(data.table)
dplyr
Il suffit d'utiliser arrange_
, qui est la version d'évaluation standard pour arrange
.
df1 <- tbl_df(iris)
#using strings or formula
arrange_(df1, c('Petal.Length', 'Petal.Width'))
arrange_(df1, ~Petal.Length, ~Petal.Width)
Source: local data frame [150 x 5]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
(dbl) (dbl) (dbl) (dbl) (fctr)
1 4.6 3.6 1.0 0.2 setosa
2 4.3 3.0 1.1 0.1 setosa
3 5.8 4.0 1.2 0.2 setosa
4 5.0 3.2 1.2 0.2 setosa
5 4.7 3.2 1.3 0.2 setosa
6 5.4 3.9 1.3 0.4 setosa
7 5.5 3.5 1.3 0.2 setosa
8 4.4 3.0 1.3 0.2 setosa
9 5.0 3.5 1.3 0.3 setosa
10 4.5 2.3 1.3 0.3 setosa
.. ... ... ... ... ...
#Or using a variable
sortBy <- c('Petal.Length', 'Petal.Width')
arrange_(df1, .dots = sortBy)
Source: local data frame [150 x 5]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
(dbl) (dbl) (dbl) (dbl) (fctr)
1 4.6 3.6 1.0 0.2 setosa
2 4.3 3.0 1.1 0.1 setosa
3 5.8 4.0 1.2 0.2 setosa
4 5.0 3.2 1.2 0.2 setosa
5 4.7 3.2 1.3 0.2 setosa
6 5.5 3.5 1.3 0.2 setosa
7 4.4 3.0 1.3 0.2 setosa
8 4.4 3.2 1.3 0.2 setosa
9 5.0 3.5 1.3 0.3 setosa
10 4.5 2.3 1.3 0.3 setosa
.. ... ... ... ... ...
#Doing the same operation except sorting Petal.Length in descending order
sortByDesc <- c('desc(Petal.Length)', 'Petal.Width')
arrange_(df1, .dots = sortByDesc)
plus d'infos ici: https://cran.r-project.org/web /packages/dplyr/vignettes/nse.html
Il est préférable d'utiliser la formule car il capture aussi l'environnement pour évaluer une expression dans
data.table
dt1 <- data.table(iris) #not really required, as you can work directly on your data.frame
sortBy <- c('Petal.Length', 'Petal.Width')
sortType <- c(-1, 1)
setorderv(dt1, sortBy, sortType)
dt1
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1: 7.7 2.6 6.9 2.3 virginica
2: 7.7 2.8 6.7 2.0 virginica
3: 7.7 3.8 6.7 2.2 virginica
4: 7.6 3.0 6.6 2.1 virginica
5: 7.9 3.8 6.4 2.0 virginica
---
146: 5.4 3.9 1.3 0.4 setosa
147: 5.8 4.0 1.2 0.2 setosa
148: 5.0 3.2 1.2 0.2 setosa
149: 4.3 3.0 1.1 0.1 setosa
150: 4.6 3.6 1.0 0.2 setosa
L 'ARRANGEMENT () dans dplyer est mon option préférée. Utilisez l'opérateur de conduite et passer du moins important aspect le plus important
dd1 <- dd %>%
arrange(z) %>%
arrange(desc(x))
Par souci d'exhaustivité: vous pouvez également utiliser la fonction sortByCol()
du paquet BBmisc
:
library(BBmisc)
sortByCol(dd, c("z", "b"), asc = c(FALSE, TRUE))
b x y z
4 Low C 9 2
2 Med D 3 1
1 Hi A 8 1
3 Hi A 9 1
Comparaison des performances:
library(microbenchmark)
microbenchmark(sortByCol(dd, c("z", "b"), asc = c(FALSE, TRUE)), times = 100000)
median 202.878
library(plyr)
microbenchmark(arrange(dd,desc(z),b),times=100000)
median 148.758
microbenchmark(dd[with(dd, order(-z, b)), ], times = 100000)
median 115.872
Tout comme les trieuses de cartes mécaniques d'il y a longtemps, Tri le moins clé importante, la prochaine plus importante, etc. Aucune bibliothèque nécessaire, travaille avec un certain nombre de clés et une combinaison de touches ascendante et descendante.
dd <- dd[order(dd$b, decreasing = FALSE),]
Maintenant, nous sommes prêts à faire la clé la plus importante. Le tri est stable, et les liens en ont déjà été résolus la clé la plus importante.
dd <- dd[order(dd$z, decreasing = TRUE),]
Cela peut ne pas être le plus rapide, mais il est certainement simple et fiable
Une autre alternative, en utilisant le package rgr
:
> library(rgr)
> gx.sort.df(dd, ~ -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
Je luttais avec les solutions ci-dessus quand je voulais automatiser mon processus de commande pour n colonnes, dont les noms colonne pourrait être différent à chaque fois. J'ai trouvé une fonction super utile du paquet psych
de le faire d'une manière simple:
dfOrder(myDf, columnIndices)
où columnIndices
sont des indices d'une ou plusieurs colonnes, dans l'ordre dans lequel vous voulez les trier. Plus d'informations ici:
Juste pour être complet, puisque pas beaucoup a été dit sur le tri par nombre de colonnes ... On peut certainement affirmer qu'il est souvent pas souhaitable (parce que l'ordre des colonnes pourrait changer, ouvrant la voie à des erreurs ), mais dans certaines situations spécifiques (lorsque, par exemple, vous avez besoin d'un travail rapide fait et il n'y a pas de risque de colonnes changeantes commandes), il peut être la chose la plus sensée à faire, surtout lorsqu'ils traitent avec un grand nombre de colonnes.
Dans ce cas, do.call()
vient à la rescousse:
ind <- do.call(what = "order", args = iris[,c(5,1,2,3)])
iris[ind, ]
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 14 4.3 3.0 1.1 0.1 setosa
## 9 4.4 2.9 1.4 0.2 setosa
## 39 4.4 3.0 1.3 0.2 setosa
## 43 4.4 3.2 1.3 0.2 setosa
## 42 4.5 2.3 1.3 0.3 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 48 4.6 3.2 1.4 0.2 setosa
## 7 4.6 3.4 1.4 0.3 setosa
## (...)