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


Quiero ordenar un data.frame por varias columnas. Por ejemplo, con data.frame a continuación me gustaría ordenar por columna z (descendiendo) luego por 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
    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


origen


Respuestas:


Puedes usar el order() Funciona directamente sin recurrir a herramientas complementarias. Vea esta respuesta más simple que usa un truco directamente desde la parte superior del documento. example(order) código:

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

Edite unos 2+ años después:  Simplemente se me preguntó cómo hacer esto por índice de columna. La respuesta es simplemente pasar la (s) columna (s) de clasificación deseada (s) al order() función:

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> 

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


1416
2017-08-18 21:51



Tus opciones

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

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


Recientemente agregué sort.data.frame a un paquete CRAN, por lo que es compatible con las clases como se explica 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 puedes usar el arrange() función 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 causa sort para volver "Los siguientes objetos están enmascarados de 'x (posición 17)': b, x, y, z", y de cargar el sobreescritura del paquete Deducer 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))

microbenchmark plot

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


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


382
2017-07-29 10:48



La respuesta de Dirk es genial. También destaca una diferencia clave en la sintaxis utilizada para indexar data.frames y 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 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.tablete 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. Usted revisa su código y agrega un objeto lastquarterlyreport en varios lugares y de alguna manera (¿cómo diablos?) terminas 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á anidado 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 programació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 necesitas 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.


128
2018-05-25 16:25



Aquí hay muchas respuestas excelentes, pero dplyr proporciona la única sintaxis 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)

    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



El paquete R data.table proporciona ambos rápido y memoria eficiente ordenamiento de data.tables con una sintaxis directa (una parte de la cual Matt ha resaltado muy bien en su respuesta) Ha habido bastantes mejoras y también una nueva función setorder() desde entonces. De 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 tabla de datos.

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 son de ejecución 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.tablees DT[order(...)] la sintaxis era ~ 10x más rápido que el más rápido de otros métodos (dplyr), mientras consume la misma cantidad de memoria que dplyr.

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

características de data.table:

Velocidad:

  • tabla de datosEl pedido es extremadamente rápido porque implementa pedido de radix.

  • La sintaxis DT[order(...)] está optimizado internamente para usar tabla de datosTambién está ordenando rápido. Puedes seguir usando la sintaxis básica de la base R pero acelerar el proceso (y usar menos memoria).

Memoria:

  • La mayoría de las veces, no requerimos el original marco de datos o tabla de datos 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. Ser memoria eficiente, tabla de datos por lo tanto, también proporciona una función setorder().

    setorder() reordena data.tables  by reference (en su lugar), sin hacer copias adicionales. Solo usa memoria extra igual al tamaño de una columna.

Otras características:

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

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

  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 tabla de datos, podemos hacer, por ejemplo, dat[order(-x)] o setorder(dat, -x).


69
2018-03-29 15:52



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

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



o puede usar el paquete doBy

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

33
2018-01-19 20:44



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

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

Si quieres orden ascendente entonces reemplaza "-" sin nada. Puedes tener algo como

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

dónde x y z hay algunas columnas en data.frame  A. Esto significa tipo data.frame  A por x descendiendo, y ascendiendo y z descendiendo


31
2018-01-25 13:10



Alternativamente, usando el paquete Deducer

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

24
2017-08-20 19:43