我想按多列对数据框进行排序。例如,对于下面的数据框,我想按列“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()
以便更轻松/更直接地访问)。
您的选择
从基地订购
从 dplyr 安排
来自 data.table 的 setorder 和 setorderv
从 plyr 安排
从 taRifx 排序
来自 doBy 的 orderBy
来自 Deducer 的 sortData
大多数情况下,您应该使用 dplyr
或 data.table
解决方案,除非没有依赖关系很重要,在这种情况下使用 base::order
。
我最近将 sort.data.frame 添加到 CRAN 包中,使其类兼容,如下所述:Best way to create generic/method consistency for 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 )
如果您是此功能的原作者之一,请与我联系。关于公共领域的讨论在这里:https://chat.stackoverflow.com/transcript/message/1094290#1094290
正如 Hadley 在上述线程中指出的那样,您还可以使用 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)
无法加载 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))
https://i.stack.imgur.com/9z6Oq.png
(线从下四分位数延伸到上四分位数,点是中位数)
鉴于这些结果并权衡了简单性与速度,我不得不对 plyr
包中的 arrange
表示赞同。它具有简单的语法,但几乎与带有复杂机制的基本 R 命令一样快。通常出色的哈德利·威克姆(Hadley Wickham)作品。我唯一的抱怨是它打破了标准的 R 命名法,其中排序对象被 sort(object)
调用,但我理解为什么 Hadley 这样做是由于上面链接的问题中讨论的问题。
taRifx::autoplot.microbenchmark
。
b
的排序方式。默认是按升序排序的,所以您不要将它包装在 desc
中。两者都升序: arrange(dd,z,b)
。两者都降序:arrange(dd,desc(z),desc(b))
。
?arrange
:“# 注意:plyr 函数不保留 row.names”。如果想要保留 row.names
,这使得出色的 arrange()
函数不是最理想的。
sort.list(x, method=“radix”)
,其中一些使用 order
的速度可能会快一些。
德克的回答很棒。它还强调了用于索引 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
可帮助您做到这一点。
以下是变量名称重复可能会给您带来麻烦的示例:
让我们从 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)]
这是一个非常小的差异,但它可能有一天会拯救你的脖子。在权衡这个问题的不同答案时,考虑将变量名称的重复次数作为决定的标准之一。有些答案有很多重复,有些则没有。
subset()
只是为了避免在一次调用中重复引用同一个对象。
setorder
函数,因为这个线程是我们发送所有 order
类型欺骗的地方。
这里有很多很好的答案,但 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
dd[order(-z, b)]
非常易于使用和记忆。
data.table
在许多其他方面也对 R
做出了巨大贡献。我想对我来说,可能是在这种情况下少了一组括号(或少了一种类型的括号)将认知负荷减少了几乎无法感知的量。
arrange()
是完全声明性的,而 dd[order(-z, b)]
不是。
R 包 data.table
提供 data.tables 的 快速 和 内存效率 排序,语法简单(Matt 强调了其中的一部分)非常好in his answer)。从那时起,已经有了很多改进,还有一个新功能 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.table 的 DT[order(...)] 语法比其他最快的方法 (dplyr) 快约 10 倍,同时消耗与 dplyr 相同的内存量。
data.table 的 setorder() 比其他最快的方法 (dplyr) 快约 14 倍,同时仅占用 0.4GB 额外内存。 dat 现在是我们需要的顺序(因为它是通过引用更新的)。
数据表功能:
速度:
data.table 的排序非常快,因为它实现了基数排序。
语法 DT[order(...)] 在内部进行了优化,以使用 data.table 的快速排序。您可以继续使用熟悉的基本 R 语法,但会加快进程(并使用更少的内存)。
记忆:
大多数时候,我们不需要重新排序后的原始 data.frame 或 data.table。也就是说,我们通常将结果分配回同一个对象,例如: DF <- DF[order(...)] 问题是这至少需要原始对象的两倍 (2x) 内存。为了提高内存效率,data.table 因此还提供了一个函数 setorder()。 setorder() 通过引用(就地)重新排序 data.tables,而不制作任何额外的副本。它只使用等于一列大小的额外内存。
其它功能:
它支持整数、逻辑、数字、字符甚至 bit64::integer64 类型。请注意,因子、日期、POSIXct 等类都是具有附加属性的整数/数字类型,因此也受支持。在基础 R 中,我们不能在字符向量上使用 - 来按该列按降序排序。相反,我们必须使用 -xtfrm(.)。但是,在 data.table 中,我们可以只执行例如 dat[order(-x)] 或 setorder(dat, -x)。
使用 this (very helpful) function by 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
假设您有一个 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
中的一些列。这意味着按 x
降序、y
升序和 z
降序对 data.frame
A
进行排序。
或者你可以使用包 doBy
library(doBy)
dd <- orderBy(~-z+b, data=dd)
如果 SQL 对您来说很自然,那么 sqldf
包会按照 Codd 的意图处理 ORDER BY
。
或者,使用包 Deducer
library(Deducer)
dd<- sortData(dd,c("z","b"),increasing= c(FALSE,TRUE))
我通过以下示例了解了 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
排序,而不是按 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),]
以下行有效,因为 order
对 my.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
require(data.table); my.dt <- data.table(my.data); my.dt[order(age)]
这是有效的,因为列名在 [] 括号内可用。
with
或 $
。
do.call
投票,这使得排序多列数据框的工作变得很短。只需 do.call(sort, mydf.obj)
,就会得到一个漂亮的级联排序。
响应 OP 中添加的关于如何以编程方式排序的评论:
使用 dplyr
和 data.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
最好使用公式,因为它还捕获环境来评估表达式
数据表
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
dplyr 中的arrange() 是我最喜欢的选项。使用管道运算符并从最不重要的方面到最重要的方面
dd1 <- dd %>%
arrange(z) %>%
arrange(desc(x))
Dirk 的回答很好,但如果您需要排序持久化,您需要将排序应用回该数据框的名称。使用示例代码:
dd <- dd[with(dd, order(-z, b)), ]
只是为了完整起见,因为关于按列号排序的说法不多……可以肯定地说,这通常是不可取的(因为列的顺序可能会改变,为错误铺平道路),但是在某些特定情况下(例如,当您需要快速完成工作并且不存在列更改顺序的风险时),这可能是最明智的做法,尤其是在处理大量列时。
在这种情况下,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
## (...)
为了完整起见:您还可以使用 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
data.frame
上使用基准的价值
就像很久以前的机械卡片分类器一样,首先按最不重要的键排序,然后是下一个最重要的键,等等。不需要库,可以使用任意数量的键以及升序和降序键的任意组合。
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
包中找到了一个非常有用的函数,可以直接执行此操作:
dfOrder(myDf, columnIndices)
其中 columnIndices
是一列或多列的索引,按照您要对它们进行排序的顺序。更多信息在这里:
dfOrder function from 'psych' package
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"]),]
在两列上对其进行排序。dd[ order(-dd[,4], dd[,1]), ]
,但不能将with
用于基于名称的子集。dd[ order(-dd[,4],, ]
无效或 'dd[ order(-dd[,4], ]' 基本上为什么需要dd[,1]
?如果您只想按 1 列排序,-dd[,4]
还不够吗?xtfrm
中来解决它,例如dd[ order(-xtfrm(dd[,4]), dd[,1]), ]
。