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

我正在使用data.table,有许多function需要我设置一个键(例如X[Y] )。 因此,我希望了解一个密钥在我的数据表中正确设置密钥的function。


我读的一个来源是?setkey

setkey()data.table进行sorting并将其标记为已sorting。 sorting的列是关键。 密钥可以是任何顺序的任何列。 列总是按升序排列。 该表通过引用进行更改。 除了一列之外的临时工作记忆,完全没有拷贝。

这里我的外卖是一个关键将“sorting”data.table,导致与order()非常相似的效果。 但是,这并没有解释拥有钥匙的目的。


data.table FAQ 3.2和3.3解释:

3.2我在大桌子上没有钥匙,但分组依然很快。 这是为什么?

data.table使用基数sorting。 这比其他sortingalgorithm显着快。 基数只针对整数,参见?base::sort.list(x,method="radix") 。 这也是为什么setkey()很快的原因之一。 当没有密钥设置,或者我们按照与密钥不同的顺序进行分组时,我们称它为特设。

3.3为什么密钥中的列按分组速度快于临时密钥?

由于每个组在RAM中是连续的,因此可以最小化页面抓取,并且可以批量复制内存(C中的memcpy ),而不是在C中循环。

从这里,我猜想设置一个关键点允许R使用“基数sorting”优于其他algorithm,这就是为什么它更快。


10分钟快速入门指南还有一个关键指南。

  1. 按键

首先考虑data.frame,特别是rownames(或英文,行名)。 也就是说,属于单个行的多个名称。 属于单行的多个名字? 这不是我们习惯的data.frame。 我们知道每行至多有一个名字。 一个人至less有两个名字,一个名字和一个第二名字。 这对组织一个电话号码簿很有用,例如,按照姓氏sorting,然后名字sorting。 但是,data.frame中的每一行只能有一个名字。

一个键由一列或多列rownames组成,可以是整数,因子,字符或其他类,而不是简单的字符。 此外,行按键sorting。 因此,data.table最多只能有一个键,因为它不能以多种方式sorting。

唯一性不强制执行,即允许重复键值。 由于行按键sorting,因此键中的任何重复项都将连续出现

电话簿有助于理解一个关键是什么,但是与具有一个因子列相比,关键是没有什么不同。 而且,这并不能解释为什么需要一个密钥(特别是使用某些函数)以及如何select列来设置密钥。 另外,在一个data.table中,随着时间的推移,如果将其他列设置为key,也可能会混淆time列,这使得它更加令人困惑,因为我不知道是否允许将其他列设置为键。 有人能够赐教吗?

次要更新:请参考新的HTML小插曲 。 这个问题突出了我们计划的其他小插曲。


我已经根据新的on=function再次更新了这个答案(2016年2月),以允许ad-hoc连接。 查看更早(过期)答案的历史logging。

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

它有两件事情:

  1. 通过引用提供的列( ab )将data.table DT的行重新sorting,总是以递增的顺序。
  2. 通过设置一个名为sortedDT的属性,将这些列标记为关键列。

重新sorting是快速的(由于data.table的内部基数sorting)和内存效率(只有一个额外的types列分配)。

什么时候需要setkey()

对于分组操作, setkey()从来就不是绝对的要求。 也就是说,我们可以进行一场冷战或者近

 ## "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]forms的连接需要在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")] 

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

唯一需要key设置的操作是foverlaps()函数。 但是我们正在研究一些更多的function,这些function在完成后将会消除这个需求

  • 那么实现on= argument的原因是什么?

    有很多原因。

    1. 它允许将操作清楚地区分为涉及两个data.tables的操作。 只是做X[Y]不能区分这一点,虽然可以通过适当地命名variables来清楚。

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

    3. 通过引用添加或更新列的操作中, on=操作的性能要高得多,因为它不需要整个data.table被重新sorting以添加/更新列。 例如,

       ## 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")] 

      在第二种情况下,我们不必重新sorting。 这不是计算耗费时间的顺序,而是在RAM中对data.table进行物理重新sorting,避免这种情况,我们保留原始顺序,同时也是高性能的。

    4. 除此之外,除非重复执行连接,否则键控连接和特殊连接之间应该没有明显的性能差异。

这导致了一个问题,键入一个data.table有什么好处呢?

  • 键入data.table有没有优势?

    键入一个data.table物理重新sorting它基于RAM中的列。 计算顺序通常不是耗时的部分,而是重新sorting 。 但是,一旦我们将数据分类到RAM中,属于同一组的行在RAM中都是连续的,因此非常高效。 这是加速键控data.tables操作的分类。

    因此,重要的是要弄清楚重新sorting整个data.table花费的时间是否值得进行caching有效的join/聚合。 通常,除非在相同的键控 data.table上执行重复的分组/连接操作,否则不应该有明显的差异。

因此在大多数情况下,不需要再设置密钥。 我们build议on=任何可能的地方使用on= ,除非设置键在性能上有显着提高,您可以利用。

问题:如果使用setorder()重新sortingdata.table并使用on= ?,那么您认为性能如何与键控连接相比? 如果你到目前为止,你应该能够弄清楚:-)。

关键是基本上是一个数据集的索引,它允许非常快速和高效的sorting,过滤和连接操作。 这些可能是使用数据表而不是数据框的最好理由(使用数据表的语法也更方便用户,但与键无关)。

如果你不懂索引,可以考虑一下:电话本是按名字“索引”的。 所以,如果我想查找某人的电话号码,这是非常简单的。 但是,假设我想通过电话号码search(例如,查询谁有一个特定的电话号码)? 除非我能通过电话号码“重新索引”电话簿,否则需要很长时间。

考虑下面的例子:假设我有一张美国邮政编码(> 33,000)的邮政编码表和相关信息(城市,州,人口,收入中位数等)。 如果我想查找特定邮政编码的信息,那么如果我setkey(ZIP,zipcode) ,则search(过滤)速度将快1000倍。

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

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

这是一个“join”的意思,我join了从一个共同的领域(邮编)的2个表中的信息。 像这样在非常大的表格上joindataframe非常缓慢,数据表格速度非常快。 在一个真实的例子中,我必须在整个邮政编码表上做超过20,000个连接。 用数据表脚本花了大约20分钟。 跑步。 我甚至没有用dataframe来尝试它,因为它会花费2个星期以上。

恕我直言,你不应该只是阅读,但研究常见问题和介绍材料。 如果你有一个实际的问题来应用这个问题,那更容易掌握。

[回复@弗兰克的评论]

回复: sorting与索引 – 基于这个问题的答案,似乎setkey(...)确实重新排列表中的列(例如,物理sorting),并没有创build数据库中的索引感。 这有一些实际的含义:一方面,如果您使用setkey(...)在表中设置密钥,然后更改键列中的任何值,则data.table仅声明该表不再被sorting(通过closuressorted属性); 它不会dynamic地重新索引以维护正确的sorting顺序(就像在数据库中发生的那样)。 此外,使用setky(DT,NULL) “删除密钥” 不会将表还原为原始未sorting的顺序。

Re: 过滤与连接 – 实际区别在于过滤从一个数据集中提取了一个子集,而连接根据一个公共字段合并了来自两个数据集的数据。 有许多不同种类的连接(内部,外部,左边)。 上面的例子是一个内部联接(只有两个表共有键的logging被返回),这与过滤有很多相似之处。