Como ordenar um dataframe por várias colunas?

Eu quero classificar um data.frame por várias colunas. Por exemplo, com o data.frame abaixo eu gostaria de classificar pela coluna z (descendente) e depois pela coluna 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 

Você pode usar a function order() diretamente sem recorrer a ferramentas adicionais – veja esta resposta mais simples que usa um truque logo no topo do 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 mais de 2 anos depois: Foi perguntado como fazer isso pelo índice da coluna. A resposta é simplesmente passar a (s) coluna (s) de ordenação desejada (s) para a function 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> 

em vez de usar o nome da coluna (e with() para access mais fácil / mais direto).

Suas escolhas

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

Na maioria das vezes você deve usar as soluções data.table ou data.table , a menos que tenha dependencies não-importantes, caso em que use base::order .


Eu adicionei recentemente sort.data.frame a um pacote CRAN, tornando-o compatível com as classs, conforme discutido aqui: Melhor maneira de criar consistência genérica / método para sort.data.frame?

Portanto, dado o data.frame dd, você pode classificar da seguinte maneira:

 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 ) 

Se você é um dos autores originais desta function, entre em contato comigo. Discussão quanto ao domínio público está aqui: http://chat.stackoverflow.com/transcript/message/1094290#1094290


Você também pode usar a function plyr arrange() de plyr como Hadley apontou no tópico acima:

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

Benchmarks: Note que eu carreguei cada pacote em uma nova session R, pois havia muitos conflitos. Em particular, carregar o pacote doBy faz com que o sort retorne "Os seguintes objects são mascarados de 'x (posição 17)': b, x, y, z", e o carregamento do pacote sort.data.frame sobrescreve sort.data.frame de Kevin Wright ou o pacote 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 ) 

Tempos médios:

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

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

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

Tempo médio: 1,567

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

Tempo médio: 862

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

Tempo médio: 1.694

Note que doBy leva um bom tempo para carregar o pacote.

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

Não foi possível fazer a carga do Deducer. Precisa de consola JGR.

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

Não parece ser compatível com o microbenchmark devido ao attach / detach.


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

gráfico de microbenchmark

(as linhas se estendem do quartil inferior ao quartil superior, o ponto é a mediana)


Tendo em conta estes resultados e pesando simplicidade versus velocidade, eu teria que dar o sinal para arrange o pacote plyr . Ele tem uma syntax simples e ainda é quase tão rápido quanto a base R comanda com suas maquinações complicadas. Tipicamente shiny trabalho Hadley Wickham. Minha única queixa é que ele quebra a nomenclatura R padrão onde os objects de ordenação são chamados por sort(object) , mas eu entendo porque Hadley fez dessa forma devido a questões discutidas na questão acima.

A resposta de Dirk é ótima. Ele também destaca uma diferença fundamental na syntax usada para indexar data.frame e 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)] 

A diferença entre as duas chamadas é pequena, mas pode ter consequências importantes. Especialmente se você escrever código de produção e / ou estiver preocupado com a correção em sua pesquisa, é melhor evitar a repetição desnecessária de nomes de variables. data.table ajuda você a fazer isso.

Veja um exemplo de como a repetição de nomes de variables ​​pode causar problemas:

Vamos mudar o contexto da resposta de Dirk e dizer que isso faz parte de um projeto maior, onde há muitos nomes de objects e são longos e significativos; em vez de dd é chamado de quarterlyreport . Se torna :

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

Ok, tudo bem. Nada de errado com isso. Em seguida, seu chefe pede que você inclua o relatório do último trimestre no relatório. Você passa pelo seu código, adicionando um object lastquarterlyreport em vários lugares e de alguma forma (como na terra?) Você acaba com isso:

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

Não foi isso que você quis dizer, mas você não percebeu porque fez isso rápido e está nested em uma página de código semelhante. O código não cai (sem aviso e sem erro) porque R pensa que é o que você quis dizer. Você esperaria que quem lê o seu relatório perceba, mas talvez não. Se você trabalha muito com linguagens de programação, então esta situação pode ser familiar. Foi um “erro de digitação” que você dirá. Vou consertar o “erro” que você dirá ao seu chefe.

Na data.table estamos preocupados com pequenos detalhes como este. Então fizemos algo simples para evitar digitar nomes de variables ​​duas vezes. Algo muito simples. i é avaliado dentro do quadro de dd já, automaticamente. Você não precisa with() em tudo.

Ao invés de

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

é apenas

 dd[order(-z, b)] 

E em vez de

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

é apenas

 quarterlyreport[order(-z,b)] 

É uma diferença muito pequena, mas pode salvar seu pescoço um dia. Ao ponderar as diferentes respostas a essa pergunta, considere contar as repetições de nomes de variables ​​como um dos critérios para decidir. Algumas respostas têm algumas repetições, outras não.

Há muitas respostas excelentes aqui, mas o dplyr fornece a única syntax que posso lembrar com rapidez e facilidade (e agora uso com muita frequência):

 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 o problema do 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 

O pacote de dados data.table fornece data.table rápida e eficiente de memory de dados.tabelas com uma syntax simples (uma parte da qual Matt destacou muito bem em sua resposta ). Houve muitas melhorias e também uma nova function setorder() desde então. A partir de v1.9.5+ , setorder() também funciona com data.frames .

Primeiro, vamos criar um dataset suficientemente grande e comparar os diferentes methods mencionados em outras respostas e, em seguida, listar os resources do data.table .

Dados:

 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) 

Benchmarks:

Os tempos relatados são da execução de system.time(...) nessas funções mostradas abaixo. Os tempos estão tabulados abaixo (na ordem do mais lento ao mais 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 foi ~ 10x mais rápida que o mais rápido de outros methods ( dplyr ), enquanto consumia a mesma quantidade de memory que dplyr .

  • O setorder() foi ~ 14x mais rápido que o mais rápido dos outros methods ( dplyr ), enquanto se obtém apenas 0.4GB de memory extra . dat está agora na ordem que precisamos (como é atualizado por referência).

resources do data.table:

Rapidez:

  • A ordenação do data.table é extremamente rápida porque implementa a ordenação do radix .

  • A syntax DT[order(...)] é otimizada internamente para usar o fast ordering do data.table . Você pode continuar usando a syntax familiar da base R, mas acelerar o processo (e usar menos memory).

Memória:

  • Na maioria das vezes, não exigimos o data.frame ou data.table original após a reordenação. Ou seja, geralmente atribuímos o resultado ao mesmo object, por exemplo:

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

    A questão é que isso requer pelo menos duas vezes (2x) a memory do object original. Para ser eficiente na memory , o data.table também fornece uma function setorder() .

    setorder() reordena data.tables by reference ( in-place ), sem fazer cópias adicionais. Ele usa apenas memory extra igual ao tamanho de uma coluna.

Outras características:

  1. Suporta tipos integer , logical , numeric , character e até bit64::integer64 .

    Observe que as classs factor , Date , POSIXct etc. são todas integer / numeric com atributos adicionais e, portanto, são suportadas também.

  2. Na base R, não podemos usar - em um vetor de caracteres para classificar por essa coluna em ordem decrescente. Em vez disso, temos que usar -xtfrm(.) .

    No entanto, em data.table , podemos apenas fazer, por exemplo, dat[order(-x)] ou setorder(dat, -x) .

Com essa function (muito útil) de Kevin Wright , postada na seção de dicas do wiki R, isso é facilmente alcançado.

 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 

ou você pode usar o pacote doBy

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

Suponha que você tenha um data.frame A e queira classificá-lo usando a coluna chamada x ordem descendente. Ligue para o dado classificado data.frame newdata

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

Se você quiser ordem crescente, substitua "-" por nada. Você pode ter algo como

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

onde x e z são algumas colunas em data.frame . Isso significa classificar data.frame A por x descendente, y crescente e z descendente.

Como alternativa, usando o pacote Deducer

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

se o SQL vier naturalmente para você, o sqldf manipula ORDER BY como o Codd pretendia.

Eu aprendi sobre a order com o seguinte exemplo, o que me confundiu por um longo tempo:

 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 

A única razão pela qual esse exemplo funciona é porque a order está sendo classificada pelo vector Age , não pela coluna chamada Age nos data frame data .

Para ver isso, crie um quadro de dados idêntico usando read.table com nomes de colunas ligeiramente diferentes e sem fazer uso de nenhum dos vetores acima:

 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) 

A estrutura de linha acima para order não funciona mais porque não há um vetor chamado age :

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

A linha a seguir funciona porque a order classifica a age da coluna em my.data .

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

Eu achei que valeria a pena postar, considerando o quanto fiquei confuso com esse exemplo por tanto tempo. Se esta postagem não for considerada apropriada para o segmento, posso removê-la.

EDIT: 13 de maio de 2014

Abaixo está uma maneira generalizada de classificar um quadro de dados por cada coluna, sem especificar os nomes das colunas. O código abaixo mostra como classificar da esquerda para a direita ou da direita para a esquerda. Isso funciona se todas as colunas forem numéricas. Eu não tentei com uma coluna de caracteres adicionada.

Eu encontrei o código do código de do.call um mês ou dois atrás em um post antigo em um site diferente, mas somente após uma pesquisa extensa e difícil. Não tenho certeza se poderia realocar esse post agora. O thread atual é o primeiro hit para solicitar um data.frame em R Então, eu pensei que minha versão expandida daquele código do.call original poderia 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 

A resposta de Dirk é boa, mas se você precisar que a sorting persista, deverá aplicar a sorting novamente no nome desse quadro de dados. Usando o código de exemplo:

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

Em resposta a um comentário adicionado ao OP sobre como classificar programaticamente:

Usando dplyr e data.table

 library(dplyr) library(data.table) 

dplyr

Basta usar o arrange_ , que é a versão de avaliação padrão 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) 

mais informações aqui: https://cran.r-project.org/web/packages/dplyr/vignettes/nse.html

É melhor usar a fórmula, pois ela também captura o ambiente para avaliar uma expressão em

Tabela de dados

 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 

Por uma questão de completude: você também pode usar a function sortByCol() do pacote 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 

Comparação de desempenho:

 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 

Assim como os classificadores de cartão mecânicos de muito tempo atrás, primeiro classifique pela chave menos significativa, depois a mais significativa, etc. Não é necessária biblioteca, funciona com qualquer número de chaves e qualquer combinação de teclas ascendentes e descendentes.

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

Agora estamos prontos para fazer a chave mais significativa. O tipo é estável e quaisquer laços na chave mais significativa já foram resolvidos.

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

Isso pode não ser o mais rápido, mas é certamente simples e confiável

Outra alternativa, usando o pacote 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