〇はじめに
RMeCab
パッケージのNgram()
やdocDF()
を使ってシンプルにn-gramの頻度表を作成し、共起ネットワークを作ろうとすると、不要な単語の処理で不都合が生じました。n-gramの組ができた状態で片方の単語を削除すると、残したいもう片方の単語の連なりも当然切れてしまいます。その回避策として、処理の自由度を残した方法を考えました。
- 文書を
RMeCabText()
で処理して、①分かち書きされた単語②見出し語化された単語③品詞タグを取得する。 - 文書中の出現順序を保持したまま、①②③を用いて必要とする単語だけを残す。
- 残した単語から2-gramの頻度表を作成し、共起ネットワークを作図する。
の手順で進めます。
・主な参考書
『Rによるやさしいテキストマイニング[機械学習編]』小林雄一郎,オーム社
・使用したテキストデータ
モーニング娘。'18『Y字路の途中』の歌詞
〇2-gramの作成とネットワークグラフの作図
・使用パッケージ
library(RMeCab) #RMeCabText() library(purrr) #map_chr() library(magrittr) #extract(),%>% library(dplyr) #filter(),group_by,summarise library(stringr) #str_to_lower(),str_replace() library(igraph) #graph_from_data_frame(),tkplot()
・n-gramの作成
まず始めにMeCabを使って形態素解析を行う。
#形態素解析 data1 <- RMeCabText("テキストファイル名.txt") #ファイルパスを指定
head(data1) ## [[1]] ## [1] "Y" "名詞" "固有名詞" "組織" "*" "*" ## [7] "*" "*" "" "" ## ## [[2]] ## [1] "字" "名詞" "一般" "*" "*" "*" "*" "字" "ジ" "ジ" ## ## [[3]] ## [1] "路" "名詞" "接尾" "一般" "*" "*" "*" "路" "ロ" "ロ" ## ## [[4]] ## [1] "の" "助詞" "連体化" "*" "*" "*" "*" ## [8] "の" "ノ" "ノ" ## ## [[5]] ## [1] "途中" "名詞" "副詞可能" "*" "*" "*" ## [7] "*" "途中" "トチュウ" "トチュー" ## ## [[6]] ## [1] " " "記号" "空白" "*" "*" "*" "*" " " " " " "
結果はリストで返ってくる。
続いてn-gramを作っていく。
#抽出する単語の選択 token <- map_chr(data1, extract(1)) %>% str_to_lower() #文書中の形態素 lemma <- map_chr(data1, extract(8)) %>% str_to_lower() #見出し語化された形態素 pos1 <- map_chr(data1, extract(2)) #品詞 lemma[lemma == "*"] <- token[lemma == "*"] #見出し語化されなかった単語を置き換える df_term1 <- data.frame(TERM = lemma, POS = pos1, stringsAsFactors = FALSE) df_term2 <- df_term1 %>% filter(grepl("名詞|^動詞|形容詞", POS)) #助動詞が引っ掛からないように
## TERM POS ## 1 y 名詞 ## 2 字 名詞 ## 3 路 名詞 ## 4 途中 名詞 ## 5 濃い 形容詞 ## 6 霧 名詞
リストは10個の要素が格納されているので、そこからmap_chr()
で必要なものを取り出す。
- [1]が分かち書きされた単語
- [8]が見出し語化(辞書に載ってる形≒原型)された単語
- [2]が単語の品詞
の3つを使う。
アルファベットの大文字と小文字は別の物として判断されるので、str_to_lower()
で小文字に統一する。
単語の活用形がバラバラの状態だと別の単語として認識されてしまうため、こちらも表記を統一したい。なので[8]を使いたいが、英単語や一部の活用のない単語は[8]が"*"となっているので、その単語については[1]を使うことにする。(英文の量が多いテキストの処理についてはこの記事も読んでみてね)
lemmaに含まれる"*"の位置を検索し、tokenとlemmaに添字として使って置き換える。
#2-gramを作成 n <- nrow(df_term2) ngram1 <- data.frame(N1 = df_term2[1:(n-1), 1], N2 = df_term2[2:n, 1], POS1 = df_term2[1:(n-1), 2], POS2 = df_term2[2:n, 2], FREQ = 1) #要素を1つずらした2列で2-gramとなる ngram2 <- ngram1 %>% group_by(N1, N2) %>% summarise(FREQ = sum(FREQ)) %>% as.data.frame()
head(ngram1) ## N1 N2 POS1 POS2 FREQ ## 1 y 字 名詞 名詞 1 ## 2 字 路 名詞 名詞 1 ## 3 路 途中 名詞 名詞 1 ## 4 途中 濃い 名詞 形容詞 1 ## 5 濃い 霧 形容詞 名詞 1 ## 6 霧 なか 名詞 名詞 1 head(ngram2) ## N1 N2 FREQ ## 1 ahy 字 2 ## 2 y 字 2 ## 3 いい 決める 2 ## 4 うち 空 1 ## 5 うまい 答え 1 ## 6 えぐる つもり 1
「1つ目から最後の1つ前まで」の列(N1)と「2つ目から最後まで」の列(N2)を組み合わせることで、2-gramのデータフレームを作成する。
group_by()
で重複した組み合わせの行を統合して、summarise()
で頻度を加算する。
・削除すべき単語を確認
#共起ネットワークを作図 ngram3 <- filter(ngram2, FREQ >= 1) #プロットする頻度を指定 NROW(ngram3) #プロットする語数を確認 network1 <- graph_from_data_frame(ngram3) tkplot(network1, vertex.color = "chocolate", vertex.size = 13, vertex.label.cex = 0.75, vertex.label.dist = 0, vertex.label.family = "JP1")
n-gram頻度表(ngram2)とこの図を使って、選定すべき単語を判断する。(図右上の"く"とそこから左に伸びた先の"の"、あと右下の"する"が気になるなー)
プロットはマウスを使って操作できる。
・補足
一旦ここまでの処理を検証してみる。
#検証 result <- NgramDF("テキストファイル名.txt", type = 1, N = 2, pos = c("名詞", "動詞", "形容詞")) test1 <- paste(ngram2$N1, ngram2$N2, sep = "-") #共起語を1セットにする:上で作成したもの test2 <- paste(result$Ngram1, result$Ngram2, sep = "-") %>% str_to_lower() #共起語を1セットにする:確認用 t <- test1 %in% test2 #要素の一致数を確認 sum(t) / length(test1) #要素の一致率を確認
head(test1) ## [1] "ahy-字" "y-字" "いい-決める" "うち-空" ## [5] "うまい-答え" "えぐる-つもり" sum(t) / length(test1) ## [1] 1
MeCabの解析結果等は環境によって微妙に異なる可能性があるため、この検証によって精度を担保するものではないが、今回は同じ組み合わせを抽出できた模様。
・単語の選定と作図
では不要な単語を取り除いていく。(削除項目は一例です。)
#単語の剪定 df_term3 <- df_term2 %>% filter(!grepl("^する$|^の$|^く$", TERM)) %>% #削除 mutate(TERM = str_replace(TERM, "ahy", "y")) #置換 n2 <- nrow(df_term3) ngram4 <- data.frame(N1 = df_term3[1:n2-1, 1], N2 = df_term3[2:n2, 1], POS1 = df_term3[1:n2-1, 2], POS2 = df_term3[2:n2, 2], FREQ = 1) ngram5 <- ngram4 %>% group_by(N1, N2) %>% summarise(FREQ = sum(FREQ)) %>% as.data.frame() ngram6 <- filter(ngram5, FREQ >= 1) #プロットする頻度を指定 NROW(ngram6) #プロットする語数を確認 network2 <- graph_from_data_frame(ngram6) tkplot(network2, vertex.color = "#FFF33F", vertex.size = 13, vertex.label.cex = 0.75, vertex.label.dist = 0, vertex.label.family = "JP1")
本来の歌詞としては「"ah","y","字","路"」となって欲しいところが「"ahy","字","路"」と1つの単語とされているのでstr_replace_all()
で"ah"を取り除く(置き換える)。
"する"についてみると、「寄り道して小さな頃の秘密基地へ」「立ち漕ぎして先を急ぐ」というフレーズがある。それぞれ「"寄り道","する","頃","秘密","基地"」「"立ち","漕ぐ","する","先","急ぐ"」となっている。"する"は多くの単語と繋がるハブのようになりそうだが、"する"に注目しても意味的な観点からは重要とは思えないので、"する"も取り除くことにする。(形容動詞も含めて"小さな"も繋げた方が良かったかなぁ…)
"く"は「増えてった」「キツく感じた」のそれぞれ"た"と"く"の部分が、"の"は「大事なのは」の"の"の部分が解析の誤りで含まれたと思われる。よってこれも取り除いておく。
ただし本来ならばもっと文章量の多い分析を行うと思われるので、その場合には高頻度の単語のみを抽出することになり、このような単語は自然と取り除かれることになる。
部分一致で他の単語が巻き込まれないように正規表現の^$で囲っておく。
剪定後のプロット
head(ngram4) ## N1 N2 POS1 POS2 FREQ ## 1 y 字 名詞 名詞 1 ## 2 字 路 名詞 名詞 1 ## 3 路 途中 名詞 名詞 1 ## 4 途中 濃い 名詞 形容詞 1 ## 5 濃い 霧 形容詞 名詞 1 ## 6 霧 なか 名詞 名詞 1
「"寄り道","頃","秘密","基地"」(右下)「"立ち","漕ぐ","先","急ぐ"」(右上)が繋がっているのが確認できました。
以上です!
〇おわりに
このネットワークをどう解釈するかの勉強がまだまだ足りないので、できた!やったー!以上の意味がまだないんですよねー。今はテキストマイニングというよりかは、自然言語処理の範囲を進めています。(分野の区分けとかよく分かってないけどね笑)
あとRの基礎的なところをですね。で、この記事(【テキストマイニング入門】R言語でネットワークグラフを作成する【やってみた)を半ば書き直したのが今回の記事です。レベルアップできてる様なのはいいけども、記事のバリエーションは増えないなーとか。
にしても、インプットとアウトプットのバランスは難しい…入れなきゃ出ない、出さなきゃ抜けてく……。という訳で作業が楽になるのだろうとMarkdownを使い始めました。まさか宇宙本を最終章から読み始めることになろうとは!
最後まで読んでいただきありがとうございました!