Как отсортировать фрейм данных по нескольким столбцам?

Я хочу отсортировать данные.рамка несколькими столбцами. Например, с данными.кадр ниже я хотел бы отсортировать по столбцу z (по убыванию), затем по столбцу b (по возрастанию):

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

16 ответов


можно использовать order() функция напрямую, не прибегая к дополнительным инструментам - см. Этот более простой ответ, который использует трюк прямо сверху 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

редактировать некоторые 2+ лет спустя: его просто спросили, как это сделать по индексу столбцов. Ответ заключается в том, чтобы просто передать желаемый столбец(ы) сортировки в 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> 

вместо использования имени столбца (и with() для более прямой доступ.)


твой выбор

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

большую часть времени вы должны использовать dplyr или data.table решения, если не иметь никаких зависимостей важно, в этом случае используйте base::order.


я недавно добавил.данные.рамка для пакета CRAN, что делает его совместимым с классом, как описано здесь: лучший способ создать общую согласованность / метод для сортировки.данные.кадр?

поэтому, учитывая данные.кадр dd, вы можете отсортировать следующим образом:

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 )

если вы один из авторов этой функции, пожалуйста, свяжитесь со мной. Дискуссия по вопросу публичных domaininess здесь: http://chat.stackoverflow.com/transcript/message/1094290#1094290


вы также можете использовать


ответ Дирка велик. Он также подчеркивает ключевое различие в синтаксисе, используемом для индексирования data.frames и 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)]

разница между двумя вызовами невелика, но она может иметь важные последствия. Особенно, если вы пишете производственный код и / или обеспокоены правильностью своих исследований, лучше избегать ненужного повторения имен переменных. data.table поможет тебе сделать это.

вот пример того, как повторение переменной имена могут доставить вам неприятности:

Давайте изменим контекст из ответа Дирка и скажем, что это часть большего проекта, где есть много имен объектов, и они длинные и значимые; вместо dd это называется quarterlyreport. Это приобретает следующий вид :

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

Ок, хорошо. В этом нет ничего плохого. Затем ваш босс просит вас включить в отчет отчет за прошлый квартал. Вы проходите через свой код, добавляя объект lastquarterlyreport в разных местах и как-то (как на земля?) вы в конечном итоге с этим :

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

это не то, что вы имели в виду, но вы не заметили его, потому что вы сделали это быстро, и он расположен на странице аналогичного кода. Код не падает (без предупреждения и без ошибки), потому что R думает, что это то, что вы имели в виду. Вы надеетесь, что тот, кто читает ваш отчет, заметит это, но, возможно, они этого не делают. Если вы много работаете с языками программирования, то эта ситуация может быть всем знакома. Вы скажете, что это была опечатка. Я могу исправить "опечатку" вам сказать босс.

на data.table нас беспокоят такие мелкие детали. Таким образом, мы сделали что-то простое, чтобы избежать ввода имен переменных дважды. Что-то очень простое. i оценивается в рамках dd уже автоматически. Вам не нужно with() на всех.

вместо

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

это просто

dd[order(-z, b)]

и вместо

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

это просто

quarterlyreport[order(-z,b)]

это очень небольшая разница, но однажды это может спасти твою шею. При взвешивании различных ответов на этот вопрос рассмотрите возможность подсчета повторений имен переменных в качестве одного из критериев при принятии решения. Некоторые ответы имеют несколько повторов, другие-ни одного.


здесь есть много отличных ответов, но dplyr дает единственный синтаксис, который я могу быстро и легко запомнить (и поэтому теперь использую очень часто):

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)

для проблемы 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

пакет R data.table обеспечивает быстро и эффективная память заказ данные.таблицы С простым синтаксисом (часть которого Мэтт выделил довольно красиво в ответ). Было довольно много улучшений, а также новая функция setorder() С тех пор. От v1.9.5+, setorder() работает с данные.кадры.

во-первых, мы создадим достаточно большой набор данных и проверьте различные методы, упомянутые из других ответов, а затем перечислите функции данные.таблица.

данные:

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)

критерии:

сообщенные тайминги от запуска system.time(...) на этих функциях, показанных ниже. Тайминги табулируются ниже (в порядке от самого медленного до самого быстрого).

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 ' s DT[order(...)] синтаксис ~10x быстрее, чем самый быстрый из других методов (dplyr), потребляя тот же объем памяти, что и dplyr.

  • data.table ' s setorder() был ~14x быстрее, чем самый быстрый из других методов (dplyr), а всего 0,4 ГБ дополнительной памяти. dat теперь в порядке, который нам требуется (поскольку он обновляется по ссылке).

данные.таблица особенности:

скорость:

  • данные.таблицазаказ очень быстрый, потому что он реализует radix заказ.

  • синтаксис DT[order(...)] оптимизирован внутренне, чтобы использовать данные.таблицабыстрый заказ, а также. Вы можете продолжать использовать знакомый базовый синтаксис R, но ускорить процесс (и использовать меньше память.)

память:

  • в большинстве случаев нам не требуется оригинал данные.кадр или данные.таблица после перенумерации. То есть мы обычно присваиваем результат тому же объекту, например:

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

    проблема в том, что для этого требуется по крайней мере дважды (2x) память исходного объекта. Быть эффективная память, данные.таблица поэтому также обеспечивает функцию setorder().

    setorder() переупорядочивает данные.таблицы by reference (на месте), без внесения каких-либо дополнительных копий. Он использует только дополнительную память, равную размеру одного столбца.

другие особенности:

  1. поддерживает integer, logical, numeric, character и даже bit64::integer64 типы.

    обратите внимание, что factor, Date, POSIXct etc.. класс integer/numeric типы внизу с дополнительными атрибутами и поэтому поддерживаются также.

  2. в базе R мы не можем использовать - на вектор символов для сортировки по этому столбцу в порядке убывания. Вместо этого мы должны использовать -xtfrm(.).

    однако, в данные.таблица, мы можем просто сделать, например, dat[order(-x)] или setorder(dat, -x).


С эта (очень полезная) функция Кевина Райта, размещенный в разделе советов R wiki, это легко достигается.

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

или вы можете использовать пакет doBy

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

Предположим, у вас есть data.frame A и вы хотите отсортировать его с помощью столбца под названием x порядке убывания. Вызовите сортировку data.frame newdata

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

если вы хотите по возрастанию, то замените "-" ни с чем. Вы можете иметь что-то вроде

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

здесь x и z некоторые столбцы в data.frame A. Это означает то вроде data.frame A by x спуск, y по возрастанию и z по убыванию.


альтернативно, используя пакет Deducer

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

Если SQL приходит к вам естественным образом, sqldf обрабатывает порядок по назначению Codd.


я узнал о order со следующим примером, который затем смутил меня в течение длительного времени:

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

единственная причина, по которой этот пример работает, потому что order сортируется по vector Age, а не по графе Age на data frame data.

чтобы увидеть это, создайте идентичный фрейм данных с помощью read.table С немного разными именами столбцов и без использования любого из вышеуказанных векторов:

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)

вышеуказанная структура линии для order больше не работает, потому что нет вектора с именем age:

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

следующая строка работает, потому что order сортировка по столбцу age на my.data.

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

я думал, что это стоит опубликовать, учитывая, насколько я был смущен этим примером так долго. Если этот пост не считается подходящим для потока, я могу его удалить.

EDIT: 13 мая 2014

Ниже приведен обобщенный способ сортировки данных по каждому столбец без указания имен столбцов. Код ниже показывает, как сортировать слева направо или справа налево. Это работает, если каждый столбец числовой. Я не пробовал с добавленным столбцом символов.

нашел do.call код месяц или два назад в старом посте на другом сайте, но только после обширного и сложного поиска. Я не уверен, что смогу перенести этот пост сейчас. Настоящий поток является первым хитом для заказа a data.frame на R. Итак, я подумал, что мой Расширенная версия этого оригинала do.call код может быть полезен.

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

ответ Дирка хорош, но если вам нужно сохранить сортировку, вы захотите применить сортировку обратно к имени этого фрейма данных. Используя пример кода:

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

в ответ на комментарий, добавленный в OP для сортировки программно:

используя dplyr и data.table

library(dplyr)
library(data.table)

dplyr

просто использовать arrange_, который является стандартной версией оценки для 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)

подробнее здесь: https://cran.r-project.org/web/packages/dplyr/vignettes/nse.html

лучше использовать формулу, поскольку она также захватывает среду для оценки выражения в

данные.таблица

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

для полноты: вы также можете использовать :

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

для сравнения:

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

Так же, как механические сортировщики карт давным-давно, сначала сортируют по наименее значимому ключу, затем по следующему наиболее значимому и т. д. Не требуется библиотека, работает с любым количеством клавиш и любой комбинацией восходящих и нисходящих клавиш.

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

теперь мы готовы сделать самые важные. Сортировка стабильна, и любые связи в наиболее значимом ключе уже разрешены.

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

Это может быть не самым быстрым, но это, безусловно, просто и надежный


другая альтернатива, с помощью 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