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

Я хочу сортировать data.frame по нескольким столбцам. Например, с приведенным ниже номером data.frame, я хотел бы отсортировать по столбцу 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

Ответ 1

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

Ответ 2

Ваш выбор

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

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


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

Поэтому, учитывая data.frame 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 )

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


Вы также можете использовать функцию arrange() из plyr, как указывал Хэдли в приведенной выше теме:

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

Тесты: обратите внимание, что я загрузил каждый пакет в новом сеансе R, так как было много конфликтов. В частности, загрузка пакета doBy приводит к тому, что sort возвращает "Следующие объекты маскируются из" x (позиция 17) ": b, x, y, z", а загрузка пакета Deducer переписывает sort.data.frame из Кевина. Райт или пакет 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
)

Среднее время:

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

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

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

Среднее время: 1567

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

Среднее время: 862

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

Среднее время: 1 694

Обратите внимание, что doBy требует много времени для загрузки пакета.

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

Не удалось загрузить дедуцера. Требуется консоль JGR.

esort <- function(x, sortvar, ...) {
attach(x)
x <- x[with(x,order(sortvar,...)),]
return(x)
detach(x)
}

microbenchmark(esort(dd, -z, b),times=1000)

Похоже, что он не совместим с микробенчмарком из-за прикрепления/отсоединения.


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

(линии простираются от нижнего квартиля до верхнего квартиля, точка - это медиана)


Учитывая эти результаты и сравнивая простоту и скорость, мне пришлось бы отдать дань arrange в пакете plyr. Он имеет простой синтаксис и, тем не менее, почти такой же быстрый, как команды base R, с их запутанными махинациями. Типично блестящая работа Хэдли Уикхем. Моя единственная неприятность в том, что она нарушает стандартную номенклатуру R, в которой сортировка объектов вызывается sort(object), но я понимаю, почему Хэдли так поступила из-за проблем, обсуждаемых в вопросе, связанном выше.

Ответ 3

Ответ на Dirk велик. Он также подчеркивает ключевое различие в синтаксисе, используемом для индексирования data.frame и data.table s:

## The data.frame way
dd[with(dd, order(-z, b)), ]

## The data.table way: (7 fewer characters, but that 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)]

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

Ответ 4

Здесь есть много отличных ответов, но 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

Ответ 5

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

Сначала мы создадим набор данных достаточно большой и сравним различные методы, упомянутые в других ответах, а затем перечислим особенности data.table.

Данные:

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

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

функции data.table:

Скорость:

  • data.table упорядочивается очень быстро, потому что он реализует упорядочение радиуса.

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

Память:

  • В большинстве случаев нам не требуется исходный файл data.frame или data.table после переупорядочения. То есть мы обычно присваиваем результат обратно одному и тому же объекту, например:

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

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

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

Другие функции:

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

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

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

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

Ответ 7

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

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

Ответ 8

Предположим, что у вас есть 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 по убыванию x, y по возрастанию и z по убыванию.

Ответ 9

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

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

Ответ 10

если вам подходит SQL, пакет sqldf обрабатывает ORDER BY так, как задумал Codd.

Ответ 11

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

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

Ответ 12

Я узнал о 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

Ответ 13

В ответ на комментарий, добавленный в 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

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

data.table

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

Ответ 14

Аранжировка() в dplyer - мой любимый вариант. Используйте оператора трубы и переходите от наименее важного к наиболее важному аспекту

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

Ответ 15

Для полноты: вы также можете использовать функцию sortByCol() из пакета 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

Сравнение производительности:

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

Ответ 16

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

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

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

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

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

Ответ 17

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

Ответ 18

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

dfOrder(myDf, columnIndices)

где columnIndices - это индексы одного или нескольких столбцов в том порядке, в котором вы хотите их отсортировать. Больше информации здесь:

Функция dfOrder из пакета 'psych'

Ответ 19

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

В этом случае, do.call() приходит на помощь:

ind <- do.call(what = "order", args = iris[,c(5,1,2,3)])
iris[ind, ]

##        Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
##    14           4.3         3.0          1.1         0.1     setosa
##    9            4.4         2.9          1.4         0.2     setosa
##    39           4.4         3.0          1.3         0.2     setosa
##    43           4.4         3.2          1.3         0.2     setosa
##    42           4.5         2.3          1.3         0.3     setosa
##    4            4.6         3.1          1.5         0.2     setosa
##    48           4.6         3.2          1.4         0.2     setosa
##    7            4.6         3.4          1.4         0.3     setosa
##    (...)

Ответ 20

Вы можете сделать это:

library(dplyr) data<-data %>% arrange(data,columname)