Как отсортировать фрейм данных по нескольким столбцам
Я хочу сортировать 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))
(линии простираются от нижнего квартиля до верхнего квартиля, точка - это медиана)
Учитывая эти результаты и сравнивая простоту и скорость, мне пришлось бы отдать дань 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)
.
Ответ 6
С эта (очень полезная) функция Кевина Райт, размещенная в разделе советов 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
Ответ 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)