【问题标题】:data.table join and j-expression unexpected behaviordata.table 连接和 j-expression 意外行为
【发布时间】:2013-04-12 04:16:22
【问题描述】:

R 2.15.0data.table 1.8.9

d = data.table(a = 1:5, value = 2:6, key = "a")

d[J(3), value]
#   a value
#   3     4

d[J(3)][, value]
#   4

我希望两者都能产生相同的输出(第二个),我相信他们应该

为了澄清这不是J 语法问题,相同的预期适用于以下(与上述相同)表达式:

t = data.table(a = 3, key = "a")
d[t, value]
d[t][, value]

我希望以上两个都返回完全相同的输出。

所以让我重新表述一下这个问题 - 为什么(data.table 这样设计)key 列会自动打印在 d[t, value] 中?

更新(基于下面的答案和 cmets): 感谢@Arun 等人,我现在了解设计原因。上面打印 key 的原因是因为每次通过 X[Y] 语法进行 data.table 合并时都会出现一个隐藏的 by,而 by是关键。以这种方式设计的原因似乎如下 - 因为在合并时必须执行 by 操作,如果您打算通过密钥执行此操作,不妨利用这一点,而不是执行另一个 by合并。

话虽如此,我相信这是一个语法设计缺陷。我阅读data.table语法d[i, j, by = b]的方式是

d,应用i 操作(是子集或合并等),然后执行j 表达式“by”b

by-without-by 打破了这种阅读并介绍了一个必须特别考虑的案例(我是在 i 上合并,by 只是合并的关键,等等)。我相信这应该是data.table 的工作——当by 等于键时,在合并的一个特定情况下使data.table 更快的值得称赞的努力应该以另一种方式完成(例如通过内部检查 by 表达式是否实际上是合并的关键)。

【问题讨论】:

  • 现在做了什么;同样,d[J(3), value := 10] 按预期工作
  • 嗯?我不认为我们彼此了解。我认为(在这种情况下)d[3, value]d[J(3), value] 应该产生相同的结果。
  • 能否请您更改此问题的标题。这种行为是 100% 预期的,并且经常被利用。
  • @Arun 想象我写了一个函数fancy_sum(x, y),它通常会计算xy 的总和,除非x 等于10,在这种情况下它会相乘.想象一下,这也是记录在案的行为,称为 product-instead-of-sum。虽然有文档记录的语法设计选择,但我认为这是一个明显的语法设计缺陷
  • @Arun,阅读 FR 底部的评论。如果这仍然没有得到重点,我不知道会怎样。你在谈论事情是如何工作的,以及为什么提供不同类别的输入会产生不同的结果,而我在谈论事情应该如何工作,以及不同类别的输入如何为这个休息时间产生不同的结果用户期望。

标签: r data.table


【解决方案1】:

编辑号码 Infinity:Faq 1.12 完全回答了您的问题:(同样有用/相关的是 FAQ 1.13,此处未粘贴)。

1.12 X[Y]和merge(X,Y)有什么区别?
X[Y] 是一个连接,使用 Y(或 Y 的键,如果有的话)作为索引来查找 X 的行。 Y[X] 是一个连接,使用 X(或 X 的键,如果有的话)作为索引来查找 Y 的行。 merge(X,Y)1 同时做这两种方式。 X[Y] 和 Y[X] 的行数通常不同;而 merge(X,Y) 和 merge(Y,X) 返回的行数是相同的。但是这忽略了要点。大多数任务都需要在连接或合并后对数据执行某些操作。 为什么要合并所有数据列,然后只使用其中的一小部分?
您可能会建议merge(X[,ColsNeeded1],Y[,ColsNeeded2]),但这需要数据子集的副本,并且需要程序员确定需要哪些列。 data.table 中的X[Y,j] 为您一步完成所有这些。当你写X[Y,sum(foo*bar)],data.table 自动检查 j 表达式以查看它使用了哪些列。它只会对这些列进行子集化;其他的被忽略。仅为 j 使用的列创建内存,并且 Y 列在每个组的上下文中享受标准的 R 回收规则。假设 foo 在 X 中,而 bar 在 Y 中(以及 Y 中的 20 个其他列)。 不是X[Y,sum(foo*bar)]比合并后子集更快地编程和运行吗?


没有回答 OP 的问题的旧答案(来自 OP 的评论),保留在这里,因为我相信它确实如此)。

当您在data.table 中为j 赋值时,例如d[, 4]d[, value]j 被评估为expression。从 data.table FAQ 1.1 访问DT[, 5](第一个常见问题解答):

因为默认情况下,与 data.frame 不同,第二个参数是在 DT 范围内计算的表达式。 5 计算为 5。

因此,在您的情况下,首先要了解的是:

d[, value] # produces a "vector"
# [1] 2 3 4 5 6

i 的查询是基本索引时,这没有什么不同

d[3, value] # produces a vector of length 1
# [1] 4

但是,当i 本身是data.table 时,这不同。来自data.table简介(第6页):

d[J(3)] # is equivalent to d[data.table(a = 3)]

在这里,您正在执行join。如果您只是执行d[J(3)],那么您将获得与该连接对应的所有列。如果你这样做,

d[J(3), value] # which is equivalent to d[J(3), list(value)]

既然你说这个答案没有回答你的问题,我会指出我相信你的“改写”问题的答案在于:---> 那么你'会只得到那一列,但由于您正在执行连接,因此键列也将被输出(因为它是基于键列的两个表之间的连接)。


编辑:在您的第二次编辑之后,如果您的问题是为什么会这样?,那么我会不情愿地(或者更确切地说是无知地)回答,Matthew Dowle 的设计是为了区分在 data.table join-based-subsetindex-based-subsetting 操作之间。

您的第二种语法相当于:

d[J(3)][, value] # is equivalent to:

dd <- d[J(3)]
dd[, value]

同样,在dd[, value] 中,j 被评估为表达式,因此您得到一个向量。


回答你第三次修改的问题:第三次,这是因为它是两个data.tables之间的JOIN,基于键列。如果我加入两个data.tables,我希望有一个data.table

来自data.table的介绍,再次:

将 data.table 传递给 data.table 子集类似于基 R 中的 A[B] 语法,其中 A 是矩阵,B 是 2 列矩阵。事实上,base R 中的 A[B] 语法启发了 data.table 包。

【讨论】:

  • 您的 cmets 帮助我确定了我的期望到底在哪里中断,因此请参阅 OP 编辑​​。照原样,这个答案对我的问题没有任何帮助。
  • 那么请随意投反对票。如果有其他人了解您的问题并且我的回答告诉我这个回答对您的问题没有任何帮助,那么我很乐意将其删除。
  • @eddi 我认为这实际上相当准确地回答了您的问题。
  • 我的问题是为什么是键打印出来的,而不是如何打印出来的解释!
  • 就像设计中的为什么,而不是如何,为什么,我真的不知道如何陈述它:)
【解决方案2】:

截至data.table 1.9.3,默认行为已更改,下面的示例产生相同的结果。要获得 by-without-by 结果,现在必须指定一个明确的by=.EACHI

d = data.table(a = 1:5, value = 2:6, key = "a")

d[J(3), value]
#[1] 4

d[J(3), value, by = .EACHI]
#   a value
#1: 3     4

这里有一个稍微复杂一点的例子来说明区别:

d = data.table(a = 1:2, b = 1:6, key = 'a')
#   a b
#1: 1 1
#2: 1 3
#3: 1 5
#4: 2 2
#5: 2 4
#6: 2 6

# normal join
d[J(c(1,2)), sum(b)]
#[1] 21

# join with a by-without-by, or by-each-i
d[J(c(1,2)), sum(b), by = .EACHI]
#   a V1
#1: 1  9
#2: 2 12

# and a more complicated example:
d[J(c(1,2,1)), sum(b), by = .EACHI]
#   a V1
#1: 1  9
#2: 2 12
#3: 1  9

【讨论】:

  • 由于这篇文章很可能会是查看此更改的地方,因此详细回答是有意义的。有时间我也会尝试编辑。
  • @Arun 我添加了一个更复杂的示例,显然可以随意编辑
  • @Arun 这个变化对我来说是一个重大的破坏。我同意,从新用户的角度来看,这可能更直观,但是,如果将 by = .EACHI 保留为向后兼容的默认值不是更好吗?至少,对依赖于 data.table 的所有其他包给予高度关注是非常重要的
【解决方案3】:

不是意外的行为,它是记录在案的行为。 Arun 在常见问题解答中做了很好的解释和演示,其中清楚地记录了这一点。

有一个功能请求 FR 1757 建议在这种情况下使用 drop 参数

实施后,您想要的行为可能会被编码

d = data.table(a = 1:5, value = 2:6, key = "a")

d[J(3), value, drop = TRUE]

【讨论】:

  • 谢谢,我现在理解设计的原因(也就是说,如果您支持 by-without-by,那么这是一个隐藏的 by,因此应该有 by 列),并且到目前为止,所有这一切(对我而言)的结论似乎是 by-without-by 是一个糟糕的语法特性(而不是手动指定 by 如果你想和 X[Y,j] 的默认行为是与X[Y][,j] 相同)。我很愿意通过一些例子来说服我。
  • by-without-byby 甚至是键控by明显。我认为额外的几个按键是值得的。如果您想进行长时间的讨论,请移至 data.table 邮件列表。如果您想评论 drop = TRUE 的功能请求,请这样做。
  • 那么该功能应该作为一个额外的选项提供,但不会以正常语法和理解为代价;无论如何,我猜我原来的问题已经回答了; fwiw 在这个 by-without-by 业务中的另一个不一致之处是隐藏的 by 列实际上在 j 表达式中不可用,因此 d[J(3), a] 失败
  • by-without-by is 普通 data.table 语法。您可以提出功能请求以删除 by-without-by,但我认为问题在于您的理解和期望,这(显然)与 data.table 作者(和大多数用户)不同步。 d[.(3), a] 是一个有趣的案例,我不确定它是如何得到结果的。如果您在i 中命名,它(可以)工作d[.(a=3), a]。也许这本身就值得一个问题(也许在邮件列表中)
  • @mnel +1 表示 FR#2693。将尝试并实施。
【解决方案4】:

我同意 Arun 的回答。这是另一种说法:在进行连接之后,您通常会将连接列用作参考或作为进一步转换的输入。因此,您可以保留它,并且可以选择使用(更迂回的)双 [ 语法来丢弃它。从设计的角度来看,保留频繁相关的信息,然后在需要时丢弃,比过早丢弃并冒丢失难以重建的数据的风​​险要容易。

您希望保留连接列的另一个原因是您可以在执行连接的同时执行聚合操作(by 没有 by)。例如,通过加入 join 列,这里的结果更加清晰:

d <- data.table(a=rep.int(1:3,2),value=2:7,other=100:105,key="a")
d[J(1:3),mean(value)]
#   a  V1
#1: 1 3.5
#2: 2 4.5
#3: 3 5.5

【讨论】:

  • 我会考虑这个,但是 atm 我不太明白需要d[J(1:3), mean(value)] 语法来获得这个结果,而不是我认为使用@987654324 更可口的选择@ 语法(我实际上没有检查这是否有效,但我希望它以这种方式工作)。我想我不明白 by without by 语法的需要(和想要),因为在我的情况下,它所做的只是混淆了很少的明显好处。
  • 正如@Simon101 引用 Ripley 教授的话,“您的偏好无关紧要。重要的是文档”(并且没有未能给出解释)。也就是说,MatthewDowle 非常友善且乐于接受,他可能会配合。我很高兴现在这样。
  • @eddi by-without-by 的动机是当您拥有已知组的 子集 时的速度。假设您有 1000 个组(每个组有 1000 行和 100 列),但您只需要 6 个组的一列的平均值。这 6 个组的所有列的子集,后跟 by 会比直接 by-without-by 和花哨的 j 列使用检查要慢。请参阅常见问题解答 1.12 以了解有关此处思考的更多信息。
  • @MatthewDowle 我没有反对 by without by 的 concept,我的抱怨是反对它的 syntax。我相信语法应该是“正常的”,所有的魔法都应该在内部发生。
  • 我想要的是通过按键加速的概念,而不是当前的语法。我不想沉默。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-16
  • 1970-01-01
  • 2012-11-09
  • 2017-03-01
  • 1970-01-01
相关资源
最近更新 更多