How to sort a dataframe by multiple column(s)
我想按多列对data.frame排序。例如,在下面的data.frame中,我希望按列
1 2 3 4 5 6 7 8 9 10 | 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 |
您可以直接使用
1 2 3 4 5 6 | 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年后:刚刚有人问如何通过列索引来实现这一点。答案是简单地将所需的排序列传递给
1 2 3 4 5 6 7 | 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> |
而不是使用列的名称(和
你的选择
- 来自
base 的order 。 - 来自
dplyr 的arrange 。 setorder 和setorderv 来自data.table 。- 来自
plyr 的arrange 。 - 来自
taRifx 的sort 。 - 来自
doBy 的orderBy 。 - 来自
Deducer 的sortData 。
大多数情况下,您应该使用
我最近在cran包中添加了sort.data.frame,使其与这里讨论的类兼容:为sort.data.frame创建通用/方法一致性的最佳方法?
因此,给定data.frame dd,您可以按以下方式排序:
1 2 3 4 5 6 | 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/script/message/1094290 1094290
您也可以使用来自
1 2 | library(plyr) arrange(dd,desc(z),b) |
基准测试:请注意,我在新的R会话中加载了每个包,因为有很多冲突。尤其是加载doby包会导致
1 2 3 4 5 6 7 8 9 10 11 12 | #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 ) |
中位数:
江东十一〔41〕778
埃多克斯1〔42〕788
1 2 | library(taRifx) microbenchmark(sort(dd, f= ~-z+b ),times=1000) |
中位数时间:1567
1 2 | library(plyr) microbenchmark(arrange(dd,desc(z),b),times=1000) |
中位数时间:862
1 2 | library(doBy) microbenchmark(orderBy(~-z+b, data=dd),times=1000) |
中位数时间:1694
请注意,多比需要很长的时间来加载包。
1 2 | library(Deducer) microbenchmark(sortData(dd,c("z","b"),increasing= c(FALSE,TRUE)),times=1000) |
无法进行演绎加载。需要Jgr控制台。
1 2 3 4 5 6 7 8 | esort <- function(x, sortvar, ...) { attach(x) x <- x[with(x,order(sortvar,...)),] return(x) detach(x) } microbenchmark(esort(dd, -z, b),times=1000) |
由于附加/分离,似乎与微基准不兼容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 2 3 4 5 | ## 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)] |
这两个电话之间的差别很小,但可能会产生重要的后果。尤其是如果您编写生产代码和/或关心您的研究中的正确性,最好避免不必要的变量名重复。
下面是一个例子,说明变量名的重复可能会给您带来麻烦:
让我们从德克的回答中改变上下文,说这是一个更大的项目的一部分,其中有很多对象名,它们很长而且有意义;而不是
1 | quarterlyreport[with(quarterlyreport,order(-z,b)),] |
好的,很好。没什么问题。接下来,你的老板要求你在报告中包括上个季度的报告。你通过你的代码,在不同的地方添加一个对象
1 | quarterlyreport[with(lastquarterlyreport,order(-z,b)),] |
这不是你的意思,但你没有发现它,因为你做得很快,而且它依偎在一页类似的代码上。代码不会失效(没有警告也没有错误),因为R认为这就是您的意思。你希望阅读你的报告的人能发现它,但也许他们不会。如果你经常使用编程语言,那么这种情况可能是大家都熟悉的。你会说这是个"打字错误"。我会改正你对你老板说的"打字错误"。
在
而不是
1 | dd[with(dd, order(-z, b)), ] |
只是
1 | dd[order(-z, b)] |
而不是
1 | quarterlyreport[with(lastquarterlyreport,order(-z,b)),] |
只是
1 | quarterlyreport[order(-z,b)] |
这是一个很小的区别,但有一天它可能会挽救你的脖子。在权衡这个问题的不同答案时,考虑将变量名的重复次数作为决定的标准之一。有些答案有很多重复,有些则没有。
这里有很多很好的答案,但是dplyr给出了我能快速且容易记住的唯一语法(因此现在经常使用):
1 2 3 4 5 | 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的问题:
1 2 3 4 5 6 7 | 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的特性。
数据:1 2 3 4 5 6 7 8 9 10 11 12 | 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) |
Benchmarks:
报告的计时来自于在下面显示的这些函数上运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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(...)] 在内部进行了优化以使用数据。表的排序也很快。您可以继续使用熟悉的baser语法,但可以加快进程(并使用较少的内存)。
记忆:
大多数情况下,重新排序后不需要原始data.frame或data.table。也就是说,我们通常将结果分配回相同的对象,例如:
1DF <- DF[order(...)]问题是这至少需要原始对象的两倍(2x)内存。因此,为了节省内存,data.table还提供了一个函数
setorder() 。setorder() 重新排序数据。表by reference 已就位,无需制作任何额外副本。它只使用等于一列大小的额外内存。
其他特点:
它支持
Note that
factor ,Date ,POSIXct etc.. classes are allinteger /numeric types underneath with additional attributes and are therefore supported as well.
在R基中,不能使用字符向量上的
但是,在data.table中,我们可以这样做,例如,
KevinWright的这个(非常有用的)功能发布在R wiki的Tips部分,很容易实现。
1 2 3 4 5 6 | 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 |
或者你可以用包裹多比
1 2 | library(doBy) dd <- orderBy(~-z+b, data=dd) |
假设您有一个
1 | newdata <- A[order(-A$x),] |
如果你想要升序,那就把
1 | newdata <- A[order(-A$x, A$y, -A$z),] |
其中
如果您自然而然地会遇到SQL,那么sqldf会按照codd的预期处理order by。
或者,使用包演绎器
1 2 | library(Deducer) dd<- sortData(dd,c("z","b"),increasing= c(FALSE,TRUE)) |
我用下面的例子了解了
1 2 3 4 5 6 7 8 9 10 11 | 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 |
这个例子之所以有效是因为
要了解这一点,请使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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) |
由于没有名为
1 | databyage = my.data[order(age),] |
下一行工作是因为
1 | databyage = my.data[order(my.data$age),] |
考虑到我对这个例子困惑了这么久,我认为这是值得发表的。如果这篇文章不适合这个线程,我可以删除它。
编辑:2014年5月13日
下面是按每列对数据帧进行排序而不指定列名的通用方法。下面的代码显示了如何从左到右或从右到左排序。如果每一列都是数字的,这是有效的。我没有尝试添加字符列。
一两个月前,我在一个不同的网站上的一篇旧文章中找到了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
为了响应在操作中添加的关于如何以编程方式排序的注释:
使用
1 2 | library(dplyr) library(data.table) |
DPLYR
只需使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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
最好使用公式,因为它还捕获环境以在
数据表1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
Dirk的回答很好,但是如果需要保持排序,您将希望将排序应用到该数据帧的名称上。使用示例代码:
1 | dd <- dd[with(dd, order(-z, b)), ] |
dplyer中的arrange()是我最喜欢的选项。使用管道操作员,从最不重要的方面到最重要的方面
1 2 3 | dd1 <- dd %>% arrange(z) %>% arrange(desc(x)) |
为了完整起见:您还可以使用
1 2 3 4 5 6 7 | 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 |
性能比较:
1 2 3 4 5 6 7 8 9 10 | 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 |
就像很久以前的机械卡片分类机一样,首先按最不重要的键分类,然后按次最重要的键分类,等等。不需要库,可以使用任意数量的键和任何升序和降序键的组合。
1 | dd <- dd[order(dd$b, decreasing = FALSE),] |
现在我们准备好做最重要的事情了。排序是稳定的,最重要的键中的任何关系都已解决。
1 | dd <- dd[order(dd$z, decreasing = TRUE),] |
这可能不是最快的,但肯定是简单可靠的
另一种选择,使用
1 2 3 4 5 6 7 | > 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 |
只是为了完整性,因为关于按列号排序的讨论不多…可以肯定的是,这通常是不可取的(因为列的顺序可能会改变,从而导致错误),但在某些特定情况下(例如,当您需要快速完成一项工作,并且不存在列更改顺序的风险),这可能是最明智的做法,尤其是在处理大量的F列。
在这种情况下,
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 ## (...) |
当我想自动化N列的排序过程时,我正努力使用上述解决方案,因为N列的列名每次都可能不同。我从
1 | dfOrder(myDf, columnIndices) |
其中,
"psych"包中的dforder函数