Excelに整理した「アンケート自由記述」を分析しよう！（その4）〜感情分析編〜


2021/12/29：この記事は感情分析に**asari**使用、以下の記事は**Google NL使用**（感情分析精度がよいのは以下）

https://qiita.com/hima2b4/items/d8909531830f323d3744


##はじめに
**Excelに整理した「アンケート自由記述」を分析しよう！** は今回で4回目。

とにかく**「自由記述分析のアレルギー反応を抑えたい・・・」** 
この思いから、初手としての可視化、語彙による自由記述のグループ区分等を実行すべく、nlplot、Word Cloud、TF-IDF、Word2Vec、Doc2Vecなど、いろいろ試してみました。

できたことはできたのですが、以下のような反映点も。
>- **すこし盛り込みすぎたため、サクサク動作とはいえなくなった。**
- **可視化はわかりやすいが、可視化対象が多すぎて、みるのがイヤになってきた。**
- **グループ区分は実行できたが、「うん、まぁ、参考程度かな」というレベルだった。**


そこで、
>- **学習済モデルを必要としない軽いライブラリに絞る。**
- **感情分析の結果も活かせるようにし、Word Cloudをネガポジ区分別に描く。**

ことにしました。

また、
**アンケートにおける満足度**と**自由記述のネガポジ度**の関係も気になっていました。
自由記述がポジであるほど満足度が高い（＝相関がある）かどうか？です。
自由記述の内容は満足度別に確認することが多いですが、相関がなければこの見方はナンセンスということになるからです。

　

##実行条件など
>- **Google colab**で実行
- **手元の表形式データ（アンケート自由記述結果）**で実行

※使用したデータは開示できませんが、データ形式は以下です。カラムはUserID、comment、csの3つ。commentは自由記述回答、csは満足度（1～6）です。

|userID|comment|cs|
|---|---|---|
|U001  |総合的には満足してますが、○○が細かく調整できたらもっと使いやすいと思います。|4  |
|U002  |○○ボタンが押しにくいです。|2  |
|U003  |特にありません。|3  |
|U004  |既存品と取付位置が合わないことがつらいです。|1  |


##語彙のベクトル化について
Userの訴えかけを語彙傾向で分けるためには、語彙をベクトル化しないといけません。
この記事では、TF-IDFによる方法を扱っています。
各Userの声、形態素の結果、グループ分け（クラスタ分け）の結果をデータフレームに格納、csvデータに変換し、ローカルファイルに出力するようにしています。
グループ分けした結果は、EXCELで並べ替えるなどして見たほうがよいでしょう。


##備忘
- コード操作を不要にするため、ファイル指定やフォームなど、GoogleColabの機能を活用。
- 品詞は名詞・動詞・形容詞（動詞と形容詞は基礎型）を抽出対象とした。


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

```Python:
#Mecabのインストール
!pip install mecab-python3==0.996.5
```
```Python:
#matplotlib日本語化
!pip install japanize-matplotlib
```
```Python:
#感情分析ライブラリasari
pip install asari
```
```Python:
#scikit-learn_Ver指定（ランタイム再起動が必要）
pip install scikit-learn==0.20.4
```
```Python:
#Janome_Ver指定
pip install Janome==0.3.7
```
```Python:
#ライブラリインポート
from pathlib import Path
import pandas as pd
import re
import MeCab
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import asari
from asari.api import Sonar
```
**注意：ランタイム再起動しないとエラーとなります。一度実行してからランタイム再起動し、再度実行すれば実行できます。**

##ファイル選択
```Python:
#@title csvファイル（UTF-8）を指定してください
from google.colab import files
#print('csvファイル(UTF-8）を指定してください')
uploaded = files.upload()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/a765d0fa-ef3b-ab8b-9703-d1c32e86786d.png)
※ライブラリのインストールとインポートが完了すると、[ ファイル選択 ]がうながされますので、クリックし、ファイルを指定します。（キャプチャ画像は、これは読込み後の画面キャプチャです。）


##Option：CS_High/Low_Level & ストップワード指定
※CS_Level設定について
このレベル設定は、例えば6段階評価の場合、4以上を[満足]、3以下を[不満足]とする等のシキイの設定です。一部の可視化は、ここでの設定に沿いWord Cloudに反映します。

```Python:
#@title CS_High/Low設定（上：設定以上をCS高/下：設定以下をCS低） { run: "auto" }
CS_High_level_x_or_More = 4 #@param {type:"number"}
CS_Low__level_x_or_Less = 3 #@param {type:"number"}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/c60be5f4-739f-78ff-ea0e-d61a89bb2765.png)

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

##モジュール構築
```Python:
#@title データフレーム格納＆欠損値削除
if len(uploaded.keys()) != 1:
    print("アップロードは１ファイルにのみ限ります")
else:
    target = list(uploaded.keys())[0]

df = pd.read_csv(target)

df.dropna(subset=['comment'], inplace=True)
df.head()
```

```Python:
#@title 形態素解析（一般名詞・動詞:基礎型・形容詞:基礎型）＆カンマ･スペース区切りをデータフレームに格納
#形態素解析（一般名詞・動詞・形容詞（動詞と形容詞は基礎型）を抽出対象とした）
#スペース区切り分かち書き
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

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

#表示
df
```

```Python:
#@title 形態素結果を層別しデータフレームに格納
#全データをデータフレームに格納
all_words =' '.join(df['words'])
df_all = pd.Series(all_words)

#CSが高いuserの声（words）をデータフレームに格納
drop_index = df.index[df['cs'] <=CS_Low__level_x_or_Less]
#条件にマッチしたIndexを削除
df_high = df.drop(drop_index)
all_high_words=' '.join(df_high['words'])
df_all_high_words = pd.Series(all_high_words)

#CSが低いuserの声（words）をデータフレームに格納
drop_index2 = df.index[df['cs'] >=CS_High_level_x_or_More]
#条件にマッチしたIndexを削除
df_low = df.drop(drop_index2)
all_low_words=' '.join(df_low['words'])
df_all_low_words = pd.Series(all_low_words)

print('CS高ユーザーの声(Words）：')
print(df_all_high_words)
print('CS低ユーザーの声(Words）：')
print(df_all_low_words)
```

```Python:
#@title ワード出現回数カウント（表示する場合は#外す）
#カンマ区切り分かち書きしたワードをリスト化
words_list = df.words2.tolist()
words_list = sum(words_list,[])

from collections import Counter

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

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

##満足度（CS）グラフ化
```Python:
#@title CS満足度傾向_円グラフ & ヒストグラム
cs = pd.DataFrame(df['cs'].value_counts())
cs.plot.pie(subplots=True,autopct="%1.1f%%",startangle=90,counterclock=False)
plt.show()

plt.hist(df['cs'],bins=7)
# x軸とy軸のラベルをつける
plt.xlabel('CS')
plt.ylabel('Count')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/8159f9ea-2daf-bce7-5dbf-34490c8494ad.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/3e2a3657-d186-dfde-03f3-1fe9599994f2.png)
※これはアンケートデータにあった満足度を円グラフとヒストグラムで表現しただけ。
　1～6段階で選択される値なのですが、0があります。これはおかしいですが、気にしない。
　満足度（CS）は高い方に偏っています。

##自由記述のグループ分け
```Python:
#@title TF-IDFマトリクス作成＆データフレーム格納
# ライブラリインポート
from sklearn.feature_extraction.text import TfidfVectorizer

# TF-IDFのベクトル処理
vectorizer = TfidfVectorizer(use_idf=True)
tfidf = vectorizer.fit_transform(df['words'] )

# TF-IDF値を「センテンス×ワード」マトリクスをデータフレーム化
df_tfidf = pd.DataFrame(tfidf.toarray(), columns=vectorizer.get_feature_names(), index=df['words'])
#display(df_tfidf)
```

```Python:
#@title **Option**：エルボー法（SSE値の低下がサチる場所を最適なクラスター数とみなす方法）

#ライブラリインポート
from sklearn.preprocessing import StandardScaler
from sklearn import preprocessing
from sklearn.cluster import KMeans

X=df_tfidf
sc = preprocessing.StandardScaler()
sc.fit(X)
X_norm = sc.transform(X)
#print(type(X_norm))

distortions = []

for i  in range(1,31):                # 1～30クラスタまで一気に計算 
    km = KMeans(n_clusters=i,
                init='k-means++',     # k-means++法によりクラスタ中心を選択
                n_init=10,
                max_iter=300,
                random_state=0)
    km.fit(X)                         # クラスタリングの計算を実行
    distortions.append(km.inertia_)   # km.fitするとkm.inertia_が得られる

plt.plot(range(1,31),distortions,marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Distortion')
plt.show()
```

```Python:
#@title クラスター数（分けたいグループ数）設定 { run: "auto" }
num_clusters = 8 #@param {type:"number"}
```

```Python:
#@title TF-IDF_k-meansによる各意見のグループ分け
# kmean_clustring
from sklearn.cluster import KMeans

# 設定クラスタ数で実行
clusters = KMeans(n_clusters=num_clusters).fit_predict(tfidf)

#データフレームにclusterを反映
df['cluster_tfidf'] = clusters
df
```

**TF-IDFについて**
TF-IDF は文書に含まれる単語がどれだけ重要かを示す手法の一つで、TF (= Term Frequency: 単語の出現頻度)と IDF (Inverse Document Frequency: 逆文書類度)の2つを使って計算します。

**クラスタリング（k-means法）について**
数値化（TF-IDF）した各意見のクラスタリングを行います。クラスタリングは、scikit-learn の KMeans（非階層的クラスタ分析）を使用します。KMeans（非階層的クラスタリング）では、設定したクラスタ数にしたがって、近い属性のデータをグループ化します。
参考として適性なクラスター数を導く手法（エルボー法）の結果を表示します。横軸：クラスタ数、縦軸：SSE（残差平方和）としたグラフで、クラスタ数を増やしてもSSEがほとんど改善しない点のクラスタ数を選ぶというものです。
適切な点が見いだせない場合も含め、クラスター数は任意に設定できるようにしています。「クラスター数設定」で設定します。


##感情分析

```Python:
#@title インスタンス作成
#インスタンスを作る
sonar = Sonar()

#テキストにポジティブな感情がどのくらいあるか
def get_positive_power(text):
    info = sonar.ping(text= text)
    posi_vector = info["classes"][1]["confidence"]
    return posi_vector

##テキストにネガティブな感情がどのくらいあるか
def get_negative_power(text):
    info = sonar.ping(text= text)
    nega_vector = info["classes"][0]["confidence"]
    return nega_vector

#ポジティブかネガティブかどっちか
def which_sentiment(text):
    info = sonar.ping(text= text)
    sentiment = info["top_class"]
    return sentiment
```


```Python:
#@title 感情分析結果をデータフレームに反映

#各セリフのポジティブな感情のスコア
df["sentiment_positive"] = df.comment.map(lambda x : get_positive_power(x))

#各セリフのネガティブな感情のスコア
df["sentiment_negative"] = df.comment.map(lambda x : get_negative_power(x))

#ポジネガ判定結果
df["sentiment"] = df.comment.map(lambda x : which_sentiment(x))

#差し引き
df["sentiment_net"] = df.sentiment_positive - df.sentiment_negative

#足し上げ
df["accumulation"] = df.sentiment_net.cumsum()

df
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/b34bf7d4-f8b2-832a-d31d-10006d6deacb.png)

最終的に、データフレームは上記のようになります。各カラムの意味は以下を参照ください。

* **cluster_tfidf**：TF-IDFで自由記述をグループ分けしたグループ番号
* **sentiment_positive**：どれだけポジティブであるかを数値化したもの（0～1）
* **sentiment_negative**：どれだけネガティブであるかを数値化したもの（0～1）
* **sentiment**：sentiment_positive － sentiment_negative がプラスならば[positive]、マイナスならば[negative]
* **sentiment_net**：＝ sentiment_positive － sentiment_negative
* **accumulation**：sentiment_netの累計

```Python:
#@title 感情分析値（sentiment_net）ヒストグラム
plt.hist(df['sentiment_net'],bins=10)
# x軸とy軸のラベルをつける
plt.xlabel('Sentiment')
plt.ylabel('Frequency')
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/6b80f535-a975-36ef-ca5e-0c13a7085f0e.png)
※0がニュートラル、プラスがポジティブ、マイナスがネガティブとなります。
ポジティブ側に偏った分布となりました。
　


```Python:
#@title 感情分析値の統計量
df['sentiment_net'].describe()
```
> count    141.000000
 mean       0.387111
 std        0.537571
 min       -0.984410
 25%        0.135423
 50%        0.453090
 75%        0.858388
 max        0.999903
 Name: sentiment_net, dtype: float64

```Python:
#@title Sentiment-CS 相関
sns.jointplot(data=df, x="sentiment_net", y="cs", kind="reg",
              line_kws={"color":"red"})
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/fee70998-d4d4-b4bf-1092-7aa1e4458ad5.png)
**感情（Sentiment）と満足度（CS）に、まったく相関はみられませんでした。**
私が扱ったデータの場合、**満足度別に自由記述を見ても意味がない**ということになります。


## **Word Cloud**
- ワードクラウドは、文章中で出現頻度が高い語を複数選び出し、その頻度に応じた大きさで図示する手法です。
- 表示するワードクラウドは全6種。①ワード出現回数ベース／②TF-IDFベース（全データ）/③TF-IDFベース（CS高データ）/④TF-IDFベース（CS低データ）/⑤TF-IDFベース（ポジティブデータ）/⑥TF-IDFベース（ネガティブデータ）

※語の表示数はmax_words, Word Cloudの表示サイズはwidth, heightで設定できます


```Python:
#@title Word Cloud by word_count（All Data）：🔲型 → #maskの#外すと🍩型に

#wordcloud取込用にresultを辞書型ヘ変換
dic_result = dict(result)

#Word Cloudで画像生成（#max_words, width, heightは任意設定）
from wordcloud import WordCloud

#画像データダウンロード（biwakoの画像リンクもあり。変更する場合は#調整）
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

#Word Cloudで画像生成（#max_words, width, heightは任意設定）
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=800,
                      height=500,
                      ).fit_words(dic_result)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```

```Python:
#@title Word Cloud with TF-IDF（All DATA)：🔲型 → #maskの#外すと🍩型に
# TF-IDF計算
tfidf_vec2 = vectorizer.fit_transform(df_all).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}

#Word Cloudで画像生成（#max_words, width, heightは任意設定）
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      #mask=custom_mask,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=800,
                      height=500,
                      ).generate_from_frequencies(tfidf_dict2)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```

```Python:
#@title Word Cloud with High CS Data (TF-IDF)：🔲型 → #maskの#外すと🍩型に
tfidf_vec = vectorizer.fit_transform(df_all_high_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}

#Word Cloudで画像生成（#max_words, width, heightは任意設定）
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      #mask=custom_mask,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=800,
                      height=500,
                      ).generate_from_frequencies(tfidf_dict)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```

```Python:
#@title Word Cloud with Low CS Data (TF-IDF)：🔲型 → #maskの#外すと🍩型に
tfidf_vec = vectorizer.fit_transform(df_all_low_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}

#Word Cloudで画像生成（#max_words, width, heightは任意設定）
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      #mask=custom_mask,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=800,
                      height=500,
                      ).generate_from_frequencies(tfidf_dict)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```

sentiment_net は、分布の平均及び中央値≒0.4でしたので、これをシキイ（＞0.4をPositiveに、≦0.4をNegativeに）とし、以下、Word Cloudに反映しました。
※ニュートラルは0なので、本来は0をシキイとすべきなのかもしれませんが、「弱いPositiveはNegativeにしちゃえ」というノリで、分布もにらみつつ0.4に設定したということです。

```Python:
#@title ネガポジ区分（sentiment_netでネガポジ区分したい値を指定：Default=0.4） { run: "auto" }

Thresh_hold = 0.4 #@param {type:"slider", min:0, max:0.5, step:0.1}

#ポジの声（words）をデータフレームに格納
drop_index3 = df.index[df['sentiment_net'] <=Thresh_hold]
#条件にマッチしたIndexを削除
df_posi = df.drop(drop_index3)
all_posi_words=' '.join(df_posi['words'])
df_all_posi_words = pd.Series(all_posi_words)

#ネガの声（words）をデータフレームに格納
drop_index4 = df.index[df['sentiment_net'] >Thresh_hold]
#条件にマッチしたIndexを削除
df_nega = df.drop(drop_index4)
all_nega_words=' '.join(df_nega['words'])
df_all_nega_words = pd.Series(all_nega_words)

print('ポジの声(Words）：')
print(df_all_posi_words)
print('ネガの声(Words）：')
print(df_all_nega_words)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/2e10ec9f-eb56-3d78-6ec1-734db6dc56be.png)
※ネガポジをSentment_netのどの値で区分するかは、上記のように任意に設定できます。
　

```Python:
#@title Word Cloud with Positive Data (TF-IDF)：🔲型 → #maskの#外すと🍩型に
tfidf_vec = vectorizer.fit_transform(df_all_posi_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}

#Word Cloudで画像生成（#max_words, width, heightは任意設定）
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      #mask=custom_mask,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=800,
                      height=500,
                      ).generate_from_frequencies(tfidf_dict)

#生成した画像の表示
plt.figure(figsize=(15,10))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
```

```Python:
#@title Word Cloud with Negative Data (TF-IDF)：🔲型 → #maskの#外すと🍩型に
tfidf_vec = vectorizer.fit_transform(df_all_nega_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}

#Word Cloudで画像生成（#max_words, width, heightは任意設定）
wordcloud = WordCloud(background_color='white',
                      max_words=125,
                      #mask=custom_mask,
                      font_path='/usr/share/fonts/truetype/fonts-japanese-gothic.ttf',
                      width=800,
                      height=500,
                      ).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/7f7f181b-1d7a-b803-8b68-cd398d6c7c14.png)
すべてのWord Cloudをお見せすることができませんが、これは感情分析でネガ判定された自由記述によるWord Cloudです。
調整や設定、取付けなどに関し、誤りにくく、簡単にでき、わかる等、改善が望まれていることが垣間見える内容となっています。
※満足度が低いデータのWord Cloudよりも、User訴えがよく見える結果となりました。今回私が扱ったデータの場合、感情分析をベースに自由記述を分析したほうがよさそうです。


##結果出力
```Python:
#@title データフレームをcsvに変換し、ローカルファイルに保存
from google.colab import files
filename =  'sentiment&cluster.csv'
df.to_csv(filename, encoding = 'utf-8-sig') 
files.download(filename)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1275001/33a73bae-b565-7915-5f51-abda68b45b66.png)
※分析結果を反映した sentiment&cluster.csv を出力します。
　

##最後に
**自由記述で分析したネガポジ傾向とアンケートの満足度傾向にまったく相関がなかった**というのは意外でした。
「満足度が高いUserの自由記述内容はポジティブ」、「満足度が低いUserの自由記述内容はネガティブ」という傾向はなく、
>- **自由記述内容はネガティブなのに満足度が高いUser がおられる**
- **自由記述内容はポジティブなのに満足度が低いUser がおられる**

というのは新鮮な発見でした。
満足度だけで一喜一憂せず、潜在不満・潜在期待がいりまじった自由記述から読み取れる訴えにしっかり耳を傾けねばならんなと、あらためて感じました。

また、今回はサクサク動作するライブラリに絞りましたので、上記の発見含め、個人的には満足です。
　
　
※以下、取説ほど充実していませんが、このライブラリの使用説明は「このライブラリの説明」を見てください。
　　
　
***
##このライブラリの説明
表形式で整理されたアンケートの自由記述の語彙分析、感情分析を行い、分類を行うライブラリです。分類した結果に基づき、複数の Word Cloudも描きます。
分析したい文書「csvファイル」を読込むだけで、以下の処理を自動で行います。

- **センテンスのグループ分け**：User毎の自由記述の語彙傾向でグループ分けを行います。[ ベクトル計算：TF-IDF⇒クラスタリング：k-means ]
- **感情分析**：User毎の自由記述の感情分析（ネガポジ分類）を行います。[ ライブラリ：asari ]
- **Word Cloud**：出現頻度が高い語を複数選び出し、その頻度に応じた大きさで図示します。文書に含まれる語がどれだけ重要かを示すTF-IDFというベクトル計算を行った結果によるWord Cloudも図示します。（満足度別、ネガポジ別に図示します）
- **結果出力**：語彙傾向によるグループ分け、感情分析の結果を反映したcsvを出力します。

**前準備**
1. **csvデータの表形式は以下としてください。**
- **カラム名は1列目を userID、2列目を comment（自由記述）、3列目を cs（満足度）としてください。3列目はなくても構いません。**
- [**注意**] csvデータは文字コードを「UTF-8」としてください。

|userID|comment|cs|
|---|---|---|
|U001  |総合的には満足してますが、○○が細かく調整できたらもっと使いやすいと思います。|4  |
|U002  |○○ボタンが押しにくいです。|2  |
|U003  |特にありません。|3  |
|U004  |既存品と取付位置が合わないことがつらいです。|1  |

**実行手順**
1. メニューバーの「ランタイム」から「すべてのセルを実行」をクリック。
2. ライブラリインストール完了後、[ファイル選択]ボタンをクリックし、分析したい文書（csvファイル）を指定する。

**<font color="DarkRed">注意：<font color="DarkRed">「ランタイム」⇒「すべてのセルを実行」でエラーとなる場合は、「ランタイム」⇒「ランタイムを再起動」してから、「すべてのセルを実行」してください。</font>**
　

**このライブラリの使い方について**

- テキスト分析は文書量が多いと目を通すだけでも大変です。このライブラリで文書全体把握につながる語彙の特徴やセンテンスのグルーピングができますので、初手として活用することで効率化できます。
- 満足度データがある場合、自由記述を満足度で区分することが多いが、自由記述から読み取れるネガポジと満足度は合わないことも多く、記述内容から心情を探る＝感情の切り口から見た方がよいといえます。
- Word Cloudで文書全体の訴えをながめた上、ライブラリが行ったグループ分け毎に個々のテキストに目を通すとよいと思います。
　

**その他**

- グループ分けの数（クラスター数）は任意に設定できます。
- 満足度を数値化している場合、高い/低いランクを任意に設定できます。
- Word Cloudは、🍩型に変更することができます。
- [**注意**] k-means はアルゴリズム上、実行ごとに結果が変わることがあります。


　
***
##参考サイト
https://zenn.dev/megane_otoko/articles/013_tf_idf_trial

https://boxcode.jp/python%e3%81%a7%e6%84%9f%e6%83%85%e5%88%86%e6%9e%90%ef%bc%88%e5%bd%a2%e6%85%8b%e7%b4%a0%e8%a7%a3%e6%9e%90%e6%ba%96%e5%82%99%e7%b7%a8%ef%bc%89-mecab

https://www.youtube.com/watch?v=cU2RmYaR44I

https://github.com/Hironsan/asari

https://hironsan.hatenablog.com/entry/japanese-sentiment-analyzer

https://note.com/natto_nebaneba/n/n48ed3daa1745

https://qiita.com/hima2b4/items/43e7c36fa969f85951f2

https://qiita.com/hima2b4/items/f6df2314e576550da3b4

https://qiita.com/hima2b4/items/1c8375623e59b94db9db