¿Cómo ordenar un dataframe por múltiples columnas?

Quiero ordenar un data.frame por varias columnas. Por ejemplo, con el data.frame a continuación me gustaría ordenar por la columna z (descendente) y luego por la columna b (ascendente):

 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 bxyz 1 Hi A 8 1 2 Med D 3 1 3 Hi A 9 1 4 Low C 9 2 

Puede utilizar la función order() directamente sin recurrir a herramientas adicionales: consulte esta respuesta más sencilla que utiliza un truco directamente desde la parte superior del código de example(order) :

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

Edite unos 2+ años más tarde: simplemente se le preguntó cómo hacerlo por índice de columna. La respuesta es simplemente pasar la (s) columna (s) de clasificación deseada (s) a la función de order() :

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

en lugar de usar el nombre de la columna (y with() para un acceso más fácil / más directo).

Tus opciones

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

La mayoría de las veces debe usar las soluciones dplyr o data.table , a menos que no tenga ninguna dependencia, en cuyo caso use base::order .


Recientemente, agregué sort.data.frame a un paquete CRAN, haciéndolo compatible con las clases como se discute aquí: ¿La mejor forma de crear una coherencia genérica / de método para sort.data.frame?

Por lo tanto, dado el data.frame dd, puede ordenar de la siguiente manera:

 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 usted es uno de los autores originales de esta función, contácteme. La discusión sobre la dominio público está aquí: http://chat.stackoverflow.com/transcript/message/1094290#1094290


También puede usar la función de arrange() de plyr como Hadley señaló en el hilo anterior:

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

Puntos de referencia: tenga en cuenta que cargué cada paquete en una nueva sesión R ya que hubo muchos conflictos. En particular, cargar el paquete doBy hace que la sort regrese "Los siguientes objetos están enmascarados desde 'x (posición 17)': b, x, y, z", y al cargar el paquete sort.data.frame sobrescribe sort.data.frame de Kevin Wright o el paquete 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 ) 

Tiempos medios:

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

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

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

Tiempo medio: 1,567

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

Tiempo medio: 862

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

Tiempo medio: 1,694

Tenga en cuenta que doBy tarda un buen rato en cargar el paquete.

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

No se pudo cargar a Deducer. Necesita consola de JGR.

 esort <- function(x, sortvar, ...) { attach(x) x <- x[with(x,order(sortvar,...)),] return(x) detach(x) } microbenchmark(esort(dd, -z, b),times=1000) 

No parece ser compatible con microbenchmark debido a la conexión / separación.


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

diagrama microbenchmark

(las líneas se extienden desde el cuartil inferior al cuartil superior, el punto es la mediana)


Teniendo en cuenta estos resultados y sopesando la simplicidad frente a la velocidad, tendría que dar el visto bueno para arrange en el paquete plyr . Tiene una syntax simple y, sin embargo, es casi tan rápido como los comandos base R con sus intrincadas maquinaciones. Por lo general, el shiny trabajo de Hadley Wickham. Mi única queja es que rompe la nomenclatura R estándar en la que los objetos de clasificación se llaman por sort(object) , pero entiendo por qué Hadley lo hizo de esa manera debido a problemas discutidos en la pregunta vinculada anteriormente.

La respuesta de Dirk es genial. También destaca una diferencia clave en la syntax utilizada para indexar data.frame y 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 diferencia entre las dos llamadas es pequeña, pero puede tener consecuencias importantes. Especialmente si escribe código de producción y / o le preocupa la corrección en su investigación, es mejor evitar la repetición innecesaria de nombres de variables. data.table te ayuda a hacer esto.

Aquí hay un ejemplo de cómo la repetición de nombres de variables puede meterlo en problemas:

Cambiemos el contexto de la respuesta de Dirk, y digamos que esto es parte de un proyecto más grande donde hay muchos nombres de objetos y son largos y significativos; en lugar de dd se llama quarterlyreport . Se vuelve :

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

Está bien. Nada de malo con eso. Luego, su jefe le pide que incluya el informe del último trimestre en el informe. lastquarterlyreport tu código, lastquarterlyreport un objeto en el lastquarterlyreport en varios lugares y de alguna manera (¿cómo diablos?) lastquarterlyreport con esto:

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

Eso no es lo que querías decir, pero no lo viste porque lo hiciste rápido y está nested en una página de código similar. El código no se cae (no hay advertencia ni error) porque R cree que es lo que usted quiso decir. Esperarías que quien lea tu informe lo vea, pero quizás no lo vean. Si trabaja mucho con lenguajes de progtwigción, esta situación puede ser familiar. Fue un “error” que dirá. Arreglaré el “error tipográfico” que le dirás a tu jefe.

En data.table estamos preocupados por pequeños detalles como este. Así que hemos hecho algo simple para evitar escribir nombres de variables dos veces. Algo muy simple. i se evalúa dentro del marco de dd ya, automáticamente. No es necesario with() en absoluto.

En lugar de

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

es solo

 dd[order(-z, b)] 

Y en lugar de

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

es solo

 quarterlyreport[order(-z,b)] 

Es una diferencia muy pequeña, pero podría salvarle el cuello un día. Al sopesar las diferentes respuestas a esta pregunta, considere contar las repeticiones de nombres de variables como uno de sus criterios para decidir. Algunas respuestas tienen bastantes repeticiones, otras no.

Aquí hay muchas respuestas excelentes, pero dplyr ofrece la única syntax que puedo recordar rápida y fácilmente (y ahora la uso muy a menudo):

 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) 

Para el problema de OP:

 arrange(dd, desc(z), b) bxyz 1 Low C 9 2 2 Med D 3 1 3 Hi A 8 1 4 Hi A 9 1 

El paquete R data.table proporciona un ordenamiento rápido y eficiente de la memoria de data.tables con una syntax sencilla (una parte de la cual Matt ha destacado muy bien en su respuesta ). Ha habido bastantes mejoras y también una nueva función setorder() desde entonces. Desde v1.9.5+ , setorder() también funciona con data.frames .

Primero, crearemos un conjunto de datos lo suficientemente grande y compararemos los diferentes métodos mencionados de otras respuestas y luego enumeraremos las características de data.table .

Datos:

 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) 

Puntos de referencia:

Los tiempos informados provienen de ejecutar system.time(...) en estas funciones que se muestran a continuación. Los tiempos se tabulan a continuación (en el orden de más lento a más rápido).

 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.table DT[order(...)] data.table era ~ 10x más rápida que la más rápida de los otros métodos ( dplyr ), mientras consumía la misma cantidad de memoria que dplyr .

  • data.table de setorder() era ~ 14x más rápido que el más rápido de los otros métodos ( dplyr ), mientras tomaba solo 0.4GB de memoria extra . dat ahora está en el orden que requerimos (ya que se actualiza por referencia).

características de data.table:

Velocidad:

  • El ordenamiento de data.table es extremadamente rápido porque implementa el pedido de radix .

  • La syntax DT[order(...)] se optimiza internamente para usar el ordenamiento rápido de data.table también. Puedes seguir usando la syntax básica de la base R pero acelerar el proceso (y usar menos memoria).

Memoria:

  • La mayoría de las veces, no requerimos el data.frame o data.table original después de reordenar. Es decir, normalmente asignamos el resultado al mismo objeto, por ejemplo:

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

    El problema es que esto requiere al menos el doble (2x) de la memoria del objeto original. Para ser eficiente con la memoria , data.table por lo tanto también proporciona una función setorder() .

    setorder() reordena los data.tables by reference ( in situ ), sin hacer ninguna copia adicional. Solo usa memoria extra igual al tamaño de una columna.

Otras características:

  1. bit64::integer64 tipos integer , logical , numeric , de character e incluso bit64::integer64 .

    Tenga en cuenta que las clases factor , Date , POSIXct etc. son todos tipos integer / numeric debajo con atributos adicionales y, por lo tanto, también son compatibles.

  2. En la base R, no podemos usar - en un vector de caracteres para ordenar por esa columna en orden decreciente. En cambio, tenemos que usar -xtfrm(.) .

    Sin embargo, en data.table , podemos hacer, por ejemplo, dat[order(-x)] or setorder(dat, -x) .

Con esta función (muy útil) de Kevin Wright , publicada en la sección de consejos de la R wiki, esto se logra fácilmente.

 sort(dd,by = ~ -z + b) # bxyz # 4 Low C 9 2 # 2 Med D 3 1 # 1 Hi A 8 1 # 3 Hi A 9 1 

o puede usar el paquete doBy

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

Supongamos que tiene un data.frame A y desea ordenarlo usando una columna llamada x orden descendente. Llamar a los datos ordenados.frame newdata

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

Si quiere una orden ascendente, reemplace "-" sin nada. Puedes tener algo como

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

donde x y z son algunas columnas en data.frame A Esto significa ordenar data.frame A por x descendente, y ascendente z descendiente.

Alternativamente, usando el paquete Deducer

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

si SQL es algo natural para usted, sqldf maneja ORDER BY como pretendía Codd.

Aprendí sobre el order con el siguiente ejemplo que luego me confundió por un largo tiempo:

 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 única razón por la que este ejemplo funciona es porque el order se order por el vector Age , no por la columna denominada Age en los data frame data .

Para ver esto, cree un dataframe idéntico usando read.table con nombres de columna levemente diferentes y sin hacer uso de ninguno de los vectores anteriores:

 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 estructura de línea anterior para el order ya no funciona porque no hay un vector llamado age :

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

La siguiente línea funciona porque el order ordena en la columna age en my.data .

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

Pensé que esto valía la pena publicar dado lo confundido que estaba por este ejemplo por tanto tiempo. Si esta publicación no se considera apropiada para el hilo, puedo eliminarla.

EDITAR: 13 de mayo de 2014

A continuación se muestra una forma generalizada de ordenar un dataframe por cada columna sin especificar los nombres de las columnas. El siguiente código muestra cómo ordenar de izquierda a derecha o de derecha a izquierda. Esto funciona si cada columna es numérica. No lo he intentado con una columna de personaje añadida.

Encontré el código do.call uno o dos meses en una publicación anterior en un sitio diferente, pero solo después de una búsqueda extensa y difícil. No estoy seguro de poder reubicar esa publicación ahora. El hilo actual es el primer golpe para ordenar un data.frame en R Por lo tanto, pensé que mi versión expandida de ese código do.call original podría ser útil.

 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 

La respuesta de Dirk es buena, pero si necesita que el tipo persista, querrá volver a aplicar el género al nombre de ese dataframe. Usando el código de ejemplo:

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

En respuesta a un comentario agregado en el OP sobre cómo ordenar programáticamente:

Usando dplyr y data.table

 library(dplyr) library(data.table) 

dplyr

Simplemente use arrange_ , que es la versión de Evaluación Estándar para 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) 

más información aquí: https://cran.r-project.org/web/packages/dplyr/vignettes/nse.html

Es mejor usar fórmula ya que también captura el entorno para evaluar una expresión en

tabla de datos

 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 

En aras de la exhaustividad: también puede usar la función sortByCol() del paquete BBmisc :

 library(BBmisc) sortByCol(dd, c("z", "b"), asc = c(FALSE, TRUE)) bxyz 4 Low C 9 2 2 Med D 3 1 1 Hi A 8 1 3 Hi A 9 1 

Comparación de rendimiento:

 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 

Al igual que los clasificadores de tarjetas mecánicos de hace mucho tiempo, primero ordena por la clave menos significativa, luego la siguiente más significativa, etc. No se requiere biblioteca, funciona con cualquier número de teclas y cualquier combinación de teclas ascendentes y descendentes.

  dd <- dd[order(dd$b, decreasing = FALSE),] 

Ahora estamos listos para hacer la clave más importante. El género es estable y todos los vínculos en la clave más significativa ya se han resuelto.

 dd <- dd[order(dd$z, decreasing = TRUE),] 

Puede que no sea el más rápido, pero sin duda es simple y confiable

Otra alternativa, usando el paquete rgr :

 > library(rgr) > gx.sort.df(dd, ~ -z+b) bxyz 4 Low C 9 2 2 Med D 3 1 1 Hi A 8 1 3 Hi A 9 1