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