nlplotを使って自然言語データを可視化してみた

* 製造業出身のデータサイエンティストがお送りする記事
* 今回は自然言語データの可視化にnlplotというライブラリーが良さそうでしたので試してみました。

##はじめに
今までずっと構造化データを扱っておりましたが、非構造化データも扱えるようになりたいと思い、まずは自然言語データを対象に可視化から勉強してみました。

##nlplotとは
nlplotは基本的には、自然言語（NLP）において基本的な可視化を簡単にできるようにしたパッケージらしいです。
NLPにおいては、知識が無いので、詳しいことは[nlplotのGithub](https://github.com/takapy0210/nlplot)を参考にご確認ください。
[ブログの記事](https://www.takapy.work/entry/2020/05/17/192947)も見つけましたので、確認して頂けますと幸いです。

##nlplotを使ってみる
今回、使用するデータはlivedoorニュースコーパスの「[ldcc-20140209.tar.gz](https://www.rondhuit.com/download.html)」を使います。

まず、データフレームを作成します。

```php
import os
from glob import glob
import pandas as pd
import linecache
import nlplot
import plotly
from plotly.subplots import make_subplots

pd.set_option('display.max_columns', 300)
pd.set_option('display.max_rows', 300)
pd.options.display.float_format = '{:.3f}'.format
pd.set_option('display.max_colwidth', 5000)

# カテゴリを配列で取得
categories = [name for name in os.listdir("text") if os.path.isdir("text/" + name)]
print(categories)
#['dokujo-tsushin', 'it-life-hack', 'kaden-channel', 'livedoor-homme', 'movie-enter', 'peachy', 'smax', 'sports-watch', 'topic-news']

df = pd.DataFrame(columns=["title", "category"])
for cat in categories:
    path = "text/" + cat + "/*.txt"
    files = glob(path)
    for text_name in files:
        title = linecache.getline(text_name, 3)
        s = pd.Series([title, cat], index=df.columns)
        df = df.append(s, ignore_index=True)

# データフレームシャッフル
df = df.sample(frac=1).reset_index(drop=True)
df.head()
```

![スクリーンショット 2021-03-26 20.09.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/0b582b18-d39c-fb97-1781-c8ad12304dc9.png)


次にデータを各々インスタンス化します。

```php
# 全データ・livedoor-homme・#kaggleをそれぞれインスタンス化
npt = nlplot.NLPlot(df, target_col='title')
npt_livedoor = nlplot.NLPlot(df.query('category == "livedoor-homme"'), target_col='title')
npt_movie = nlplot.NLPlot(df.query('category == "movie-enter"'), target_col='title')
```

次にストップワードの計算をします。

```php
# top_nで頻出上位単語, min_freqで頻出下位単語を指定できる
# 今回は上位5単語（livedoor-homme・movie-enter）をストップワードに指定
stopwords = npt.get_stopword(top_n=5, min_freq=0)
stopwords
#['ゆるっとcafe', '【Sports', 'by', 'PHONE', '-']
```

ここから可視化になります。
最初は「N-gram bar chart」です。

```php
npt.bar_ngram(
    title='uni-gram',
    xaxis_label='word_count',
    yaxis_label='word',
    ngram=1,
    top_n=50,
    width=800,
    height=1100,
    color=None,
    horizon=True,
    stopwords=stopwords,
    verbose=True,
    save=False,
)
```

![newplot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/7428142f-e462-f031-43f9-2241440409da.png)


```php
npt.bar_ngram(
    title='bi-gram',
    xaxis_label='word_count',
    yaxis_label='word',
    ngram=2,
    top_n=50,
    width=800,
    height=1100,
    color=None,
    horizon=True,
    stopwords=stopwords,
    verbose=True,
    save=False,
)
```

![newplot (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/62168b9c-eb74-8b43-5ebc-bc25787fea66.png)

```php
npt.bar_ngram(
    title='tri-gram',
    xaxis_label='word_count',
    yaxis_label='word',
    ngram=3,
    top_n=50,
    width=1000,
    height=1100,
    color=None,
    horizon=True,
    stopwords=stopwords,
    verbose=True,
    save=False,
)
```

![newplot (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/04cb3609-32dd-7cab-8829-237bac0e1fe5.png)

次はラベル毎に比較します。

```php
# livedoor-hommeのfigを取得
fig_unigram_livedoor = npt_livedoor.bar_ngram(
    stopwords=stopwords,
    title='uni-gram',
    xaxis_label='word_count',
    yaxis_label='word',
    ngram=1,
    top_n=50,
    width=600,
    height=1100,
    color=None,
    horizon=True,
    verbose=True,
    save=False,
)

# movie-enterのfigを取得
fig_unigram_movie = npt_movie.bar_ngram(
    stopwords=stopwords,
    title='uni-gram',
    xaxis_label='word_count',
    yaxis_label='word',
    ngram=1,
    top_n=50,
    width=600,
    height=1100,
    color=None,
    horizon=True,
    verbose=True,
    save=False,
)

# subplot
trace1 = fig_unigram_livedoor['data'][0]
trace2 = fig_unigram_movie['data'][0]

fig = make_subplots(rows=1, cols=2, subplot_titles=('livedoor-homme', 'movie-enter'), shared_xaxes=False)
fig.update_xaxes(title_text='word count', row=1, col=1)
fig.update_xaxes(title_text='word count', row=1, col=2)

fig.update_layout(height=1100, width=1900, title_text='unigram livedoor-homme vs. movie-enter')
fig.add_trace(trace1, row=1, col=1)
fig.add_trace(trace2, row=1, col=2)

plotly.offline.plot(fig, filename='unigram livedoor-homme_vs_movie-enter.html', auto_open=False)

fig.show()
```

![newplot (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/777a455f-9193-c8f6-17b8-01b8a5ff5d55.png)

次はN-gram tree Mapを可視化してみます。

```php
npt.treemap(
    title='Tree of Most Common Words',
    ngram=1,
    top_n=30,
    stopwords=stopwords,
)
```

![newplot (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/25c33de7-143b-e31b-86e7-3ccc02213f30.png)

次は単語の出現頻度の分布を可視化します。

```php
# 単語数の分布
npt.word_distribution(
    title='number of words distribution',
    xaxis_label='count',
    yaxis_label='',
    width=1000,
    height=500,
    color=None,
    template='plotly',
    bins=None,
    save=False,
)
```

![newplot (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/35ab450a-ed35-5df8-24a1-44d7c62fde56.png)

ラベル毎に出現頻度の分布も可視化できます。


```php
# livedoor-hommeのfigを取得
fig_wd_livedoor = npt_livedoor.word_distribution(
    title='number of words distribution',
    xaxis_label='length',
    yaxis_label='',
    width=1000,
    height=500,
    color=None,
    template='plotly',
    bins=None,
    save=False,
)

# movie-enterのfigを取得
fig_wd_movie = npt_movie.word_distribution(
    title='number of words distribution',
    xaxis_label='length',
    yaxis_label='',
    width=1000,
    height=500,
    color=None,
    template='plotly',
    bins=None,
    save=False,
)

trace1 = fig_wd_livedoor['data'][0]
trace2 = fig_wd_movie['data'][0]

fig = make_subplots(rows=2, cols=1, subplot_titles=('livedoor-homme', 'movie-enter'), shared_xaxes=True)

fig.update_layout(height=900, width=1200, title_text='words distribution livedoor-homme vs. movie-enter')
fig.add_trace(trace1, row=1, col=1)
fig.add_trace(trace2, row=2, col=1)

plotly.offline.plot(fig, filename='words distribution #データサイエンティストvs#kaggle.html', auto_open=False)

fig.show()
```

![newplot (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/373f9fb9-8341-c49c-e6fb-248f7678f8e0.png)

良く見るword cloudも簡単に使えます。

```php
npt.wordcloud(
    stopwords=stopwords,
    width=1000,
    height=600,
    max_words=100,
    max_font_size=100,
    colormap='tab20_r',
    mask_file=None,
    save=True
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/3d8503b8-4aba-741b-cd3b-27e7738777ff.png)

次は共起ネットワークの可視化です。

```php
# ビルド（データ件数によっては処理に時間を要します）※ノードの数のみ変更
npt.build_graph(stopwords=stopwords, min_edge_frequency=5)

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

npt.co_network(
    title='Co-occurrence network',
)
```

![スクリーンショット 2021-03-26 20.20.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/1d0016d9-59c7-3c40-bd41-dacd6f1fb04d.png)

![newplot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/9a2bffbb-ee22-47fc-dfd3-d2e7a1cd628a.png)

次は、sunburst chartを可視化します。

```php
npt.sunburst(
    title='All sentiment sunburst chart',
    colorscale=True,
    color_continuous_scale='Oryel',
    width=800,
    height=600,
    save=True
)
```

![newplot (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/93a47594-5d3e-351b-59c4-4f2f4de065e4.png)

最後はpyLDAvisです。


```php
npt.ldavis(num_topics=2, passes=5, save=True)
```

![スクリーンショット 2021-03-26 20.23.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/3d7aab6f-6829-19ad-8b79-c931e6791704.png)

##さいごに
最後まで読んで頂き、ありがとうございました。
今回、初めて自然言語の可視化ライブラリーnlplotを使って自然言語データの可視化をしてみました。
シンプルに可視化できて凄い良いですね。今後、非構造化データについても扱っていけるように勉強しようと思います。

訂正要望がありましたら、ご連絡頂けますと幸いです。