在 data.table 中设置键的目的是什么?

我使用的是 data.table,有很多函数需要我设置一个键(例如 X[Y])。因此,我希望了解键的作用,以便在我的数据表中正确设置键。


我读到的一个来源是 ?setkey

setkey()data.table进行排序并将其标记为已排序。已排序的列是键。键可以是任何顺序的任何列。列总是按升序排序。该表通过引用更改。除了一列大小的临时工作内存外,根本不进行任何复制。

My takeaway here is that a key would "sort" the data.table, resulting in a very similar effect to order(). However, it doesn't explain the purpose of having a key.


Table FAQ 3.2和3.3解释道:

3.2我没有大桌子上的密钥,但是分组还是很快的。为什么呢?

Table 使用基数排序 基数是专门用于整数的,请参见 这也是原因之一 setkey()是快速的。当没有设置键时,或者我们按照不同的顺序分组 我们称之为 ad hoc by。

3.3为什么按键中的列分组比临时分组快?

Because each group is contiguous in RAM, thereby minimising page 可以批量复制内存(C 中的 memcpy) ,而不是 在 C 中循环。

从这里开始,我猜想设置一个键可以让 R 在其他算法之上使用“基数排序”,这就是为什么它更快的原因。


10分钟快速启动指南也有一个关于键的指南。

  1. 钥匙

让我们首先考虑 data.frame,特别是行名(或 in English, row names). That is, the multiple names belonging to a single 行的多个名称? 这不是什么 我们习惯于在一个 data.frame 中,我们知道每行最多只有一个 一个人至少有两个名字,第一个名字和第二个名字。 举例来说,举办一个电话簿是很有用的 按姓氏排序,然后按名字排序 Frame 只能有一个名称。

一个密钥由一个或多个组成 columns of rownames, which may be integer, factor, character or some 其他类,而不仅仅是字符。此外,行是按照 因此,data.table 最多只能有一个键,因为它 不能以多种方式进行排序。

独特性不是强制的, 例如,允许重复的键值 密钥,密钥中的任何重复都会连续出现

这个电话簿有助于理解什么是密钥,但似乎密钥与有一个因子列没有什么不同。此外,它没有解释为什么需要键(特别是为了使用某些函数)以及如何选择要设置为键的列。另外,在 data.table 中,把 time 作为一个列,把任何其他列设置为 key 可能也会把 time 列搞乱,这使得它更加令人困惑,因为我不知道是否允许把任何其他列设置为 key。有人能给我解释一下吗?

47290 次浏览

键基本上是数据集的索引,它支持非常快速有效的排序、筛选和连接操作。这些可能是使用数据表而不是数据框架的最佳理由(使用数据表的语法也更加用户友好,但这与键无关)。

如果您不了解索引,请考虑这一点: 电话簿是按名称“索引”的。所以如果我想查一个人的电话号码很简单。但是假设我想通过电话号码进行搜索(例如,查找谁有一个特定的电话号码) ?除非我可以通过电话号码“重新索引”电话簿,否则这将需要很长的时间。

考虑下面的例子: 假设我有一个美国所有邮政编码(> 33,000)的邮政编码表(ZIP)以及相关信息(城市、州、人口、中位收入等)。如果我想查找特定邮政编码的信息,搜索(过滤器)是大约1000倍的速度,如果我 setkey(ZIP, zipcode)第一。

另一个好处与连接有关。假设在一个数据表中有一个人员和他们的邮政编码列表(称为“ PPL”) ,我想从 ZIP 表中追加信息(例如城市、州等)。下面的代码可以做到这一点:

setkey(ZIP, zipcode)
setkey(PPL, zipcode)
full.info <- PPL[ZIP, nomatch = FALSE]

从某种意义上说,这是一个“连接”(join) ,即我将基于一个公共字段(zipcode)的2个表中的信息连接起来。在非常大的表上这样的连接对于数据帧来说是非常慢的,对于数据表来说则是非常快的。在一个真实的示例中,我必须在一个完整的邮政编码表中执行超过20,000个这样的连接。对于数据表,脚本大约需要20分钟。逃跑。我甚至没有尝试与数据帧,因为这将需要超过2个星期。

恕我直言,你不应该只是阅读,但 学习的常见问题和介绍材料。如果您有一个实际的问题要应用它,那么更容易理解。

[回应@Frank 的评论]

Re: 排序与索引-基于对 这个问题的回答,看起来 setkey(...)确实重新排列了表中的列(例如,物理排序) ,并且没有创建数据库意义上的索引。这有一些实际的含义: 首先,如果你用 setkey(...)设置表中的键,然后改变键列中的任何值,data.table 只是声明表不再排序(通过关闭 sorted属性) ; 它会动态地重新索引以维护正确的排序顺序(就像在数据库中发生的那样)。而且,使用 setkey(DT, NULL)“移除键”的 没有会将表恢复到原来的未排序顺序。

Re: 过滤器与联接-实际的区别在于,过滤从单个数据集提取子集,而 join 基于一个公共字段组合来自两个数据集的数据。有许多不同类型的连接(内、外、左)。上面的示例是一个内部连接(只返回具有两个表共同键的记录) ,这与筛选有许多相似之处。

除了这个答案,请参考小品 二次索引与自动索引基于键和快速二进制搜索的子集以及。

This issue highlights the other vignettes that we plan to.


I've updated this answer again (Feb 2016) in light of the new on= feature that allows 临时的 joins as well. See history for earlier (outdated) answers.

setkey(DT, a, b)到底是做什么的?

它有两个作用:

  1. 根据所提供的(B) 参考资料列对 Data.table DT的行进行重新排序,排序顺序始终为 越来越多
  2. 通过将名为 sorted的属性设置为 DT,将这些列标记为 钥匙列。

重新排序既快(由于 Data.table的内部基数排序)又高效(只分配了一个额外的 双倍类型列)。

什么时候需要 setkey()

对于分组操作,setkey()从来都不是绝对的要求。也就是说,我们可以执行 cold-by临时搭建的

## "cold" by
require(data.table)
DT <- data.table(x=rep(1:5, each=2), y=1:10)
DT[, mean(y), by=x] # no key is set, order of groups preserved in result

然而,在 v1.9.6之前,表格 x[i]的连接需要在 x上设置 key使用 v1.9.6 + 中的新 on=参数,这不再是真实的,因此设置键是 没有在这里也是一个绝对的要求。

## joins using < v1.9.6
setkey(X, a) # absolutely required
setkey(Y, a) # not absolutely required as long as 'a' is the first column
X[Y]


## joins using v1.9.6+
X[Y, on="a"]
# or if the column names are x_a and y_a respectively
X[Y, on=c("x_a" = "y_a")]

注意,即使对于 keyed连接也可以显式指定 on=参数。

唯一需要完全设置 key的操作是 重叠()函数。但是我们正在开发一些更多的特性,这些特性一旦完成就可以消除这个需求。

  • 那么,实现 on=参数的原因是什么?

    原因有很多。

    1. 它允许清楚地区分涉及两个 Data.tables的操作。仅仅执行 X[Y]并不能很好地区分这一点,尽管通过适当地命名变量可以清楚地看到这一点。

    2. 它还允许通过查看代码行(而不必回溯到相应的 setkey()行)来理解 连接/子集正在立即执行的列。

    3. 在添加或更新 参考资料列的操作中,on=操作的性能要高得多,因为它不需要仅仅为了添加/更新列而对整个 data.table 进行重新排序。比如说,

       ## compare
      setkey(X, a, b) # why physically reorder X to just add/update a column?
      X[Y, col := i.val]
      
      
      ## to
      X[Y, col := i.val, on=c("a", "b")]
      

      在第二种情况下,我们不必重新订购。计算顺序并不耗费时间,而是在 RAM 中对 data.table 进行物理重新排序,通过避免这种情况,我们保留了原来的顺序,而且它也具有性能。

    4. 即使在其他情况下,除非重复执行连接,否则 keyed临时的连接之间的性能应该没有明显的差异。

这就引出了一个问题,键入 Data.table还有什么优势?

  • Is there an advantage to keying a data.table?

    键入 Data.table会根据 RAM 中的那些列对其进行物理重新排序。计算订单通常不是耗时的部分,而是 重新排序本身。但是,一旦我们将数据在 RAM 中排序,属于同一组的行在 RAM 中都是相邻的,因此缓存效率非常高。正是排序加速了键控数据表的操作。

    因此,必须弄清楚花在重新排序整个 data.table 上的时间是否值得花时间进行高效缓存的连接/聚合。通常,除非在同一个 有钥匙 data.table 上执行重复的分组/连接操作,否则不应该有明显的差异。

因此,在大多数情况下,不再需要设置键。我们建议尽可能使用 on=,除非设置键在性能方面有明显的改进,您可以利用这一点。

问: 如果使用 setorder()Data.table进行重新排序并使用 on=,您认为与 有钥匙联接相比,性能会怎样?如果你已经跟踪了这么久,你应该能够找出答案: ——)。