我想按多列对数据帧进行排序。例如,对于下面的数据帧,我希望按列“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

当前回答

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

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

其他回答

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

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

您可以直接使用order()函数,而无需使用附加工具——请参阅这个更简单的答案,它使用了示例(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

两年多后编辑:只是被问到如何按列索引进行编辑。答案是简单地将所需的排序列传递给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()可以更方便/更直接地访问)。

德克的回答很好。它还强调了用于索引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)]

这两个调用之间的差异很小,但可能会产生重要的后果。特别是如果您编写生产代码和/或关注研究中的正确性,最好避免不必要的重复变量名。数据表帮助您做到这一点。

下面是一个重复变量名称可能会给您带来麻烦的示例:

让我们从Dirk的答案中改变上下文,并说这是一个更大项目的一部分,其中有很多对象名称,它们很长,很有意义;而不是dd,它被称为季度报告。它变成:

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

好的,好的。这没什么错。接下来,你的老板要求你在报告中包括上一季度的报告。你通过代码,在不同的地方添加一个对象最后一个季度报告,不知怎么的(到底是怎么回事?)你最终得到了这样的结果:

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

这不是你的意思,但你没有发现它,因为你做得很快,而且它位于一个类似代码的页面上。代码不会出错(没有警告和错误),因为R认为这就是你的意思。你希望无论谁读你的报告都能发现,但也许他们没有。如果您经常使用编程语言,那么这种情况可能非常熟悉。你会说这是个“错别字”。我会纠正你对老板说的“错别字”。

在data.table中,我们关注像这样的微小细节。因此,我们做了一些简单的操作,以避免两次键入变量名。非常简单的事情。我已经在dd的框架内自动评估了。你根本不需要with()。

而不是

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

只是

dd[order(-z, b)]

而不是

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

只是

quarterlyreport[order(-z,b)]

这是一个很小的区别,但也许有一天它会拯救你的脖子。在权衡这个问题的不同答案时,考虑将变量名称的重复次数作为决定的标准之一。有些答案有相当多的重复,其他答案没有。

R包data.table使用简单的语法(Matt在回答中很好地强调了这一点)提供了data.table的快速排序和内存高效排序。从那时起,已经有了很多改进,并且有了一个新的函数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现在按照我们要求的顺序(通过引用更新)。

数据表功能:

速度:

表的排序非常快,因为它实现了基数排序。语法DT[order(…)]在内部进行了优化,以使用data.table的快速排序。您可以继续使用熟悉的基本R语法,但可以加快处理速度(并且使用更少的内存)。

内存:

大多数时候,我们不需要重新排序后的原始data.frame或data.table。也就是说,我们通常将结果分配回同一个对象,例如:DF<-DF[订单(…)]问题是,这至少需要原始对象的两倍(2倍)内存。为了提高内存效率,data.table还提供了一个函数setorder()。setorder()通过引用(就地)重新排序data.tables,而无需创建任何其他副本。它只使用相当于一列大小的额外内存。

其他功能:

它支持整数、逻辑、数字、字符和偶数位64::integer64类型。请注意,factor、Date、POSIXct等.类下面都是带有附加属性的整数/数字类型,因此也受支持。在基R中,我们不能使用字符向量上的-按该列降序排序。相反,我们必须使用-xtfrm(.)。然而,在data.table中,我们可以只做dat[order(-x)]或setorder(dat,-x)。

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

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

此示例之所以有效,唯一的原因是顺序是按向量Age排序,而不是按数据帧数据中名为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)

由于没有名为age的向量,上述order的行结构不再有效:

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

以下行之所以有效,是因为顺序根据my.data中的列年龄排序。

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