按多列对数据帧行进行排序(顺序)

我想按多列对数据帧进行排序。例如,对于下面的数据帧,我想按列“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))ddb x y z1  Hi A 8 12 Med D 3 13  Hi A 9 14 Low C 9 2
1325445 次浏览

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

您可以直接使用#0函数,而无需使用附加工具——请参阅这个更简单的答案,它使用了example(order)代码顶部的技巧:

R> dd[with(dd, order(-z, b)), ]b x y z4 Low C 9 22 Med D 3 11  Hi A 8 13  Hi A 9 1

大约2+年后编辑:刚刚有人问如何按列索引执行此操作。答案是简单地将所需的排序列传递给order()函数:

R> dd[order(-dd[,4], dd[,1]), ]b x y z4 Low C 9 22 Med D 3 11  Hi A 8 13  Hi A 9 1R>

而不是使用列的名称(和with()更容易/更直接的访问)。

或者,使用软件包演绎器

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

或者您可以使用包doBy

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

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

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

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

如果你想要升序,那么用无替换"-"。你可以有这样的东西

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

其中xzdata.frameA中的一些列。这意味着按x降序、y升序和z降序对data.frameA进行排序。

Dirk的回答很好,但是如果你需要排序来持久化,你需要将排序应用回该数据帧的名称上。使用示例代码:

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

你的选择

  • orderbase
  • arrangedplyr
  • setordersetordervdata.table
  • arrangeplyr
  • sorttaRifx
  • orderBydoBy
  • sortDataDeducer

大多数情况下,您应该使用dplyrdata.table解决方案,除非无依赖关系很重要,在这种情况下使用base::order


我最近将sort.data.frame添加到CRAN包中,使其与这里讨论的类兼容:为sort.data.frame?创建泛型/方法一致性的最佳方法

因此,给定data.framedd,您可以按以下方式排序:

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 )

如果您是此函数的原始作者之一,请联系我。关于公共领域的讨论在这里:https://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”,并加载D的包覆盖了Kevin Wright或taRifx包的sort.data.frame

#Load each timedd <- 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 benchmarksmicrobenchmark(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)

无法使D的负载。需要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))

微基准图

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


考虑到这些结果,并权衡简单性与速度,我不得不同意#1包中的#0。它有一个简单的语法,但几乎和基本的R命令一样快,它们有复杂的阴谋。典型的辉煌Hadley Wickham工作。我唯一的抱怨是它打破了标准的R命名法,其中排序对象由sort(object)调用,但我理解为什么Hadley这样做,因为上面链接的问题中讨论了问题。

Dirk的回答很棒。它还突出了用于索引data.framedata.table的语法的一个关键区别:

## The data.frame waydd[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认为这就是你的意思。你希望阅读你的报告的人能发现它,但也许他们没有。如果你经常使用编程语言,那么这种情况可能就很熟悉了。你会说这是一个“错字”。我会修复你对老板说的“错字”。

#0中,我们关心这样的小细节。所以我们做了一些简单的事情来避免两次输入变量名。非常简单的事情。i已经在dd的框架内自动计算。你根本不需要with()

而不是

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

这只是

dd[order(-z, b)]

而不是

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

这只是

quarterlyreport[order(-z,b)]

这是一个非常小的差异,但它可能有一天会救你的命。在权衡这个问题的不同答案时,考虑将变量名的重复数作为你决定的标准之一。有些答案有相当多的重复,有些答案没有。

我通过下面的例子了解了order,这让我困惑了很长时间:

set.seed(1234)
ID        = 1:10Age       = 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排序的,而不是按data frame data中名为Age的列排序的。

要看到这一点,请使用read.table创建一个相同的数据帧,其列名略有不同,并且不使用上述任何向量:

my.data <- read.table(text = '
id age  diagnosis1  49 Depression2  50 Depression3  51 Depression4  48 Depression5  50 Depression6  51    Bipolar7  49    Bipolar8  49    Bipolar9  49    Bipolar10  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

这里有很多很好的答案,但是dplyr给出了我唯一可以快速轻松记住的语法(所以现在经常使用):

library(dplyr)# sort mtcars by mpg, ascending... use desc(mpg) for descendingarrange(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 z1 Low C 9 22 Med D 3 13  Hi A 8 14  Hi A 9 1

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

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

现在我们准备好执行最重要的密钥。排序是稳定的,并且最重要密钥中的任何联系都已经解决。

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

这可能不是最快的但它肯定是简单和可靠的

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)     ## doByplyr::arrange(dat, desc(z), b)     ## plyrarrange(dat, desc(z), b)           ## dplyrsort(dat, f = ~ -z + b)            ## taRifxdat[with(dat, order(-z, b)), ]     ## base R
# convert to data.table, by referencesetDT(dat)
dat[order(-z, b)]                  ## data.table, base R like syntaxsetorder(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的额外内存。

data.table特点:

速度:

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

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

内存:

  • 大多数时候,重新排序后我们不需要原来的data.framedata.table。也就是说,我们通常会将结果赋回同一个对象,例如:

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

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

    setorder()重新排序data.tablesby reference就地),不做任何额外的副本。它只使用等于一列大小的额外内存。

其他特点:

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

    请注意,factorDatePOSIXct等…类都是integer/numeric类型,下面有额外的属性,因此也受支持。

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

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

为了完整起见:您也可以使用BBmisc包中的sortByCol()函数:

library(BBmisc)sortByCol(dd, c("z", "b"), asc = c(FALSE, TRUE))b x y z4 Low C 9 22 Med D 3 11  Hi A 8 13  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

针对OP中添加的有关如何以编程方式排序的注释:

使用dplyrdata.table

library(dplyr)library(data.table)

dplyr

只需使用arrange_,这是arrange的标准评估版本。

df1 <- tbl_df(iris)#using strings or formulaarrange_(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  setosa2           4.3         3.0          1.1         0.1  setosa3           5.8         4.0          1.2         0.2  setosa4           5.0         3.2          1.2         0.2  setosa5           4.7         3.2          1.3         0.2  setosa6           5.4         3.9          1.3         0.4  setosa7           5.5         3.5          1.3         0.2  setosa8           4.4         3.0          1.3         0.2  setosa9           5.0         3.5          1.3         0.3  setosa10          4.5         2.3          1.3         0.3  setosa..          ...         ...          ...         ...     ...

#Or using a variablesortBy <- 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  setosa2           4.3         3.0          1.1         0.1  setosa3           5.8         4.0          1.2         0.2  setosa4           5.0         3.2          1.2         0.2  setosa5           4.7         3.2          1.3         0.2  setosa6           5.5         3.5          1.3         0.2  setosa7           4.4         3.0          1.3         0.2  setosa8           4.4         3.2          1.3         0.2  setosa9           5.0         3.5          1.3         0.3  setosa10          4.5         2.3          1.3         0.3  setosa..          ...         ...          ...         ...     ...
#Doing the same operation except sorting Petal.Length in descending ordersortByDesc <- 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.framesortBy <- c('Petal.Length', 'Petal.Width')sortType <- c(-1, 1)setorderv(dt1, sortBy, sortType)dt1Sepal.Length Sepal.Width Petal.Length Petal.Width   Species1:          7.7         2.6          6.9         2.3 virginica2:          7.7         2.8          6.7         2.0 virginica3:          7.7         3.8          6.7         2.2 virginica4:          7.6         3.0          6.6         2.1 virginica5:          7.9         3.8          6.4         2.0 virginica---146:          5.4         3.9          1.3         0.4    setosa147:          5.8         4.0          1.2         0.2    setosa148:          5.0         3.2          1.2         0.2    setosa149:          4.3         3.0          1.1         0.1    setosa150:          4.6         3.6          1.0         0.2    setosa

另一种选择,使用rgr包:

> library(rgr)> gx.sort.df(dd, ~ -z+b)b x y z4 Low C 9 22 Med D 3 11  Hi A 8 13  Hi A 9 1

当我想自动化n列的排序过程时,我正在努力解决上述解决方案,它们的列名每次都可能不同。我从psych包中找到了一个超级有用的函数,以简单的方式做到这一点:

dfOrder(myDf, columnIndices)

其中columnIndices是一个或多个列的索引,按照您要对它们进行排序的顺序。更多信息在这里:

来自“心理”包的dfOrder函数

dplyr中的安排()是我最喜欢的选项。使用管道运算符,从最不重要的方面转到最重要的方面

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

只是为了完整起见,因为没有太多关于按列编号排序的说法……可以肯定的是,这通常是不可取的(因为列的顺序可能会改变,为错误铺平道路),但在某些特定情况下(例如,当您需要快速完成工作并且没有列更改顺序的风险时),这可能是最明智的做法,特别是在处理大量列时。

在这种情况下,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##    (...)