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

时间:2013-11-18 02:56:50

标签: r data.table

我正在使用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分钟的快速入门指南还有一个按键指南。

  
      
  1.         

    让我们首先考虑data.frame,特别是rownames(或者   英文,行名)。也就是说,属于单个的多个名称   行。属于单行的多个名称?那不是什么   我们习惯于data.frame。我们知道每行最多只有一行   名称。一个人至少有两个名字,第一个名字和第二个名字。   这对于组织电话目录很有用,例如,哪个   按姓氏排序,然后是rst姓名。但是,每一行都有一个   data.frame只能有一个名称。

         

    密钥由一个或多个组成   rownames的列,可以是整数,因子,字符或一些   其他课程,不仅仅是性格。此外,行按排序   钥匙。因此,data.table最多只能有一个键,因为它   不能以多种方式排序。

         

    未强制执行唯一性,   即,允许重复的键值。由于行按排序   密钥,密钥中的任何重复项将连续出现

电话簿有助于理解密钥是什么,但与具有因子列相比,似乎密钥没有区别。此外,它没有解释为什么需要密钥(特别是使用某些功能)以及如何选择要设置为密钥的列。此外,似乎在data.table中将time作为列,将任何其他列设置为键也可能会使时间列混乱,这使得它更加混乱,因为我不知道是否允许将任何其他列设置为键。有人可以开导我吗?

2 个答案:

答案 0 :(得分:114)

次要更新:请同时参阅new HTML vignettesThis issue突出了我们计划的其他小插曲。


我已根据允许 ad-hoc 加入的新on=功能再次更新此答案(2016年2月)。查看早期(过时)答案的历史记录。

setkey(DT, a, b)究竟做了什么?

它做了两件事:

  1. 按提供的列重新排序 data.table DT的行( a b )< em>通过引用,始终以增加顺序。
  2. 通过将名为sorted的属性设置为DT,将这些列标记为列。
  3. 重新排序既快又快(由于 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=参数的原因是什么?

      有很多原因。

      1. 它允许将操作清楚地区分为涉及两个 data.tables 的操作。只是做X[Y]并没有区分这一点,尽管通过适当地命名变量可以很清楚。

      2. 它还允许通过查看该行代码来理解立即执行 join / subset 的列(而不必追溯到相应的{{1} } line)。

      3. 在通过引用添加或更新列的操作中,setkey()操作的性能要高得多,因为它不需要将整个data.table重新排序到添加/更新列。例如,

        on=

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

      4. 即使不这样做,除非您重复执行连接,否则键控 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:过滤器与连接 - 实际差异在于过滤从单个数据集中提取子集,而连接则根据公共字段组合来自两个数据集的数据。有许多不同种类的连接(内部,外部,左侧)。上面的例子是一个内连接(只返回两个表共有的键的记录),这与过滤有许多相似之处。