在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分钟快速入门指南还有一个关键指南。
- 按键
首先考虑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)
做什么的?
它有两件事情:
- 通过引用提供的列( a , b )将data.table
DT
的行重新sorting,总是以递增的顺序。 - 通过设置一个名为
sorted
到DT
的属性,将这些列标记为关键列。
重新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的原因是什么?有很多原因。
-
它允许将操作清楚地区分为涉及两个data.tables的操作。 只是做
X[Y]
不能区分这一点,虽然可以通过适当地命名variables来清楚。 -
它还允许通过查看该行代码(而不必回溯到相应的
setkey()
行)来立即理解正在执行连接/子集的列。 -
在通过引用添加或更新列的操作中,
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,避免这种情况,我们保留原始顺序,同时也是高性能的。
-
除此之外,除非重复执行连接,否则键控连接和特殊连接之间应该没有明显的性能差异。
-
这导致了一个问题,键入一个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被返回),这与过滤有很多相似之处。