【保存版】ビジネスで使える13のデータ分析手法を分かりやすく解説 を眺めていて、コレスポンデンス分析という分析手法があることを知りました。

Google Analytics から取得したデータを元にコレスポンデンス分析をできないか調べたところ、R での参考記事を見つけたので、python で同じようなことができるか試してみました。
結果を以下に記載します。


参考


分析して得る情報

記事のカテゴリ、記事を読んだ人の興味を持っているジャンルの傾向の情報


python で コレスポンデンス分析を行うライブラリ

python でコレスポンデンス分析を実行できるライブラリには以下があります。
今回はワインの味を分析してリア充達のクリスマスディナーを台無しにしよう - Qiita を参考に、mca を使ってみます。


ライブラリのインストール

必要なライブラリのインストールをします。   

mca のインストール

コレスポンデンス分析を行うesafak/mca: Multiple correspondence analysis というライブラリをインストールします。

  • pip install

    python3 -m pip install --user mca
    

  • Output

    Collecting mca
      Using cached mca-1.0.2.tar.gz
    Requirement already satisfied: scipy in /Users/xxxxxx/Library/Python/3.6/lib/python/site-packages (from mca)
    Requirement already satisfied: numpy in /Users/xxxxxx/Library/Python/3.6/lib/python/site-packages (from mca)
    Requirement already satisfied: pandas in /Users/xxxxxx/Library/Python/3.6/lib/python/site-packages (from mca)
    Requirement already satisfied: pytz>=2011k in /Users/xxxxxx/Library/Python/3.6/lib/python/site-packages (from pandas->mca)
    Requirement already satisfied: python-dateutil>=2 in /usr/local/lib/python3.6/site-packages (from pandas->mca)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.6/site-packages (from python-dateutil>=2->pandas->mca)
    Building wheels for collected packages: mca
      Running setup.py bdist_wheel for mca ... done
      Stored in directory: /Users/xxxxxx/Library/Caches/pip/wheels/85/0d/24/fe459ababfb49e7669f16d9607247c055d25537c0f5c7c0d93
    Successfully built mca
    Installing collected packages: mca
    Successfully installed mca-1.0.2
    

その他のライブラリのインストール

gspreadoauth2client をインストールします。

python3 -m pip install gspread --user
python3 -m pip install oauth2client --user
python3 -m pip install df2gspread --user

データの入手

Google Analytics Spreadsheet Add-on  |  Analytics Implementation Guides and Solutions  |  Google Developers で Google Analytics からデータを取得します。
以下のMetrics と Dimensions を指定します。

Metrics

  • ga:pageviews

Dimensions

  • ga:dimension6 (ブログ記事のカテゴリ)
  • ga:interestAffinityCategory
  • ga:pagePath

手順

以下の流れで、可視化まで行います。

  1. データの取得
  2. データ加工
  3. mca の呼び出し
  4. 可視化

1. データの取得

Google スプレッドシートからデータを、Pandas のデータフレームとして取得します。

from oauth2client.service_account import ServiceAccountCredentials
def download_as_df(sheet_id, wks_name):
    """
    Google Spread Sheet からデータを取得、pandas data frame として返す。
    """
    from df2gspread import gspread2df as g2d
    # key_file 以下の指定方法だと、notebook と同じディレクトリにあるキーファイルを取得しています。
    key_file = "spreadsheet_api_key.json"
    scope = ['https://spreadsheets.google.com/feeds']
    credentials = ServiceAccountCredentials.from_json_keyfile_name(key_file, scope)    
    df = g2d.download(sheet_id, wks_name=wks_name, col_names=True, row_names=False, credentials=credentials, start_cell = 'A15')
    return df
# ファイルのダウンロード
df = download_as_df("1ruwQJIt35zFDXrcFlibwqJcMn1QJwtoJsTBQw7L-1WQ","ユーザの好みと、ページカテゴリの関係")

# データ出力
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
ga:dimension6 ga:interestAffinityCategory ga:pagePath ga:pageviews
0 Elasticsearch Lifestyles & Hobbies/Business Professionals /blog/Implement-Paging-with-Elasticsearch-Java... 29
1 Elasticsearch Lifestyles & Hobbies/Green Living Enthusiasts /blog/Implement-Paging-with-Elasticsearch-Java... 58
2 Elasticsearch Lifestyles & Hobbies/Shutterbugs /blog/Implement-Paging-with-Elasticsearch-Java... 24
... ... ... ... ...
569 python Technology/Technophiles /blog/pycharm-terminal-からpythonスクリプトを実行できるようにする/ 98

599 rows × 4 columns

2.データ加工

データ加工として、以下を実施します。
* Affinity Category の 日本語化 と大カテゴリの切り出し。 * 大カテゴリを元にデータを再集計する。
* クロス集計表に変換する。

# Affinity Category  の日本語化をするファンクションの定義
import requests

translation_dictionary = {}

def create_dictionary():
    r = requests.get("https://gist.githubusercontent.com/kemsakurai/6c287eab5aa45415bf5c50df1e61a47f/raw/3ea1cf361a8914ad20c70ac46e8473e2590597e9/AffinityCategoryTranslation.tsv")
    lines = r.text.split("\n")
    index = 0
    for line in lines:
        index = index + 1
        if index == 1:
            continue
        elems = line.split("\t")
        translation_dictionary.update({elems[0]: elems[1]})

def translate_affinity_category(english_string):
    results = []
    for elem in english_string.split("/"):
        results.append(translation_dictionary.get(elem, "undefined"))
    return '/'.join(results)
# 辞書の初期化
create_dictionary()

# 大カテゴリのみを切り出し
df["ga:interestAffinityCategory"] = [i.split("/")[0] for i in df["ga:interestAffinityCategory"]] 
# 切り出したカテゴリを日本語に変換
df["ga:interestAffinityCategory_ja"] =  [translate_affinity_category(i) for i in df["ga:interestAffinityCategory"]] 
# 後続の集計処理がうまく計算できないので、整数に変換する
df["ga:pageviews"] = df["ga:pageviews"].astype(int)
# 結果の出力
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
ga:dimension6 ga:interestAffinityCategory ga:pagePath ga:pageviews ga:interestAffinityCategory_ja
0 Elasticsearch Lifestyles & Hobbies /blog/Implement-Paging-with-Elasticsearch-Java... 29 ライフスタイル&趣味
1 Elasticsearch Lifestyles & Hobbies /blog/Implement-Paging-with-Elasticsearch-Java... 58 ライフスタイル&趣味
2 Elasticsearch Lifestyles & Hobbies /blog/Implement-Paging-with-Elasticsearch-Java... 24 ライフスタイル&趣味
3 Elasticsearch Media & Entertainment /blog/Implement-Paging-with-Elasticsearch-Java... 51 メディア&エンターテイメント
4 Elasticsearch Media & Entertainment /blog/Implement-Paging-with-Elasticsearch-Java... 46 メディア&エンターテイメント
5 Elasticsearch Media & Entertainment /blog/elasticsearch-の-kuromoji-plugin-が削除されてin... 13 メディア&エンターテイメント
... ... ... ... ... ...
598 データ分析 Technology /blog/mac-os-siera-に-superset-をインストールする/ 14 技術

599 rows × 5 columns

# 不要なカラムを削除
del df["ga:interestAffinityCategory"]
del df["ga:pagePath"]
#  再集計
df = df.groupby(['ga:dimension6','ga:interestAffinityCategory_ja'])['ga:pageviews'].sum().reset_index()
#  ソート
df = df.sort_values(by="ga:pageviews",ascending=False) 
# 結果の出力
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
ga:dimension6 ga:interestAffinityCategory_ja ga:pageviews
200 python メディア&エンターテイメント 1316
... ... ... ...
177 msites;資格取得 ホーム&ガーデン 15

219 rows × 3 columns

# クロス集計
df = df.pivot_table(values='ga:pageviews', index='ga:dimension6', columns='ga:interestAffinityCategory_ja', aggfunc = 'sum')
# クロス集計結果を整数に変換
df = df.fillna(0).astype(int)
# 結果の出力
df
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
ga:interestAffinityCategory_ja スポーツ&フィットネス ニュース&政治 フード&ダイニング ホーム&ガーデン メディア&エンターテイメント ライフスタイル&趣味 技術 旅行 自動車&輸送 買い物好き 銀行&金融
ga:dimension6
Elasticsearch 31 0 0 0 110 111 126 11 0 73 0
Elasticsearch;java 26 16 0 18 42 70 48 15 0 28 0
Google Apps Script 17 61 15 19 112 80 63 17 0 34 14
Google Spread Sheet 121 236 83 83 507 418 286 75 11 211 50
Jupyter Notebook 14 34 0 0 18 32 33 0 0 21 0
Lombok;findbugs;java 0 0 0 0 11 21 24 0 0 13 0
MongoDB 0 12 0 0 0 14 12 0 0 13 0
SEO;python;データ分析 24 30 0 22 22 97 58 23 0 29 25
SonarQube 16 0 0 0 16 47 35 0 0 21 0
SonarQube;python 11 25 0 17 25 50 37 11 0 21 0
Web API;python 28 76 16 29 157 133 100 22 0 56 18
amp 0 0 0 0 0 0 13 0 0 12 0
django 76 194 37 45 408 306 288 37 0 164 35
django;mezzanine 0 0 0 17 0 15 40 0 0 21 0
django;mezzanine;python 13 0 0 0 0 44 32 0 0 16 0
django;postgresql 0 14 0 0 13 43 30 0 0 16 0
eclipselink;java 0 0 0 11 0 24 17 0 0 15 0
findbugs 0 0 0 0 0 13 17 0 0 17 0
gradle 22 54 0 23 89 94 68 14 0 36 0
jackson;java 33 93 16 40 141 159 126 22 0 78 15
java 160 270 85 86 644 543 422 63 0 260 54
java;pmd 26 71 0 0 88 92 86 0 0 43 0
java;rest;wicket 0 0 0 0 0 0 36 0 0 21 0
java;wicket 0 0 0 0 48 41 42 0 0 33 0
javascript 0 22 0 18 118 73 63 0 0 33 0
linux;python 0 0 0 0 17 20 23 0 0 23 0
memcached 54 111 26 33 169 174 135 27 0 74 31
mezzanine;python 0 0 0 0 0 43 42 0 0 32 0
msites;資格取得 18 0 0 15 30 48 43 13 0 25 0
pmd 0 0 0 0 0 21 35 0 0 17 0
postgresql 31 65 16 26 113 117 94 18 0 49 16
python 250 565 110 194 1316 918 773 129 13 476 71
python;データ分析 26 0 0 0 45 71 53 0 0 29 0
spring-boot 0 0 0 0 15 56 45 0 0 24 0
wicket 0 0 0 0 0 0 13 0 0 0 0
データ分析 0 0 0 0 0 14 14 0 0 0 0

3.mca の呼び出し

クロス集計データの準備ができたので、mca を呼び出します。

# コレスポンデンス分析のライブラリ
import mca
# コレスポンデンス分析
ncols = df.shape[1]
# Benzécri? 補正するかどうか、データ構造の問題か、True だとエラーとなったため、False で設定
mca_ben = mca.MCA(df, ncols=ncols, benzecri=False)
mca_ben.fs_r(N=2)
array([[  4.04399401e-01,  -1.52979611e-01],
       [  1.32693892e-01,   2.68842331e-01],
       [ -2.23112167e-01,   3.42928950e-02],
       [ -1.59749683e-01,   2.37024719e-02],
       [  1.30182305e-01,  -5.47186735e-02],
       [  6.84718573e-01,  -1.58233299e-01],
       [  4.44952860e-01,   4.10821376e-03],
       [  2.85366767e-02,   6.05926046e-01],
       [  5.39503680e-01,   3.58668344e-02],
       [  8.45671201e-02,   3.06657490e-01],
       [ -1.22543395e-01,   4.62416906e-02],
       [  1.23966422e+00,  -2.25021302e-01],
       [ -5.67431872e-02,  -6.78476641e-02],
       [  7.72658336e-01,   4.27289369e-01],
       [  7.21808152e-01,   2.17287190e-01],
       [  4.30533385e-01,  -4.64137005e-02],
       [  6.54749307e-01,   4.96416905e-01],
       [  1.02923931e+00,  -3.24243400e-02],
       [ -1.24779452e-02,   5.91214264e-02],
       [ -2.55507110e-02,   1.04271517e-01],
       [ -7.17114562e-02,  -2.35859666e-02],
       [  8.42241200e-02,  -1.78772790e-01],
       [  1.24523117e+00,  -2.02165463e-01],
       [  4.49509345e-01,  -3.42900571e-01],
       [  5.81775959e-02,  -2.59105041e-01],
       [  6.35473732e-01,  -2.65732128e-01],
       [ -1.23921506e-01,   1.17432604e-01],
       [  9.62526283e-01,   4.09888658e-02],
       [  2.68648888e-01,   3.02809495e-01],
       [  1.02700453e+00,   7.05955985e-04],
       [ -7.98232747e-02,   1.09810136e-01],
       [ -1.03646230e-01,  -6.40915368e-02],
       [  3.98004987e-01,  -7.22225097e-02],
       [  7.19449170e-01,  -3.80962202e-02],
       [  1.26361260e+00,  -1.26698070e-01],
       [  8.72559804e-01,   1.77653717e-01]])
mca_ben.fs_c(N=2)
array([[-0.09804034,  0.11634891],
       [-0.30316912, -0.00814008],
       [-0.43927978, -0.03347052],
       [-0.16271903,  0.36136428],
       [-0.19793054, -0.17165508],
       [ 0.11685867,  0.06648366],
       [ 0.30667071, -0.01747563],
       [-0.26910795,  0.33520229],
       [-0.53301903, -0.1729311 ],
       [ 0.29456212, -0.0457295 ],
       [-0.39228832,  0.36158167]])
import pandas as pd
# 表頭の座標を書き出す
result_row = pd.DataFrame(mca_ben.fs_r(N=2))
result_row.index = list(df.index)
result_row
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1
Elasticsearch 0.404399 -0.152980
Elasticsearch;java 0.132694 0.268842
Google Apps Script -0.223112 0.034293
Google Spread Sheet -0.159750 0.023702
Jupyter Notebook 0.130182 -0.054719
Lombok;findbugs;java 0.684719 -0.158233
MongoDB 0.444953 0.004108
SEO;python;データ分析 0.028537 0.605926
SonarQube 0.539504 0.035867
SonarQube;python 0.084567 0.306657
Web API;python -0.122543 0.046242
amp 1.239664 -0.225021
django -0.056743 -0.067848
django;mezzanine 0.772658 0.427289
django;mezzanine;python 0.721808 0.217287
django;postgresql 0.430533 -0.046414
eclipselink;java 0.654749 0.496417
findbugs 1.029239 -0.032424
gradle -0.012478 0.059121
jackson;java -0.025551 0.104272
java -0.071711 -0.023586
java;pmd 0.084224 -0.178773
java;rest;wicket 1.245231 -0.202165
java;wicket 0.449509 -0.342901
javascript 0.058178 -0.259105
linux;python 0.635474 -0.265732
memcached -0.123922 0.117433
mezzanine;python 0.962526 0.040989
msites;資格取得 0.268649 0.302809
pmd 1.027005 0.000706
postgresql -0.079823 0.109810
python -0.103646 -0.064092
python;データ分析 0.398005 -0.072223
spring-boot 0.719449 -0.038096
wicket 1.263613 -0.126698
データ分析 0.872560 0.177654
# 表側の座標を書き出す
result_col = pd.DataFrame(mca_ben.fs_c(N=2))
result_col.index = list(df.columns)
result_col
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0 1
スポーツ&フィットネス -0.098040 0.116349
ニュース&政治 -0.303169 -0.008140
フード&ダイニング -0.439280 -0.033471
ホーム&ガーデン -0.162719 0.361364
メディア&エンターテイメント -0.197931 -0.171655
ライフスタイル&趣味 0.116859 0.066484
技術 0.306671 -0.017476
旅行 -0.269108 0.335202
自動車&輸送 -0.533019 -0.172931
買い物好き 0.294562 -0.045729
銀行&金融 -0.392288 0.361582

4.可視化

matplotlib で グラフを描画します。

# 作図用ライブラリ
import matplotlib.pyplot as plt
import matplotlib
%matplotlib inline

plt.rcParams['font.family'] = 'IPAPGothic' #全体のフォントを設定
plt.rcParams["figure.figsize"] = [20, 12] # グラフのサイズを指定
plt.rcParams['font.size'] = 12 #フォントサイズを設定 default : 12
plt.rcParams['xtick.labelsize'] = 10 # 横軸のフォントサイズ
plt.rcParams['ytick.labelsize'] = 10 # 縦軸のフォントサイズ
matplotlib.font_manager._rebuild()

import random as rnd

# 図の設定(任意)
plt.figure(figsize=(10,10))
plt.rcParams["font.size"] = 10
<matplotlib.figure.Figure at 0x1089c03c8>
# 表頭をプロット
plt.scatter(result_col[0], result_col[1], s=100, marker="o")

# ラベル付け
cnt = 0
for label in list(result_col.index):
    r = rnd.random() * 0.1
    plt.text(result_col.iloc[cnt, 0]+r, result_col.iloc[cnt, 1]+r, label)
    plt.plot([result_col.iloc[cnt, 0]+r, result_col.iloc[cnt, 0]], [result_col.iloc[cnt, 1]+r, result_col.iloc[cnt, 1]])
    cnt += 1

表頭

# 表側をプロット
plt.scatter(result_row[0], result_row[1], s=100, marker="o")
# ラベル付け
cnt = 0
for label in list(result_row.index):
    r = rnd.random() * 0.1
    plt.text(result_row.iloc[cnt, 0]+r, result_row.iloc[cnt, 1]+r, label)
    plt.plot([result_row.iloc[cnt, 0]+r, result_row.iloc[cnt, 0]], [result_row.iloc[cnt, 1]+r, result_row.iloc[cnt, 1]])
    cnt += 1

表側

結果の分析

点と点の距離が近いものは、実際に似通った傾向が出ているデータです。   
どんな傾向が出ているかをグラフ上に書き込みました。

表頭のグラフ

表頭

上記、丸をつけた箇所の説明です。

  • 緑色
    ページの種類に因らず、PageView数が多いグループになります。

  • 赤色
    PageView数が少ないグループになります。 自動車&輸送系のカテゴリに興味がある人は、ページのアクセスが偏っていました。

  • 黄色
    PageView数は少ない。python 関係のページにアクセス数が多いグループのようでした。

表側のグラフ

表側

上記丸をつけた箇所の説明です。

  • 赤丸
    PageViewが多く、ユーザーのアフィニティカテゴリに偏りが少なかったグループになります。

  • 緑色
    PageViewはそこそこで、ユーザーのアフィニティカテゴリに偏りがあったグループになります。

  • 紫色
    PageViewが少ない、ユーザーのアフィニティカテゴリに偏りがあったグループになります。
    アフィニティカテゴリの偏りは、緑色のグループとは異なります。


まとめ

Google Analytics のデータを python でコレスポンデンス分析してみました。
以下まとめます。

  • mca を使うことで、python での コレスポンデンス分析は実施可能だった。
  • カスタムディメンションを予め送信しておくことで、独自指標での分析ができる。
  • 図の解釈を人間が行わないといけないので、ラベルとして実データを表示できるインタラクティブなグラフのほうが分析しやすいかもしれない。

以上です。

コメント