Я хочу отсортировать 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
1422
Christopher DuBois 19 Авг 2009 в 01:33

19 ответов

Лучший ответ

Ваш выбор

  • 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, сделав его совместимым с классами, как описано здесь: Лучший способ создать универсальную целостность / согласованность методов для сортировки .data.frame?

Следовательно, учитывая dd data.frame, вы можете отсортировать его следующим образом:

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 )

Если вы являетесь одним из первых авторов этой функции, свяжитесь со мной. Обсуждение общественного достояния находится здесь: https://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)

Среднее время: 1,567

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)

Не удалось загрузить Deducer. Требуется консоль 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 . У него простой синтаксис, но при этом он почти так же быстр, как и базовые команды R с их запутанными махинациями. Типично блестящая работа Хэдли Уикхэм. Единственное, что меня беспокоит, это то, что он нарушает стандартную номенклатуру R, в которой объекты сортировки вызываются sort(object), но я понимаю, почему Хэдли сделал это таким образом из-за проблем, обсуждаемых в вопросе, приведенном выше.

521
Community 18 Янв 2021 в 12:34

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

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

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

159
Josh O'Brien 25 Май 2012 в 21:42

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

Для проблемы ОП:

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
136
Ben 18 Фев 2014 в 21:29

Пакет R data.table обеспечивает как быстрое , так и эффективное использование памяти упорядочивание data.tables с помощью простого синтаксиса (часть которого Мэтт довольно хорошо выделил в своем ответе). С тех пор было внесено много улучшений, а также появилась новая функция 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
# ------------------------------------------------------------
  • Синтаксис DT[order(...)] data.table был ~ 10x быстрее, чем самый быстрый из других методов (dplyr), при этом потреблял тот же объем памяти, что и dplyr.

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

Возможности data.table:

< Сильный > Скорость:

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

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

Память:

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

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

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

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

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

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

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

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

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

89
Community 23 Май 2017 в 10:31

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

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
77
MichaelChirico 24 Авг 2016 в 14:49

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

42
Marek 26 Май 2011 в 15:21

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

library(doBy)
dd <- orderBy(~-z+b, data=dd)
41
George Dontas 19 Янв 2010 в 20:44

Если SQL вам понятен, пакет sqldf обрабатывает ORDER BY, как задумал Кодд.

35
M-- 4 Окт 2019 в 15:53

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

library(Deducer)
dd<- sortData(dd,c("z","b"),increasing= c(FALSE,TRUE))
33
Ian Fellows 20 Авг 2009 в 19:43

В ответ на комментарий, добавленный в 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
21
info_seekeR 5 Фев 2016 в 21:11

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

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

РЕДАКТИРОВАТЬ: 13 мая 2014 г.

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

Я нашел код do.call месяц или два назад в старом посте на другом сайте, но только после тщательного и трудного поиска. Я не уверен, что смогу переместить этот пост сейчас. Текущий поток является первым попаданием для заказа 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
20
Mark Miller 13 Май 2014 в 22:53

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

dd <- dd[with(dd, order(-z, b)), ] 
19
Andrew 26 Май 2011 в 15:08

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

dd1 <- dd %>%
    arrange(z) %>%
    arrange(desc(x))
18
Martin Schmelzer 20 Авг 2020 в 08:38

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

В этом случае на помощь приходит 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
##    (...)
9
Dominic Comtois 11 Апр 2019 в 03:58

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

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

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

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

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

7
Rick 15 Янв 2015 в 04:28

Для полноты: вы также можете использовать функцию 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
7
Lars Kotthoff 7 Авг 2015 в 04:03

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

dfOrder(myDf, columnIndices)

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

функция dfOrder из пакета 'mental'

5
AHegde 24 Окт 2018 в 22:32

Другой вариант, используя пакет 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
4
Stéphane Laurent 1 Май 2018 в 10:18