如何按多个列对数据框进行排序

我想按多个列对 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()可以更容易 / 更直接地访问)。

您的选择

  • base order
  • dplyr arrange
  • setordersetordervdata.table
  • plyr arrange
  • taRifx sort
  • 来自doBy orderBy
  • sortDataDeducer

多数情况下,除非没有依赖性很重要,否则应使用dplyrdata.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


如哈德利在上述线程中所指出的,您还可以使用plyr arrange()函数:

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

基准测试:请注意,由于存在很多冲突,因此我在新的 R 会话中加载了每个软件包。特别是,加载 doBy 包会导致sort返回 “从'x(位置 17)' 屏蔽以下对象:b,x,y,z“,并且加载 Deducer 包将覆盖 Kevin 的sort.data.frame Wright 或 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)

无法进行演绎器加载。需要 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))

微基准图

(线从下四分位延伸到上四分位,点是中位数)


考虑到这些结果并权衡简单性与速度,我不得不点头arrangeplyr软件包中 。它具有简单的语法,但几乎与基本 R 命令一样复杂,具有复杂的机加工。通常是出色的哈德利 · 威克姆(Hadley Wickham)的作品。我唯一的困扰是它打破了由sort(object)调用排序对象的标准 R 术语,但是我理解为什么 Hadley 会因为上述问题中讨论的问题而采用这种方式。

德克的答案很好。它还突出显示了用于索引data.framedata.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可以帮助您做到这一点。

这是一个例子,说明变量名的重复可能会给您带来麻烦:

让我们从 Dirk 的答案中更改上下文,并说这是一个更大的项目的一部分,该项目中有很多对象名称,它们又长又有意义。而不是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提供既快速又用一个简单的语法data.tables内存使用效率排序(其中马特很好地突出了一部分在他的回答 )。从那时起,已经有了很多改进,并且还增加了一个新的函数setorder() 。从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.tableDT[order(...)]语法比其他最快的方法( dplyr )快约 10 倍 ,同时消耗的内存量与dplyr相同。

  • data.tablesetorder()比其他方法中最快的方法( dplyr )快约 14倍,而仅占用 0.4GB 的额外内存 。现在dat符合我们要求的顺序(因为它已通过引用进行更新)。

data.table 功能:

速度:

  • data.table的排序非常快,因为它实现了基数排序

  • 语法DT[order(...)]在内部进行了优化,以使用data.table的快速排序。您可以继续使用熟悉的 base R 语法,但是可以加快处理速度(并使用更少的内存)。

记忆:

  • 在大多数情况下,重新排序后不需要原始的data.framedata.table 。也就是说,我们通常将结果分配回同一对象,例如:

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

    问题在于,这至少需要原始对象的两倍(2 倍)的内存。为了提高内存效率data.table因此还提供了一个setorder()函数。

    setorder()重排序data.tables by reference就地 ),而不进行任何附加的拷贝。它仅使用等于一列大小的额外内存。

其它功能:

  1. 它支持integerlogicalnumericcharacter甚至bit64::integer64类型。

    注意factorDatePOSIXct等类都是带有附加属性的integer / numeric类型,因此也受支持。

  2. 在基数 R 中,我们不能在字符向量上使用-以该列按降序排序。相反,我们必须使用-xtfrm(.)

    但是,在data.table 中 ,我们可以执行例如dat[order(-x)]setorder(dat, -x)

通过 R wiki 的提示部分中的Kevin Wright 的(非常有用的)功能 ,可以轻松实现这一点。

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

其中xzdata.frame A中的某些列。这意味着对data.frame Ax降序, y升序和z降序排序。

如果 SQL 自然而然地出现,则sqldf包将按 Codd 的意图处理ORDER BY

或者,使用软件包 Deducer

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