自然言語を可視化･分析できるライブラリ「nlplot」はすごいよ

>2021/11/13：本文抽出+ノイズ除去済の[**「学問ノススメ」**](https://raw.githubusercontent.com/hima2b4/Natural-language-processing/main/sample.txt) ← 必要な方は**こちら**をクリック

#はじめに

[**前回の記事**](https://qiita.com/hima2b4/items/bc9c0fac30b6437c5737)では「TF-IDF」によるワードクラウド描画にチャレンジしましたが、思い通りにならなかった点（以下）がありましたので、再度チャレンジしました。

- scikit-learnの「TfidfVectorizer」というライブラリがうまく使えなかった…
- nlplot（自然言語可視化・分析ライブラリ）もフルで試せなかった

実力不足のため、苦労しましたが、なんとか任意のテキストデータで「Word Cloud」、「nlplotによる各種可視化」、「（TfidfVectorizer）によるTF-IDF計算」、「TF-IDFによるWord Cloud」が実行できるようになったたので、備忘も兼ね、記事にしたものです。


##実行条件など
>・**Google colab**で実行
・**青空文庫の「学問ノススメ」**で実行
※コードは任意データでの実行を意図していますので、「こころ」限定ではありません。


##備忘
- 読み込ませたテキストデータは、見やすさ・扱いやすさからデータフレームにした。
- 分かち書きは、スペース区切り（語A 語B 語C）とカンマ区切り [語A,語B,語C] の2パターンとした。1パターンだけで対応したかったが…私の修行が足りないのであろう。TF-IDF計算はスペース区切り、Word Cloudは カンマ区切り、nlplotはどちらでもいけたと思う。
- 文章は「。」でセンテンスに分けた。TF-IDFの計算・TF-IDFによるWord Cloud描画は、センテンス毎と全文書の2通りとした。
- 品詞は一般名詞・動詞・形容詞（動詞と形容詞は基礎型）を対象とした。


#ライブラリのインストール

```:nlplot
#nlplotをインストール
pip install nlplot
```

```:日本語フォント
#日本語フォントをインストール
!apt-get -y install fonts-ipafont-gothic
```

```:MeCabのインストール
#Mecabのインストール
!pip install mecab-python3==0.996.5
```

```:ライブラリインポート
from pathlib import Path
import pandas as pd
import MeCab
import matplotlib.pyplot as plt
```

#テキストファイル & ストップワード指定

```:テキストファイル指定
#テキストファイル名を指定（※textファイルの文字コードは「UTF-8」としてください）
filename = 'sample.txt'
```

```:ストップワード指定
#ストップワード設定
stop_words = ["し","い","ある", "おる", "せる", "ない", "いる", "する", "の", "よう", "なる", "それ", "そこ", "これ", "こう", "ため", "そう", "れる", "られる"]
```

#モジュールの準備

- 前処理（改行や空白の処理、センテンス化）や形態素分析、ワード出現回数処理を行います。
- 文章をワードに分解後、一般名詞・動詞・形容詞（動詞と形容詞は基礎型）のみを取り出しています。（※追加や変更はコード操作が必要です）

```:ライブラリインポート
import MeCab as mc
import re           # 正規表現
import numpy as np
```

```:ファイル読込みと前処理
with open(filename, 'r', encoding='utf-8') as file:
    lines = file.readlines()

#with open(filename, 'r', encoding='utf-8') as all:
#    all_text = all.readlines()

source = open(filename, 'r', encoding='utf-8')
all_text = source.read()
all_text = ''.join(all_text)
table = str.maketrans({
  '\u3000': '',
  ' ': '',
  '\t': ''
})
all_text = all_text.translate(table)

lines = [l.strip() for l in lines]
all_text = [l.strip() for l in all_text]

sentences = []
for sentence in lines:
    texts = sentence.split('。')
    sentences.extend(texts)
```

```:全文書結合と表示
all_text = ''.join(all_text)
all_text
```

```:センテンスをデータフレームに展開
#抽出したワードをデータフレームdfに
import pandas as pd
import numpy as np

df_text = pd.DataFrame(sentences, columns = ['text'], index=None)

#空白をNaNに置き換え
df_text['text'].replace('', np.nan, inplace=True)

#Nanを削除 inplace=Trueでdf上書き
df_text.dropna(subset=['text'], inplace=True)

df_text[:15]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/91d9ece6-5d24-42d1-2b5a-bffc38980a01.png)

```:形態素モジュール
#スペース区切り分かち書き
def mecab_analysis(text):

    t = MeCab.Tagger('-Ochasen')

    node = t.parseToNode(text)

    words = []
     
    while node:
        if node.surface != "":  # ヘッダとフッタを除外

            word_type = node.feature.split(',')[0]
            sub_type = node.feature.split(',')[1]
            features_ = node.feature.split(',')

            #品詞を選択
            if word_type in ["名詞"]: 
                if sub_type in ['一般']:
                    word = node.surface
                    words.append(word)

            #動詞、形容詞[基礎型]を抽出（名詞のみを抽出したい場合は以下コードを除く）
            elif word_type in ['動詞','形容詞'] and not (features_[6] in stop_words):
                words.append(features_[6])

        node = node.next

        if node is None:
            break

    return " ".join(words)

#カンマ区切り分かち書き
def mecab_analysis2(text):

    t = MeCab.Tagger('-Ochasen')

    node = t.parseToNode(text)

    words2 = []

    while(node):

        if node.surface != "":  # ヘッダとフッタを除外
            word_type = node.feature.split(',')[0]
            sub_type = node.feature.split(',')[1]
            features_ = node.feature.split(',')

            if word_type in ['名詞']:  # 名詞をリストに追加する
                if sub_type in ['一般']:
                    words2.append(node.surface)

            #動詞、形容詞[基礎型]を抽出（名詞のみを抽出したい場合は以下コードを除く）
            elif word_type in ['動詞','形容詞'] and not (features_[6] in stop_words):
                    words2.append(features_[6])

        node = node.next
        if node is None:
            break
    return words2

#スペース区切り分かち書き（全文書一括）
def mecab_analysis3(text):

    t = MeCab.Tagger('-Ochasen')

    node = t.parseToNode(all_text)

    words3 = []
     
    while node:
        if node.surface != "":  # ヘッダとフッタを除外

            word_type = node.feature.split(',')[0]
            sub_type = node.feature.split(',')[1]
            features_ = node.feature.split(',')

            #品詞を選択
            if word_type in ["名詞"]: 
                if sub_type in ['一般']:
                    all_text_word = node.surface
                    words3.append(all_text_word)

            #動詞、形容詞[基礎型]を抽出（名詞のみを抽出したい場合は以下コードを除く）
            elif word_type in ['動詞','形容詞'] and not (features_[6] in stop_words):
                words3.append(features_[6])

        node = node.next

        if node is None:
            break

    return " ".join(words3)

#形態素結果をリスト化し、データフレームdfに結果を列追加する
df_text['words'] = df_text['text'].apply(mecab_analysis)
df_text['words2'] = df_text['text'].apply(mecab_analysis2)

df_text[:15]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/275a22a5-5676-d876-bf4f-1d079f527f60.png)

この表を見ると、各センテンスからの語の抽出状況がよくわかります。
textがセンテンス、wordsがスペース区切り分かち書き、words2がカンマ区切り分かち書きが格納されています。

```:全文書の分かち書きをデータフレームに格納
df = pd.Series(all_text)
df = df.apply(mecab_analysis3)
```

```:ワード出現回数カウント
#filter the df to one candidate, and create a list of responses from them
words_list = df_text.words2.tolist()
words_list = sum(words_list,[])

from collections import Counter

#出現回数を集計し、最頻順にソート
words_count = Counter(words_list)
result = words_count.most_common()

#出現回数結果の画面出力
for word, cnt in result:
    print(word, cnt)
```

#ワードクラウド
- ワードクラウドは、文章中で出現頻度が高い単語を複数選び出し、その頻度に応じた大きさで図示する手法です。
- ワードクラウドは任意の形状に描画できるため、以下では①長方形（通常）と②ドーナツ型の2パターンで示しています。

##ワードクラウド
```:ワードクラウド描画
#wordcloud取込用に辞書型ヘ変換
dic_result = dict(result)

#Word Cloudで画像生成
from wordcloud import WordCloud

wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=1000,
                      height=600,
                      ).fit_words(dic_result)

#生成した画像の表示
import matplotlib.pyplot as plt 
from matplotlib import rcParams

plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/c82475db-12b1-daf8-f991-18639eacbc9e.png)

##ワードクラウド描画（ドーナツ型）
```:ドーナツデータダウンロード
#donutsデータダウンロード
import requests

url = "https://github.com/hima2b4/Word-Cloud/raw/main/donuts.png"
#url = "https://github.com/hima2b4/Word-Cloud/raw/main/biwa.png"

file_name = "donuts.png"
#file_name = "biwa.png"

response = requests.get(url)
image = response.content

with open(file_name, "wb") as f:
    f.write(image)
```

```:ワードクラウド（ドーナツ型）描画
#ライブラリインポート
from PIL import Image
import numpy as np

custom_mask = np.array(Image.open('donuts.png'))
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      mask=custom_mask,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=1200,
                      height=1200
                      ).fit_words(dic_result)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/9d018426-6814-c187-38d5-ccd89cf4ee2e.png)


#nlplot

「nlplot」は、自然言語の可視化・分析できるライブラリです。

- N-gram bar chart, N-gram tree Map, Histogram of the word count, wordcloud, co-occurrence networks（共起ネットワーク）, sunburst chart（サンバースト）を描きます。

https://www.takapy.work/entry/2020/05/17/192947

##nlplot(uni-gram)

```:uni-gram描画
import nlplot

npt = nlplot.NLPlot(df_text, target_col='words')

# top_nで頻出上位単語, min_freqで頻出下位単語を指定できる
#stopwords = npt.get_stopword(top_n=0, min_freq=0)  #前処置にて除去しているため適用していない

npt.bar_ngram(
    title='uni-gram',
    xaxis_label='word_count',
    yaxis_label='word',
    ngram=1,
    top_n=50,
#    stopwords=stopwords,
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/5f3ded82-70bd-84e5-7c89-c1ab97a6f9e5.png)


##nlplot(tree-map)

```:treemap描画
npt.treemap(
    title='Tree of Most Common Words',
    ngram=1,
    top_n=30,
#    stopwords=stopwords,  #前処置にて除去しているため適用していない
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/497d5210-9511-2fef-192b-de7f286f2fda.png)


##nlplot(wordcloud)

```:wordcloud描画
npt.wordcloud(
    max_words=100,
    max_font_size=100,
    colormap='tab20_r',
#    stopwords=stopwords,  #前処置にて除去しているため適用していない
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/1d8eb57e-02f7-fa8f-888b-11553b7237ae.png)

##nlplot(word_distribution)

```:word_distribution描画
# 単語数の分布
npt.word_distribution(
    title='number of words distribution',
    xaxis_label='count',
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/0d46b19c-eada-06a7-7766-df7db617750b.png)

##nlplot(build_graph)

```:共起ネットワーク描画
# ビルド（データ件数によっては処理に時間を要します）※ノードの数のみ変更
npt.build_graph(min_edge_frequency=1,
                #stopwords=stopwords,
                )

display(
    npt.node_df.head(), npt.node_df.shape,
    npt.edge_df.head(), npt.edge_df.shape
)

npt.co_network(
    title='Co-occurrence network',
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/96bb99b2-e727-2fa1-641b-5d4edc7e2363.png)

##nlplot(sunburst)

```:サンバースト描画
npt.sunburst(
    title='All sentiment sunburst chart',
    colorscale=True,
    color_continuous_scale='Oryel',
    width=800,
    height=600,
    #save=True
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/cebd94c5-961d-41e0-a7a6-f128b9c9f677.png)

#TF-IDF
TF-IDF は ワードの重要度 を測るための指標の1つ。ワードの出現頻度とレア度が考慮された指標。TF、IDF、TF-IDF の定義は以下の通り。

\begin{eqnarray} TF(d,w) &=& \frac{文書d における語wの出現回数}{文書d における全語の出現回数の和}\\ IDF(w) &=& log(\frac{全文書数}{語w を含む文書数})\\ TFIDF(d,w) &=& TF(d,w) \times IDF(w) \end{eqnarray}

- sentence毎にTF-IDFを算出、sentence×wordマトリクスをcsv出力(tfidf.csv)します。
- [**参考**] 指定したsentenceのWord cloudを描きます。※sentenceは個別指定
- 文書全体でTF-IDFを算出した時のWord cloudも描きます。


```:TF-IDF算出
from sklearn.feature_extraction.text import TfidfVectorizer

# TF-IDFをデータフレームに展開（センテンス×word）
vectorizer = TfidfVectorizer(use_idf=True)
tfidf = vectorizer.fit_transform(df_text['words'])

df_tfidf = pd.DataFrame(tfidf.toarray(), columns=vectorizer.get_feature_names(), index=df_text['words'])
display(df_tfidf)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/aaa139de-04c1-e69b-bbcf-7a3c18d36d85.png)

```:TF-IDFのcsv出力
#sentence×TF-IDFをcsv形式で出力
from google.colab import files
df_tfidf.to_csv('tfidf.csv',encoding='utf_8_sig')
#files.download('tfidf.csv') 
```
　
**[参考サイト]**

https://zenn.dev/megane_otoko/articles/013_tf_idf_trial

##Word Cloud with TF-IDF for each Sentence
指定したsentenceのWord Cloudを表示しているだけなので参考程度。
※tfidf_vec = vectorizer.fit_transform(df_text['words']).toarray()[0] ←この数値で描画したいsentenceを指定する。

```:TF-IDF辞書化
tfidf_vec = vectorizer.fit_transform(df_text['words']).toarray()[0]
# 単語: tf-idfの辞書にする。
tfidf_dict = dict(zip(vectorizer.get_feature_names(), tfidf_vec))
# 値が正のkeyだけ残す
tfidf_dict = {k: v for k, v in tfidf_dict.items() if v > 0}
```

```:センテンスのTF-IDFワードクラウド
#Word Cloudで画像生成
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=1000,
                      height=600,
                      ).generate_from_frequencies(tfidf_dict)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/f1661682-2b17-9563-d524-36d8a4e1e37d.png)


##Word cloud with TF-IDF for All texts

```:TF-IDF辞書化
tfidf_vec2 = vectorizer.fit_transform(df).toarray()[0]
# 単語: tf-idfの辞書にする。
tfidf_dict2 = dict(zip(vectorizer.get_feature_names(), tfidf_vec2))
# 値が正のkeyだけ残す
tfidf_dict2 = {k: v for k, v in tfidf_dict2.items() if v > 0}
```
```:全文書のTF-IDFワードクラウド
#Word Cloudで画像生成
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=1000,
                      height=600,
                      ).generate_from_frequencies(tfidf_dict2)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/27984e7f-5757-d147-bc50-dcf447d97db9.png)


#最後に
すこし冗長的なコードもあると思いますが、なんとかやりたいことはできました。
nlplot含め、使用したライブラリは本当にすばらしいです。
テキストから何かを探りたい、何か探れないか？でもいいと思います。
ぜひ実行いただければと思います。
最後まで見ていただきありがとうございました。


#参考サイト
https://qiita.com/str32/items/4539e417a9cb333abd52

https://qiita.com/kngsym2018/items/3719f8da1f129793257c

https://ichi.pro/python-de-kasutamuwa-dokuraudo-o-sakuseisuru-145227399773811

https://analytics-note.xyz/programming/frequencies-word-cloud/

http://non-kuri.blogspot.com/2017/10/python.html

https://www.ai-shift.co.jp/techblog/1751

https://qiita.com/y-s-y-s/items/a945e7888fb3f7cf8f8b

https://rinsaka.com/python/nltk/04-tfidf.html