r - 在 data.table 中包含迭代循环的矢量化函数
问题描述
我有一个数据集,我需要在其中迭代地根据它们的结构规律提取邮政编码。我首先需要检测“AA00 0AA”,然后才能检测到“A00 0AA”,因为如果我还没有排除它,第二个也会检测到第一个。我不是在寻找解决方法(比如通过在正则表达式之前添加一个空格或类似的东西来改进正则表达式),我试图直观地理解以下问题,因为我的工作中出现了更多具有类似问题的函数:
数据:
library("data.table")
library("stringr")
testdata <- data.table(Address = c("1 Some Street, sometown, AA00 0AA",
"1 Some Street, sometown, A00 0AA"))
我的功能如下所示:
my_postcode_fun <- function(myscalar){
allpatterns <- c("[[:alpha:]][[:alpha:]][[:digit:]][[:digit:]][[:space:]][[:digit:]][[:alpha:]][[:alpha:]]",
# this is AA00 0AA
"[[:alpha:]][[:digit:]][[:digit:]][[:space:]][[:digit:]][[:alpha:]][[:alpha:]]"
# this is A00 0AA
)# these are the patterns I'm looking for
for(i in allpatterns){
if(!is.na(str_extract(myscalar, regex(i))[1])){ # only run the next steps, if found at least once
output <- list(postcode = str_extract(myscalar, regex(i)), # extract the postcode
leftover = str_replace(myscalar, regex(i), "")) # extract old string without the postcode
break # stop if you've found one
}
}
return(output) # return both elements
}
此函数适用于标量:
my_postcode_fun(testdata[1])
我可以通过一个向量循环它:
for(i in 1:2){
print(my_postcode_fun(testdata[i,]))
}
当我强制 data.table 逐行运行时,它也有效:
new <- testdata[, c("postcode", "Address"):=
my_postcode_fun(Address),
by = seq_len(nrow(testdata))]
new
但这是非常低效的。我不能让这个函数在 data.table 中使用它的“同时对每个 i 完成所有事情”逻辑。
testdata[, c("postcode", "Address"):=
my_postcode_fun(Address)]
我认为有两个问题,如果我错了,请纠正我。
第一个问题是 data.table 对每一行执行整个操作,然后进入循环的下一步。您可以通过注释掉函数中的“break”来看到这一点,在这种情况下,它只返回循环的第二步(并且可能已经覆盖了第一步)。
我认为的第二个问题是“中断”将停止所有行的循环。至少这是我对 data.table 如何处理循环的直觉。如果它没有找到特定行的任何内容,我希望它继续。所有元素的第一步,然后是所有元素的第二步等。
那么,我怎样才能对它进行矢量化并在 data.table 中使用它并有一个迭代(!)循环,该循环需要在允许第二步发生之前发生第一步?我试图让这个例子更简单,但我害怕错过重要的部分,因为我不完全理解它。
解决方案
首先,if
条件需要单个逻辑,而不是向量,如果myscalar
长度超过 1(或仅 0),那么它将失败。此外,从if
某种意义上说,应该确实具有矢量化比较,因为您可能有一个模式匹配一个但不是全部,等等。
为此,人们可能会认为ifelse
这是一种矢量化if
/else
替代方法,但我认为另一种方法是一种归约方法,在这种方法中,在任何尚未匹配的输入上尝试每个模式,并在一切都匹配时停止处理。
my_postcode_fun <- function(myvector){
allpatterns <- c("[[:alpha:]][[:alpha:]][[:digit:]][[:digit:]][[:space:]][[:digit:]][[:alpha:]][[:alpha:]]",
# this is AA00 0AA
"[[:alpha:]][[:digit:]][[:digit:]][[:space:]][[:digit:]][[:alpha:]][[:alpha:]]"
# this is A00 0AA
)# these are the patterns I'm looking for
post <- rep(NA_character_, length(myvector))
for (ptn in allpatterns) {
isna <- is.na(post)
if (!any(isna)) break
post[isna] <- str_extract(myvector[isna], regex(ptn))
}
isna <- is.na(post)
if (any(!isna)) {
myvector[!isna] <- mapply(sub, post[!isna], "", myvector[!isna])
}
list(postcode = post, leftover = myvector)
}
testdata <- data.table(Address = c("1 Some Street, sometown, AA00 0AA",
"1 Some Street, sometown, A00 0AA",
"1 Some Street, sometown, "))
testdata[, c("post","left") := my_postcode_fun(Address)][]
# Address post left
# <char> <char> <char>
# 1: 1 Some Street, sometown, AA00 0AA AA00 0AA 1 Some Street, sometown,
# 2: 1 Some Street, sometown, A00 0AA A00 0AA 1 Some Street, sometown,
# 3: 1 Some Street, sometown, <NA> 1 Some Street, sometown,
(post
andleft
字符串肯定可以被清理掉,也许trimws
,但这是一项不同的任务。)
快速演练:
- 我们首先生成一个
post
与 input 一样长的向量myscalar
,但 allNA
,这是一个有意的起点。 - 对于
ptn
模式中的每一个,首先我们只想查看那些post
是NA
(是的,第一遍总是全真,但是这个状态机的重点是它不需要知道它是否在第一次或n
第一次通过)。为此,我们定义isna
了一个变量来跟踪哪些post
是NA
,这意味着哪些myscalar
还没有匹配。 - 快速检查:如果所有内容都匹配(即
!any(isna)
),则停止处理。这是一个很好的断点,因为这意味着如果我们有 1M 输入并且第一个模式匹配所有这些,那么我们不必继续处理任何其他模式。 str_extract
剩余的输入与 thisptn
,并存储在尚未匹配的post
输出中。- 在
for
循环之后,我们再填充isna
一次,以便我们可以从原始输入中删除邮政编码。sub
没有对其模式进行矢量化(仅在其输入x=
矢量上),因此我mapply
在某种意义上使用对其进行矢量化(还有其他方法可以这样做)。- 注意:这里有一个非常小的风险:如果输入中有两个明显的邮政编码(尽管不太可能),并且它们包含相同的匹配子字符串,那么第一个将被提取到
post
中,sub
并将删除第一个匹配项。这些步骤都不会承认或删除第二个。我怀疑这可能,但我想确定这种可能性。
- 注意:这里有一个非常小的风险:如果输入中有两个明显的邮政编码(尽管不太可能),并且它们包含相同的匹配子字符串,那么第一个将被提取到
- 顺便说一句:虽然我用
postcode
andleftover
(如您的问题)命名了函数输出,但在这种情况下,这些名称被 inside 删除c("post","left") :=
了data.table
。我以不同的方式命名它们来证明这一点。该函数可以很容易地返回并且它也list(post, leftover)
可以正常工作(尽管我认为名称是好的,声明性的,如果这个函数曾经在 之外使用data.table
,这些名称可能会很有帮助)。
推荐阅读
- xcode - 如何在 Storyboard 上移动视图控制器?
- android - iOS设备ios麦克风的权限访问
- database - 如何在 oracle 数据库表中插入 csv 或 .txt 文件的日期列?
- java - 开放世界游戏状态保存安卓
- python - Python 多处理性能不佳
- apache-spark - 从 Spark DataFrame 中删除空白值并合并为单行
- python-3.x - 每次我创建一个新的 virtualenv - 我都必须更新 Pip
- android - 将 compilesdkversion 更新为 27 后未调用 Recyclerview 适配器方法
- sql - 在 SQL 中解析 JSON 嵌套数组
- ruby - 使用 Cairo 的 Ruby Gtk3 内存泄漏