スプレッドシートに蓄積したデータをColaboratoryからTF-IDFとWord2Vecで分析してみたメモ

# 概要
自分の勉強で実装した内容のメモです。
テキストを分析するための処理を理解するために実装してみた処理をメモ代わりに残しています。
技術を共有する目的のためではないので、綺麗に残しているわけではないですが、処理上の改善点等がありましたらコメント頂けると嬉しいです。
もう少し理解が深められたら、分析目標等を明確にして記事をアップデートしたいと思っています。
また、本文はcolaboratory環境での実装内容となります。

# 分析内容
ソフトウェア開発の不具合に対して分析を行なった内容をメモした物です。
不具合をJIRAのチケットで管理していたので、ダンロードしたファイルをスプレッドシートとしてGドライブに格納してから分析を行なっています。
実際の分析ではもう少し細かい設定や前処理等を行いました。

# 分析の目的
普段の分析ではある程度の問題に対する仮説を立て、その裏付けをする形で分析を行うのですが、
今回は分析を行うこと自体がR＆D的な取り組みだったため、
* 特定の機能やキーワードに関連する偏りがないか
* 影響度が高い不具合に何かしらの偏りがないか
といった不具合傾向などが見つけられないかといった目的で作業を開始ししました。

# 分析の結果
結論としては、組織的な理由で分析自体が中断してしまったので発表できるような分析結果までは達成できませんでした。
途中でしたが、キーワードの分類だけでも影響度が高い不具合が集中しているグループがあったりと、さらに分析を進めることで問題を検出できそうな手応えは得ることができました。

# googleドライブ関連の設定を行う
```py
from google.colab import drive
from google.colab import auth
from oauth2client.client import GoogleCredentials

!pip install --upgrade -q gspread

drive.mount('/content/drive')

import gspread
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# スプレッドシートのURL
file_url = "" ####ここに分析したいスプレッドシートのURLを記載する###
worksheet = gc.open_by_url(file_url)


# ファイルの内容を取得
tmpValues = worksheet.sheet1.get_all_values()

#スプレッドシートから取得した情報を配列に格納
#今回は2列目(B列)にタイトル、3列目(C列)に本文が格納されている
titles = [x[1] for x in tmpValues]
texts = [x[2] for x in tmpValues]
```
# モジュールの準備
```py

#colaboratoryに足りないモジュールをpip
!apt install aptitude
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3==0.7
!pip install mojimoji
```

```py

import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
import MeCab
from gensim.models.word2vec import Word2Vec
import mojimoji
from sklearn.cluster import KMeans
from collections import Counter
```

# 内部関数の定義

```py

#ストップワードの取得関数
def sloth():
    import urllib3
    from bs4 import BeautifulSoup

    slothlib_path = 'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt'
    http = urllib3.PoolManager()
    #↑urlib3系のおまじない
    slothlib_file =http.request('GET',slothlib_path)
    soup=BeautifulSoup(slothlib_file.data,'lxml').get_text()
    soup=str(soup).split()#soupは文字列じゃないので注意
    #SlothLibに存在しないストップワードを自分で追加↓
    mydict=['いる','おり','ない','あり','ある','いく','なっ','する','あっ']
    soup.extend(mydict)
    return soup
```


```py

#テキストの形式を統一する関数
def clean_text(text):
    #カタカナを全角
    text = mojimoji.han_to_zen(text, digit=False, ascii=False)
    #英語、数値を半角
    text = mojimoji.zen_to_han(text, kana=False)
    #小文字に統一
    text = text.lower()
    return text
print(clean_text('試験ｱｲｳaｂc０12'))
```

```py

#ストップワード読み込み
stop_word = tuple(sloth())
# Counterオブジェクトに単語をセット
word_counter = Counter()
#分かち処理とキーワードの取得関数
def text2wakati(text):
    tagger = MeCab.Tagger('-Ochasen')
    parsed_text = tagger.parse(text)
    # 除外する品詞
    stop_parts = ('名詞-接尾-形容動詞語幹', 'その他', 'フィラー', '副詞', '助動詞', '助詞', '動詞-接尾', '動詞-非自立', '名詞-動詞非自立的', '名詞-特殊-助動詞語幹', '名詞-接尾-サ変接続', '名詞-接尾-副詞可能', '名詞-接尾-人名', '名詞-接尾-助動詞語幹', '名詞-接尾-形容動詞語幹', '名詞-接尾-特殊', '名詞-非自立', '感動詞', '接続詞', '接頭詞-動詞接続', '接頭詞-形容詞接続', '形容詞-接尾', '形容詞-非自立', '記号-一般', '記号-句点', '記号-括弧閉', '記号-括弧開', '記号-空白', '記号-読点', '連体詞')
    retmp = '' if not parsed_text else ' '.join([y[2] for y in [x.split('\t') for x in parsed_text.splitlines()[:-1]] if (len(y) == 6) and (not y[3].startswith(stop_parts)) and (not y[2].startswith(stop_word))])
    word_counter.update(retmp.split())
    return retmp
```

```py

#TF-IDFで主要キーワードを
def get_tfidf_and_feature_names(corpus):
    #デフォルトでは1次の単語トークンが認識されないため「token_pattern」を設定する
    vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
    return vectorizer.fit_transform([text2wakati(clean_text(text)) for text in corpus]), vectorizer.get_feature_names()
test=["試験ｱｲｳaｂc０12","テストの実行"]
test1, test2 = get_tfidf_and_feature_names(test)
print(test1)
print(type(test1))
print(test2)
```

```py

%cd /content/drive/My Drive/Colab Notebooks
model_path = "./word2vec.gensim.model"
model = Word2Vec.load(model_path)

def word_vectors(word):
  try:
      return model[word]
  except KeyError as error:
      return [x for x in np.zeros(model.layer1_size)] 
```
#分析処理(メイン)

```py

#タイトルのtf-idfとベクトル化
w, feature_names = get_tfidf_and_feature_names(titles)
v = np.asarray([word_vectors(word) for word in feature_names])
doc_title_vectors = (w.toarray() @ v).T / w.toarray().sum(axis=1)
doc_title_vectors.shape
```

```py

#本文のtf-idfとベクトル化
w2, feature_names2 = get_tfidf_and_feature_names(texts)
v2 = np.asarray([word_vectors(word) for word in feature_names2])
doc_text_vectors = (w2.toarray() @ v2).T / w2.toarray().sum(axis=1)
doc_text_vectors.shape
```

```py

#タイトルと本文の内積計算
doc_vectors = np.nanmean([doc_title_vectors, doc_text_vectors], axis=0)
```

```py
# クラスタリング分析
clusters = KMeans(n_clusters=3, random_state=0).fit_predict(doc_vectors)
for tag, cls in zip(titles, clusters):
    print(cls, tag)
```
### 番外編：本文をtf-idfしたキーワードをベクトル化してクラスタ分析してみる

```py

w3, feature_names3 = get_tfidf_and_feature_names(texts)
v3 = np.asarray([word_vectors(word) for word in feature_names3])
clusters = KMeans(n_clusters=10, random_state=0).fit_predict(v3)
for tag, cls in zip(feature_names3, clusters):
    print(cls, tag)
```
### 番外編：単純なキーワードの頻出数の上位10ワードを表示してみる
```py

# 頻度上位10語の取得
size = 10
#ｓｉｚｅの数だけ、上位の単語を表示する
list_word = word_counter.most_common(size)
print(list_word)
```
# 参考サイト
■Pythonで国会議事録から、話題の政治ワードを抽出してみた
https://blog.aidemy.net/entry/2018/05/11/162024

■Word2VecとTF-IDFで社内文書を検索するサービスを作ってMattermostから使えるようにした
https://qiita.com/hrappuccino/items/19bcdc097246865bea86#fn5

■word2vecの学習済み日本語モデルを公開します
http://aial.shiroyagi.co.jp/2017/02/japanese-word2vec-model-builder/