Python
MongoDB
Twitter
自然言語処理
statistics

スタバのTwitterデータをpythonで大量に取得し、データ分析を試みる その3

More than 3 years have passed since last update.

スタバTwitterシリーズも第3回となりました。下記は今までの投稿です。


その1:Twitter REST APIsでデータを取り込みmongoDBにインポート

http://qiita.com/kenmatsu4/items/23768cbe32fe381d54a2

その2:取得したTwitterデータからスパムの分離

http://qiita.com/kenmatsu4/items/8d88e0992ca6e443f446

その3:ある日を境にツイート数が増えたわけは?(今回)

http://qiita.com/kenmatsu4/items/02034e5688cc186f224b

その4:Twitterにひそむ位置情報の視覚化

http://qiita.com/kenmatsu4/items/114f3cff815aa5037535


さて、この前回の時系列ツイート数グラフをよく見ると3/18からグラフが全体的に上に持ち上がっているように見えないでしょうか?この日から何ががあったのではないかと思うので、これを分析してみようと思います。

timeseries_no_spam-compressor.png

ツイートの内容に何か変化がないかをツイート本文から分析する、ということを試みます。

まず考え方として、ツイートを3/18 0:00を境に前後に分割します。

で、その前後でつぶやきに含まれる単語で増加率が高い単語が何か、というのをあぶり出すことをやってみたいと思います。

しばらくデータ処理の説明になるので、結果を先に見たい人は3. 分析結果に飛んでください。


1.単語の分かち書き

ツイート本文を統計的に処理するにあたり、単語の分かち書きという処理が必要となります。英語の文書であれば単語毎に間にスペースが入っているので、スペース区切りで単語を特定できるのですが、日本語は膠着語と呼ばれる区切りが無い言語のためこの分かち書きが必要です。

そこで、かの有名なMeCabを導入して分析に使いたいと思います。


1-1.MeCabの導入

mecab-ipadic-neologdとは、


mecab-ipadic-neologd は、多数のWeb上の言語資源から得た新語を

追加することでカスタマイズした MeCab 用のシステム辞書です。


とあるように、Web上をクローリングしてデータ収集して新語を取り入れた、@overlastさんが作成されたMeCab辞書です。Tweetデータなどは従来の単語辞書には含まれていなかった単語が多く含まれているので、この記事を書く直前にこのような辞書がリリースされたのはラッキーでした!

ご本人が第47回R勉強会@東京のセッションで説明されていた通り、超簡単に導入できるので、皆さんも是非^^

MeCab自体のインストールと、この新語対応の辞書mecab-ipadic-neologdのインストールも

https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md

にインストール方法の記載があるので、ここを参考にMeCabとその新語対応辞書mecab-ipadic-neologdをインストールします。


1-2.mecab-pythonの導入

https://code.google.com/p/mecab/downloads/list

(※Google Code のダウンロード機能が 2014/1 で終了したようなので下記からDLください)

Google Drive Mecab Python download

ここからダウンロードしてインストールします。

$ python setup.py build

$ python setup.py install

基本的に回答してこの2つのコマンドでインストールされるはず。


1-3.ツイートを形態素に分ける

PythonからMeCabの軌道を試してみましょう。mecab-ipadic-neologdを導入しているのでTaggerの生成時に引数でそのインストール先ディレクトリを指定しています。

import MeCab as mc

t = mc.Tagger('-Ochasen -d /usr/local/Cellar/mecab/0.996/lib/mecab/dic/mecab-ipadic-neologd/')
sentence = u'今日は良い天気ですが、雨ですね。キャラメルマキアートがうまいです。'
text = sentence.encode('utf-8')
node = t.parseToNode(text) # 先頭行はヘッダのためスキップ

while(node):
if node.surface != "":
print node.surface +"\t"+ node.feature
node = node.next
if node is None:
break

下記のように文章を単語に分けてくれて、品詞もつけてくれます。ちゃんと"キャラメルマキアート"という固有名詞も拾ってくれます!

<<<output>>>

今日 名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
良い 形容詞,自立,*,*,形容詞・アウオ段,基本形,良い,ヨイ,ヨイ
天気 名詞,一般,*,*,*,*,天気,テンキ,テンキ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
が 助詞,接続助詞,*,*,*,*,が,ガ,ガ
、 記号,読点,*,*,*,*,、,、,、
雨 名詞,一般,*,*,*,*,雨,アメ,アメ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
ね 助詞,終助詞,*,*,*,*,ね,ネ,ネ
。 記号,句点,*,*,*,*,。,。,。
キャラメルマキアート 名詞,固有名詞,一般,*,*,*,キャラメル・マキアート,キャラメルマキアート,キャラメルマキアート
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
うまい 形容詞,自立,*,*,形容詞・アウオ段,基本形,うまい,ウマイ,ウマイ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。

で、この形態素解析をツイート本文に適用します。本文をMeCabにかけて、今回は名詞・動詞・形容詞・副詞のみを抽出、それぞれをツイートのレコードに列追加していきます。(※正確にはmongoDBではレコードはドキュメント、列はフィールドです)助詞、助動詞等を抜いているのですが、これらは本文の内容を表すような特徴をそこまで表現していないと思いましたので除外しています。

# mecab 形態素分解

def mecab_analysis(sentence):
t = mc.Tagger('-Ochasen -d /usr/local/Cellar/mecab/0.996/lib/mecab/dic/mecab-ipadic-neologd/')
sentence = sentence.replace('\n', ' ')
text = sentence.encode('utf-8')
node = t.parseToNode(text)
result_dict = defaultdict(list)
for i in range(140): # ツイートなのでMAX140文字
if node.surface != "": # ヘッダとフッタを除外
word_type = node.feature.split(",")[0]
if word_type in ["名詞", "形容詞", "動詞"]:
plain_word = node.feature.split(",")[6]
if plain_word !="*":
result_dict[word_type.decode('utf-8')].append(plain_word.decode('utf-8'))
node = node.next
if node is None:
break
return result_dict

下記で全Tweetデータに対して形態素に分けていく処理を行います。

for d in tweetdata.find({},{'_id':1, 'id':1, 'text':1,'noun':1,'verb':1,'adjective':1,'adverb':1}):

res = mecab_analysis(unicodedata.normalize('NFKC', d['text'])) # 半角カナを全角カナに

# 品詞毎にフィールド分けして入れ込んでいく
for k in res.keys():
if k == u'形容詞': # adjective
adjective_list = []
for w in res[k]: adjective_list.append(w)
tweetdata.update({'_id' : d['_id']},{'$push': {'adjective':{'$each':adjective_list}}})
elif k == u'動詞': # verb
verb_list = []
for w in res[k]:
verb_list.append(w)
tweetdata.update({'_id' : d['_id']},{'$push': {'verb':{'$each':verb_list}}})
elif k == u'名詞': # noun
noun_list = []
for w in res[k]: noun_list.append(w)
tweetdata.update({'_id' : d['_id']},{'$push': {'noun':{'$each':noun_list}}})
elif k == u'副詞': # adverb
adverb_list = []
for w in res[k]:
adverb_list.append(w)
tweetdata.update({'_id' : d['_id']},{'$push': {'adverb':{'$each':adverb_list}}})
# 形態素解析済みのツイートにMecabedフラグの追加
tweetdata.update({'_id' : d['_id']},{'$set': {'mecabed':True}})


2. CountVectorizerで単語を数える

Scikit-learnというpythonの機械学習ライブラリにCountVectorizerというものがあるのですが、単語数を数えるのにこれを使ってみたいと思います。


2-1. CountVectorizerの使い方の簡単な例

3つの文章を例にとってCountVectorizerでどういうことができるのかを説明します。CountVectorizerはfit関数に渡された文字列データに含まれる単語を単語毎にその出現回数をカウントして、それをベクトルとして表現してくれるクラスです。文字での説明より、下記のコードを見たほうが早いかもしれませんw

# 3つの文章 (1つの文が1つのツイート本文と思ってください)

data=["This is a pen.",
"This is also a pen. Pen is useful.",
"These are pencils.",
]

c_vec = CountVectorizer() # CountVectorizerオブジェクトの生成
c_vec.fit(data) # 対象ツイート全体の単語の集合をセットする
c_terms = c_vec.get_feature_names() # ベクトル変換後の各成分に対応する単語をベクトル表示
c_tran = c_vec.transform([data[1]]) # 2つ目の文章の数を数える

print c_terms
print data[1]
print c_tran.toarray()

下記がアウトプット結果です。

1行目にget_feature_names()関数で生成されたベクトルが表示されています。1つ目の成分はalsoのカウント数、2つ目の成分はareのカウント数…のように解釈します。fit()関数に渡したデータ一式に含まれる単語がuniqueになって入っています。

2行目はtransform()関数に渡したカウントしたい対象の文章です。

3行目が欲しかった単語毎のカウント数です。このように"This is also a pen. Pen is useful."という文章にはalsoが1つ、areは無し、isが2つ、penも2つ入っている、と言ったように単語数をカウントした数字が入っています。これはテストなのでベクトルの成分が8個しかないですが、このあと行うTweetデータに対してこの処理を行うと45001個の成分を持つベクトルが生成されます。30万件のツイートにユニークワードが4万5千個あるということですね。

<<<output>>>

[u'also', u'are', u'is', u'pen', u'pencils', u'these', u'this', u'useful']
This is also a pen. Pen is useful.
[[1 0 2 2 0 0 1 1]]


2-2. TweetデータをCountVectorizerにかける

では、Tweetデータで同じことをしていきたいと思います。

まずは必要なライブラリのインポート、DBへの接続と、Utility的な関数の宣言です。

from sklearn.feature_extraction.text import CountVectorizer

from pymongo import Connection
import numpy as np
from collections import defaultdict
import sys, datetime

connect = Connection('localhost', 27017)
db = connect.starbucks
tweetdata = db.tweetdata

def str_to_date_jp_utc(str_date):
if str_date is not None:
return datetime.datetime.strptime(str_date,'%Y-%m-%d %H:%M:%S') - datetime.timedelta(hours=9)
else:
return None

次にMeCabで分かち書きをした文字列を取得する関数です。これはmongoDBに蓄積されたTweetデータから指定した期間の物を取り出し、MeCabで単語化されたものをリストに集約してそれを返します。

# mecabで分解した単語を連結して文字列化する。

def get_mecabed_strings(from_date_str=None, to_date_str=None,include_rt=False):
tweet_list = []
tweet_texts = []

from_date = str_to_date_jp_utc(from_date_str)
to_date = str_to_date_jp_utc(to_date_str)

# 取得対象期間の条件設定
if (from_date_str is not None) and (to_date_str is not None):
query = {'created_datetime':{"$gte":from_date, "$lt":to_date}}
elif (from_date_str is not None) and (to_date_str is None):
query = {'created_datetime':{"$gte":from_date}}
elif (from_date_str is None) and (to_date_str is not None):
query = {'created_datetime':{"$lt":to_date}}
else:
query = {}

# spam除去
query['spam'] = None

# リツイートを含むか否か
if include_rt == False:
query['retweeted_status'] = None
else:
query['retweeted_status'] = {"$ne": None}

# 指定した条件のツイートを取得
for d in tweetdata.find(query,{'noun':1, 'verb':1, 'adjective':1, 'adverb':1,'text':1}):
tweet = ""
# Mecabで分割済みの単語をのリストを作成
if 'noun' in d:
for word in d['noun']:
tweet += word + " "
if 'verb' in d:
for word in d['verb']:
tweet += word + " "
if 'adjective' in d:
for word in d['adjective']:
tweet += word + " "
if 'adverb' in d:
for word in d['adverb']:
tweet += word + " "
tweet_list.append(tweet)
tweet_texts.append(d['text'])
return {"tweet_list":tweet_list,"tweet_texts":tweet_texts}

上記関数を利用して何か変化がありそうだった3/18 0:00の前後でそれぞれ単語のリストを取得します。今回リツイートは除外しています。

# "2015-03-18 00:00:00"以前

ret_before = get_mecabed_strings(to_date_str="2015-03-18 00:00:00")
tw_list_before = ret_before['tweet_list']

# "2015-03-18 00:00:00"以降
ret_after = get_mecabed_strings(from_date_str="2015-03-18 00:00:00")
tw_list_after= ret_after['tweet_list']

# 全期間
ret_all = get_mecabed_strings()
tw_list_all = ret_all['tweet_list']

ここから先は先ほど説明したCountVectorizerの出番です。

c_vec = CountVectorizer(stop_words=[u"スタバ"])  # 「スタバ」は全Tweetに含まれるので除外

c_vec.fit(tw_list_all) # 全Tweetに含まれる単語の集合をここでセット
c_terms = c_vec.get_feature_names() # 各ベクトル要素に対応する単語を表すベクトル

# 期間の前後でひとまとまりと考え、transformする
transformed = c_vec.transform([' '.join(tw_list_before),' '.join(tw_list_after)])

はい、これで単語を数えることができました。ここからがやりたかったことですが、この期間の前後で増えている単語が何か、を表示させていきます。


# afterからbeforeを引くことで増分をsubに代入
sub = transformed[1] - transformed[0]

# トップ50がどの位置にあるかを取り出す
arg_ind = np.argsort(sub.toarray())[0][:-50:-1]

# トップ50の表示
for i in arg_ind:
print c_vec.get_feature_names()[i]


3. 分析結果

長かったですが、やっとたどり着きました!

下記が期間の前後で増加率の高い単語トップ50です。

「カウント順位」は単純に前後での該当単語が出現するツイート数の差分ですが、前後のツイート数が違うので、率を算出してランキングしています。

(表が見にくくてスミマセン・・・)












順位
単語

出現率

の差

カウント差

カウント

順位

before
after



出現


出現しない

出現率


出現


出現しない

出現率


1
新作
7.75%
7,816
1
2,446
119,717
2.00%
10,262
94,928
9.76%


2
飲む
3.34%
2,203
3
9,455
112,708
7.74%
11,658
93,532
11.08%


3
アーモンドミルク
2.23%
2,266
2
561
121,602
0.46%
2,827
102,363
2.69%


4
ハニー
1.38%
1,420
4
263
121,900
0.22%
1,683
103,507
1.60%


5
美味しい
1.24%
726
9
4,167
117,996
3.41%
4,893
100,297
4.65%


6
新しい
1.21%
1,174
6
748
121,415
0.61%
1,922
103,268
1.83%


7
アーモンド
1.21%
1,230
5
290
121,873
0.24%
1,520
103,670
1.45%


8
http
1.02%
-3,605
-
33,705
88,458
27.59%
30,100
75,090
28.61%


9
クランチ
0.90%
932
7
139
122,024
0.11%
1,071
104,119
1.02%


10
with
0.86%
869
8
287
121,876
0.23%
1,156
104,034
1.10%


11
おいしい
0.80%
592
10
1,772
120,391
1.45%
2,364
102,826
2.25%


12
フラペチーノ
0.73%
378
12
2,775
119,388
2.27%
3,153
102,037
3.00%


13
今日
0.68%
-185
-
6,497
115,666
5.32%
6,312
98,878
6.00%


14
甘い
0.67%
547
11
1,121
121,042
0.92%
1,668
103,522
1.59%


15
やつ
0.56%
268
18
2,291
119,872
1.88%
2,559
102,631
2.43%


16
https
0.45%
57
75
3,023
119,140
2.47%
3,080
102,110
2.93%


17
みる
0.42%
-14
-
3,255
118,908
2.66%
3,241
101,949
3.08%


18
商品
0.40%
301
13
829
121,334
0.68%
1,130
104,060
1.07%


19
うまい
0.39%
274
17
982
121,181
0.80%
1,256
103,934
1.19%


20
くる
0.34%
-740
-
7,878
114,285
6.45%
7,138
98,052
6.79%


21
ちゃ
0.30%
284
16
228
121,935
0.19%
512
104,678
0.49%


22
問題
0.30%
298
14
102
122,061
0.08%
400
104,790
0.38%


23
人種
0.28%
291
15
29
122,134
0.02%
320
104,870
0.30%


24
期間限定
0.26%
233
19
264
121,899
0.22%
497
104,693
0.47%


25
はちみつ
0.23%
233
20
81
122,082
0.07%
314
104,876
0.30%


26
飲める
0.23%
53
81
1,338
120,825
1.10%
1,391
103,799
1.32%


27
すぎる
0.22%
-199
-
3,104
119,059
2.54%
2,905
102,285
2.76%


28
開始
0.22%
221
21
75
122,088
0.06%
296
104,894
0.28%


29
ハチミツ
0.22%
220
22
64
122,099
0.05%
284
104,906
0.27%


30
自宅
0.20%
200
25
103
122,060
0.08%
303
104,887
0.29%


31
印象
0.20%
179
26
208
121,955
0.17%
387
104,803
0.37%


32
届ける
0.20%
201
23
31
122,132
0.03%
232
104,958
0.22%


33
購入
0.19%
176
27
204
121,959
0.17%
380
104,810
0.36%


34
新サービス
0.19%
201
24
0
122,163
0.00%
201
104,989
0.19%


35
line
0.18%
141
33
378
121,785
0.31%
519
104,671
0.49%


36
買う
0.18%
-316
-
3,660
118,503
3.00%
3,344
101,846
3.18%


37
交換
0.17%
161
30
153
122,010
0.13%
314
104,876
0.30%


38
感じ
0.16%
59
72
791
121,372
0.65%
850
104,340
0.81%


39
行く
0.16%
-1,843
-
14,473
107,690
11.85%
12,630
92,560
12.01%


40
プーさん
0.16%
165
28
13
122,150
0.01%
178
105,012
0.17%


41
キャンペーン
0.16%
159
31
33
122,130
0.03%
192
104,998
0.18%


42
昨日
0.15%
-9
-
1,232
120,931
1.01%
1,223
103,967
1.16%


43
ceo
0.15%
161
29
8
122,155
0.01%
169
105,021
0.16%


44
latte
0.15%
-78
-
1,704
120,459
1.39%
1,626
103,564
1.55%


45
一言
0.15%
134
35
162
122,001
0.13%
296
104,894
0.28%


46
呼び方
0.15%
142
32
97
122,066
0.08%
239
104,951
0.23%


47
ビスケット
0.14%
136
34
87
122,076
0.07%
223
104,967
0.21%


48
家族
0.14%
123
39
167
121,996
0.14%
290
104,900
0.28%


49
微妙
0.14%
131
36
86
122,077
0.07%
217
104,973
0.21%


50
美味い
0.13%
93
47
322
121,841
0.26%
415
104,775
0.39%

「新作」、「アーモンドミルク」など 3/18から始まったアーモンドミルクラテに関するツイートが増加していたのですね!

トップ10の単語を並べ替えると「新作 アーモンドミルク with ハニー クランチ 飲む 美味しい」です!どうやら好評のようです(^ー^*)これ、狙って作ったデータではないのですが、キレイに結果が出ました。

「アーモンドミルク ラテ with ハニー クランチ」はこれです。





「新作」がダントツトップなのは、「スタバの新作飲みたい!」みたいに具体的な名称「アーモンドミルクラテ」がでてこないツイートが結構多かったことが理由のようです。

次に上位の単語のうち「飲む」を除いた"新作", "アーモンドミルク","ハニー", "アーモンド", "新しい"が含まれるツイート数を集計してみます。

def is_include_word_list(text, word_list,f):

for word in word_list:
if text.find(word) > -1:
return True
return False

date_dict = defaultdict(int)

word_list = [u"新作", u"アーモンドミルク",u"ハニー", u"アーモンド", u"新しい", "with", u"クランチ"]

with open('armond.txt','w') as f:
for d in tweetdata.find({'spam': None, 'retweeted_status': None},{'created_datetime':1,'text':1}):
str_date = date_to_Japan_time(d['created_datetime']).strftime('%Y\t%m/%d %H %a')

text = d['text']
if is_include_word_list(text, word_list,f):
date_dict[str_date] += 1
# マッチした対象をファイルに書き出す(for 検証用)
ret_str = str_date +' '+ text.replace('\n', ' ')+'\n'
f.write(ret_str.encode('utf-8'))

print "date_dict", len(date_dict)
print "階級数:", len(date_dict)
print "日付" + "\t\t\t" + "# of Tweet"
keys = date_dict.keys()
keys.sort()
for k in keys:
print k + "\t" + str(date_dict[k])

結果をプロットしたのが下記のグラフです。紫の線がリストアップした単語を含むツイート数です。

listed-compressor.png

この紫の数を、リツイート含まないツイート数(青い線)から引いてあげると…

listed_diff-compressor.png

ほとんど山が平らになりました!3/18日以降に増えていた単語は先ほどリストアップしたものでだいたい説明がつくようです。

でも、毎週のツイート数サイクルがこんなに安定しているのはなんででしょうかね、大数の法則ってこと(・ω・)? この謎を解ける人がいたら是非教えて欲しいです。


このスタバTweet分析もあと、1回か2回くらい続く予定です。

なんか、統計学的に難しいモデルとか使えないかと思って始めたのですが、データ眺めて加工するだけでも結構面白いことができた気がします。

次回はもう少し検定とか推定とかそんな話をしたいかなと思っています。もう少しお付き合いいただければ幸いです。

使用したコードの全体はgistにあります。

https://gist.github.com/matsuken92/72a0da8d9b42bed28e61


APPENDIX:期間前後の単語の出現確率の検定

3/18 0:00の前後、各単語の出現有無を2x2のマトリクスにして考えると下記のようになります。これはカイ二乗適合度検定が行える形ですね。beforeとafterで本当に出現率に差があったのかどうか、を検定してみます。




単語:
アーモンドミルク

出現

出現
しない



Before
561
121,602
122,163


After
2,827
102,363
105,190



3,388
223,965
227,353

下記がカイ二乗検定をかけた結果の表です。No.39を除き全て有意な結果が出ているので、やはり3/18以前と以降で出現率に変化があったと考えて良さそうです。No.39「行く」ですが、今回の分析対象としては、この単語が増えたと言えないことにさほど不都合はないかなと思いますので、良い結果かと思います。







順位
単語
chi2
p-val
結果


1
新作
6437.337845
0
有意


2
飲む
749.5007818
5.15E-165
有意


3
アーモンドミルク
1910.25734
0
有意


4
ハニー
1275.398025
2.51E-279
有意


5
美味しい
227.0216513
2.66E-51
有意


6
新しい
717.7324129
4.17E-158
有意


7
アーモンド
1042.145835
1.24E-228
有意


8
http
29.34665299
6.05E-08
有意


9
クランチ
871.5525659
1.50E-191
有意


10
with
667.7020689
3.16E-147
有意


11
おいしい
200.486648
1.64E-45
有意


12
フラペチーノ
116.9898183
2.89E-27
有意


13
今日
49.35872884
2.13E-12
有意


14
甘い
207.6474174
4.48E-47
有意


15
やつ
83.8410031
5.36E-20
有意


16
https
44.31937829
2.79E-11
有意


17
みる
35.19560616
2.98E-09
有意


18
商品
103.1110711
3.17E-24
有意


19
うまい
87.88641968
6.93E-21
有意


20
くる
10.3550916
0.001291181
有意


21
ちゃ
155.9814931
8.54E-36
有意


22
問題
224.6028811
8.96E-51
有意


23
人種
288.2657541
1.19E-64
有意


24
期間限定
110.5936405
7.26E-26
有意


25
はちみつ
174.3777441
8.19E-40
有意


26
飲める
24.391691
7.86E-07
有意


27
すぎる
10.62339463
0.001116659
有意


28
開始
166.5727271
4.15E-38
有意


29
ハチミツ
173.6897162
1.16E-39
有意


30
自宅
130.4736816
3.23E-30
有意


31
印象
83.82905236
5.39E-20
有意


32
届ける
184.6605771
4.65E-42
有意


33
購入
82.49458545
1.06E-19
有意


34
新サービス
231.4807808
2.83E-52
有意


35
line
48.21110495
3.83E-12
有意


36
買う
6.279272571
0.012215823
有意


37
交換
81.93429413
1.41E-19
有意


38
感じ
20.1122379
7.30E-06
有意


39
行く
1.355305558
0.244352729
有意でない


40
プーさん
167.4411384
2.68E-38
有意


41
キャンペーン
136.6923979
1.41E-31
有意


42
昨日
12.43299481
0.000421815
有意


43
ceo
170.5916319
5.49E-39
有意


44
latte
8.815476835
0.002986861
有意


45
一言
61.49900119
4.43E-15
有意


46
呼び方
82.67724362
9.66E-20
有意


47
ビスケット
81.23778177
2.00E-19
有意


48
家族
53.7386464
2.29E-13
有意


49
微妙
77.40857135
1.39E-18
有意


50
美味い
29.5887298
5.34E-08
有意

カイ二乗適合度検定を行ったコードは下記です。

data = np.array([[2446,119717,10262,94928],

[9455,112708,11658,93532],
[561,121602,2827,102363],
[263,121900,1683,103507],
[4167,117996,4893,100297],
[748,121415,1922,103268],
[290,121873,1520,103670],
[33705,88458,30100,75090],
[139,122024,1071,104119],
[287,121876,1156,104034],
[1772,120391,2364,102826],
[2775,119388,3153,102037],
[6497,115666,6312,98878],
[1121,121042,1668,103522],
[2291,119872,2559,102631],
[3023,119140,3080,102110],
[3255,118908,3241,101949],
[829,121334,1130,104060],
[982,121181,1256,103934],
[7878,114285,7138,98052],
[228,121935,512,104678],
[102,122061,400,104790],
[29,122134,320,104870],
[264,121899,497,104693],
[81,122082,314,104876],
[1338,120825,1391,103799],
[3104,119059,2905,102285],
[75,122088,296,104894],
[64,122099,284,104906],
[103,122060,303,104887],
[208,121955,387,104803],
[31,122132,232,104958],
[204,121959,380,104810],
[0,122163,201,104989],
[378,121785,519,104671],
[3660,118503,3344,101846],
[153,122010,314,104876],
[791,121372,850,104340],
[14473,107690,12630,92560],
[13,122150,178,105012],
[33,122130,192,104998],
[1232,120931,1223,103967],
[8,122155,169,105021],
[1704,120459,1626,103564],
[162,122001,296,104894],
[97,122066,239,104951],
[87,122076,223,104967],
[167,121996,290,104900],
[86,122077,217,104973],
[322,121841,415,104775]])

from scipy.stats import chi2_contingency as chi2_con
for i, d in zip(range(len(data)), data):
chi2, p, dof, ex = chi2_con(d.reshape(2,2))
print i+1, " chi2", chi2, " p-val", p, u"有意" if p < 0.05 else u"有意でない"


参考

MeCab:形態素解析ライブラリ

http://mecab.googlecode.com/svn/trunk/mecab/doc/index.html

MeCabの新語辞書:Neologism dictionary for MeCab

https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md

Scikit-leaern:Python用機械学習ライブラリ

http://scikit-learn.org/stable/