LoginSignup
26
24

More than 3 years have passed since last update.

【自然言語処理】Slackコミュニティにおける各メンバーの発言を可視化してみた

Last updated at Posted at 2020-05-10

本記事について

この記事では、Slackコミュニティで各メンバーの発言内容をWordcloudで可視化する手法について紹介します。

ソースコードはこちらにあります :octocat:

あわせて読みたい:【自然言語処理】Slackコミュニティで今週盛り上がった話題を可視化してみた

目次

  1. 使い方と出力例
  2. Slackからメッセージを取得
  3. 前処理:テーブル作成/クリーニング/形態素解析/正規化/ストップワード除去
  4. 前処理:重要語句抽出(tf-idf)
  5. Wordcloudで可視化処理
  6. おまけ

※前処理については、今後別記事にまとめたいと思います

1. 使い方と出力例

1.1. 使い方

詳細は、READMEのGetting started を参照ください。
流れは、こんな感じです。

  1. docker-compose up -d で仮想環境構築
  2. docker exec -it ds-py3 bash でシェルに入る
  3. run_wordcloud_by_user.sh を実行

1.2. 出力例

実際に出力した例です。それぞれ異なるメンバーの発言をWordcloudにしています。

anim_.gif

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. メッセージを発言したメンバーごとにグルーピング
  2. 1グループのメッセージ群を1文書として、tf-idfを計算
  3. tf-idfのスコアが閾値以上の単語を抽出する(辞書として出力)
important_word_extraction.py
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 に行きたいですよね。

今回実際に出力した辞書はこちらです。
(ユーザー名は伏せています)

important_word_tfidf_by_user.json
{
    "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. おまけ

特に参考にした記事

その他の参考資料(大量)は、こちら :octocat: にまとめています。

宣伝

今回は、Data Learning Guild というSlackコミュニティのデータを利用させていただいています。
Data Learning Guild はデータ分析人材が集まるオンラインコミュニティです。気になる方は、こちらをチェックしてみてください。

データラーニングギルド公式ホームページ

データラーニングギルド 2019 Advent Calendar

26
24
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
24