トップ 履歴 一覧 Farm ソース 検索 ヘルプ RSS ログイン

RcppMeCabパッケージ


キーワード

最終更新時間:2020年06月27日 13時52分45秒
アフィリエイト・広告について
プライバシーポリシー

この記事はRcppMeCabとtidytextではじめるテキストマイニングシリーズの一部です。まだ書きあげてませんけど。


第86回Tokyo.RでRcppMeCabについて発表しました

目次


RcppMeCabパッケージは、Junhewk Kim氏による、MeCabをRから呼び出すためのパッケージです。名前の通り、Rcpp (Rの関数をC++で実装するための仕組み) で作成されており、大量のテキストを処理する際にも高速です。また、関数は pos() または並列処理を行う posParallel() のみとシンプルで、形態素解析するという目的に特化しています。形態素解析の結果はデータフレーム (またはベクトル) で出力されるため、tidyverseの各種パッケージとも相性がよく、モダンあるいはtidyなデータ処理が可能です。

RcppMeCabパッケージのインストール

RcppMeCabパッケージは、CRANに登録されていますが、標準では韓国語バージョンのパッケージがインストールされ、日本語環境でのパッケージのコンパイルに失敗するため[1]、以下のように筆者が改変したパッケージのレポジトリを指定してインストールします。なお、ソースコードからのコンパイルとなるため、WindowsではRToolsをインストールしておく必要があります。基本的に、"recommended" と表示されているものをダウンロードし、インストールします。RToolsをインストールしてから、以下の関数を実行します。Linuxでもインストール手順は同一です。

Sys.setenv(MECAB_LANG = 'ja')
library(devtools) # パッケージが入っていない場合はインストールする
install_github("ltl-manabi/RcppMeCab")

RcppMeCabパッケージが提供する関数

前述のように、RcppMeCabパッケージは pos() 関数と posParallel() の2つしか関数を提供しません。また、両者は並列処理を行うかどうかだけの違いで、テキストデータを与えると形態素解析の結果が返ってくる、という意味では同一です。
なお、名称からすると posParallel() 関数が大量のテキストを効率的に並列処理してくれそうですが、関数のヘルプによると、Rcppが文字列ベクトルの並列処理に対応していないため、大量のテキストを処理する際には pos() 関数を使った方がよいそうです。以下が、ヘルプの該当部分です。

Parallelizing over a character vector is not supported by ‘RcppParallel’. Thus, this function makes duplicates of the input and the output. Therefore, if your data volume is large, use ‘pos’ or divide the vector to several sub-vectors.

1文が長いデータよりは、短い文章が (ベクトルの要素として) 大量にあるような場合に並列化は効果を発揮しそうです。

pos()関数の使い方

※以下の説明・出力例は形態素解析器MeCabの記事で紹介した64bit版MeCabおよびNEologd辞書のインストールを行っていることが前提です。

ここでは、pos() 関数の使い方を紹介します。posParallel() 関数の使い方も同一です。基本的な書式は以下の通りです。

pos(文字列ベクトル, join = TRUE / FALSE, format = "list" / "data.frame",
sys_dic = MeCabシステム辞書, user_dic = MeCabユーザー辞書)

pos() 関数の引数は文字列ベクトルです。ファイルを与えることはできません。[2]なお、ここまでの手順を経るとMeCabがUTF-8でインストールされているため、Windowsでは文字列を iconv("テキスト", from = "cp932", to ="UTF-8")
としてUTF-8に変換して渡す必要があります。utf8パッケージをインストールすると、as_utf8("テキスト") 関数でよりシンプルに記述できます。

join = TRUE / FALSE オプションで TRUE を指定すると、出力が "表層形/品詞分類" という形式になります。FALSE を選択すると、表層形の形態素のみが出力されます。

format = "list" / "data.frame" は出力フォーマットを指定します。デフォルトは list です。形態素解析の結果を用いて、テキストマイニングをする際には、data.frame で出力した方が後の扱いが便利です。

sys_dic および user_dic はMeCabの辞書ファイルを指定するオプションですが、ここでは上述のようにシステム辞書としてNEologd辞書を使うようにしているため、特に設定する必要はありません。

それでは、サンプルテキストを解析してみましょう。ここでは、utf8パッケージを使ってテキストを渡します。

library(RcppMeCab)
library(utf8)
pos(as_utf8("May J.がmacOSを搭載したMacBook ProをAir DOの機内に持ち込んだ。"))

出力は以下のようになります。

$`May J.がmacOSを搭載したMacBook ProをAir DOの機内に持ち込んだ。`
 [1] "May J./名詞"      "が/助詞"          "macOS/名詞"      
 [4] "を/助詞"          "搭載/名詞"        "し/動詞"         
 [7] "た/助動詞"        "MacBook Pro/名詞" "を/助詞"         
[10] "Air DO/名詞"      "の/助詞"          "機内/名詞"       
[13] "に/助詞"          "持ち込ん/動詞"    "だ/助動詞"       
[16] "。/記号"         

特にオプションを指定していないため、デフォルトの join = TRUE, format = "list" が選択されています。次に、join = FALSE として出力します。

pos(as_utf8("May J.がmacOSを搭載したMacBook ProをAir DOの機内に持ち込んだ。"), join = FALSE)
$`May J.がmacOSを搭載したMacBook ProをAir DOの機内に持ち込んだ。`
         名詞          助詞          名詞          助詞          名詞 
     "May J."          "が"       "macOS"          "を"        "搭載" 
         動詞        助動詞          名詞          助詞          名詞 
         "し"          "た" "MacBook Pro"          "を"      "Air DO" 
         助詞          名詞          助詞          動詞        助動詞 
         "の"        "機内"          "に"    "持ち込ん"          "だ" 
         記号 
         "。"

出力が表層形のみになりました。続いて、format = "data.frame" とします。

pos(as_utf8("May J.がmacOSを搭載したMacBook ProをAir DOの機内に持ち込んだ。"), format = "data.frame")
   doc_id sentence_id token_id       token    pos  subtype        base             yomi
1       1           1        1      May J.   名詞 固有名詞      May J.       メイジェイ
2       1           1        2          が   助詞   格助詞          が               ガ
3       1           1        3       macOS   名詞 固有名詞       macOS   マックオーエス
4       1           1        4          を   助詞   格助詞          を               ヲ
5       1           1        5        搭載   名詞 サ変接続        搭載         トウサイ
6       1           1        6          し   動詞     自立        する               シ
7       1           1        7          た 助動詞                   た               タ
8       1           1        8 MacBook Pro   名詞 固有名詞 MacBook Pro マックブックプロ
9       1           1        9          を   助詞   格助詞          を               ヲ
10      1           1       10      Air DO   名詞 固有名詞      Air DO         エアドゥ
11      1           1       11          の   助詞   連体化          の               ノ
12      1           1       12        機内   名詞     一般        機内           キナイ
13      1           1       13          に   助詞   格助詞          に               ニ
14      1           1       14    持ち込ん   動詞     自立    持ち込む         モチコン
15      1           1       15          だ 助動詞                   だ               ダ
16      1           1       16          。   記号     句点          。               。

出力にbase列があり、原形が表示されていますが、これは筆者独自の改変です[3]。一般に、日本語でテキストマイニングする場合、活用があり、語尾が変化する表層形ではなく、原形で集計などを行うためです。基本的に、RcppMeCabパッケージが提供する機能は、これだけです。

RcppMeCabでは、出力がデータフレームで得られるため、tidyverseの各種パッケージと組み合わせて様々な分析が可能です。詳細は後述しますが、例えばdplyrパッケージと組み合わせて、単語の出現頻度をカウントできます。

library(tidyverse)
pos(as_utf8("May J.がmacOSを搭載したMacBook ProをAir DOの機内に持ち込んだ。"),
format = "data.frame") %>% group_by(base) %>% summarise(count = n())

# A tibble: 15 x 2
   base        count
   <fct>       <int>
 1 。              1
 2 Air DO          1
 3 MacBook Pro     1
 4 macOS           1
 5 May J.          1
 6 が              1
 7 する            1
 8 た              1
 9 だ              1
10 に              1
11 の              1
12 を              2
13 機内            1
14 持ち込む        1
15 搭載            1

短い文章なので集計する必要もないですが、上記のように頻度分析を簡単に行うことができます。

複数の文書・センテンスをまとめて解析、集計する

続いて、応用として複数の文書、センテンスをいちどにRcppMeCabで解析、集計する例を示します。ここで言う「センテンス」とは句点「。」など "EOS" (End of speech) までの1文、「文書」とは改行で区切られた複数のセンテンス[4]を意味します。

テキストマイニングにおいては、複数の文書、センテンスを比較して何らかの考察をすることが多く、RcppMeCabでも doc_idsentence_id で文書、センテンスを管理しています。

 1つのファイルに複数の文書が含まれている場合

ここでは、横浜市金沢区がオープンデータとして公開している、「子育てサービスアイデア一覧」のファイルを読み込み、記載されている複数の文書、センテンスを解析、集計してみます。データはWebサイト上でCSVファイルとして公開されているので、readrパッケージの read_csv() 関数で読み込み、パイプを介してRcppMeCabに渡します。なお、以下のコードは、Windows環境で実行すると、文字コード (UTF-8) の関係で、正常に処理できなかったり、Rがハングアップすることがあります。ハングアップしても構わない (他に重要な処理をしていない、大事なファイルを開いていない) 環境で、何度か実行してみてください。

library(tidyverse)
library(utf8)
library(stringr)
library(RcppMeCab)
res <- read_csv("https://www.city.yokohama.lg.jp/kanazawa/kusei/tokei/opendata/data-portal/kz-opendata.files/0245_20181102.csv",
                locale = locale(encoding = "cp932")) %>%
select(アイデア詳細or補足) %>% pull() %>% str_replace_all(., "\\n", "") %>%
pos(format = "data.frame") %>% mutate(doc_id = factor(doc_id, levels = 1:length(levels(.$doc_id)))) %>%
group_by(doc_id, base) %>% summarise(count = n()) %>% arrange(doc_id, desc(count))

res %>% do(head(., 2))

実行結果は、以下のようになります。各文書 (doc_id) の頻出形態素上位2語を出力しています。

# A tibble: 106 x 3
# Groups:   doc_id [53]
   doc_id base   count
   <fct>  <fct>  <int>
 1 1      て         3
 2 1      の         3
 3 2      、         2
 4 2      に         2
 5 3      ・         6
 6 3      に         4
 7 4      ・         3
 8 4      「         2
 9 5      ・         3
10 5      て         2
11 6      ・         2
12 6      できる     2
13 7      ・         3
14 7      できる     3
15 8      の         6
16 8      を         4
17 9      の         5
18 9      ”          4
19 10     ・         3
20 10     の         3
# … with 86 more rows

一連のプログラムについて、少し解説します。

library(tidyverse)
library(utf8)
library(stringr)
library(RcppMeCab)

ライブラリを読み込んでいます。

res <- read_csv("https://www.city.yokohama.lg.jp/kanazawa/kusei/tokei/opendata/data-portal/kz-opendata.files/0245_20181102.csv",
                locale = locale(encoding = "cp932")) %>%

Webサイトからファイルをダウンロードして読み込んでいます。文字コードにCP932 (Windowsの標準) を指定しています。

select(アイデア詳細or補足) %>%

解析対象の列のみを抽出しています。

pull() %>%

データフレーム (tibble) をベクトルに変換しています。

str_replace_all(., "\\n", "") %>%

データの中に、「セル内改行」が含まれているため、置換して削除しています。

pos(format = "data.frame") %>%

RcppMeCabパッケージで形態素解析しています。

mutate(doc_id = factor(doc_id, levels = 1:length(levels(.$doc_id)))) %>%

doc_id 列はfactor型なのですが、文字の順に "1", "10", "11",... となっているため、"1", "2", "3",... となるよう水準を再設定しています。

group_by(doc_id, base) %>%

文書番号 (doc_id) と原形 (base) でグループ化しています。

summarise(count = n()) %>%

グループ単位で件数をカウントしています。

arrange(doc_id, desc(count))

文書番号の昇順と件数の降順で並べ替えています。

res %>% do(head(., 2))

グループ単位で引数に与えた関数 (ここでは head() 関数) を適用します。

 複数のファイルに文書が分かれている場合

次に、複数のファイルに文書が分かれている場合の処理を紹介します。具体的には、アンケートなどで1人の回答が1つのファイルに記述されていて、それが人数分あるというようなものです。ここでは、上と同様に、横浜市金沢区がオープンデータとして公開している、「子育てを応援する仕組みとして『あったらいいな』というもの」というアンケートを使用します。オリジナルのファイルは1つのCSVファイルですが、あらかじめ1件の回答ごとに1つのファイルに書き出し、分割したデータを筆者が用意したので、それをダウンロードし、処理します。なお、以下のコードは、Windows環境で実行すると、文字コード (UTF-8) の関係で、正常に処理できなかったり、Rがハングアップすることがあります。ハングアップしても構わない (他に重要な処理をしていない、大事なファイルを開いていない) 環境で、何度か実行してみてください。

library(tidyverse)
library(fs)
library(utf8)
library(RcppMeCab)
download.file("https://media.githubusercontent.com/media/ltl-manabi/R/master/kanazawa_ward_opendata_0253_20181102.zip",
              destfile = "kanazawa_ward_opendata_0253_20181102.zip")
unzip("kanazawa_ward_opendata_0253_20181102.zip")
files <- dir_ls("kanazawa_ward_opendata_0253_20181102", glob = "*.txt")
combined_text <- map_chr(files, readLines, encoding = "UTF-8") %>% unname()
res <- combined_text %>% pos(as_utf8(.), format = "data.frame") %>%
mutate(doc_id = factor(doc_id, levels = 1:length(levels(.$doc_id)))) %>%
group_by(doc_id, base) %>% summarise(count = n()) %>% arrange(doc_id, desc(count))

res %>% do(head(., 2))

結果は以下のようになります。

# A tibble: 168 x 3
# Groups:   doc_id [84]
   doc_id base  count
   <fct>  <fct> <int>
 1 1      「        1
 2 1      」        1
 3 2      の        5
 4 2      、        4
 5 3      。        2
 6 3      の        2
 7 4      や        2
 8 4      た        1
 9 5      、        3
10 5      。        3
# … with 158 more rows

こちらも、プログラムについて解説します。

library(tidyverse)
library(fs)
library(utf8)
library(RcppMeCab)

ライブラリを読み込んでいます。fsパッケージはファイル名などを柔軟に扱うためのパッケージです。

download.file("https://media.githubusercontent.com/media/ltl-manabi/R/master/kanazawa_ward_opendata_0253_20181102.zip",
              destfile = "kanazawa_ward_opendata_0253_20181102.zip")
unzip("kanazawa_ward_opendata_0253_20181102.zip")
files <- dir_ls("kanazawa_ward_opendata_0253_20181102", glob = "*.txt")

zipファイルをダウンロードし、展開しています。そして、フォルダー内のファイル名を取得し、オブジェクトに格納しています。

combined_text <- map_chr(files, readLines, encoding = "UTF-8") %>% unname()

それぞれのファイル名について、readLines() 関数で読み込んでいます。purrr::map_chr() 関数は、第1引数に対して、第2引数の関数を並列で適用し、結果を文字列ベクトルで返します。ファイル名がベクトルの名前として付与されるので、unname() 関数で除去しています。

res <- combined_text %>% pos(as_utf8(.), format = "data.frame") %>%
mutate(doc_id = factor(doc_id, levels = 1:length(levels(.$doc_id)))) %>%
group_by(doc_id, base) %>% summarise(count = n()) %>% arrange(doc_id, desc(count))

res %>% do(head(., 2))

ここは、上記の例と同一です。形態素解析を行い、文書ごとに形態素の出現頻度を集計して表示しています。

このように、RcppMeCabとtidyverseをはじめとしたパッケージを組み合わせることで、モダンな[5]テキストマイニングが可能です。


  • [1]Windows (64bit) ではインストール中に呼び出すmecab.dllが32bit版になっていて失敗します。Linuxでは、MeCabライブラリ (libmecab.so) の呼び出しがソースコードになく、失敗します。
  • [2]tidyverseのパイプを使って、前の処理でテキストファイルを読み込み、その出力を入力として受け取ることはできます。
  • [3]CRANや開発者のGitHubからインストールした場合、表示されません。
  • [4]Rの文脈では、ベクトルの1つの要素。
  • [5]何をもってモダンと言うのかはよくわかりませんが。

関連ページ: R言語を学ぶための参考書籍リスト
カテゴリ: [R,データ分析,テキストマイニング,RcppMeCabとtidytextではじめるテキストマイニング]