2017-04-20 11 views
1

mapplyでこの問題を解決しようとしていましたが、この作業をするためにいくつかのネストされたアプリケーションを使用する必要があります。本当の混乱。あるテーブルのキーワードが別のテーブルの文字列内にあるかどうかを確認するR

問題は次のとおりです。

データフレームには約400個のキーワードが含まれています。これらはおよそ15のカテゴリに分類されます。 データフレーム2には、文字列の説明フィールドと、データフレーム1に記載されているカテゴリにそれぞれ対応する15個の追加の列が含まれています。これには何百万行もあります。

データフレーム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はキーワードの数で、dの説明の数です。

  • 利用ベクトル化事業:

    は、高速かつメモリがあまりなく、この問題を解決するようにするいくつかのトリックがあります。これらは、forループを使用するよりもはるかに迅速に実行できます。 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) 

この300万本の記述に15個のカテゴリーに400個のキーワードに一致するように140秒かかり、14ギガバイトRAM。

+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

あなたが探しているのは、NLP(自然言語処理)に由来するいわゆる文書用語行列(またはdtm)です。利用可能な多くのオプションがあります。私は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

あなたの答えをありがとう!私は潜在的なスピードの利益に興味があります。文字列に複数のキーワードがある場合はどうなりますか?このソリューションは、それらのすべてを識別し、すべての人にフラグを提供させますか?例えば、 "dogs catching carp"は、Aの値2とCの値1を設定する必要があります。 – dmrzl

+0

同様に機能します。 DTMには、すべての文書(テキスト)と用語(語彙)の関係がリストされています。したがって、 "犬を捕まえる犬"は、 "犬"と "鯉"(それらは両方とも語彙/用語である)を挙げるでしょう。 大きなデータセットをお持ちの場合は、dtmを消去してからdata.frameに変換することができます。 – David

+0

残念ながら、これは一致があるたびにまったく新しい行を作成します。このための回避策はありますか? 実際、すべての文字列を見つけることに問題があります。行数をキーワード数と同じにしようとします。これはmapplyと同じ問題です。 – dmrzl

関連する問題