【保存版】ビジネスで使える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

ga:dimension6ga:interestAffinityCategoryga:pagePathga:pageviews
0ElasticsearchLifestyles & Hobbies/Business Professionals/blog/Implement-Paging-with-Elasticsearch-Java...29
1ElasticsearchLifestyles & Hobbies/Green Living Enthusiasts/blog/Implement-Paging-with-Elasticsearch-Java...58
2ElasticsearchLifestyles & Hobbies/Shutterbugs/blog/Implement-Paging-with-Elasticsearch-Java...24
...............
569pythonTechnology/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

ga:dimension6ga:interestAffinityCategoryga:pagePathga:pageviewsga:interestAffinityCategory_ja
0ElasticsearchLifestyles & Hobbies/blog/Implement-Paging-with-Elasticsearch-Java...29ライフスタイル&趣味
1ElasticsearchLifestyles & Hobbies/blog/Implement-Paging-with-Elasticsearch-Java...58ライフスタイル&趣味
2ElasticsearchLifestyles & Hobbies/blog/Implement-Paging-with-Elasticsearch-Java...24ライフスタイル&趣味
3ElasticsearchMedia & Entertainment/blog/Implement-Paging-with-Elasticsearch-Java...51メディア&エンターテイメント
4ElasticsearchMedia & Entertainment/blog/Implement-Paging-with-Elasticsearch-Java...46メディア&エンターテイメント
5ElasticsearchMedia & 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

ga:dimension6ga:interestAffinityCategory_jaga:pageviews
200pythonメディア&エンターテイメント1316
............
177msites;資格取得ホーム&ガーデン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

ga:interestAffinityCategory_jaスポーツ&フィットネスニュース&政治フード&ダイニングホーム&ガーデンメディア&エンターテイメントライフスタイル&趣味技術旅行自動車&輸送買い物好き銀行&金融
ga:dimension6
Elasticsearch31000110111126110730
Elasticsearch;java2616018427048150280
Google Apps Script1761151911280631703414
Google Spread Sheet1212368383507418286751121150
Jupyter Notebook14340018323300210
Lombok;findbugs;java000011212400130
MongoDB012000141200130
SEO;python;データ分析24300222297582302925
SonarQube1600016473500210
SonarQube;python1125017255037110210
Web API;python287616291571331002205618
amp0000001300120
django76194374540830628837016435
django;mezzanine000170154000210
django;mezzanine;python130000443200160
django;postgresql0140013433000160
eclipselink;java000110241700150
findbugs00000131700170
gradle2254023899468140360
jackson;java339316401411591262207815
java160270858664454342263026054
java;pmd26710088928600430
java;rest;wicket0000003600210
java;wicket000048414200330
javascript022018118736300330
linux;python000017202300230
memcached5411126331691741352707431
mezzanine;python00000434200320
msites;資格取得180015304843130250
pmd00000213500170
postgresql31651626113117941804916
python25056511019413169187731291347671
python;データ分析2600045715300290
spring-boot000015564500240
wicket000000130000
データ分析0000014140000

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

01
Elasticsearch0.404399-0.152980
Elasticsearch;java0.1326940.268842
Google Apps Script-0.2231120.034293
Google Spread Sheet-0.1597500.023702
Jupyter Notebook0.130182-0.054719
Lombok;findbugs;java0.684719-0.158233
MongoDB0.4449530.004108
SEO;python;データ分析0.0285370.605926
SonarQube0.5395040.035867
SonarQube;python0.0845670.306657
Web API;python-0.1225430.046242
amp1.239664-0.225021
django-0.056743-0.067848
django;mezzanine0.7726580.427289
django;mezzanine;python0.7218080.217287
django;postgresql0.430533-0.046414
eclipselink;java0.6547490.496417
findbugs1.029239-0.032424
gradle-0.0124780.059121
jackson;java-0.0255510.104272
java-0.071711-0.023586
java;pmd0.084224-0.178773
java;rest;wicket1.245231-0.202165
java;wicket0.449509-0.342901
javascript0.058178-0.259105
linux;python0.635474-0.265732
memcached-0.1239220.117433
mezzanine;python0.9625260.040989
msites;資格取得0.2686490.302809
pmd1.0270050.000706
postgresql-0.0798230.109810
python-0.103646-0.064092
python;データ分析0.398005-0.072223
spring-boot0.719449-0.038096
wicket1.263613-0.126698
データ分析0.8725600.177654

# 表側の座標を書き出す
result_col = pd.DataFrame(mca_ben.fs_c(N=2))
result_col.index = list(df.columns)
result_col

01
スポーツ&フィットネス-0.0980400.116349
ニュース&政治-0.303169-0.008140
フード&ダイニング-0.439280-0.033471
ホーム&ガーデン-0.1627190.361364
メディア&エンターテイメント-0.197931-0.171655
ライフスタイル&趣味0.1168590.066484
技術0.306671-0.017476
旅行-0.2691080.335202
自動車&輸送-0.533019-0.172931
買い物好き0.294562-0.045729
銀行&金融-0.3922880.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 での コレスポンデンス分析は実施可能だった。
  • カスタムディメンションを予め送信しておくことで、独自指標での分析ができる。
  • 図の解釈を人間が行わないといけないので、ラベルとして実データを表示できるインタラクティブなグラフのほうが分析しやすいかもしれない。

以上です。

コメント