reorganizar - sort data frame r




¿Cómo ordenar un marco de datos por varias columnas? (12)

Tus opciones

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

La mayoría de las veces debe usar las soluciones dplyr o data.table , a menos que sea importante no tener dependencias, en cuyo caso use base::order .

Recientemente agregué sort.data.frame a un paquete CRAN, haciéndolo compatible con la clase como se explica aquí: ¿La mejor manera de crear una consistencia 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 eres uno de los autores originales de esta función, contáctame. La discusión sobre 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, la carga del paquete doBy hace que la sort devuelva "Los siguientes objetos están enmascarados desde 'x (posición 17)': b, x, y, z", y al cargar el paquete del 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 medianos:

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 poco de tiempo en cargar el paquete.

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

No se pudo hacer carga Deducer. Necesita la 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 / desconexió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))

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

Dados estos resultados y un peso simple frente a la velocidad, tendría que dar el visto bueno para arrange el paquete plyr . Tiene una sintaxis simple y, sin embargo, es casi tan veloz como los comandos de la base R con sus complicadas maquinaciones. Típicamente brillante trabajo de Hadley Wickham. Mi única queja es que rompe la nomenclatura R estándar donde los objetos se clasifican por sort(object) , pero entiendo por qué Hadley lo hizo de esa manera debido a los problemas tratados en la pregunta vinculada anteriormente.

Quiero ordenar un data.frame por varias columnas. Por ejemplo, con el cuadro de datos 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
    b x y z
1  Hi A 8 1
2 Med D 3 1
3  Hi A 9 1
4 Low C 9 2

Al igual que los clasificadores mecánicos de tarjetas de hace mucho tiempo, primero se ordenan por la clave menos significativa, luego por la siguiente más significativa, etc. No se requiere una 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 significativa. El orden es estable, y todos los vínculos en la clave más significativa ya se han resuelto.

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

Esto puede no ser el más rápido, pero es ciertamente simple y confiable


Aprendí sobre el order con el siguiente ejemplo, que luego me confundió durante mucho 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 marco de datos idéntico utilizando read.table con nombres de columnas ligeramente 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 clasifica en la age la columna en my.data .

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

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

EDITAR: 13 de mayo de 2014

A continuación se muestra una forma generalizada de ordenar un marco de datos por cada columna sin especificar nombres de columna. El código siguiente muestra cómo ordenar de izquierda a derecha o de derecha a izquierda. Esto funciona si cada columna es numérica. No he probado con una columna de caracteres añadida.

Encontré el código de 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 ese puesto ahora. El hilo presente es el primer hit para ordenar un data.frame en R Entonces, pensé que mi versión expandida de ese código original de do.call 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

Aquí hay muchas respuestas excelentes, pero dplyr ofrece la única sintaxis que puedo recordar rápida y fácilmente (y que ahora utilizo 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 del 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

El arreglo () en dplyer es mi opción favorita. Utilice el operador de tubería y pase del aspecto menos importante al más importante.

dd1 <- dd %>%
    arrange(z) %>%
    arrange(desc(x))

En aras de la integridad: también puede utilizar la función sortByCol() del paquete 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

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

Estaba luchando con las soluciones anteriores cuando quería automatizar mi proceso de pedido para n columnas, cuyos nombres de columna podrían ser diferentes cada vez. Encontré una función súper útil del paquete psych para hacer esto de una manera directa:

dfOrder(myDf, columnIndices)

donde columnIndices son índices de una o más columnas, en el orden en que desea ordenarlos. Más información aquí:

Función dfOrder del paquete 'psych'


La data.table paquete R proporciona una ordenación rápida y eficiente de la memoria de data.tables con una sintaxis sencilla (una parte de la cual Matt ha destacado bastante bien en su respuesta ). Ha habido muchas 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, evaluaremos los diferentes métodos mencionados en otras respuestas y luego listaremos 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 reportados son de la ejecución de 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 fue ~ 10 veces más rápida que la de otros métodos ( dplyr ), mientras que consumía la misma cantidad de memoria que dplyr .

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

Características de la tabla de datos:

Velocidad:

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

  • La sintaxis DT[order(...)] está optimizada internamente para usar el orden rápido de data.table también. Puede seguir usando la sintaxis de base R familiar pero acelerar el proceso (y usar menos memoria).

Memoria:

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

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

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

    setorder() reordena data.tables by reference ( en el lugar ), sin hacer copias adicionales. 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 de 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 su lugar tenemos que usar -xtfrm(.) .

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


La respuesta de Dirk es genial. También destaca una diferencia clave en la sintaxis utilizada para la indexación de data.frame sy 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 un código de producción y / o está preocupado por 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 podría causarle 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. A continuación, su jefe le pide que incluya el informe del último trimestre en el informe. lastquarterlyreport tu código, lastquarterlyreport un objeto al lastquarterlyreport en varios lugares y de alguna manera (¿cómo demonios?) lastquarterlyreport con esto:

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

Eso no es lo que quisiste decir, pero no lo viste porque lo hiciste rápido y está ubicado en una página de código similar. El código no se cae (sin advertencia ni error) porque R cree que es lo que querías decir. Esperaría que quienquiera que lea su informe lo encuentre, pero tal vez no lo hagan. Si trabajas mucho con lenguajes de programación, esta situación puede ser muy familiar. Fue un "error tipográfico", dirás. 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 salvarte el cuello algún 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 tienen ninguna.


Otra alternativa, usando el paquete 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

Si SQL le resulta natural, sqldf maneja ORDER BY según lo previsto por Codd.


Supongamos que tiene un data.frame A y quiere clasificarlo usando la columna llamada x orden descendente. Llame a los datos ordenados newdata

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

Si desea un orden ascendente, entonces reemplace "-" con 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 descendente.





r-faq