日本全国各所で猛暑日が続き、
facebookは野外フェスと海と海外旅行とビアガーデンの写真が溢れ始めております。

しかし猛暑だろうが極寒だろうが、
データ分析官は、週末のビールを信じて
黙々と(たまに独り言と、思い出し笑いをしながら)RとSQLを回す日々なはずです。

ところが、この週末ビールを阻む大いなる壁があります。
そう、もうお気づきかと思いますが、みなさんおなじみ”既読スルー”です。
飲みに誘ってもレスポンスが無いのですから飲みに行けません。(一人飲みをするには勇気が足りない)

「え?既読スルーて、友人の間でもカジュアルに起こる事なの?」と思った貴君、
私だってこの状態が当たり前だとは思っておりません。当然疑問に感じています。

メールコミュニケーションの時代は、返信が来なかろうが
「もう寝たんだろう」「海外旅行中なんだろう」「ケータイ紛失したんだろう」
「深爪してメールを打つのも困難なんだろう」などと、
あり得ないほどオプティミストな思考を人誰しもが身につけて生きてきたかと思いますが、
今のチャットアプリの時代では「既読」がありますので、そうはいきません。

前置きが長くなりましたが、本稿では、
LINEのやりとりデータをランダムフォレストを用いて機械学習し、
“返信”と”既読スルー”の予測モデルを構築
」します。
これによって、オプティミストな思考をしなくとも、
木曜日20時以降で、伊藤に対して飲みの用事で送ったのであれば78%返信が来なくて当然(キリッ
と論理的に判断する事ができ、もう一歩踏み込むと、既読スルーされないためには、
何曜日何時、誰に、どのようなトピックでLINEするべきか、がわかります。

以下が今回分析に用いた私の直近のLINEデータとなります。
※本物のデータを用いないと(私のプライベートにとって)意味がありませんので、そのままのデータです。
 相手の名前のみ偽名です。
※LINEデータの取得方法は過去エントリー「LINEのトーク履歴を取得する。」を参照くださいませ。
※ローデータから、各種前処理・分類をしてパラメータを生成しております。
※やり取りが発生以降は、連続したセッションとしてまとめております。
 相手から最初のレスポンスがあるまでのやりとりのみ抽出しております。

変数は以下の通りです。

week 曜日
time 送信時間
topic drink(飲み)、play(遊び)、question(質問)、
birthday(誕生日祝)、information(情報共有)の5種
to 送信相手
length 文字数
result yes(24時間以内に返信あり)
no(24時間以内に返信無し)

得られたデータは下記となります。

week time topic to length result
tue 22 drink sasaki 8 no
fri 23 drink sasaki 12 no
fri 14 drink sasaki 15 yes
sat 12 drink sasaki 11 yes
sat 15 drink sasaki 19 yes
sat 15 drink sasaki 57 yes
sat 18 drink sasaki 16 yes
sat 19 drink sasaki 13 yes
sun 19 play sasaki 25 yes
tue 11 play sasaki 17 yes
mon 9 play sasaki 36 no
sun 16 question matsuda 9 yes
fri 17 play matsuda 19 yes
wed 9 play konishi 29 yes
wed 23 play konishi 36 no
fri 8 play konishi 44 yes
sat 22 drink satomura 24 yes
fri 22 play satomura 65 yes
sat 0 birthday hirata 54 yes
tue 11 question hirata 75 yes
fri 10 drink hirata 183 no
mon 11 drink kuwano 60 no
wed 21 play kuwano 63 no
sat 13 play kuwano 30 yes
sat 12 play kuwano 44 no
sun 1 play kuwano 40 yes
mon 3 information sato 105 yes
sat 20 drink sato 185 no
tue 15 drink sato 100 no
wed 22 drink sato 86 no
wed 2 drink sato 133 no
mon 11 information sato 350 yes
wed 12 information sato 395 yes
thu 21 drink sato 165 yes
sat 12 drink sato 17 yes
sun 12 drink sato 45 yes
sat 10 information hashimoto 187 no
sat 19 drink hashimoto 72 yes
thu 18 drink hashimoto 44 yes
fri 13 drink hashimoto 71 yes
thu 9 drink hashimoto 161 yes
thu 12 drink fujimoto 277 yes
wed 18 information saito 74 no
tue 10 drink matsumura 67 no
sun 4 drink matsumura 83 no
thu 22 drink matsumura 20 no
sat 19 drink matsumura 43 yes
thu 19 drink matsumura 55 yes
mon 12 information matsumura 89 no
fri 8 question murata 165 yes
sat 18 drink murata 124 yes

まずは、上記データをRに取り込み、データ分析する前に、可視化してデータ探索してみます。
おなじみggplotを使っていきましょう。

縦軸が送信曜日、横軸が送信時間、
凡例は、緑色が「返信あり」、赤色が「返信なし」です。
※曜日の並びがアルファベット順になっていること、ご注意くださいませ、。

random_forest3

次に、送信時間と送信相手を見てみましょう。

random_forest1

縦軸が送信時間、横軸がLINEの送信先の相手、
凡例は、緑色が「返信あり」、赤色が「返信なし」です。

fujimotoさん、matsudaさん、murataさん、satomuraさん、どんなときもありがとうございます。
matsumuraさん、ほぼ返信がありませんが、どういうことでしょうか。

最後に、縦軸が送信時間、横軸が文字数、
凡例は、緑色が「返信あり」、赤色が「返信なし」です。

random_forest2

文字数が長くなるほど返信があるようで何よりです。
とはいえ、200文字近いLINEを送って既読スルーされたときもあるようです。
これは送る方も、送られた方も、辛いです。

さて、ではこのデータを解析していきましょう。
今回はランダムフォレストを用いようと思います。
ランダムフォレストは決定木の応用となります。

なお決定木は、ジニ係数などの分岐基準を用いて、
最も結果を分けるのにふさわしいパラメータを選択し続けていくものです。

なぜ、決定木では充分ではないのでしょうか。
そこで、ランダムフォレストの簡単な説明もかねて、
まずは決定木で本データを解析してみましょう。

decision_tree

「曜日で分類して、”月”・”火”・”水”に送ると返信来ないことのほうが多いから、
 木曜日以降のほうがいい」という結果です。
データ分析官にデータ依頼して1週間待って出てきたのがこれだけだと震えますよね。
もっと他にいろんな要素とか無いものだろうか、とお思いかと思います。
そこで、決定木では、「繁らせる(分岐を増やす)」ことと「プルーニング(分岐を減らす)」を
適宜行っていく事で、精度および実用性を上げていきます。

R上では、controlとして指定します。
木の複雑さを表すcpが、経験上デフォルトだと厳しいと思いますので、少し緩めてみます。

desicion_tree2

分岐上限が増え、決定木らしくなりました。
上記のようなcpをどの程度にしたら良いかは、別途方法があるのですが、
今回は決定木はここまでにしておきます。

さて、このようにして得られる決定木は解釈が容易な反面、
必ずしも最適なパラメータが選択されるとは限らない、という欠点があります。
たとえば、異常値や影響の大きいパラメータの存在などによって、
最初の分岐が(最適なものではなく)決まると以降の分岐は、その前提で進んでいくことになります。

そこで、全てのパラメータを用いるのではなく、任意のパラメータを部分的にランダムに選択してきて、
そこから決定木を作る、ということを複数回行うことで、
一部のパラメータのみに引っ張られることが無いようにし、
その複数の決定木の結果を多数決(ないしは平均値)で予測するという考えが
ランダムフォレスト、となります。

R上では非常にシンプルに下記で実行できます。

本データでは下記の結果となりました。

Call:
randomForest(formula = result ~ ., data = private_mail)
Type of random forest: classification
Number of trees: 500
No. of variables tried at each split: 2

OOB estimate of error rate: 43.14%
Confusion matrix:
no yes class.error
no 4 14 0.7777778
yes 8 25 0.2424242

no yes class.error
no 4 14 0.77
yes 8 25 0.24

yesもnoのときも、どちらもyesと予測してしまっている数が多いようです。
ちなみに現時点でのエラー率は43.14%のようです。

まずは今回、yes=33、no=18と不均衡データとなっておりますので、
重みをつけて調整することにしましょう。
また、ランダムフォレストは、全パラメータの中からどれだけの数を各決定木で選択するかを、
“mtry”、という値で調整することが出来ます。

tuneRFというコマンドを用いることで、自動的に最適なmtryパラメータを探索できます。
最初にパラメータ、次に目的変数を記入します。
※6行目が目的変数で、それ以外が説明変数、yesとnoの数が33:18で逆に重みをかけて調整するときは下記。

tuneRF

縦軸がエラー率ですので、各決定木に使うパラメータ数は2が良いようなので、mtry=2とします。
ではもう一度実行してみましょう。ちなみに作成する決定木の数もntreeで指定する事が出来ます。
この数が少ないと毎回結果がブレますので、大きめに2000としておきましょう。
まとめると下記となります。

Call:
randomForest(formula = result ~ ., data = private_mail, mtry = 2, weight = c(18, 33), ntree = 2000)
Type of random forest: classification
Number of trees: 2000
No. of variables tried at each split: 2

OOB estimate of error rate: 37.25%
Confusion matrix:
no yes class.error
no 4 14 0.7777778
yes 5 28 0.1515152

no yes class.error
no 4 14 0.77
yes 5 28 0.15

エラー率は37.25%に改善されました。
yesの時にnoと予測する点が改善されたようです。
本当は、noのときにyesと予測してしまう点を改善したかったのですが。
(返信が来るときでも来ないと予測する機会損失よりも、
返信が来ないのに来ると予測するリスクを改善したかった。
いえ、ある意味、一歩踏み出すためのポジティブな予測モデルとして、
これでよいのかもしれません。)

最後にこのモデルで計算された各変数の重要度(ジニ係数)を下記のようにして算出しておきます。

variable MeanDecreaseGini
week 5.235269
time 5.070753
topic 1.697444
to 5.415680
length 5.221605

もしパラメータが多いデータの場合は、上記をもとに、
パラメータを重要なものに絞ることが出来ます。
※パラメータを重要なものに絞るためにランダムフォレストを前処理として行い、
 以降を別の処理にかけることも考えられます。

さて、以上で予測モデルは完成となります。
LINEの返答率の結果ですが、
「誰に出すか」が最も重要で、次に「曜日」が影響あるようです。
※あくまで私個人のLINE履歴の傾向であることに留意くださいませ。

作成されたモデルに、新たなデータを入力として用いれば、その入力の結果を予測出来ます。

random_forest5

では、最後に自信を持って、
fujimotoさん、matsudaさん、murataさん、satomuraさん、どんなときもありがとうございます。
matsumuraさん、ほぼ返信がありませんが、どういうことでしょうか。