提问



我想按多列对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)代码顶部的技巧:[155]


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

其它参考1


您的选择




  • order来自base

  • arrange来自dplyr

  • setordersetorderv来自data.table

  • arrange来自plyr

  • sort来自taRifx

  • orderBy来自doBy

  • sortData来自Deducer



大多数情况下你应该使用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()函数,正如Hadley在上面的主题中指出的那样:


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)


中位时间: 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))





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





考虑到这些结果并且简化了速度,我必须在plyr包中给出 arrange的点头。它有一个简单的语法,但几乎就像快速的基本R命令与他们错综复杂的阴谋。通常辉煌的哈德利威克姆工作。我唯一的抱怨是它打破了标准的R术语,排序对象被sort(object)调用,但我理解为什么哈德利这样做由于上面链接的问题中讨论的问题的方式。

其它参考2


Dirk的答案很棒。它还强调了用于索引data.framedata.table s的语法的主要区别:


## 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()[158]


代替


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


只是


dd[order(-z, b)]


而不是


quarterlyreport[with(lastquarterlyreport,order(-z,b)),]


只是


quarterlyreport[order(-z,b)]


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

其它参考3


这里有很多优秀的答案,但是dplyr提供了我能够快速且容易记住的唯一语法(现在经常使用):[159]


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

其它参考4


R包data.table提供了 fast 和内存高效的 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.tables DT[order(...)]语法〜10x 比其他方法中最快的[[dplyr)快,同时消耗的内存量与[[dplyr

  • data.tables setorder() 〜14x 比其他方法中的最快(dplyr快,而只需0.4GB额外内存dat现在按我们要求的顺序(因为它通过引用更新)。



data.table功能:



速度



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

  • 语法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)


其它参考5


有了Kevin Wright的这个(非常有用的)功能,发布在R维基的提示部分,这很容易实现。[162]


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

其它参考6


或者你可以使用包doBy


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

其它参考7


假设您有一个data.frame A,并且您想使用名为x降序的列对其进行排序。调用已排序的data.frame newdata


newdata <- A[order(-A$x),]


如果您想升序,则无需替换"-"。你可以拥有类似的东西


newdata <- A[order(-A$x, A$y, -A$z),]


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

其它参考8


或者,使用Deducer包


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

其它参考9


如果SQL对您来说很自然,那么sqldf会按照Codd的意图处理ORDER BY。

其它参考10


我通过下面的例子了解了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


这个例子有效的唯一原因是因为ordervector Age排序,而不是data frame data中名为Age的列排序。


要使用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),]


以下行有效,因为ordermy.data中的age列进行排序。


databyage = my.data[order(my.data$age),]


我认为这是值得发布的,因为这个例子对我这么长时间的困惑。如果该帖子不适合该帖子,我可以将其删除。


编辑:2014年5月13日


下面是按列分类数据框而不指定列名的通用方法。下面的代码显示了如何从左到右或从右到左排序。如果每列都是数字,则此方法有效。我没有尝试添加字符列。


我在一个或两个月之前在另一个网站的旧帖子中找到了do.call代码,但只是经过了大量而艰难的搜索。我不确定我现在可以重新安排那篇文章。目前的主题是在R中排序data.frame的第一个命中。所以,我认为我的原始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

其它参考11


Dirk的答案很好,但是如果你需要这种类型来坚持你,你会想要将排序应用到该数据框的名称上。使用示例代码:


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

其它参考12


响应OP中添加的关于如何以编程方式排序的注释:


使用dplyrdata.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 [163]


最好使用公式,因为它还捕获环境以评估表达式


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

其它参考13


为了完整起见:您还可以使用BBmisc包中的sortByCol()功能:


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

其它参考14


就像很久以前的机械卡分拣机一样,首先按最不重要的键排序,然后是下一个最重要的键,等等。不需要库,可以使用任意数量的键以及上升键和下降键的任意组合。


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


现在我们已经准备好做最重要的关键。排序是稳定的,并且最重要的关键字中的任何关系都已经解决了。


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


这可能不是最快的,但它确实简单可靠

其它参考15


另一种选择,使用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