我正在使用data.table,并且有许多功能要求我设置密钥(例如X[Y]
)。因此,我希望了解密钥在我的数据表中正确设置密钥的作用。
我读过的一个来源是?setkey
。
setkey()
对data.table
进行排序并将其标记为已排序。排序列是关键。密钥可以是任何顺序的任何列。列始终按升序排序。该表通过引用更改。除了临时工作内存和一列之外,根本不会复制。
我的意思是,密钥会对data.table进行“排序”,从而产生与order()
非常相似的效果。但是,它没有解释拥有密钥的目的。
data.table FAQ 3.2和3.3解释:
3.2我没有大表上的密钥,但分组仍然非常快。那是为什么?
data.table使用基数排序。这明显快于其他人 排序算法。 Radix仅用于整数,请参阅
?base::sort.list(x,method="radix")
。这也是原因之一setkey()
很快。没有设置密钥时,或者我们按不同的顺序分组 从密钥的角度来看,我们将其称为临时性的。3.3为什么密钥中的列分组比ad hoc更快?
因为每个组在RAM中是连续的,因此最小化页面 提取和内存可以批量复制(C中为
memcpy
)而不是 在C中循环。
从这里开始,我想设置一个键以某种方式允许R使用“基数排序”而不是其他算法,这就是它更快的原因。
10分钟的快速入门指南还有一个按键指南。
- 键
醇>让我们首先考虑data.frame,特别是rownames(或者 英文,行名)。也就是说,属于单个的多个名称 行。属于单行的多个名称?那不是什么 我们习惯于data.frame。我们知道每行最多只有一行 名称。一个人至少有两个名字,第一个名字和第二个名字。 这对于组织电话目录很有用,例如,哪个 按姓氏排序,然后是rst姓名。但是,每一行都有一个 data.frame只能有一个名称。
密钥由一个或多个组成 rownames的列,可以是整数,因子,字符或一些 其他课程,不仅仅是性格。此外,行按排序 钥匙。因此,data.table最多只能有一个键,因为它 不能以多种方式排序。
未强制执行唯一性, 即,允许重复的键值。由于行按排序 密钥,密钥中的任何重复项将连续出现
电话簿有助于理解密钥是什么,但与具有因子列相比,似乎密钥没有区别。此外,它没有解释为什么需要密钥(特别是使用某些功能)以及如何选择要设置为密钥的列。此外,似乎在data.table中将time作为列,将任何其他列设置为键也可能会使时间列混乱,这使得它更加混乱,因为我不知道是否允许将任何其他列设置为键。有人可以开导我吗?
答案 0 :(得分:114)
次要更新:请同时参阅new HTML vignettes。 This issue突出了我们计划的其他小插曲。
我已根据允许 ad-hoc 加入的新on=
功能再次更新此答案(2016年2月)。查看早期(过时)答案的历史记录。
setkey(DT, a, b)
究竟做了什么?它做了两件事:
DT
的行( a , b )< em>通过引用,始终以增加顺序。sorted
的属性设置为DT
,将这些列标记为键列。重新排序既快又快(由于 data.table 的内部基数排序)和内存效率(只分配了一个 double 类型的额外列)。 / p>
setkey()
?对于分组操作,setkey()
绝不是绝对要求。也就是说,我们可以执行 cold-by 或 adhoc-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]
表单的联接需要在key
上设置x
。 使用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()函数。但是我们正在开发更多功能,这些功能在完成时将删除此要求。
那么实施on=
参数的原因是什么?
有很多原因。
它允许将操作清楚地区分为涉及两个 data.tables 的操作。只是做X[Y]
并没有区分这一点,尽管通过适当地命名变量可以很清楚。
它还允许通过查看该行代码来理解立即执行 join / subset 的列(而不必追溯到相应的{{1} } line)。
在通过引用添加或更新列的操作中,setkey()
操作的性能要高得多,因为它不需要将整个data.table重新排序到添加/更新列。例如,
on=
在第二种情况下,我们没有重新排序。它不是计算耗时的顺序,而是在RAM中对data.table进行物理重新排序,并且通过避免它,我们保留原始顺序,并且它也具有高性能。
即使不这样做,除非您重复执行连接,否则键控和 ad-hoc 连接之间应该没有明显的性能差异。
这导致了一个问题, data.table 有什么优势?
键入data.table是否有优势?
键入 data.table 根据RAM中的那些列对其进行物理重新排序。计算订单通常不是耗时的部分,而是重新排序本身。但是,一旦我们将数据排序到RAM中,属于同一组的行在RAM中都是连续的,因此非常有效。这是加速关键数据操作的排序。表。
因此,必须弄清楚重新排序整个data.table所花费的时间是否值得花时间进行缓存高效的连接/聚合。通常,除非在相同的键控 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")]
,除非设置密钥在您要利用的性能方面有显着提升。
问题:如果使用on=
重新排序数据,您认为与键控加入相比的效果如何? .table 并使用setorder()
?如果你到目前为止,你应该能够弄清楚: - )。
答案 1 :(得分:19)
键基本上是数据集的索引,它允许非常快速有效的排序,过滤和连接操作。这些可能是使用数据表而不是数据帧的最佳理由(使用数据表的语法也更加用户友好,但这与密钥无关)。
如果您不了解索引,请考虑以下事项:电话簿按名称“编入索引”。因此,如果我想查找某人的电话号码,那就非常简单了。但是假设我想通过电话号码搜索(例如,查找具有特定电话号码的人)?除非我可以通过电话号码“重新索引”电话簿,否则需要很长时间。
考虑以下示例:假设我有一张表ZIP,其中包含美国所有邮政编码(> 33,000)以及相关信息(城市,州,人口,收入中位数等)。如果我想查找特定邮政编码的信息,那么如果我setkey(ZIP,zipcode)
首先搜索(过滤器)的速度要快1000倍。
另一个好处与连接有关。假设a在数据表中有一个人员及其邮政编码列表(称之为“PPL”),我想从ZIP表中添加信息(例如城市,州等)。以下代码将执行此操作:
setkey(ZIP,zipcode)
setkey(PPL,zipcode)
full.info <- PPL[ZIP, nomatch=F]
这是一个“加入”,因为我正在加入来自基于公共字段(zipcode)的2个表的信息。在非常大的表上加入像这样的数据帧非常慢,并且数据表非常快。在一个现实生活中的例子中,我必须在完整的邮政编码表上完成超过20,000个这样的连接。使用数据表,脚本大约需要20分钟。跑步。我甚至没有尝试使用数据帧,因为它需要超过2周。
恕我直言,你不应该只阅读学习常见问题解答和介绍材料。如果你有一个实际的问题,那就更容易掌握。
[回应@弗兰克的评论]
重新:排序与索引 - 根据this question的答案,setkey(...)
实际上确实重新排列了表格中的列(例如,物理sort),并且不会在数据库意义上创建索引。这有一些实际意义:首先,如果你在一个表中设置了setkey(...)
的密钥然后更改了键列中的任何值,data.table只是声明表不再排序(通过转动关闭sorted
属性);它不动态重新索引以维持正确的排序顺序(就像在数据库中一样)。此外,使用setky(DT,NULL)
“删除密钥”会使不将表恢复为原始的未分类顺序。
Re:过滤器与连接 - 实际差异在于过滤从单个数据集中提取子集,而连接则根据公共字段组合来自两个数据集的数据。有许多不同种类的连接(内部,外部,左侧)。上面的例子是一个内连接(只返回两个表共有的键的记录),这与过滤有许多相似之处。