题 如何按多列对数据帧进行排序?


我想按多列对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

1130
2017-08-18 21:33


起源




答案:


你可以使用 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() 更容易/更直接的访问)。


1420
2017-08-18 21:51



@Dirk Eddelbuettel有一个类似简单的矩阵方法吗? - Jota
应该以同样的方式工作,但你不能使用 with。尝试 M <- matrix(c(1,2,2,2,3,6,4,5), 4, 2, byrow=FALSE, dimnames=list(NULL, c("a","b"))) 创建一个矩阵 M,然后使用 M[order(M[,"a"],-M[,"b"]),] 在两列上订购。 - Dirk Eddelbuettel
很容易: dd[ order(-dd[,4], dd[,1]), ],但不能使用 with 用于基于名称的子集。 - Dirk Eddelbuettel
运行第二个示例时,我有“一元运算符的无效参数”错误。 - Nailgun
使用带有字符列的减号时,会出现“一元运算符的无效参数”错误。通过包裹列来解决它 xtfrm, 例如 dd[ order(-xtfrm(dd[,4]), dd[,1]), ]。 - Richie Cotton


你的选择

  • 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 来自Kevin 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)

中位数时间: 1567

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

中位数时间: 862

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

中位数时间: 1694

请注意,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)但我明白为什么哈德利会这样做,因为上面提到的问题中讨论的问题。


382
2017-07-29 10:48



虽然我承认我找到了,但仍然是彻底的+1 microbenchmark 输出相当难读... - Ben Bolker
上面的ggplot2微基准测试功能现在可用 taRifx::autoplot.microbenchmark。 - Ari B. Friedman
@ AriB.Friedman y轴间隔是什么/比例是多少? - naught101
@AME看看怎么样 b 在样本中排序。默认值是按升序排序,因此您只需将其包装 desc。两者均上升: arrange(dd,z,b) 。在两者中下降: arrange(dd,desc(z),desc(b))。 - Ari B. Friedman
按照 ?arrange:“#NOTE:plyr函数不保留row.names”。这使得优秀 arrange() 如果想要保留,则功能不是最理想的 row.names。 - landroni


德克的答案很棒。它还强调了用于索引的语法的主要区别 data.frames和 data.tableS:

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

这是一个非常小的差异,但它可能只是挽救你的脖子一天。权衡此问题的不同答案时,请考虑将变量名称的重复计算为您决定的标准之一。有些答案有很多重复,有些则没有。


128
2018-05-25 16:25



+1这是一个很好的观点,并且详细了解R的语法,这经常让我感到烦恼。我有时会用 subset() 只是为了避免在一次调用中重复引用同一个对象。 - Josh O'Brien
任何想法 为什么 这些以不同的方式工作? - naught101
@ naught101 data.table FAQ 1.9是否回答了这个问题? - Matt Dowle
我想你可以添加新的 setorder 函数也在这里,因为这个线程是我们发送所有的 order 类型欺骗。 - David Arenburg


这里有很多优秀的答案,但是 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

106
2018-02-18 21:29



当我的列是或类型因素(或类似的东西)时,接受的答案不起作用,并且我想以降序方式对此因子列进行排序,然后按升序方式对整数列进行排序。但这很好用!谢谢! - Saheel Godhane
为什么“只”?我找到了data.table的 dd[order(-z, b)] 非常容易使用和记住。 - Matt Dowle
同意,这两种方法之间没有多少关系,而且 data.table 是一个巨大的贡献 R 在许多其他方面也。我想,对于我来说,在这种情况下,可能只需要少一组括号(或一个较少类型的括号)可以将认知负荷降低一个几乎不可察觉的数量。 - Ben
对我而言,归结为这样的事实 arrange() 是完全声明的, dd[order(-z, b)] 不是。 - Mullefa


R包 data.table 提供两者 快速 和 记忆效率高 订购 data.tables 使用简单的语法(Matt突出显示的一部分非常好 在他的回答中)。有很多改进,也有新功能 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(...)] 语法是 〜10倍 比其他方法中的最快速度快(dplyr),同时消耗相同数量的内存 dplyr

  • data.tablesetorder() 是 〜14X 比其他方法中的最快速度快(dplyr),同时服用 只有0.4GB的额外内存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. 它支持 integerlogicalnumericcharacter 乃至 bit64::integer64 类型。

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

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

    但是,在 data.table,我们可以做,例如, dat[order(-x)] 要么 setorder(dat, -x)


69
2018-03-29 15:52



感谢关于data.table的这个非常有启发性的答案。虽然,我不明白什么是“峰值记忆”以及你如何计算它。你能解释一下吗?谢谢 ! - Julien Navarre
我用了 仪器  - >分配并报告“所有堆和分配VM”大小。 - Arun
你评论中的@Arun the Instruments链接已经死了。小心发布更新? - MichaelChirico
@MichaelChirico以下是Apple制作的仪器信息的链接: developer.apple.com/library/content/documentation/... - n1k31t4


Kevin Wright的这个(非常有用的)功能,发布在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

57
2017-08-18 21:37



请参阅我的答案,以便对此函数中使用的算法进行基准测试。 - Ari B. Friedman


或者你可以使用包doBy

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

33
2018-01-19 20:44