2016.09.17
訪日外国人の発言内容の「感情分析」を行う
近年増加し続けている「訪日外国人」旅行者は日本でどのような物事に関心を示しているのでしょうか。SNS発言内容の「感情分析」を行うことで動向についてRと拡張パッケージを使って明らかにすることを試みます。
1. はじめに
海外から日本へ訪れる「訪日外国人」旅行者の数(訪日外客数)は、近年増加をし続けています。市街地や観光地を歩けば、日本語よりも海外の言語が多く聞こえてくる、ということも珍しくなくなりました。国際観光の振興を図る日本政府観光局の発表では、昨年2015年の訪日外客数は過去最高の1,973万人超であり [1]、その勢いは年間2,000万人に達する見込みです。訪日外国人の国内での行動や消費の概要に関しては統計データなどを通して読み解くことができますが、その詳細は統計データからは見えにくいものです。一体彼らは日本でどのような物事に関心を示しているのでしょうか。
訪日外国人の詳しい動向を知る手段の1つとして、Twitterを中心としたソーシャル・ネットワーク・サービス(SNS)のデータを活用することが考えられます。それはSNSには個人の意思や行動が内容、時間、場所を問わずに投稿されるからです。例えば美味しい料理を食べた時や非現実的な経験をした時には、記録や共有のためにSNSへ投稿をしたいという気持ちは国籍を超えて共通のものでしょう。旅行先でSNSへ投稿を行うことは、現代では多くの方が経験のあるごく普通な体験であるといえます。加えてSNSデータはその自由度の高さから、統計データで得られない現場の声に近いデータと言い換えることができると考えられます(ただしSNSに投稿される情報は全体の一部でしかないので、データが全体を代表するものではないという前提を理解しておくことが必要です)。
今回はこのSNSデータを利用した訪日外国人の発言内容の「感情分析」を行うことで、その動向について明らかにすることを試みます。SNSデータを統計解析言語Rとその機能拡張であるパッケージ(特に最近開発が行われた新しいパッケージ)によって処理することで、これまで面倒の多かったテキスト処理を簡易に実行できるようになることをこの記事で示します。
2. 訪日外国人データについて
今回の分析では著者の所属する株式会社ナイトレイのサービスの1つであるInbound Insight から得られるSNSデータを利用します。Inbound Insight では、Twitterおよび新浪微博(Sina Weibo 中国国内外で人気のSNS)からデータを取得しており、独自の収集手法によって投稿内容と位置情報、国籍推定の結果などを紐づけることで、訪日外国人の動向を分析することを可能としています。
利用するデータの仕様は以下の通りです。
・ TwitterおよびWeiboから収集された2016年4月のSNSデータ
・ 中国、アメリカ、イギリス、スペインなど15国籍のユーザー
Inbound Insight ではこの他にも、投稿が行われた地名、施設名、性別などの情報を得ることができます。興味を持たれた方はウェブサイトをご覧ください。
3. 分析に利用するパッケージの用意
分析作業に入る前に、利用するパッケージの準備をしておきましょう。今回実行するテキスト分析に必要な2つのパッケージは、Rパッケージの管理機関であるCRANにも登録されていますので次のコードの実行によりインストールが可能です。
# CRANからのインストール install.packages("tidytext") install.packages("sentimentr") # devtools::install_github("juliasilge/tidytext") # 開発版をインストールする場合はこちらを実行 # devtools::install_github("trinker/sentimentr")
インストールしたパッケージをlibrary()を使ってRの実行環境に読み込みます。
library(tidytext) # 文字データの整形・集計 library(sentimentr) # 感情分析
また次のパッケージも処理のために必要なので読み込んでおきましょう。これらのパッケージのうち、インストールされていないものがあれば、先と同様にinstall.packages()によってインストールを行ってください(これらのパッケージもすべてCRANからインストールが可能です)。
library(magrittr) # パイプ演算子の有効化 library(knitr) # HTMLファイルへの表データ出力 library(feather) # ファイル読み込み library(tidyr) # データ整形 library(dplyr) # データ操作全般 library(lubridate) # 日付データの処理 library(ggplot2) # 画像描画 library(viridis) # 画像の装飾
4. 前処理とデータの読み込み
分析を始める前にいくつかの処理が必要となります。「1. はじめに」の部分でも述べましたが、投稿文の中に不要な情報が含まれるためです。今回の分析で利用するテキストマイニングの手法を行う上で、純粋なテキストと区分するために除外するなどの処理を行いました。こうしてInbound Insightが提供するSNSデータに前処理を加えてできたのが、次のinbounds_1604.feather
ファイルです。
こちらのファイルをfeatherパッケージを使って読み込みます。拡張子featherのファイルを用いる理由は、比較的大規模なデータを、データ分析で利用機会の多いRとPythonといった言語間で利用するためです。またデータを軽量なサイズのファイルに圧縮することで高速な読み取りを可能にしています。R言語用のfeatherパッケージには、featherファイルの読み込みと保存のための関数が用意されています。
# Apache Projectの1つに端を発して開発された # RおよびPythonといったデータ分析環境で利用可能な # 互換性のあるデータ形式のファイルを読み込む df.inbs <- read_feather("inbounds_1604.feather")
ファイルをR上に読み込んだら、投稿ごとに識別番号を与えていきます。この処理はdplyrパッケージ内の、変数に処理を加えるmutate()と行番号を与えるrow_number()という関数を利用することで可能となります。
df.inbs %<>% mutate(text_n = row_number())
データに対する前処理はこれで終わりですのでいったんデータの中身を確認してみましょう。データ構造を把握するためにglimpse()を利用します。
glimpse(df.inbs)
Observations: 16,506 Variables: 3 $ tweeted_at <time> 2016-04-01 00:01:31, 2016-04-01 00:04:39, 2016-04... $ trimed_text <chr> "Japanese companies are increasingly looking to bl... $ text_n <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,...
各変数の値は次の通りです。
・ tweeted_at: 投稿時間
・ trimed_text: URLやハッシュタグを除いた投稿文
・ text_n: 時系列に並べた投稿の順番
5. 訪日外国人のSNS投稿の感情分析
トークン化
「トークン化」とは、文字列の連続であるテキストを意味のある単位に分割する作業です。分割の単位は段落や単語、1文字ごとというように用途に応じて異なります。分割の単位を「トークン」と呼びます。今回は、単語ごとの集計と感情スコアの算出が目的ですので、投稿文を単語ごとにトークン化することになります。
次のコードが、Rで文字列のトークン化を行うtokenizersパッケージの関数となります。tokenize_words()で引数に与えた文字列を単語区切りに分割しています。
tokenizers::tokenize_words("Hello, I'm Shinya.")
[[1]] [1] "hello" "i'm" "shinya"
今回利用するtidytextパッケージでは、このようなトークン化やテキスト分析の処理結果をR上で操作しやすい形に整形します。例えば、unnest_tokens()では1つのトークンについて行ごとに分割されたデータフレームオブジェクトを返します。トークンが保存された列と、元の投稿文と紐付いた投稿時間や識別番号の情報を同時に扱えるため、元の投稿の情報とトークン化の結果を直接扱えるという利点があります。実行結果をみると、1つの投稿(行)のデータがunnest_tokens()の引数tokenで指定した単語区切りでトークン化され、それぞれが別の行に格納されていることがわかります。
# 投稿文を単語区切りにし、異なる列のデータとして扱うようにする df.tidy <- df.inbs %>% unnest_tokens(output = word, input = trimed_text, token = "words", drop = FALSE)
tweeted_at | trimed_text | text_n | word |
---|---|---|---|
2016-04-01 00:06:14 | cherryblossom sakura and me princessiceberg japan 2016 | 3 | cherryblossom |
2016-04-01 00:06:14 | cherryblossom sakura and me princessiceberg japan 2016 | 3 | sakura |
2016-04-01 00:06:14 | cherryblossom sakura and me princessiceberg japan 2016 | 3 | and |
2016-04-01 00:06:14 | cherryblossom sakura and me princessiceberg japan 2016 | 3 | me |
2016-04-01 00:06:14 | cherryblossom sakura and me princessiceberg japan 2016 | 3 | princessiceberg |
2016-04-01 00:06:14 | cherryblossom sakura and me princessiceberg japan 2016 | 3 | japan |
2016-04-01 00:06:14 | cherryblossom sakura and me princessiceberg japan 2016 | 3 | 2016 |
不要な単語の除去
投稿文を単語単位でトークン化したあとは、不要な単語を除く処理を行います。ストップワードとは使用頻度の高い一般的な単語や、除外すべき単語の集まりを指します。例えばandやtoなどは文章中で頻出し重大な働きをしますが、個々の単語としては重要ではないのでストップワードに含まれます。このようなストップワードの除外は自然言語処理の作業では一般的に行われます。
tidytextでは、テキストマイニングに利用されるtmパッケージが提供する英単語の3つの辞書を元にして作成されたストップワードのデータセットが備わっています [2]。このデータセットを利用して不要な単語が含まれている行を取り除く処理を行います。
# ストップワードのデータの一部を見てみる data("stop_words", package = "tidytext") stop_words %>% head(5) %>% kable(format = "markdown")
word | lexicon |
---|---|
a | SMART |
a's | SMART |
able | SMART |
about | SMART |
above | SMART |
# トークン化した結果を保存したデータフレームからストップワードとなる単語を除外する df.tidy %<>% anti_join(stop_words, by = "word") # anti_join()により、word列が一致しないデータのみ結合する
感情の正負による単語のカウント
ストップワードを除いたSNS投稿で頻出する単語について、その単語がポジティブなものなのか、ネガティブなものなのかといった情報を与え、出現回数の集計処理を行ってみましょう。このような分析は感情分析と呼ばれます。
感情分析には、先に行ったトークン化あるいは形態素解析の結果と、単語に対して与えられる感情の種類や正負の判定(ポジティブかネガティブか)を行った辞書を利用します。今回の分析ではtidytextパッケージに内蔵のデータセットを感情分析の辞書として用います。このデータセット辞書は3つの辞書を組み合わせたもので [3]、単純にポジティブな発言を示す単語かネガティブな発言を示す単語かが分かればよい、という理由からbingの辞書を使うことにします。
data("sentiments", package = "tidytext") # bingからの辞書データを抽出 df.sent.dic <- sentiments %>% filter(lexicon == "bing") %>% select(-score)
単語のポジティブさ・ネガティブさという視点で見た際の、単語の出現頻度は次のようになりました。横軸方向に単語が並びます。縦軸の値は出現した回数を示します。ネガティブな単語については集計値を負の値にすることでマイナス方向へ積み重ねています。
df.tidy %>% inner_join(df.sent.dic, by = "word") %>% # word列を基準にした内部結合 count(word, sentiment, sort = TRUE) %>% # 単語と感情の正負に応じて頻度を集計 ungroup() %>% filter(n > 40) %>% # 出現回数が40以上の単語を抽出 mutate(n = if_else(sentiment == "negative", -n, n), # 負の感情をもつ単語の場合、値をマイナスにする word = reorder(word, n)) %>% # 頻度の多い単語順に並び替えを指定する ggplot(aes(word, n, fill = sentiment)) + geom_bar(stat = "identity") + ylab("word count") + scale_fill_manual(values = c("#FB3F1C", "#7ABA3A")) + theme(axis.text.x = element_text(angle = 90, hjust = 1))
ポジティブな単語として出現頻度の高いものにはlove
、beautiful
、happy
といった楽しさや美しさを表す単語が並びました。blossom
、bloom
といった単語は春の訪れを感じさせる単語となっています。対してネガティブな印象を持つ単語の中では、miss
、lost
が出現頻度の高い単語となりました。旅行中、何か忘れ物などをした方が多かったのかもしれません。
投稿単位で見た感情の正負と投稿時間の関係
先の結果は投稿文を単語に区切った集計でしたが、1つの投稿文全体に対して感情の得点付けを行いたい場合などがあります。このような場合には、今回利用する感情分析のための2つ目のパッケージであるsentimentrの関数を用いることで対応が可能です [4]。
sentimentrパッケージが提供する関数を利用すると、次のような投稿に対して投稿全体、一文ごとといった単位で感情分析の得点付けを行うことができます。
df.inbs$trimed_text[34]
[1] "The wonders of Japan. Akihabara Electronic Towncity Tokyo "
df.inbs$trimed_text[34] %>% sentiment() # ピリオドによる一文の区切りからスコアリングを実施
element_id sentence_id word_count sentiment 1: 1 1 4 0.5 2: 1 2 4 0.0
df.inbs$trimed_text[34] %>% sentiment_by() # 投稿を1つの文としてスコアリング
element_id word_count sd ave_sentiment 1: 1 8 0.3535534 0.25
sentiment()では投稿文の一文の区切りごとに感情分析の得点が、sentimentの値として与えられますが、sentiment_by()のほうでは投稿文全体の得点をave_sentimentによって返すようになっています。ではSNSデータの投稿文に感情分析による得点を付与していきましょう。
df.sent.score <- df.inbs$trimed_text %>% sentiment_by() %>% select(text_n = element_id, sentiment = ave_sentiment)
得られた得点と元の投稿データを、分析の最初に与えたtext_nという列によって結合します。また今回は、4月の曜日ごとの投稿件数と感情分析のスコアを確認するため、次のようなカレンダー状のグラフを描画します。
df.inbs %>% inner_join(df.sent.score, by = "text_n") %>% filter(sentiment != 0) %>% dplyr::mutate( week = floor_date(tweeted_at, "day") %>% week(), wday = wday(tweeted_at, label = TRUE) ) %>% group_by(week, wday) %>% summarise(n = n(), mean = mean(sentiment)) %>% ggplot(aes(wday, week, size = n)) + geom_point(aes(fill = mean), shape = 21) + scale_fill_viridis(option = "D") + theme_bw() + scale_size("n") + scale_y_reverse(lim = c(18, 14), labels = c("0401-0407", "0408-0414", "0415-0421", "0422-0428", "0429-0431")) + theme(axis.text.x = element_text(angle = 60, hjust = 1))
円の大きさが投稿数を示し、明るい色(黄色から青色)になるにつれ、ポジティブな発言が増えていることを示します。特に4月1週2週は投稿数が多いようです。一方で第3週以降は投稿数が減少しています。このことは、blossom
、bloom
という単語の出現回数が多かったことと合わせて花見による投稿数の増加の効果を示唆しています。また第3週以降の投稿数の減少は、桜の見頃が過ぎてしまったことが原因になっていると考えられます。一方で投稿文全体の感情スコアを見ると日付や曜日といった時間軸での傾向はつかみにくく、その影響は旅行中の訪日外国人に対しては大きく影響しないことが考えられます。
6. おわりに
SNS投稿データを利用した訪日外国人の感情分析、いかがだったでしょうか。扱ったデータが4月のものということもあり、綺麗な桜・花見を連想させるような分析結果が得られたと思います。今回の記事の内容を整理すると次のようになります。
・ Inbound Insightの訪日外国人データを使ったSNS投稿の感情分析を統計解析言語Rとそのパッケージを用いて実施
・ 先進的なRパッケージを利用することで、処理が面倒なテキストデータを簡便に扱うことが可能
・ 扱った4月の訪日外国人データからは、桜、花見、美しい、といった日本人にも共通する単語や感情を持った投稿が多く見られた
・実行環境
package | version | date | source |
---|---|---|---|
dplyr | 0.5.0 | 2016-06-24 | CRAN (R 3.3.0) |
feather | 0.3.0 | 2016-08-25 | CRAN (R 3.3.1) |
ggplot2 | 2.1.0 | 2016-03-01 | CRAN (R 3.3.0) |
knitr | 1.14 | 2016-08-13 | CRAN (R 3.3.0) |
lubridate | 1.5.6 | 2016-04-06 | CRAN (R 3.2.4) |
magrittr | 1.5 | 2014-11-22 | CRAN (R 3.3.0) |
sentimentr | 0.2.3 | 2016-08-19 | CRAN (R 3.3.1) |
tidyr | 0.6.0 | 2016-08-12 | CRAN (R 3.3.0) |
tidytext | 0.1.1 | 2016-06-25 | CRAN (R 3.3.0) |
viridis | 0.3.4 | 2016-03-12 | CRAN (R 3.2.4) |
・参考資料・URL
[1] http://www.jnto.go.jp/jpn/statistics/data_info_listing/pdf/160119_monthly.pdf
[2] onix http://www.lextek.com/manuals/onix/stopwords1.html, SMART http://jmlr.csail.mit.edu/papers/volume5/lewis04a/a11-smart-stop-list/english.stop, snowball http://snowball.tartarus.org/algorithms/english/stop.txt
[3] NRC Emotion Lexicon http://saifmohammad.com/WebPages/lexicons.html, Microsoft Live/Bing Search https://www.cs.uic.edu/~liub/FBS/sentiment-analysis.html, AFINN (Finn Arup Nielsen) http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6010
[4] 感情スコアを算出するためのアルゴリズムや辞書データの詳細はパッケージのリポジトリに書かれています https://github.com/trinker/sentimentr