统计一个词是否出现在400万观察数据集的每一行中

Count if a word occurs in each row of a 4 million observation data set

我正在使用 R 并编写一个脚本来计算在 400 万个观察数据文件的每一行中是否出现约 2000 个单词中的一个。包含观察值 (df) 的数据集包含两列,一列包含文本 (df$lead_paragraph),一列包含日期 (df$date).

使用以下内容,我可以计算列表 (p) 中的任何单词是否出现在 df 文件的 lead_paragraph 列的每一行中,并将答案输出为新列。

   df$pcount<-((rowSums(sapply(p, grepl, df$lead_paragraph, 
   ignore.case=TRUE) == TRUE, na.rm=T) > 0) * 1)

但是,如果我在列表 p 中包含太多单词,运行 代码会导致 R 崩溃。

我的替代策略是将其简单地分解成多个部分,但我想知道是否有更好、更优雅的编码解决方案可用于此处。我倾向于使用 for 循环,但我正在阅读的所有内容都表明这在 R 中不是首选。我是 R 的新手而且不是一个很好的编码员,所以如果不清楚,我深表歉意。

    df$pcount1<-((rowSums(sapply(p[1:100], grepl, df$lead_paragraph, 
    ignore.case=TRUE) == TRUE, na.rm=T) > 0) * 1)
    df$pcount2<-((rowSums(sapply(p[101:200], grepl, df$lead_paragraph, 
    ignore.case=TRUE) == TRUE, na.rm=T) > 0) * 1) 
    ...
    df$pcount22<-((rowSums(sapply(p[2101:2200], grepl, df$lead_paragraph, 
    ignore.case=TRUE) == TRUE, na.rm=T) > 0) * 1)

我没有完成这个...但这应该为您指明了正确的方向。使用 data.table 包速度更快,但希望这能让您了解该过程。

我使用随机日期和字符串重新创建了你的数据集 从 http://www.norvig.com/big.txt 提取到 data.frame 名为 nrv_df

library(stringi)

> head(nrv_df)
                                                             lead_para       date
1     The Project Gutenberg EBook of The Adventures of Sherlock Holmes 2018-11-16
2                                            by Sir Arthur Conan Doyle 2019-06-05
3                           15 in our series by Sir Arthur Conan Doyle 2017-08-08
4  Copyright laws are changing all over the world Be sure to check the 2014-12-17
5 copyright laws for your country before downloading or redistributing 2016-09-13
6                            this or any other Project Gutenberg eBook 2015-06-15

> dim(nrv_df)
[1] 103598      2

I then randomly sampled words from the entire body to get 2000 unique words
> length(p)
[1] 2000
> head(p)
[1] "The"        "Project"    "Gutenberg"  "EBook"      "of"         "Adventures"
> tail(p)
[1] "accomplice" "engaged"    "guessed"    "row"        "moist"      "red"   

然后,利用 stringi 包并使用正则表达式匹配完成 单词的情况下,我将每个字符串加入向量 p 中,并且 collapsed then with a |, 所以我们正在寻找任何带有 word-boundary 的单词 之前或之后:

> p_join2 <- stri_join(sprintf("\b%s\b", p), collapse = "|")
> p_join2

[1] "\bThe\b|\bProject\b|\bGutenberg\b|\bEBook\b|\bof\b|\bAdventures\b|\bSherlock\b|\bHolmes\b|\bby\b|\bSir\b|\bArthur\b|\bConan\b|\bDoyle\b|\b15\b|\bin\b|\bour\b|\bseries\b|\bCopyright\b|\blaws\b|\bare\b|\bchanging\b|\ball\b|\bover\b|\bthe\b|\bworld\b|\bBe\b|\bsure\b|\bto\b|\bcheck\b|\bcopyright\b|\bfor\b|\byour\b|\bcountry\b|..."

然后简单地计算单词数,您可以nrv_df$counts <-将其添加为一列...

> stri_count_regex(nrv_df$lead_para[25000:26000], p_join2, stri_opts_regex(case_insensitive = TRUE))
[1] 12 11  8 13  7  7  6  7  6  8 12  1  6  7  8  3  5  3  5  5  5  4  7  5  5  5  5  5 10  2  8 13  5  8  9  7  6  5  7  5  9  8  7  5  7  8  5  6  0  8  6
[52]  3  4  0 10  7  9  8  4  6  8  8  7  6  6  6  0  3  5  4  7  6  5  7 10  8 10 10 11

编辑:

因为查找匹配项的数量无关紧要... 首先是一个函数,用于对每个段落进行处理,并检测 p2 中的任何 stirngs 是否存在于 lead_paragraph

的正文中
f <- function(i, j){
     if(any(stri_detect_fixed(i, j, omit_no_match = TRUE))){
         1
     }else {
         0
     }
 }

现在...在 linux 上使用 parallel 库。并且只测试 1000 行,因为它是一个例子给了我们:

library(parallel)
library(stringi)
> rst <- mcmapply(function(x){
    f(i = x, j = p2)
}, vdf2$lead_paragraph[1:1000], 
mc.cores = detectCores() - 2,
USE.NAMES = FALSE)
> rst
   [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
  [70] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [139] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
 [208] 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [277] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [346] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 0 1 1 1 1
 [415] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [484] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [553] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [622] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [691] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [760] 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [829] 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [898] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1
 [967] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

这也有效:

library(corpus)

# simulate the problem as in @carl-boneri's answer
lead_para <- readLines("http://www.norvig.com/big.txt")

# get a random sample of 2000 word types
types <- text_types(lead_para, collapse = TRUE)
p <- sample(types, 2000)

# find whether each entry has at least one of the terms in `p`
ix <- text_detect(lead_para, p)

即使只使用单核,它也比以前的解决方案快 20 多倍:

system.time(ix <- text_detect(lead_para, p))
##  user  system elapsed 
## 0.231   0.008   0.240

system.time(rst <- mcmapply(function(x) f(i = x, j = p_join2),
                            lead_para, mc.cores = detectCores() - 2,
                            USE.NAMES = FALSE))
##   user  system elapsed 
## 11.604   0.240   5.805