首页 文章

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

提问于
浏览
95

我正在使用data.table,并且有许多功能要求我设置密钥(例如 X[Y] ) . 因此,我希望了解密钥在我的数据表中正确设置密钥的作用 .


我读到的一个来源是 ?setkey .

setkey()对data.table进行排序并将其标记为已排序 . 排序列是关键 . 密钥可以是任何顺序的任何列 . 列始终按升序排序 . 该表通过引用更改 . 除了临时工作内存大到一列之外,根本不会复制 .

我在这里得到的是,一个键将"sort" data.table,导致与 order() 非常相似的效果 . 但是,它没有解释拥有密钥的目的 .


data.table FAQ 3.2和3.3解释了:

3.2我没有大 table 上的钥匙,但分组仍然非常快 . 这是为什么? data.table使用基数排序 . 这明显快于其他排序算法 . Radix仅用于整数,参见?base :: sort.list(x,method =“radix”) . 这也是setkey()快速的一个原因 . 如果没有设置密钥,或者我们按照与密钥不同的顺序进行分组,我们称之为ad hoc . 3.3为什么密钥中的列按比临时分组更快?因为每个组在RAM中是连续的,从而最小化页面提取,并且可以批量复制内存(在C中为memcpy)而不是在C中循环 .

从这里开始,我猜设置一个键以某种方式允许R使用“基数排序”而不是其他算法,这就是为什么它更快 .


10分钟的快速入门指南还有一个按键指南 .

键让我们从考虑data.frame,特别是rownames(或英文,行名)开始 . 也就是说,属于单行的多个名称 . 属于单行的多个名称?这不是我们在data.frame中习惯的 . 我们知道每行最多只有一个名称 . 一个人至少有两个名字,第一个名字和第二个名字 . 这对于组织电话目录很有用,例如,按姓氏排序,然后是第一个名称 . 但是,data.frame中的每一行只能有一个名称 . 一个键由一列或多列rownames组成,可以是整数,因子,字符或其他类,而不仅仅是字符 . 此外,行按键排序 . 因此,data.table最多只能有一个键,因为它不能以多种方式排序 . 不强制执行唯一性,即允许重复的键值 . 由于行按键排序,因此键中的任何重复项都将连续出现

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

2 回答

  • 105

    密钥基本上是数据集的索引,它允许非常快速和有效的排序,过滤和连接操作 . 这些可能是使用数据表而不是数据帧的最佳理由(使用数据表的语法也更加用户友好,但这与键无关) .

    如果您不理解索引,请考虑以下因素:电话簿按名称“编入索引” . 因此,如果我想查找某人的电话号码,那就非常简单了 . 但是假设我想通过电话号码搜索(例如,查找具有特定电话号码的人)?除非我可以通过电话号码“重新索引”电话簿,否则需要很长时间 .

    请考虑以下示例:假设我有一张表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周 .

    恕我直言,你不应该只阅读 study 常见问题和介绍材料 . 如果您有实际问题,将更容易掌握 .

    [回应@Frank's评论]

    Re: sorting vs. indexing - 根据对this question的回答,看来 setkey(...) 实际上确实重新排列了表中的列(例如,物理排序),并且没有在数据库意义上创建索引 . 这有一些实际意义:首先,如果您使用 setkey(...) 在表中设置键然后更改键列中的任何值,data.table只是声明表不再排序(通过关闭 sorted 属性) );它会动态重新索引以维持正确的排序顺序(就像在数据库中一样) . 此外,"removing the key"使用 setky(DT,NULL) 确实 not 将表恢复为原始的未排序顺序 .

    Re: filter vs. join - 实际差异在于过滤从单个数据集中提取子集,而join则基于公共字段组合来自两个数据集的数据 . 有许多不同种类的连接(内部,外部,左侧) . 上面的示例是内部联接(仅返回具有两个表共用的键的记录),这与筛选有许多相似之处 .

  • 17

    次要更新:请参阅new HTML vignettes . This issue突出了我们计划的其他小插曲 .


    我已经根据允许ad-hoc连接的新 on= 功能再次更新了这个答案(2016年2月) . 查看早期(过时)答案的历史记录 .

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

    它做了两件事:

    • 按引用提供的(a,b)列重新排序data.table DT 的行,始终按递增顺序排列 .

    • 通过将名为 sorted 的属性设置为 DT 将这些列标记为键列 .

    重新排序既快又快(由于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
    

    但是,在 v1.9.6 之前, x[i] 形式的连接需要在 x 上设置 key . With the new on= argument from 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")]
    

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

    需要键绝对设置的唯一操作是foverlaps()函数 . 但我们正在研究一些更多的功能,这些功能在完成时将删除此要求 .

    • So what's the reason for implementing on= argument?

    有很多原因 .

    • 它允许将操作清楚地区分为涉及两个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")]
    

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

    • 否则,除非您重复执行连接,否则键控连接和临时连接之间应该没有明显的性能差异 .

    这就引出了一个问题,即将数据关键字有什么优势?

    • Is there an advantage to keying a data.table?

    键入data.table会根据RAM中的那些列对其进行物理重新排序 . 计算订单通常不是耗时的部分,而是重新排序本身 . 但是,一旦我们__77925_ s加速键控data.tables操作的排序 .

    因此,必须弄清楚重新排序整个data.table所花费的时间是否值得花时间进行缓存高效的连接/聚合 . 通常,除非在相同的键控数据表上执行重复的分组/连接操作,否则不应存在明显的差异 .

    因此,在大多数情况下,不再需要设置键 . 我们建议尽可能使用on =,除非设置键在性能方面有显着改进,您希望利用它 .

    Question: 如果使用 setorder() 重新排序data.table并使用 on= ,您认为与键控连接相比的性能如何?如果你到目前为止,你应该能够弄清楚:-) .

相关问题