2017-04-20 55 views
1

我一直試圖用mapply解決這個問題,但我相信我將不得不使用幾個嵌套應用來完成這項工作,並且它已經獲得真正令人困惑。檢查一個表中的關鍵字是否在另一個表中的字符串中使用R

問題如下:

數據幀一個包含大約400個關鍵字。這些大致分爲15類。 Dataframe two包含一個字符串描述字段和15個額外的列,每個列命名爲與數據框1中提及的類別相對應。這有數百萬行。

如果從數據幀1的關鍵詞在數據幀2串字段存在,其中關鍵字所在的類別,應在數據幀標記2

我想應該是這個樣子:

> #Dataframe1 df1 
    >> keyword category 
    >> cat  A 
    >> dog  A 
    >> pig  A 
    >> crow  B 
    >> pigeon  B 
    >> hawk  B 
    >> catfish C 
    >> carp  C 
    >> ... 
    >> 
    > #Dataframe2 df2 
    >> description A B C .... 
    >> false cat  1 0 0 .... 
    >> smiling pig 1 0 0 .... 
    >> shady pigeon 0 1 0 .... 
    >> dogged dog  2 0 0 .... 
    >> sad catfish 0 0 1 .... 
    >> hawkward carp 0 1 1 .... 
    >> .... 

我試圖使用mapply來使這個工作,但它失敗了,給我的錯誤「更長的參數不是一個更短的長度的倍數」。它也計算這隻適用於df2中的第一個字符串。我還沒有超越這個階段,即試圖獲得類別標誌。

> mapply(grepl, pattern = df1$keyword, x = df2$description) 

任何人都可以幫忙嗎?我非常感謝你。我是R新手,所以如果有人可以提到一些用於將循環轉換爲應用函數的「拇指規則」,也會有所幫助。我不能使用循環來解決這個問題,因爲這會花費太多時間。

回答

0

無論執行什麼,計數每個類別的匹配數量需要k x d比較,其中k是關鍵字的數量和d的描述數量。

有一些技巧,使快速,無需大量的內存解決這個問題:

  • 使用矢量操作。這些可以比使用循環更快地執行。請注意,lapply,mapply或vapply只是for循環的縮寫。我對這些關鍵字進行了並行化處理(請參見下一步),以便向量化可以覆蓋最大維度的描述。
  • 使用並行化。優化使用多核可以以增加內存爲代價加快處理速度(因爲每個內核都需要自己的副本)。

實施例:

keywords   <- stringi::stri_rand_strings(400, 2) 
categories   <- letters[1:15] 
keyword_categories <- sample(categories, 400, TRUE) 
descriptions  <- stringi::stri_rand_strings(3e6, 20) 

keyword_occurance <- function(word, list_of_descriptions) { 
    description_keywords <- str_detect(list_of_descriptions, word) 
} 

category_occurance <- function(category, mat) { 
    rowSums(mat[,keyword_categories == category]) 
} 

list_keywords <- mclapply(keywords, keyword_occurance, descriptions, mc.cores = 8) 
df_keywords <- do.call(cbind, list_keywords) 
list_categories <- mclapply(categories, category_occurance, df_keywords, mc.cores = 8) 
df_categories <- do.call(cbind, list_categories) 

與我的計算機這需要140秒和14GB的RAM 15級中的類別3點百萬的描述匹配400名的關鍵字。

+0

謝謝!此解決方案也適用,但我沒有在大型數據集上嘗試過。我很好奇記憶考慮如何工作。關於[ikop](http://stackoverflow.com/users/7760498/ikop)上面的答案,它在此期間創建了一個非常大的列表,那麼創建大型數據框的解決方案將會更多或更少的內存密集? – dmrzl

+0

我已經編輯了我的答案,以便實現快速和高效的內存實現。 – Pieter

+0

不幸的是,我得到錯誤''mc.cores'> 1在Windows上不受支持。有什麼解決方法嗎?你說得對,大數據集的應用函數太慢了。 – dmrzl

0

有可能是一個更優雅的方式來做到這一點,但是這是我想出了:

## Your sample data: 
df1 <- structure(list(keyword = c("cat", "dog", "pig", "crow", "pigeon", "hawk", "catfish", "carp"), 
    category = c("A", "A", "A", "B", "B", "B", "C", "C")), 
    .Names = c("keyword", "category"), 
    class = "data.frame", row.names = c(NA,-8L)) 
df2 <- structure(list(description = structure(c(2L, 6L, 5L, 1L, 4L,3L), 
    .Label = c("dogged dog", "false cat", "hawkward carp", "sad catfish", "shady pigeon", "smiling pig"), class = "factor")), 
    .Names = "description", row.names = c(NA, -6L), class = "data.frame") 

## Load packages: 
library(stringr) 
library(dplyr) 
library(tidyr) 

## For each entry in df2$description count how many times each keyword 
## is contained in it: 
outList <- lapply(df2$description, function(description){ 
     outDf <- data.frame(description = description, 
       value = vapply(stringr::str_extract_all(description, df1$keyword), 
         length, numeric(1)), category = df1$category) 
    }) 

## Combine to one long data frame and aggregate by category: 
outLongDf<- do.call('rbind', outList) %>% 
    group_by(description, category) %>% 
    dplyr::summarise(value = sum(value)) 

## Reshape from long to wide format: 
outWideDf <- tidyr::spread(data = outLongDf, key = category, 
    value = value) 

outWideDf 
# Source: local data frame [6 x 4] 
# Groups: description [6] 
# 
#  description  A  B  C 
# *  <fctr> <dbl> <dbl> <dbl> 
# 1 dogged dog  2  0  0 
# 2  false cat  1  0  0 
# 3 hawkward carp  0  1  1 
# 4 sad catfish  1  0  1 
# 5 shady pigeon  1  1  0 
# 6 smiling pig  1  0  0 

這種方法,但是也可以抓住的「豬」,在「鴿子」和「貓」在「鮎魚」。不過,我不知道這是不是你想要的。

+0

這是完美的!非常感謝。是的,我不希望「鮎魚」中的「貓」被發現,但這只是次要的。 – dmrzl

+0

不幸的是,事實證明這種方法對於較大的數據集來說太慢了。 – dmrzl

1

您在尋找的是所謂的文檔 - 術語矩陣(簡稱dtm),它源於NLP(自然語言處理)。有很多選項可用。我更喜歡text2vec。這個軟件包速度非常快(如果它將在這裏超過其他解決方案,我會不會感到驚訝),尤其是與tokenizers相結合。

在你的情況下,代碼會是這個樣子:

# Create the data 
df1 <- structure(list(keyword = c("cat", "dog", "pig", "crow", "pigeon", "hawk", "catfish", "carp"), 
         category = c("A", "A", "A", "B", "B", "B", "C", "C")), 
       .Names = c("keyword", "category"), 
       class = "data.frame", row.names = c(NA,-8L)) 
df2 <- structure(list(description = structure(c(2L, 6L, 5L, 1L, 4L,3L), 
               .Label = c("dogged dog", "false cat", "hawkward carp", "sad catfish", "shady pigeon", "smiling pig"), class = "factor")), 
       .Names = "description", row.names = c(NA, -6L), class = "data.frame") 

# load the libraries 
library(text2vec) # to create the dtm 
library(tokenizers) # to help creating the dtm 
library(reshape2) # to reshape the data from wide to long 

# 1. create the vocabulary from the keywords 
vocabulary <- vocab_vectorizer(create_vocabulary(itoken(df1$keyword))) 

# 2. create the dtm 
dtm <- create_dtm(itoken(as.character(df2$description)), vocabulary) 

# 3. convert the sparse-matrix to a data.frame 
dtm_df <- as.data.frame(as.matrix(dtm)) 
dtm_df$description <- df2$description 

# 4. melt to long format 
df_result <- melt(dtm_df, id.vars = "description", variable.name = "keyword") 
df_result <- df_result[df_result$value == 1, ] 

# 5. combine the data, i.e., add category 
df_final <- merge(df_result, df1, by = "keyword") 
# keyword description value category 
# 1 carp hawkward carp  1  C 
# 2  cat  false cat  1  A 
# 3 catfish sad catfish  1  C 
# 4  dog dogged dog  1  A 
# 5  pig smiling pig  1  A 
# 6 pigeon shady pigeon  1  B 
+0

感謝您的回答!我對潛在的速度好處感興趣。如果一個字符串有多個關鍵字會怎樣這個解決方案是否能夠識別每一個問題,並讓我爲每個問題提供一個標記?例如,「捕捉鯉魚的狗」應爲A設置值2,爲C設置值1. – dmrzl

+0

應該也可以工作。 DTM列出了所有文檔(文本)和術語(詞彙)關係。因此,「狗抓鯉魚」將列出「狗」和「鯉魚」(因爲它們都在詞彙/術語中)。 如果您有大數據集,您可能需要在轉換爲data.frame之前清理dtm。 – David

+0

不幸的是,每當有匹配時,這將創建一個全新的行。你有解決這個問題的方法嗎? 事實上,找到所有的字符串都有問題。它試圖使行數等於關鍵字的數量。這是我與mapply相同的問題。 – dmrzl

相關問題