RMeCabで形態素解析した結果をtidy textなdata.frameで取得する
RMeCabを使っていると、品詞や品詞細分類、読みなどの結果をdata.frameの形で取得したいと思うことがあります。ここでは、品詞についての全ての結果と、品詞情報のみをdata.frameで取得する方法を示します。
例として、以下のtextを形態素解析します。
library(tidyverse)
library(magrittr)
library(RMeCab)
text <- c("吾輩は猫である。","名前はまだない。")
全結果を取得する場合
以下のように、RMeCabText関数は、一つ一つの形態素ごとに「表層形」、「品詞」、「品詞細分類1」、「品詞細分類2」、「品詞細分類3」、「活用形1」、「活用形2」、「原形」、「読み」、「発音」の長さ10のベクトルを要素に持つリストを作ります。
tmp <- tempfile()
write(text[1],tmp)
rmecab_text <- RMeCabText(tmp)
rmecab_text %>%
head(3)
## [[1]]
## [1] "吾輩" "名詞" "代名詞" "一般" "*" "*"
## [7] "*" "吾輩" "ワガハイ" "ワガハイ"
##
## [[2]]
## [1] "は" "助詞" "係助詞" "*" "*" "*" "*" "は"
## [9] "ハ" "ワ"
##
## [[3]]
## [1] "猫" "名詞" "一般" "*" "*" "*" "*" "猫" "ネコ" "ネコ"
なお、RMeCabTextはファイルから読み込むため、R上のオブジェクトをRMeCabTextに掛ける場合はtempfileで一時ファイルを作ってそれを読み込む形をとります。
ということは、textの各要素についてRMeCabTextを行い、各結果のリストをflatten_chrして全部繋げてから10列のdata.frameにすれば欲しい結果が得られます。
res1 <- text %>%
map(function(x){
tmp <- tempfile()
write(x,tmp)
func <- quietly(RMeCabText)
res <- func(tmp)$result
res_df <- res %>%
flatten_chr() %>%
matrix(ncol=10,byrow=TRUE) %>%
as.data.frame() %>%
set_colnames(c("surface","pos","pos1","pos2","pos3","form1","form2","base","yomi","hatsuon"))
file.remove(tmp)
return(res_df)
}) %>%
# textの何番目の要素を形態素解析したかというidを付けておく
enframe(name="id",value="value") %>%
unnest(value) %>%
# tibbleをdata.frameに直す(直さなくてもいい)
as.data.frame()
res1
## id surface pos pos1 pos2 pos3 form1 form2 base yomi
## 1 1 吾輩 名詞 代名詞 一般 * * * 吾輩 ワガハイ
## 2 1 は 助詞 係助詞 * * * * は ハ
## 3 1 猫 名詞 一般 * * * * 猫 ネコ
## 4 1 で 助動詞 * * * 特殊・ダ 連用形 だ デ
## 5 1 ある 助動詞 * * * 五段・ラ行アル 基本形 ある アル
## 6 1 。 記号 句点 * * * * 。 。
## 7 2 名前 名詞 一般 * * * * 名前 ナマエ
## 8 2 は 助詞 係助詞 * * * * は ハ
## 9 2 まだ 副詞 助詞類接続 * * * * まだ マダ
## 10 2 ない 形容詞 自立 * * 形容詞・アウオ段 基本形 ない ナイ
## 11 2 。 記号 句点 * * * * 。 。
## hatsuon
## 1 ワガハイ
## 2 ワ
## 3 ネコ
## 4 デ
## 5 アル
## 6 。
## 7 ナマエ
## 8 ワ
## 9 マダ
## 10 ナイ
## 11 。
RMeCabTextは読み込んだファイルパスをコンソールに出力します。これはありがたいのですが、今回読み込んでいるのは一時ファイルであり、しかもtextの1要素ずつ一時ファイルを作っているためにコンソールの出力がすごい量になるので、purrr::quietlyを用いて出力しないようにしています。
今形態素解析にかけたtextはベクトルでしたが、実際の分析では以下のようなdata.frameの場合もよくあります。
df <- data.frame(sentence_id=1:2,text=text)
df
## sentence_id text
## 1 1 吾輩は猫である。
## 2 2 名前はまだない。
その場合でも、一発でtext列と紐付いた結果が得られますね。
left_join(df,res1,by=c("sentence_id"="id")) %>%
head(3)
## sentence_id text surface pos pos1 pos2 pos3 form1 form2 base
## 1 1 吾輩は猫である。 吾輩 名詞 代名詞 一般 * * * 吾輩
## 2 1 吾輩は猫である。 は 助詞 係助詞 * * * * は
## 3 1 吾輩は猫である。 猫 名詞 一般 * * * * 猫
## yomi hatsuon
## 1 ワガハイ ワガハイ
## 2 ハ ワ
## 3 ネコ ネコ
このような綺麗な形式のdata.frameが得られると、その後の分析が楽になりますね。
語と品詞だけあればよい場合
品詞細分類などの列は不要であり、形態素解析された語と品詞の列だけあれば十分という場合も多いです。
この場合、上記のコードを実行後にselectで必要な列のみ選択してもいいのですが、以下のRMeCabCを用いる方法もあります。
RMeCabCはベクトルを引数に取り、以下のような返り値を返します。
RMeCabC(text[1]) %>%
head(3)
## [[1]]
## 名詞
## "吾輩"
##
## [[2]]
## 助詞
## "は"
##
## [[3]]
## 名詞
## "猫"
ということは、flatten_chrすれば、分かち書きされた結果のベクトルに、品詞情報が名前として付いた名前付きベクトルが得られるので、以下のようにすれば欲しい結果が得られます。
res2 <- text %>%
map(function(x){
mecab_raw <- RMeCabC(x)
mecab_vec <- flatten_chr(mecab_raw)
mecab_df <- data.frame(surface=mecab_vec,pos=names(mecab_vec))
return(mecab_df)
}) %>%
enframe(name="id",value="value") %>%
unnest(value) %>%
as.data.frame()
res2 %>%
head
## id surface pos
## 1 1 吾輩 名詞
## 2 1 は 助詞
## 3 1 猫 名詞
## 4 1 で 助動詞
## 5 1 ある 助動詞
## 6 1 。 記号
形態素解析にかけたいtextがdata.frameの形式の場合でも、先の例と同様にすれば結果が得られます。
purrr様々ですね。
tidy text
tidy textという概念があります。tidy textについては以下の書籍が詳しいです。
Rによるテキストマイニング ―tidytextを活用したデータ分析と可視化の基礎
これは、以下の英語の原文を和訳したものです。
原文の第1章冒頭で、tidy textは以下のように定義されています。
“We thus define the tidy text format as being a table with one-token-per-row. A token is a meaningful unit of text, such as a word, that we are interested in using for analysis, and tokenization is the process of splitting text into tokens.”
1行につき1トークン(単語や形態素など)のdata.frameの形でトークンが記載されているデータのことです。この記事で紹介した方法では、形態素解析の結果がtidy textな形式で得られるので、その後のデータの加工が容易になるというメリットがあります。