2015-10-12 71 views
19
entfernt

Hier ist ein Aufruf für eine bessere Möglichkeit, etwas zu tun, die ich bereits ineffizient tun kann: eine Reihe von N-Gramm-Token mit "Stoppwörter" so filtern Das Auftreten eines Stoppwortbegriffs in einem N-Gramm löst die Entfernung aus.Wie man Stoppwörter effizient aus einer Liste von Ngram-Tokens in R

Ich hätte gerne eine Lösung, die sowohl für Unigramme als auch für N-Gramme funktioniert, obwohl es in Ordnung wäre, zwei Versionen zu haben, eine mit einem "fixed" -Flag und eine mit einem "regex" -Flag. Ich stelle die zwei Aspekte der Frage zusammen, da jemand eine Lösung haben kann, die einen anderen Ansatz versucht, der sowohl Stoppwörter für feste als auch reguläre Ausdrücke anspricht.

Formate:

  • Tokens sind eine Liste von Schriftzeichenvektoren, die durch ein _ (Unterstrich) Zeichen verkettete Unigramme oder n-Gramm sein kann.

  • Stoppwörter sind ein Zeichenvektor. Im Moment bin ich damit zufrieden, dies eine feste Zeichenfolge zu sein, aber es wäre ein schöner Bonus, dies auch mit regulären Ausdruck-formatierten Stoppwörtern zu implementieren.

gewünschte Ausgabe: Eine Liste von Zeichen, passend zum Eingang Tokens aber mit jeder Komponente Token ein Stoppwort passenden entfernt wird. (Dies bedeutet einen Unigramm-Spiel oder eine Übereinstimmung mit einem der Begriffe, die das n-Gramm enthält.)

Beispiele, Testdaten und der Arbeitscode und Benchmarks aufbauen:

tokens1 <- list(text1 = c("this", "is", "a", "test", "text", "with", "a", "few", "words"), 
       text2 = c("some", "more", "words", "in", "this", "test", "text")) 
tokens2 <- list(text1 = c("this_is", "is_a", "a_test", "test_text", "text_with", "with_a", "a_few", "few_words"), 
       text2 = c("some_more", "more_words", "words_in", "in_this", "this_text", "text_text")) 
tokens3 <- list(text1 = c("this_is_a", "is_a_test", "a_test_text", "test_text_with", "text_with_a", "with_a_few", "a_few_words"), 
       text2 = c("some_more_words", "more_words_in", "words_in_this", "in_this_text", "this_text_text")) 
stopwords <- c("is", "a", "in", "this") 

# remove any single token that matches a stopword 
removeTokensOP1 <- function(w, stopwords) { 
    lapply(w, function(x) x[-which(x %in% stopwords)]) 
} 

# remove any word pair where a single word contains a stopword 
removeTokensOP2 <- function(w, stopwords) { 
    matchPattern <- paste0("(^|_)", paste(stopwords, collapse = "(_|$)|(^|_)"), "(_|$)") 
    lapply(w, function(x) x[-grep(matchPattern, x)]) 
} 

removeTokensOP1(tokens1, stopwords) 
## $text1 
## [1] "test" "text" "with" "few" "words" 
## 
## $text2 
## [1] "some" "more" "words" "test" "text" 

removeTokensOP2(tokens1, stopwords) 
## $text1 
## [1] "test" "text" "with" "few" "words" 
## 
## $text2 
## [1] "some" "more" "words" "test" "text" 

removeTokensOP2(tokens2, stopwords) 
## $text1 
## [1] "test_text" "text_with" "few_words" 
## 
## $text2 
## [1] "some_more" "more_words" "text_text" 

removeTokensOP2(tokens3, stopwords) 
## $text1 
## [1] "test_text_with" 
## 
## $text2 
## [1] "some_more_words" 

# performance benchmarks for answers to build on 
require(microbenchmark) 
microbenchmark(OP1_1 = removeTokensOP1(tokens1, stopwords), 
       OP2_1 = removeTokensOP2(tokens1, stopwords), 
       OP2_2 = removeTokensOP2(tokens2, stopwords), 
       OP2_3 = removeTokensOP2(tokens3, stopwords), 
       unit = "relative") 
## Unit: relative 
## expr  min  lq  mean median  uq  max neval 
## OP1_1 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100 
## OP2_1 5.119066 3.812845 3.438076 3.714492 3.547187 2.838351 100 
## OP2_2 5.230429 3.903135 3.509935 3.790143 3.631305 2.510629 100 
## OP2_3 5.204924 3.884746 3.578178 3.753979 3.553729 8.240244 100 
+0

das Verfahren zur Entfernung in Stoppwörter tm oder qdap ist nicht genug? Obwohl sie in die andere Richtung arbeiten, entfernen Sie zuerst die Stoppwörter und erstellen Sie dann die N-Gramme. – phiver

+1

Nein, das ist einfach genug. Ich versuche, eine effiziente Methode zu finden, um stopword-enthaltende Ngramme nach der Konstruktion zu entfernen. –

+0

Haben Sie das neue Paket von Tyler Rinker, termco auf GitHub ausgecheckt? Das sieht vielversprechend aus. Ich hatte noch keine Zeit es zu überprüfen. – phiver

Antwort

5

Dies ist nicht wirklich eine Antwort - eher ein Kommentar zu rawr Kommentar zu antworten durch alle Kombinationen gehen von Stoppwörter. Mit einer längeren stopwords Liste scheint etwas wie %in% dieses Dimensionalitätsproblem nicht zu leiden.

library(purrr) 
removetokenstst <- function(tokens, stopwords) 
    map2(tokens, 
     lapply(tokens3, function(x) { 
     unlist(lapply(strsplit(x, "_"), function(y) { 
      any(y %in% stopwords) 
     })) 
     }), 
     ~ .x[!.y]) 

require(microbenchmark) 
microbenchmark(OP1_1 = removeTokensOP1(tokens1, morestopwords), 
      OP2_1 = removeTokensOP2(tokens1, morestopwords), 
      OP2_2 = removeTokensOP2(tokens2, morestopwords), 
      OP2_3 = removeTokensOP2(tokens3, morestopwords), 
      Ak_3 = removetokenstst(tokens3, stopwords), 
      Ak_3msw = removetokenstst(tokens3, morestopwords), 
      unit = "relative") 

Unit: relative 
    expr  min  lq  mean median  uq  max neval 
    OP1_1 1.00000 1.00000 1.000000 1.000000 1.000000 1.00000 100 
    OP2_1 278.48260 176.22273 96.462854 79.787932 76.904987 38.31767 100 
    OP2_2 280.90242 181.22013 98.545148 81.407928 77.637006 64.94842 100 
    OP2_3 279.43728 183.11366 114.879904 81.404236 82.614739 72.04741 100 
    Ak_3 15.74301 14.83731 9.340444 7.902213 8.164234 11.27133 100 
Ak_3msw 18.57697 14.45574 12.936594 8.513725 8.997922 24.03969 100 

Stoppwörter

morestopwords = c("a", "about", "above", "after", "again", "against", "all", 
"am", "an", "and", "any", "are", "arent", "as", "at", "be", "because", 
"been", "before", "being", "below", "between", "both", "but", 
"by", "cant", "cannot", "could", "couldnt", "did", "didnt", "do", 
"does", "doesnt", "doing", "dont", "down", "during", "each", 
"few", "for", "from", "further", "had", "hadnt", "has", "hasnt", 
"have", "havent", "having", "he", "hed", "hell", "hes", "her", 
"here", "heres", "hers", "herself", "him", "himself", "his", 
"how", "hows", "i", "id", "ill", "im", "ive", "if", "in", "into", 
"is", "isnt", "it", "its", "its", "itself", "lets", "me", "more", 
"most", "mustnt", "my", "myself", "no", "nor", "not", "of", "off", 
"on", "once", "only", "or", "other", "ought", "our", "ours", 
"ourselves", "out", "over", "own", "same", "shant", "she", "shed", 
"shell", "shes", "should", "shouldnt", "so", "some", "such", 
"than", "that", "thats", "the", "their", "theirs", "them", "themselves", 
"then", "there", "theres", "these", "they", "theyd", "theyll", 
"theyre", "theyve", "this", "those", "through", "to", "too", 
"under", "until", "up", "very", "was", "wasnt", "we", "wed", 
"well", "were", "weve", "were", "werent", "what", "whats", "when", 
"whens", "where", "wheres", "which", "while", "who", "whos", 
"whom", "why", "whys", "with", "wont", "would", "wouldnt", "you", 
"youd", "youll", "youre", "youve", "your", "yours", "yourself", 
"yourselves", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 
"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", 
"x", "y", "z") 
+0

widerzuspiegeln Richtig, aber das tut nicht genau dasselbe, da '% in%' [nur mit der Tabelle übereinstimmt] (https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43/src/main/unique. C# L922), dh die Länge der Stoppwörter oder was auch immer Sie bekommen, wenn Sie die Strings teilen, während 'grepl' [Zeichen für Zeichen] (https://github.com/wch/r-source/blob/b156e3a711967f58131e23c1b1dc1ea90e2f0c43 /src/main/grep.c#L679). also für 'stopwords <- c (" ist "," a "," in "," dies ")', '% in%' hat vier Dinge zu tun und grepl hat viel mehr abhängig von dem Zielvektor und der Länge von denen Saiten – rawr

1

Wir können die lapply verbessern, wenn Sie viele Ebenen in Ihrer Liste mit dem parallel Paket haben.

vielen Ebenen erstellen

tokens2 <- list(text1 = c("this_is", "is_a", "a_test", "test_text", "text_with", "with_a", "a_few", "few_words"), 
       text2 = c("some_more", "more_words", "words_in", "in_this", "this_text", "text_text")) 
tokens2 <- lapply(1:500,function(x) sample(tokens2,1)[[1]]) 

Wir tun dies, weil die parallel Paket viel Overhead, so einzustellen, nur die Anzahl der Iterationen auf-Micro Erhöhung wird auch weiterhin diese Kosten entstehen. Wenn Sie die Liste vergrößern, sehen Sie die wahre Verbesserung.

library(parallel) 
#Setup 
cl <- detectCores() 
cl <- makeCluster(cl) 

#Two functions: 

#original 
removeTokensOP2 <- function(w, stopwords) { 
    matchPattern <- paste0("(^|_)", paste(stopwords, collapse = "(_|$)|(^|_)"), "(_|$)") 
    lapply(w, function(x) x[-grep(matchPattern, x)]) 
} 

#new 
removeTokensOPP <- function(w, stopwords) { 
    matchPattern <- paste0("(^|_)", paste(stopwords, collapse = "(_|$)|(^|_)"), "(_|$)") 
    return(w[-grep(matchPattern, w)]) 
} 

#compare 

microbenchmark(
    OP2_P = parLapply(cl,tokens2,removeTokensOPP,stopwords), 
    OP2_2 = removeTokensOP2(tokens2, stopwords), 
    unit = 'relative' 
) 

Unit: relative 
    expr  min  lq  mean median  uq  max neval 
OP2_P 1.000000 1.000000 1.000000 1.000000 1.000000 1.00000 100 
OP2_2 1.730565 1.653872 1.678781 1.562258 1.471347 10.11306 100 

Mit der Anzahl der Ebenen in Ihrer Liste wird die Leistung verbessert.

1

Sie migth simlifying Ihre reguläre Ausdrücke betrachten,^und fügen $ an den Kopf

remove_short <- function(x, stopwords) { 
    stopwords_regexp <- paste0('(^|_)(', paste(stopwords, collapse = '|'), ')(_|$)') 
    lapply(x, function(x) x[!grepl(stopwords_regexp, x)]) 
} 
require(microbenchmark) 
microbenchmark(OP1_1 = removeTokensOP1(tokens1, stopwords), 
       OP2_1 = removeTokensOP2(tokens2, stopwords), 
       OP2_2 = remove_short(tokens2, stopwords), 
       unit = "relative") 
Unit: relative 
    expr  min  lq  mean median  uq  max neval cld 
OP1_1 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 100 a 
OP2_1 5.178565 4.768749 4.465138 4.441130 4.262399 4.266905 100 c 
OP2_2 3.452386 3.247279 3.063660 3.068571 2.963794 2.948189 100 b 
+0

Aber dann bekomme ich ein positive Übereinstimmung für "schön" aus dem Stoppwort "if" usw. –

+1

Sie haben Recht. Dennoch gibt es eine kleine Optimierung für Ihre Regex: Statt (^ | _) ist (_ | $) | (^ | _) a (_ | $) | (^ | _) in (_ | $) | (^ | _) this (_ | $) 'Du könntest es als' (^ | _) schreiben (ist | a | in | this) (_ | $) ' Ich habe meine Antwort bearbeitet, um den Unterschied – Vlados