2019年11月4日月曜日

KH CoderでStack OverflowのDeveloper Surveyを可視化する

KH Coderはテキストデータを統計的に分析するためのフリーソフトウェアで、テキストデータを形態素解析して、テキスト中の単語の出現頻度を集計し、共起ネットワークなどの分析を行える。このブログではKH Coderで国会会議録を対応分析するなど何度か記事を書いている。

KH Coderでは基本的にはテキストデータの分析が想定されていると思うが、アンケートなどの回答も、場合によっては連結してテキストにしたらKH Coderで分析できそうな気がする。例えば、選択肢から複数選択して回答するアンケート結果で、回答のひとつを単語とみなせば、KH Coderで分析できそう。

この考えを試すのにちょうどいいデータがあった。日ごろよくお世話になっているStack Overflowが毎年アンケート調査をやっていて、結果を公開している(Developer Survey Results 2019)。そして、そのアンケート結果のデータをStack Overflow Annual Developer Surveyからダウンロードできる。

このアンケートの質問項目に、過去に経験のあるプログラミング言語やデータベースなどの技術を選択肢から複数選択するものがある。回答者をひとつの文単位とみなすと、技術は単語とみなせる。今回は、Pythonでこのアンケケート結果を加工して、KH Coderで分析してみる。


環境


KH Coder
Windows10(May 2019 Update 1903)
Windows版 KH Coder Version 3.Alpha.17b

Windows10のWSL(Ubuntu 18.04)。KH Coderで使用するためのデータの加工はPythonのPandasで行う。



Developer Survey 2019データ


Stack Overflow Annual Developer Surveyから2019年のデータ(developer_survey_2019.zip)をダウンロード。

解凍すると以下のファイルやディレクトリがある。アンケート結果のデータはsurvey_results_public.csv。質問項目はso_survey_2019.pdfで確認できる。
  • README_2019.txt
  • so_survey_2019.pdf       
  • survey_results_schema.csv
  • __MACOSX       
  • survey_results_public.csv
質問項目には、過去に経験のあるプログラミング言語やデータベース、フレームワークなど、開発で使われる技術に関する質問があって、今回はRespondent(ランダムに割り振られた回答者ID)と以下の6項目のデータを使う。この6項目は、それぞれ複数項目選択可能で、「;」区切りのデータになっている。また、選択肢として「Other(s):」があるが、今回はこれは除外する。

LanguageWorkedWith(プログラミング言語、スクリプト、マークアップ言語)
DatabaseWorkedWith(データベース)
PlatformWorkedWith(プラットフォーム)
WebFrameWorkedWith(ウェブフレームワーク)
MiscTechWorkedWith(その他の技術)
DevEnviron(開発環境)


Developer Survey 2019データの加工


KH Coderが扱えるのはテキストデータなので、Pythonでアンケート結果のcsvを加工する。使用する複数の回答を連結して、回答者(Respondent)ごとにひとつのテキストデータにする。それぞれの回答は「;」区切りなので、回答の連結も「;」で行う。その際に、「Other(s):」は除外する。

加工したデータはsurvey_results_tech.csvとして出力し、さらに、KH Coderで強制抽出する単語(技術)のリストをtech.txtとして出力する。

以下のコードをsurvey_results_public.csvと同じディレクトリで実行する。
import os
import numpy as np
import pandas as pd

def remove_others(x):
    tech_l = x.split(';')

    # 「Other(s):」を削除
    try:
        tech_l.remove('Other(s):')
    except ValueError:
        # 「Other(s):」が存在しない場合
        pass

    if len(tech_l) == 0:
        return np.NaN
    else:
        return ';'.join(tech_l)

def clean_df(df):
    # NaNが含まれる行を削除
    df.dropna(how='any', inplace=True)

    # 「Other(s):」を削除してNaNが含まれる行を削除
    return df.applymap(remove_others).dropna(how='any')

def to_df():
    # Respondent(回答者ID)をインデックスとしてcsvをDataFrameとして読み込む
    with open(os.getcwd() + '/survey_results_public.csv', mode='r') as f:
        # error_bad_lines=True: カラム数の不一致などのエラーはdropする
        # skipinitialspace=True: カンマのあとの空白を削除
        df = pd.read_csv(f, encoding='utf-8', index_col=['Respondent'], header=0, error_bad_lines=True, skipinitialspace=True,
            usecols=['Respondent','LanguageWorkedWith', 'DatabaseWorkedWith', 'PlatformWorkedWith',
            'WebFrameWorkedWith', 'MiscTechWorkedWith', 'DevEnviron'])

    print('*** DataFrame information ***')
    print(df.info())
    print()

    with pd.option_context('display.max_rows', None, 'display.max_columns', None):
        print('*** Numbers of unique data ***')
        # 列ごとのユニークなデータの数
        print(df.nunique())
        print()

    return df

def create_extraction_list(df):
    # 「;」で文字列を分割して複数列のDataFrameを作成
    expanded = df['tech'].str.split(';', expand=True)

    # DataFrame内の項目を2次元Numpy配列にして、さらに1次元に展開してユニークな値を取得
    # 'K'の場合、メモリ内の発生順序で要素を読み取り
    items_unique = pd.unique(expanded.values.ravel('K'))

    # str以外を削除(np.nanやNoneを削除)
    items_unique = [x for x in items_unique if isinstance(x, str)]

    # 「Other(s):」を削除
    try:
        items_unique.remove('Other(s):')
    except ValueError:
        # 「Other(s):」が存在しない場合
        pass

    # KH Coder用の強制抽出単語リスト作成
    with open('tech.txt', mode='w') as f:
        f.write('\n'.join(items_unique))

def main():
    # csvからデータを読み込んでDataFrameにする
    df = to_df()

    # データのクリーニング
    df = clean_df(df)

    # 「;」区切りの文字列にして半角スペースをアンダースコアに変換
    df['tech'] = df.apply(lambda row: ';'.join(row).replace(' ', '_'), axis=1)
    print(df['tech'].head())

    # 強制抽出語のリストをファイルに出力
    create_extraction_list(df)

    # KH Coderで読み込むcsv作成
    df.to_csv('survey_results_tech.csv', columns=['tech'])        

if __name__ == '__main__':
    main()

head survey_results_tech.csvの中身。


tech.txtの結果。



KH Coderでデータを読み込む



データの加工ができたら、KH Coderの[プロジェクト]>[新規]でsurvey_results_tech.csvを分析対象として選択し、分析対象とする列を「tech」とする。

次に強制抽出する語を指定する。Pythonで作成したtech.txtには強制抽出する語のリストがあるが、「C」を強制抽出すると「C++」や「C#」が抽出できなくなる。こういった語はコメントアウトしておく。さらに、「---cell---」「;」を追記する。最終的に以下のようなリストになった。


強制抽出のリストができたら、KH Coderの[前処理]>[語の取捨選択]>[強制抽出する語の指定]で「ファイルから読み込む」でtech.txtを指定する。さらに、「使用しない語の指定」にも「---cell---」「;」を追加しておく。

ここまでできたら、[前処理]>[前処理の実行]で前処理を実行する。

前処理が完了したら、まずは[ツール]>[抽出語]>[抽出リスト]で、回答項目の文字列がきちんと抽出できているか確認。


KH Coderで分析


回答文字列の抽出ができているようなので、KH Coderで分析してみる。まずは対応分析。「抽出語 X 文章」を選択し、「バブルプロット」にチェックをつけて実行。図の左側にはWindows関連の技術、下側にはPython関連の技術がまとまっている。

続いて共起ネットワークを作成する。こちらはデフォルト設定のまま。共起ネットワークでも、Windows関連の技術がまとまっている。


0 件のコメント:

コメントを投稿