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

StackOverflow https://stackoverflow.com/questions/1296646

  •  18-09-2019
  •  | 
  •  

Вопрос

Я хочу отсортировать 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
Это было полезно?

Решение

Вы можете использовать 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.


Недавно я добавил sort.data.frame в пакет CRAN, сделав его совместимым по классам, как описано здесь:Лучший способ создать согласованность общего/метода для sort.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 )

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

Среднее время: 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), но я понимаю, почему Хэдли сделал это именно так из-за проблем, обсуждаемых в вопросе, указанном выше.

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

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

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

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

Пакет Р 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 теперь находится в том порядке, который нам нужен (поскольку он обновляется по ссылке).

Особенности data.table:

Скорость:

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

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

Объем памяти:

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

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

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

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

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

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

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

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

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

С эта (очень полезная) функция Кевина Райта, размещенный в разделе советов вики-страницы 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

или вы можете использовать пакет 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 к x нисходящий, y восходящий и z нисходящий.

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

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

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

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

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

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

В ответ на комментарий, добавленный в ОП о том, как программно сортировать:

С использованием dplyr и data.table

library(dplyr)
library(data.table)

дплир

Просто используйте 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

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

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

Для полноты картины:вы также можете использовать 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

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

 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

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

dfOrder(myDf, columnIndices)

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

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

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

В таком случае, 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
##    (...)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top