「俺」は背中で語る ~一人称から見るTwitter文字数分析~

f:id:iTeresa:20171017182425p:plain

こんにちは、てれにゃんです。Pythonを使ったTwitterスクレイピングによる心理学的検討をしたいと思います。※機械学習のことはいっさい含まれていないのでご注意ください。

自分のことを何と呼ぶかで、与える印象は大きく変わりますよね。男性の場合で考えると、「おれ」だとより男性的で力強いイメージ「ぼく」だと従順で腰が低いイメージといったところでしょうか。では、実際に使う一人称によって、発言自体に違いが現れてくるのでしょうか?

今回はTwitterのツイート文字数に違いがあるかを調べました。日本の男らしい男性は、あまり多くを語らないとよく言われます。「俺」「僕」はいずれも一般的に男性が使う一人称ですが、「俺」の方がより男性性の強いイメージがあります。このことから、「俺」を使う人の方がより多くを語らないのではないかと考えられます。そこで、以下の仮説を立てました。

〜仮説〜

「俺」を使う人は「僕」を使う人より1ツイートにおける文字数が少ない?

ちなみに、俺や僕を使う女性もたまにいますが、そのへんは無視して考えたいと思います。

0.環境&準備

  • Python3系
  • jupyter notebook
  • Twitterアカウント

1.「俺」「おれ」「僕」「ぼく」を含むツイートを取得

まずはツイートを取得します。Twitterを使って分析したい場合はまずAPIを取得する必要がありますので、API取得のためのキーを持っていない方はまずこちらを参照してください。

qiita.com

以下のリンクのコードをコピペすると、API切れなども考慮された挙動ができ、大変便利です。約10000件まではAPI切れせず素早く取得できました。それ以上は15分ほど待てばさらに取得可能です。

ailaby.com


#キーワードで取得
getter = TweetsGetter.bySearch(u'俺 OR おれ OR 僕 OR ぼく')

サーチ単語をこのように書き換えてしまえばOKです。カタカナも含めた方が抜け漏れはありませんが、乳製品が多く拾われそうなので今回はやめました。

2.俺/僕判定

「俺」「おれ」「僕」「ぼく」のいずれかが含まれるツイートを取得しましたが、1ツイートの中で一人称が複数含まれる場合が考えられます。その場合、多く含まれる方を採用し、俺と僕の両方が含まれている場合は「どちらでもない」という判断をする必要があります。

以下では「俺」「おれ」が含まれていたらOREリストに1、「僕」「ぼく」が含まれていたらBOKリストに1が追加されるようになっています。ただし、俺と僕、俺とぼく、おれと僕、おれとぼくのような組み合わせの場合は、いずれのリストも0となります。


counter = Counter(tweet["text"])
ore_count = counter["俺" or "おれ"] 
bok_count = counter["僕" or "ぼく"]
ore =  0
bok = 0

#俺か僕のどちらかが含まれている場合
if ore_count + bok_count >= 1:

    #俺のフラグをたてる
    if ore_count > bok_count :
        ore = 1
        first_person = 1
    #僕のフラグをたてる
    elif bok_count > ore_count:
        bok = 1
        first_person = 2
    else:
        first_person = 0

else:
    first_person = 0

first_person は、俺が1、僕が2、その他(俺と僕が同数など)が0となるようにしています。

3.データの記録

下の for では、ツイートに含まれる複数情報の取得を20000回繰り返しています。取得したらただちに append を使ってリストに追加していきます。


cnt = 0
TweetList = []
ORE = []
BOK = []
FP = []
LEN = []

for tweet in getter.collect(total = 20000):
        cnt += 1
        tweet["text"] = tweet["text"].replace('\n','')
        TweetList.append(tweet["text"])
        ORE.append(ore)
        BOK.append(bok)
        FP.append(first_person)
        LEN.append(len(tweet["text"]))

ここでは文字数が重要になってきますので、空白や改行が邪魔になります。そのため


tweet["text"] = tweet["text"].replace('\n','')

で空白・改行を消しました。

また、len(tweet[“text”]) で取得したテキストデータから文字数をカウントしています。


import pandas as pd

#データフレームに変換
df = pd.DataFrame([TweetList,
                   ORE,
                   BOK,
                   LEN]).T
df.columns = ["tweet","ore","bok","len"]

#CSVに保存
df.to_csv('TwitterData.csv',encoding="utf-16")

utf-8だと文字化けしてcsvでテキストを読むことができないため、utf-16にしています。
リストをデータフレームにまとめたことで、dfはこのような状態になっています。
f:id:iTeresa:20171019005658p:plain

4.ヒストグラム

一人称ごとで文字数のヒストグラムを描いてみます。ここでは3つのヒストグラムが重なって表示されるようにしていますが、first_person = 0 は「俺」でも「僕」でもないので不必要です。そのため、とりあえず first_person = 0 のときだけ透明にして見えないようにしておきました。透明度は plt.hist() の中の alpha で調節できます。


import matplotlib.pyplot as plt

col = ["b","g","r"]
alp = [0, 0.3, 0.3]

for key, grp in df.head(total).reset_index().groupby(‘first_person'):
    if len(grp['len']) != 1:
        plt.hist(grp['len'], bins=20, alpha=alp[key], histtype='stepfilled',color=col[key])
plt.ylabel("Tweet (frequency)")
plt.xlabel("word count")
plt.show()

f:id:iTeresa:20171018212448p:plain

いずれの一人称も30文字あたりがピークとなっていますが、緑(俺)の方がかなり多くなっています。それに対して、ピークを過ぎてからは俺と僕で差が小さくなっています。

ぱっと見では緑(俺)の方が文字数が少ないように見えますが、そもそも緑(俺)の方がツイートが多いため、このヒストグラムではよくわかりません。そこで、正規化してもう一度ヒストグラムを出してみます。


plt.hist(grp['len'],normed = True, bins=20, alpha=0.3, histtype='stepfilled',color=col[key])

f:id:iTeresa:20171018212446p:plain

このように、上のコードに normed = True をつけたすだけで正規化できました。

縦軸は割合となり、緑と赤の合計が同じとなりました。さきほどとは異なり、60文字以上で赤(僕)が緑(俺)を上回っているのが目立ちますね。

5.棒グラフ

まず記述統計をしたいと思いますが、dfにはツイートの文字が含まれているため、数字を数字として扱うことができません。そのため、以下のようにnum_dfという数字だけのデータフレームを新たに作りました。


df.ore = pd.to_numeric(df.ore)
df.len = pd.to_numeric(df.len)
df.fp = pd.to_numeric(df.first_person)

num_df = pd.DataFrame()
num_df["ore"] = df.ore
num_df["len"] = df.len
num_df["first_person"] = df.first_person

平均値および中央値は以下のように簡単に出せます。


#平均値
len_mean = num_df.groupby("ore")["len"].mean()
print("mean:"+str(len_mean))

#中央値
len_median = num_df.groupby("ore")["len"].median()
print("median:"+str(len_median))

#一人称ごとのグラフ
X = [1,2]
Y = [len_median[0],len_median[1]]
plt.bar(X,Y, align="center")
plt.xticks(X, ["BOKU",'ORE'])
plt.xlabel("First-Person")
plt.ylabel("word count (median)")

#標準誤差バー
SD = num_df.groupby("ore")["len"].std()
ORE_SE = SD/math.sqrt(df["ore"].sum())
BOK_SE = SD/math.sqrt(df["bok"].sum())
err = [BOK_SE[0],ORE_SE[1]]
plt.errorbar(X,Y,yerr=err,fmt='k ',ecolor='k')
plt.show()

f:id:iTeresa:20171018223636p:plain

文字数について、「俺」の平均値が54.3文字、「僕」の平均値が61.6文字でした。中央値も「俺」で42文字、「僕」で52文字となり、いずれの値においても、「俺」の方が1ツイートにおける文字数が少ないという結果になりました。なお、標準誤差エラーバーもがんばって出してみましたが、小さすぎてゴミのようになってしまいました。

6.検定

scipy を使うことで統計的検定ができます。


from scipy import stats
group_ore = num_df[num_df['first_person'] == 1]['len']
group_bok = num_df[num_df['first_person'] == 2]['len']

#t検定
print(stats.ttest_ind(group_ore, group_bok))

#U検定
print(stats.mannwhitneyu(group_ore, group_bok))

ヒストグラムを見てわかるように、文字数は正規分布していないので、今回はt検定ではなくU検定をします。
俺群と僕群の間で文字数についてU検定をおこなった結果、U値が30960093.5、p < 0.05(p = 4.15e-42)で両者の差は有意となりました。つまり、「俺」は「僕」より1ツイートにおける文字数が少ないという仮説が支持されました。2万件近くもツイートがあると、p値が5%切るのは余裕ですね。検定するまでもなく明らかです。たくさんデータをとるとp値は小さくなりますが、p値は2群間の差の大きさを表すものではないので注意してください。

7.まとめ

ヒストグラムと棒グラフで視覚的に確認しても、検定を行って数値で確認しても、「俺」を使ったツイートの方が「僕」を使ったツイートよりも文字数が少ないことがわかりました。「俺」を使う男らしい(印象を与える)人は口数が少なく、背中で語るような人なのかもしれませんね。

今回紹介した統計的分析手法は心理学でもよく使われる基礎的なもので、わざわざPythonを使わなくてもできそうなものです。しかし、Pythonを使うことで、Twitterで取得した大量のデータを使って気軽に遊ぶことができます。Twitter上に埋まった大量の宝をぜひPythonで掘り出してみてくださいね!