本記事について
この記事では、Slackコミュニティで各メンバーの発言内容をWordcloudで可視化する手法について紹介します。
ソースコードはこちらにあります
あわせて読みたい:【自然言語処理】Slackコミュニティで今週盛り上がった話題を可視化してみた
目次
- 使い方と出力例
- Slackからメッセージを取得
- 前処理:テーブル作成/クリーニング/形態素解析/正規化/ストップワード除去
- 前処理:重要語句抽出(tf-idf)
- Wordcloudで可視化処理
- おまけ
※前処理については、今後別記事にまとめたいと思います
1. 使い方と出力例
1.1. 使い方
詳細は、READMEのGetting started を参照ください。
流れは、こんな感じです。
-
docker-compose up -d
で仮想環境構築 -
docker exec -it ds-py3 bash
でシェルに入る -
run_wordcloud_by_user.sh
を実行
1.2. 出力例
実際に出力した例です。それぞれ異なるメンバーの発言をWordcloudにしています。
2. Slackからメッセージを取得
こちらの記事を参照してください。
【自然言語処理】Slackコミュニティで今週盛り上がった話題を可視化してみた - 2. Slackからメッセージを取得
3. 前処理:テーブル作成/クリーニング/形態素解析/正規化/ストップワード除去
こちらの記事と同一の内容ですので、省略します。
詳細は、リンク先をご参照ください。
4. 前処理:重要語句抽出(tf-idf)
4.1. tf-idfとは?
tf-idfとは、ある文書に存在する各単語について、
「その文書のコンテクストを理解する上で重要か?」という観点でスコアリングする為の指標だと言えます。
詳細は、こちらの記事をご参照ください。
4.2. tf-idfによる単語のスコアリング処理実装
4.2.1. 何を文書・全文書とするか?
今回の目的は、あるメンバーの発言の特徴を見ることです。
ゆえに、 Slackコミュニティ内の全投稿に対して、一人のメンバーがどのような特徴をもっているか が分かるようにすべきと考えました。
したがって、
- 全文書 :これまでの全チャンネル・全ユーザーの全ての投稿
- 1文書 :あるメンバーの全ての投稿
としてtf-idfを計算しました。
4.2.2. 実装
簡単に処理の流れを書きます。
- メッセージを発言したメンバーごとにグルーピング
- 1グループのメッセージ群を1文書として、tf-idfを計算
- tf-idfのスコアが閾値以上の単語を抽出する(辞書として出力)
import pandas as pd
import json
from datetime import datetime, date, timedelta, timezone
from pathlib import Path
from sklearn.feature_extraction.text import TfidfVectorizer
JST = timezone(timedelta(hours=+9), 'JST')
# メッセージをユーザーごとにグルーピング
def group_msgs_by_user(df_msgs: pd.DataFrame) -> dict:
ser_uid = df_msgs.uid
ser_wktmsg = df_msgs.wakati_msg
# 重複なしのuid一覧を取得
ser_uid_unique = df_msgs.drop_duplicates(subset='uid').uid
# 重複なしuidごとにグルーピング
dict_msgs_by_user = {}
for uid in ser_uid_unique:
# 当該uidに該当する全wktmsgを取得
extracted = df_msgs.query('uid == @uid')
# key, value を出力用の辞書に追加
dict_msgs_by_user[uid] = ' '.join(extracted.wakati_msg.dropna().values.tolist())
return dict_msgs_by_user
# tf-idfスコアを参照しながら重要単語を抽出し辞書として返す
def extract_important_word_by_key(feature_names: list, bow_df: pd.DataFrame, uids: list) -> dict:
# > 行ごとにみていき、重要単語を抽出する(tfidf上位X個の単語)
dict_important_words_by_user = {}
for uid, (i, scores) in zip(uids, bow_df.iterrows()):
# 当該ユーザーの単語・tfidfスコアのテーブルを作る
words_score_tbl = pd.DataFrame()
words_score_tbl['scores'] = scores
words_score_tbl['words'] = feature_names
# tfidfスコアで降順ソートする
words_score_tbl = words_score_tbl.sort_values('scores', ascending=False)
words_score_tbl = words_score_tbl.reset_index()
# extract : tf-idf score > 0.001
important_words = words_score_tbl.query('scores > 0.001')
# 当該ユーザの辞書作成 'uid0': {'w0': 0.9, 'w1': 0.87}
d = {}
for i, row in important_words.iterrows():
d[row.words] = row.scores
# 当該ユーザの辞書にワードが少なくとも一つ以上ある場合のみテーブルに追加
if len(d.keys()) > 0:
dict_important_words_by_user[uid] = d
return dict_important_words_by_user
# ユーザー単位で重要単語を抽出する
def extraction_by_user(input_root: str, output_root: str) -> dict:
# ---------------------------------------------
# 1. load messages (processed)
# ---------------------------------------------
msg_fpath = input_root + '/' + 'messages_cleaned_wakati_norm_rmsw.csv'
print('load: {0}'.format(msg_fpath))
df_msgs = pd.read_csv(msg_fpath)
# ---------------------------------------------
# 2. group messages by user
# ---------------------------------------------
print('group messages by user and save it.')
msgs_grouped_by_user = group_msgs_by_user(df_msgs)
msg_grouped_fpath = input_root + '/' + 'messages_grouped_by_user.json'
with open(msg_grouped_fpath, 'w', encoding='utf-8') as f:
json.dump(msgs_grouped_by_user, f, ensure_ascii=False, indent=4)
# ---------------------------------------------
# 4. 全文書を対象にtf-idf計算
# ---------------------------------------------
print('tfidf vectorizing ...')
# > 全文書にある単語がカラムで、文書数(=user)が行となる行列が作られる。各要素にはtf-idf値がある
tfidf_vectorizer = TfidfVectorizer(token_pattern=u'(?u)\\b\\w+\\b')
bow_vec = tfidf_vectorizer.fit_transform(msgs_grouped_by_user.values())
bow_array = bow_vec.toarray()
bow_df = pd.DataFrame(bow_array,
index=msgs_grouped_by_user.keys(),
columns=tfidf_vectorizer.get_feature_names())
# ---------------------------------------------
# 5. tf-idfに基づいて重要単語を抽出する
# ---------------------------------------------
print('extract important words ...')
d_word_score_by_uid = extract_important_word_by_key(tfidf_vectorizer.get_feature_names(), bow_df, msgs_grouped_by_user.keys())
# ---------------------------------------------
# 6. uid => uname 変換
# ---------------------------------------------
print('ユーザーごとの重要単語群のキーをuidからunameに変換 ...')
user_tbl = pd.read_csv('../../data/020_intermediate/users.csv')
d_word_score_by_uname = {}
for uid, val in d_word_score_by_uid.items():
# 発言者のuidでunameを検索(アクティブユーザーでない場合存在しない可能性あり)
target = user_tbl.query('uid == @uid')
if target.shape[0] != 0:
uname = target.iloc[0]['uname']
else:
continue
print('uname: ', uname, 'type of uname: ', type(uname))
d_word_score_by_uname[uname] = val
return d_word_score_by_uname
4.2.3. 出力した辞書
次章で説明するWordcloudでは、{ "word": score }
という辞書を入力することで、スコアに応じて単語の表示サイズを変化させるWordcloudを生成することが出来ます。
【自然言語処理】Slackコミュニティで今週盛り上がった話題を可視化してみた
という記事では、「期間」でグルーピングした場合のWordcloudを出力しました。
本記事では、「メンバー」でグルーピングした訳ですが、 出力する辞書は同じ形式 にしています。
そうすることで、 「tf-idfのスコアリング処理」以外は全て同じ処理 で実現することができます。 DRY に行きたいですよね。
今回実際に出力した辞書はこちらです。
(ユーザー名は伏せています)
{
"USER_001": {
"参加": 0.1608918987478819,
"環境": 0.15024077008089046,
"良品": 0.1347222699467748,
"node": 0.1347222699467748,
"説明": 0.13378417526975775,
"サイバーセキュリティ": 0.12422689899152742,
"r": 0.12354794954617476,
"選択": 0.11973696610170319,
"置換": 0.11678031479185731,
"最終": 0.11632792524420342,
"講座": 0.11467215023122095,
"公開": 0.11324407267324783,
"解析": 0.11324407267324783,
"期限": 0.11100429535028021,
"書き方": 0.10628494383660991,
"ディープラーニング": 0.10229478898619786,
:
},
"USER_002": {
"データ": 0.170245452132736,
"参加": 0.15825283334154341,
"講座": 0.13785592895847276,
"お願い": 0.1265412327351908,
"募集": 0.12204781908784276,
"記事": 0.1197561921672133,
"環境": 0.11083230914864184,
"食品": 0.1091835225326696,
"共有": 0.10371152197590257,
"コロナ": 0.10081254351124691,
"輪読": 0.10025885742434383,
"企画": 0.09899869065055528,
"開発": 0.09571338092513401,
"目標": 0.09253887576557392,
"仕事": 0.09094257214685446,
"プロジェクト": 0.08910924912513929,
"情報": 0.08772258523428605,
"言語": 0.08636683271048684,
"channel": 0.08295159680178281,
"リリース": 0.0818876418995022,
"youtube": 0.07956948308804826,
"チーム": 0.07956948308804826,
"基本": 0.07444492553072463,
:
},
:
}
5. Wordcloudで可視化処理
こちらの記事をご参照ください。
【自然言語処理】Slackコミュニティで今週盛り上がった話題を可視化してみた - 9. Wordcloudで可視化処理
6. おまけ
特に参考にした記事
その他の参考資料(大量)は、こちら にまとめています。
宣伝
今回は、Data Learning Guild というSlackコミュニティのデータを利用させていただいています。
Data Learning Guild はデータ分析人材が集まるオンラインコミュニティです。気になる方は、こちらをチェックしてみてください。