【问题标题】:r data.table: Subsetting and assignment by reference in a for loopr data.table:在for循环中通过引用进行子集和赋值
【发布时间】:2023-03-14 10:04:01
【问题描述】:

看起来很简单,但是……嗯……

给定一个正则表达式的命名向量和一个数据表如下:

library(data.table)
regexes <- c(a="^A$") 
dt <- fread("
a,A,1
a,B,1
b,A,1
")

输入数据表是

dt
#    V1 V2 V3
# 1:  a  A  1
# 2:  a  B  1
# 3:  b  A  1

我对@9​​87654323@ 中第一个元素的目标是:

如果V1=="a" 设置V3:=2。除非V2 匹配相应的正则表达式^A$,然后是V3:=3

(anames(regexes)[1]^A$regexes[1]23 仅用于演示目的。我还有更多的名称和正则表达式要循环,数据集是大约 300.000 行。)

所以预期的输出

#    V1 V2 V3
# 1:  a  A  3 (*)
# 2:  a  B  2 (**)
# 3:  b  A  1

(*) 3 因为V1aV2 (A) 匹配正则表达式,
(**) 2 因为V1aV2 (B) 不匹配^A$

我尝试循环遍历正则表达式并像这样通过管道传递子集:

for (x in seq(regexes)) 
  dt[V1==names(regexes)[x], V3:=2][grepl(regexes[x], V2), V3:=3]

不过……

dt
#    V1 V2 V3
# 1:  a  A  3 
# 2:  a  B  2
# 3:  b  A  3 <- wrong, should remain 2

...它没有按预期工作,grepl 使用完整的V2 列,而不仅仅是V1=="a" 子集。我还尝试了一些其他的方法,这些方法有效,但耗时太长(即不是使用 data.table 的方式)。

问题:最好的数据表方式是什么?我正在使用packageVersion("data.table")‘1.9.7’


请注意,我可以走数据帧路线,例如像这样

df <- as.data.frame(dt)
for (x in seq(regexes)) {
  idx <- df$V1==names(regexes)[x]
  df$V3[idx] <- 2
  df$V3[idx][grepl(regexes[x], df$V2[idx])] <- 3 # or ifelse()
}  

但是 - 当然 - 我不想将 data.table 转换为 data.frame,然后尽可能再转换回 data.table。

提前致谢!

【问题讨论】:

  • dt[condition, blah := boo] 返回修改后的 full dt (你可以通过在末尾添加额外的空 [] 来检查) - 因此你得到的结果。将额外条件添加到第二组[]
  • 我试过了,@eddi,但是当我使用V1==names(regexes)[x] &amp; grepl(regexes[x], V2) 作为条件时(即在所有V2s 上匹配每个正则表达式),大约需要 35 秒,而数据框示例需要〜3。所以我认为这显然不是这里的路。
  • 这似乎有些可疑。您能否添加一个可以看到减速的更大示例?
  • 对我来说这听起来很合理。尽管 data.table 可能会自动为V1 构建索引,但grepl 仍然会在 300.000 行上运行多次,而不是像在数据框示例中那样运行在 200 到 2000 行的几个子集上。仍然认为一个更大的例子在这里会有所帮助吗?在那种情况下,我可以尝试建造一个人造的。
  • 好的,明白了,你是对的,不需要举例

标签: r for-loop data.table


【解决方案1】:

...它没有按预期工作,grepl 使用完整的 V2 列,而不仅仅是 V1=="a" 子集。

我会使用 stringi,它允许对正则表达式测试进行简单的矢量化:

library(stringi)
dt[V1 %in% names(regexes), 
  V3 := V3 + 1L + stri_detect(V2, regex = regexes[V1])
]

   V1 V2 V3
1:  a  A  3
2:  a  B  2
3:  b  A  1

stri_detect 系列函数类似于来自 base 的 grepl

【讨论】:

  • 谢谢弗兰克,还不知道stri_detect。然而,names(regexes)regexes 在我的真实数据中是绑定在一起的,而regexes 实际上不是数字(我担心这个例子可能会产生误导,但是......好吧......)。我仍然认为征服它的最佳方法是 (1.) 每个 names(regex) 的子集,(2.) 设置一个默认值,然后 (3.) 为匹配设置一个新值。让我知道我是否应该想出一个更好的例子......
  • @lukeA 我不关注。这种方式并不假设正则表达式是数字的(无论这可能意味着什么)......如果与您的示例的大小有关,这会使这种方式变慢,但是,是的,一个更好的示例(作为n 的函数) 会很好。
  • 我的意思是V3,目标列,不是数字。我以为我(1)将其设置为子集的默认值(=names(regexes)),然后(2)设置为该子集中正则表达式匹配的标志值。这里的关键是,每个子集都有自己的正则表达式。所以你不能对正则表达式进行矢量化。
  • @lukeA 您可以完全通过正则表达式进行矢量化。如果您的regexes 对象除了a 之外还包含b,则stringi 可以正常工作,并且比分组更有效。如果您需要 V3 的字符串值(“3”、“2”、“1”)而不是数字,这是一个简单的更改。您可以更改您的 fread 调用以使您的示例具有 V3 字符串,我将更新答案以显示我的意思。
  • 感谢您的耐心等待,但我想您的意思已经明白了,据我所知,矢量化只有在 all 正则表达式适用于 all 时才有效子集。在我的例子中,正则表达式向量的每个值都应该在其名称给出的子集中进行检查。你明白我的意思了吗?
猜你喜欢
  • 2015-06-09
  • 2017-08-02
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
  • 2015-08-14
  • 1970-01-01
  • 2016-02-15
  • 2023-04-09
相关资源
最近更新 更多