過去に、Googleアナリティクスで読了率を知りたい-Googleタグマネージャでスクロール測定する方法 | CroJa(クロジャ) - Web施策に役立つ情報配信サイト設定を行い、Google Analytics でページの読了率が取得できるようにしました。
データはそれなりの期間溜まってきたのですが、特に分析、活用をしたことがなかったので、Pandas で Google Analytics から読了率を取得し、分析してみました。 実施したことをします。


前提

OS、PythonのVersion必要なライブラリの情報を記載します。

  • OS

!sw_vers

ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G87
  • Python の Version

!python -V

Python 3.7.2

必要なライブラリのインストール

!pip install --upgrade pip
!pip install pandas
!pip install google2pandas
!pip install oauth2client

Requirement already up-to-date: pip in jupyter/.env_jupyter/lib/python3.7/site-packages (19.2.3)
Requirement already satisfied: pandas in jupyter/.env_jupyter/lib/python3.7/site-packages (0.24.1)
Requirement already satisfied: pytz>=2011k in jupyter/.env_jupyter/lib/python3.7/site-packages (from pandas) (2018.9)
Requirement already satisfied: numpy>=1.12.0 in jupyter/.env_jupyter/lib/python3.7/site-packages (from pandas) (1.16.1)
Requirement already satisfied: python-dateutil>=2.5.0 in jupyter/.env_jupyter/lib/python3.7/site-packages (from pandas) (2.7.5)
Requirement already satisfied: six>=1.5 in jupyter/.env_jupyter/lib/python3.7/site-packages (from python-dateutil>=2.5.0->pandas) (1.12.0)
Requirement already satisfied: google2pandas in jupyter/.env_jupyter/lib/python3.7/site-packages (0.1.1)
Requirement already satisfied: pandas>=0.15 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google2pandas) (0.24.1)
Requirement already satisfied: numpy>=1.7 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google2pandas) (1.16.1)
Requirement already satisfied: httplib2 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google2pandas) (0.12.1)
Requirement already satisfied: google-api-python-client in jupyter/.env_jupyter/lib/python3.7/site-packages (from google2pandas) (1.7.8)
Requirement already satisfied: pytz>=2011k in jupyter/.env_jupyter/lib/python3.7/site-packages (from pandas>=0.15->google2pandas) (2018.9)
Requirement already satisfied: python-dateutil>=2.5.0 in jupyter/.env_jupyter/lib/python3.7/site-packages (from pandas>=0.15->google2pandas) (2.7.5)
Requirement already satisfied: google-auth>=1.4.1 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google-api-python-client->google2pandas) (1.6.2)
Requirement already satisfied: uritemplate<4dev,>=3.0.0 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google-api-python-client->google2pandas) (3.0.0)
Requirement already satisfied: six<2dev,>=1.6.1 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google-api-python-client->google2pandas) (1.12.0)
Requirement already satisfied: google-auth-httplib2>=0.0.3 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google-api-python-client->google2pandas) (0.0.3)
Requirement already satisfied: rsa>=3.1.4 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google-auth>=1.4.1->google-api-python-client->google2pandas) (4.0)
Requirement already satisfied: cachetools>=2.0.0 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google-auth>=1.4.1->google-api-python-client->google2pandas) (3.1.0)
Requirement already satisfied: pyasn1-modules>=0.2.1 in jupyter/.env_jupyter/lib/python3.7/site-packages (from google-auth>=1.4.1->google-api-python-client->google2pandas) (0.2.4)
Requirement already satisfied: pyasn1>=0.1.3 in jupyter/.env_jupyter/lib/python3.7/site-packages (from rsa>=3.1.4->google-auth>=1.4.1->google-api-python-client->google2pandas) (0.4.5)
Requirement already satisfied: oauth2client in jupyter/.env_jupyter/lib/python3.7/site-packages (4.1.3)
Requirement already satisfied: pyasn1-modules>=0.0.5 in jupyter/.env_jupyter/lib/python3.7/site-packages (from oauth2client) (0.2.4)
Requirement already satisfied: six>=1.6.1 in jupyter/.env_jupyter/lib/python3.7/site-packages (from oauth2client) (1.12.0)
Requirement already satisfied: pyasn1>=0.1.7 in jupyter/.env_jupyter/lib/python3.7/site-packages (from oauth2client) (0.4.5)
Requirement already satisfied: rsa>=3.1.4 in jupyter/.env_jupyter/lib/python3.7/site-packages (from oauth2client) (4.0)
Requirement already satisfied: httplib2>=0.9.1 in jupyter/.env_jupyter/lib/python3.7/site-packages (from oauth2client) (0.12.1)

Google2Pandas の使い方

Google Analytics のデータ取得には、google2pandasいうライブラリを使用しています。
使用する際は、Google Analytics サービスアカウントのキーの発行も必要になります。
以下の記事にまとめていますのでよろしければご確認ください。
Google2Pandas で、Google Analytics のデータを pandas Dataframe に変換する | Monotalk

1ヶ月分のスクロールイベントを取得し、100%読了がどれくらい発生するのか計算する

以下、1ヶ月分のスクロールイベントを取得し、読了率を計算します。

  • データの取得方法
    pandas プラグインのgoogle2pandas使います。
    過去にgoogle2pandas使い方について、Google2Pandas で、Google Analytics のデータを pandas Dataframe に変換する | Monotalkまとめました。よろしければこちらもご確認ください。

  • スクロールイベントの
    スクロールイベントは、Google Analytics 上で以下のように記録しています。

    • イベントカテゴリの
      Scroll
    • イベント ラベルの
      スクロールした割合 をxx%記録します。
    • イベントアクションの
      スクロールイベントが発生したページパスの
    • イベント値
      未設定です。
  • Google Anlaytics のデータを取得
    1ヶ月分のスクロールイベントのデータを取得します。

from google2pandas import *
view_id = '103185238'
query = {
    'reportRequests': [{
        'viewId' : view_id,
        'dateRanges': [{
            'startDate' : '30daysAgo',
            'endDate'   : 'today'}],
        'dimensions' : [
            {'name' : 'ga:eventCategory'},
            {'name' : 'ga:eventLabel'},            
        ],
        'metrics'   : [
            {'expression' : 'ga:totalEvents'}
        ],
        'dimensionFilterClauses' : [{
                    'filters'  : [
                        {'dimensionName' : 'ga:eventCategory',
                         'expressions' : ['Scroll']}
                    ]
    }]
}]
}
conn = GoogleAnalyticsQueryV4(secrets='./ga_client.json')
df = conn.execute_query(query)

# 出力
df['totalEvents'] = df['totalEvents'].astype(int)
ga_events = df.sort_values(['eventLabel'], ascending=True)
ga_events

eventCategoryeventLabeltotalEvents
0Scroll0%4
1Scroll100%828
2Scroll20%17867
3Scroll40%14669
4Scroll60%9897
5Scroll80%3966

eventLabel に、%含まれているため、数値として認識されていません。
これを数値に変換します。

# 1.ga_events['eventLabel'].str.split('%', expand=True) で %で区切った結果を、データフレームに変換   
# 2.[0]で1列目のカラムを取得し、astype(int) で数値に変換する    
ga_events['eventLabel'] = ga_events['eventLabel'].str.split('%', expand=True)[0].astype(int)
ga_events = ga_events.sort_values(['eventLabel'], ascending=True)
ga_events

eventCategoryeventLabeltotalEvents
0Scroll04
2Scroll2017867
3Scroll4014669
4Scroll609897
5Scroll803966
1Scroll100828

現在の測り方だとスクロール率は、重複してカウントしており、100% 読了したユーザーは、80%読了したユーザーにも含まれます。
ユーザーの重複を排除するため、diff を使います。

# 1行前のカラムとの差を計算 100%の値は保持   
ga_events['totalEvents'] = ga_events['totalEvents'].diff(-1).fillna(ga_events['totalEvents'].tail())
ga_events = ga_events.query('totalEvents > 0')

  • 割合を計算
    totalEvents の割合を計算します。

ga_events['perc']= ga_events['totalEvents']/ga_events['totalEvents'].sum() * 100
ga_events

jupyter/.env_jupyter/lib/python3.7/site-packages/ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  """Entry point for launching an IPython kernel.

eventCategoryeventLabeltotalEventsperc
2Scroll203198.017.898920
3Scroll404772.026.708457
4Scroll605931.033.195276
5Scroll803138.017.563105
1Scroll100828.04.634242

100% までスクロールしてくれるのは、全体の5%以下、80% まで行くのは、20%程度ですね。

一通り計算して、ページビュー数との割合を計算しないといけないことに気づく。

読了率20%以下でページを離れた人の数を上記だと考慮していませんでした。
ページのページビュー数を取得しないと、正確な数字にはならなそうです。

from google2pandas import *
view_id = '103185238'
query = {
    'reportRequests': [{
        'viewId' : view_id,
        'dateRanges': [{
            'startDate' : '30daysAgo',
            'endDate'   : 'today'}],
        'dimensions' : [
            {'name' : 'ga:pagePath'}
        ],
        'metrics'   : [
            {'expression' : 'ga:pageviews'}
        ],
    }]
}
conn = GoogleAnalyticsQueryV4(secrets='./ga_client.json')
df = conn.execute_query(query)
df['pageviews'] = df['pageviews'].astype(int)
pageview = df['pageviews'].sum()

query = {
    'reportRequests': [{
        'viewId' : view_id,
        'dateRanges': [{
            'startDate' : '30daysAgo',
            'endDate'   : 'today'}],
        'dimensions' : [
            {'name' : 'ga:eventCategory'},
            {'name' : 'ga:eventLabel'},            
        ],
        'metrics'   : [
            {'expression' : 'ga:totalEvents'}
        ],
        'dimensionFilterClauses' : [{
                    'operator' : 'AND',
                    'filters'  : [
                        {'dimensionName' : 'ga:eventCategory',
                         'expressions' : ['Scroll']},
                        {'dimensionName' : 'ga:eventLabel',
                         'expressions' : ['100']}                        
                    ]
    }]
}]
}
conn = GoogleAnalyticsQueryV4(secrets='./ga_client.json')
df = conn.execute_query(query)
int(df['totalEvents']) / pageview * 100

3.238392508778775

3%程度になりました。

1ヶ月分のスクロールイベントを取得、ページごとの読了率とページビューとの相関を確認する

ページビューとページごとの読了率の関係を調べます。
スクロールイベントのデータと、ページビューのデータを取得、ページURLをキーにマージしたDataframe を作成して、相関係数を求めます。

スクロールイベントを取得、縦持ちを横持ちに変換する

from google2pandas import *
view_id = '103185238'
query = {
    'reportRequests': [{
        'viewId' : view_id,
        'dateRanges': [{
            'startDate' : '30daysAgo',
            'endDate'   : 'today'}],
        'dimensions' : [
            {'name' : 'ga:eventAction'},
            {'name' : 'ga:eventCategory'},
            {'name' : 'ga:eventLabel'},            
        ],
        'metrics'   : [
            {'expression' : 'ga:totalEvents'}
        ],
        'dimensionFilterClauses' : [{
                    'filters'  : [
                        {'dimensionName' : 'ga:eventCategory',
                         'expressions' : ['Scroll']}
                    ]
    }]
}]
}
conn = GoogleAnalyticsQueryV4(secrets='./ga_client.json')
df = conn.execute_query(query)

# 出力
df['totalEvents'] = df['totalEvents'].astype(int)
ga_events = df.sort_values(['eventAction','eventLabel'], ascending=True)

# 1.ga_events['eventLabel'].str.split('%', expand=True) で %で区切った結果を、データフレームに変換   
# 2.[0]で1列目のカラムを取得し、astype(int) で数値に変換する    
ga_events['eventLabel'] = ga_events['eventLabel'].str.split('%', expand=True)[0].astype(int)
ga_events = ga_events.sort_values(['eventAction','eventLabel'], ascending=True)

# totalEvents を縦持ちから横持ちに変換
pivot_ga_events = ga_events.pivot_table(values=['totalEvents'], index=['eventAction'], columns=['eventLabel'], aggfunc='sum', fill_value=0)

カラムの階層を削る

pivot した後の列は、multiindexのカラムになっているためカラム指定でアクセスができません。
droplevel()実行して、第一階層のカラムを削除します。

# カラムの階層を削る
pivot_ga_events.columns = pivot_ga_events.columns.droplevel()

ページビューのデータを取得し、イベントデータとマージする

ページビューのデータを取得し、イベントデータとマージします。
キーとして、URLが設定されている pathPath eventAction使用します。

# Pageview を取得
from google2pandas import *
view_id = '103185238'
query = {
    'reportRequests': [{
        'viewId' : view_id,
        'dateRanges': [{
            'startDate' : '30daysAgo',
            'endDate'   : 'today'}],
        'dimensions' : [
            {'name' : 'ga:pagePath'}
        ],
        'metrics'   : [
            {'expression' : 'ga:pageviews'}
        ],
    }]
}
conn = GoogleAnalyticsQueryV4(secrets='./ga_client.json')
df = conn.execute_query(query)

import pandas as pd
merge_pd = pd.merge(df, pivot_ga_events, left_on='pagePath', right_on='eventAction')
merge_pd

pagePathpageviews020406080100
0/331011376645756
1/about/3101314141514
2/ampoptimized/blog/In-the-browser,-wake-up-tex...1011111
3/ampoptimized/blog/Make-the-transition-destina...1011110
4/blog/8044444
5/blog/404_errorpage_configration_on_wicket_dro...1011100
6/blog/AB-test-tool-Web-personalization-tool-RT...25021201452
7/blog/About-formulas-in-Google-Spreadsheet-for...1020857249123
8/blog/About-JavaScript-keyboard-shortcut-library/950544327153
9/blog/About-searching-and-completing-a-string-...176014812077193
10/blog/About-statistical-information-of-blog-po...8054420
11/blog/About-txt-file-that-can-be-installed-on-...22015151363
12/blog/add-configuration-on-django-compressor/2011000
13/blog/Add-font-display-with-gulp-using-postcss...2201712400
14/blog/at-comfasterxmljacksondatabindexcunrecog...2201111830
15/blog/author/monotalk/11042113
16/blog/Block-multiple-requests-of-Wicket-Ajax/28021151050
17/blog/BooleanFiled-Null-True-on-django/110109410
18/blog/Calculate-Cauchy-distribution-in-Python/31020151310
19/blog/Calculate-coefficient-of-variation-with-...69050331630
20/blog/Calculate-hypergeometric-distribution-wi...1901210700
21/blog/Calculate-inequality-index-in-python/69045351850
22/blog/Calculate-lognormal-distribution-in-Python/165011910071141
23/blog/Calculate-normal-distribution-with-python/4340312229182627
24/blog/Calculate-polynomial-regression-with-pyt...2670177144128479
25/blog/Calculate-the-exponential-distribution-w...13701028560121
26/blog/Calculate-the-F-distribution-with-python/67042362462
27/blog/Calculate-the-gamma-distribution-with-Py...1320947152222
28/blog/Calculate-the-geometric-distribution-wit...35030302352
29/blog/Calculate-the-interquartile-range-with-p...285022616783112
...........................
219/blog/wicket-about-wicketheader-items/10087620
220/blog/Wicket-AjaxButton-Controls-behavior-when...28017161232
221/blog/With-python-statsmodel-calculate-the-ver...1280816243254
222/blog/Write-Boolean-condition-directly-in-Ecli...4033100
223/blog/WSGIRequest-object-is-not-subscriptable-...110085712850
224/blog/youtube-data-api-v3-java-paging-search/9054430
225/categories/14022221
226/ja/aada/1011111
227/ja/admin/1011110
228/ja/amp/amp/blog/Webpack-4-fontmin-webpack-Rem...1011111
229/ja/ampoptimized/blog/monitore/1011111
230/ja/apis/4044444
231/ja/apis/guessresult/5055555
232/ja/apis/s/1011111
233/ja/blog/1011111
234/ja/blog/Default-findBugs-Rules-In-Github-Repo...2011111
235/ja/blog/google-search-console/2011111
236/ja/blog/google-spread-sheet-/1011111
237/ja/blog/python-requests-post-/4044444
238/ja/blog/python-requests-post/1011111
239/ja/blog/pyton/1011111
240/ja/guess_apis/guessresult/2022222
241/ja/guessresult/1011111
242/ja/guessresult/adss/1011111
243/ja/manifest.json/1011111
244/ja/xyz_monotalk_api/guessresult/1011111
245/ja/xyz_monotalk_xyz/guessresult/blog/About-Ja...2022222
246/statistics/310107753
247/xyz_monotalk_api/pages2022222
248/xyz_monotalk_api/posts1011111

249 rows × 8 columns

データの整形と、調整

ページビューの少なすぎるデータと、スクロール率がうまく記録できていないデータ、スクロール率が0 の列を削除します。
また、ページビューに対する読了率100%の割合を、Read rateとして計算して列を追加します。

merge_pd['pageviews'] = merge_pd['pageviews'].astype(int)
# pageviewの少ないページを除外
merge_pd = merge_pd.query('pageviews >= 5')
# うまくスクロール率が記録できていないページがあるので、除外
merge_pd = merge_pd[(merge_pd['pageviews'] - merge_pd[20]) < 200] 
del merge_pd[0]
merge_pd['Read rate'] = (merge_pd[100] / merge_pd['pageviews'] * 100)
merge_pd

pagePathpageviews20406080100Read rate
1/about/31131414151445.161290
4/blog/84444450.000000
6/blog/AB-test-tool-Web-personalization-tool-RT...25212014528.000000
7/blog/About-formulas-in-Google-Spreadsheet-for...1028572491232.941176
8/blog/About-JavaScript-keyboard-shortcut-library/955443271533.157895
9/blog/About-searching-and-completing-a-string-...176148120771931.704545
10/blog/About-statistical-information-of-blog-po...8544200.000000
11/blog/About-txt-file-that-can-be-installed-on-...221515136313.636364
13/blog/Add-font-display-with-gulp-using-postcss...2217124000.000000
14/blog/at-comfasterxmljacksondatabindexcunrecog...2211118300.000000
15/blog/author/monotalk/114211327.272727
16/blog/Block-multiple-requests-of-Wicket-Ajax/28211510500.000000
17/blog/BooleanFiled-Null-True-on-django/111094100.000000
18/blog/Calculate-Cauchy-distribution-in-Python/31201513100.000000
19/blog/Calculate-coefficient-of-variation-with-...69503316300.000000
20/blog/Calculate-hypergeometric-distribution-wi...1912107000.000000
21/blog/Calculate-inequality-index-in-python/69453518500.000000
22/blog/Calculate-lognormal-distribution-in-Python/165119100711410.606061
23/blog/Calculate-normal-distribution-with-python/4343122291826271.612903
24/blog/Calculate-polynomial-regression-with-pyt...2671771441284793.370787
25/blog/Calculate-the-exponential-distribution-w...13710285601210.729927
26/blog/Calculate-the-F-distribution-with-python/67423624622.985075
27/blog/Calculate-the-gamma-distribution-with-Py...1329471522221.515152
28/blog/Calculate-the-geometric-distribution-wit...35303023525.714286
29/blog/Calculate-the-interquartile-range-with-p...285226167831120.701754
30/blog/Calculate-the-moment-with-scipy.stats.mo...20181613200.000000
31/blog/Calculate-the-previous-term-growth-rate-...966259493011.041667
32/blog/Calculate-the-probability-of-binomial-di...255184151961993.529412
33/blog/Calculate-uniform-distribution-in-Python/1157645422043.478261
34/blog/Calculate-Weibull-distribution-in-Python/192121107964073.645833
...........................
185/blog/understanding-of-aggregate-pipeline/41403733849.756098
186/blog/Update-workbox-webpack-plugin-from-v2-to...28211812327.142857
187/blog/Updated-Japanese-translations-on-Mezzanine/151396300.000000
188/blog/upgrade-from-workbox-v2-to-v4/23201711414.347826
193/blog/usage-of-image-class-in-wicket/3215139839.375000
194/blog/usage-of-modify-display-choice-name-on-d...69564917722.898551
195/blog/usage-of-upsert-java-mongodb-driver/1110106119.090909
197/blog/Use-apache-ultimate-bad-bot-blocker-to-b...201286300.000000
198/blog/Use-data-studio-community-visualizations...564644442135.357143
200/blog/Use-ifttt-and-chatwork-as-web-informatio...362924131138.333333
202/blog/Use-the-Google-Place-API-as-a-facility-s...13310589743032.255639
203/blog/Use-trubolinks-for-sites-using-Django/98752111.111111
204/blog/Use-UnCSS-with-gulp/2922179200.000000
205/blog/Use-Wicket-wicket-devutils-to-check-the-...16654200.000000
206/blog/uses-a-non-entity-orgeclipsepersistencee...13885117.692308
208/blog/using-resource-on-wicket-application/25181714814.000000
209/blog/Validate-security-settings-by-adding-dep...14986000.000000
211/blog/Verifying-the-vulnerability-of-blogs-bui...2722189113.703704
212/blog/Viewed-from-a-programmer-point-of-view-G...2015128000.000000
214/blog/Visualize-the-quality-of-the-blog-articl...11664100.000000
216/blog/Webpack-4-fontmin-webpack-Remove-unused-...28231610000.000000
217/blog/Webpack-4-OptimizeCSSAssetsPlugin-Optimi...1271038254521.574803
219/blog/wicket-about-wicketheader-items/10876200.000000
220/blog/Wicket-AjaxButton-Controls-behavior-when...28171612327.142857
221/blog/With-python-statsmodel-calculate-the-ver...1288162432543.125000
223/blog/WSGIRequest-object-is-not-subscriptable-...110857128500.000000
224/blog/youtube-data-api-v3-java-paging-search/9544300.000000
225/categories/14222217.142857
231/ja/apis/guessresult/555555100.000000
246/statistics/311077539.677419

180 rows × 8 columns

散布図の描画と、相関係数の計算

scatter_matrix散布図を描画、corr相関係数を計算します。

pd.plotting.scatter_matrix(merge_pd, alpha=0.8, figsize=(12,12), range_padding=0.5)

array([[<matplotlib.axes._subplots.AxesSubplot object at 0x11f2a17f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12905c748>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x11fb8f390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129143e80>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129527438>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12955a9b0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129585f28>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x129503518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129503550>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1295d9fd0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129608588>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129630b00>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1296620b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12968b630>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1296b1ba8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1296e2160>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1297086d8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129730c50>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129764208>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12978a780>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1297b3cf8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1297e42b0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12980a828>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129833da0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129864358>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12988c8d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1298b5e48>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1298e6400>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12990d978>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129936ef0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1299654a8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12998da20>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1299b7f98>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1299e7550>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129a0eac8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x129a40080>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129a675f8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129a90b70>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129ac2128>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129ae76a0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129b11c18>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129b401d0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x129b69748>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129b91cc0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129bc3278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129be97f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129c12d68>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129c44320>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129c6b898>]],
      dtype=object)

20190831_output_29_1.png - Google ドライブ

merge_pd.corr()

pageviews20406080100Read rate
pageviews1.0000000.9779380.9618960.9611210.8307600.659829-0.135975
200.9779381.0000000.9934870.9665160.7524640.628549-0.137896
400.9618960.9934871.0000000.9619410.7369280.640821-0.131964
600.9611210.9665160.9619411.0000000.8190360.672964-0.114497
800.8307600.7524640.7369280.8190361.0000000.753186-0.028989
1000.6598290.6285490.6408210.6729640.7531861.0000000.342774
Read rate-0.135975-0.137896-0.131964-0.114497-0.0289890.3427741.000000

Read rate pageviews相関を見ると、ページビューが多いページの100% 読了の割合が多いわけではなさそうです。

ページビュー上位20%の相関を見る

全体だとほとんど相関がなさそうでしたので、上位20%のデータの相関を見てみます。
上位20%の抽出には、quantile関数を用います。

m = merge_pd[merge_pd['pageviews'] >= merge_pd['pageviews'].quantile(0.8)]

pd.plotting.scatter_matrix(m,alpha=0.8, figsize=(12,12), range_padding=0.5)

array([[<matplotlib.axes._subplots.AxesSubplot object at 0x12a075198>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a0aa320>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a0be518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129386748>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1293a7a20>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129461f98>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x129492550>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1294b8b00>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1294b8b38>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a3065f8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a32eb70>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a35e128>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a3876a0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a3afc18>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12a3df1d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a406748>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a430cc0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a461278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a4897f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a4b1d68>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a4e3320>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12a50a898>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a532e10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a5643c8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a58a940>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a5b1eb8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a5e4470>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a60a9e8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12a632f60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a664518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a68ba90>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a6bc048>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a6e75c0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a70fb38>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a7400f0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12a767668>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a78ebe0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a7c0198>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a7e5710>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a810c88>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a840240>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a8677b8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12a891d30>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a8c12e8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a8e8860>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a911dd8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a941390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a96b908>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12a993e80>]],
      dtype=object)

20190831_output_34_1.png - Google ドライブ

m.corr()

pageviews20406080100Read rate
pageviews1.0000000.9285680.8783650.8969350.7034140.579343-0.158547
200.9285681.0000000.9808760.9097300.4921970.492201-0.193289
400.8783650.9808761.0000000.8880060.4517270.525741-0.137850
600.8969350.9097300.8880061.0000000.6364240.588516-0.078309
800.7034140.4921970.4517270.6364241.0000000.7410210.228411
1000.5793430.4922010.5257410.5885160.7410211.0000000.643966
Read rate-0.158547-0.193289-0.137850-0.0783090.2284110.6439661.000000

特に相関があるとはいえなさそうです。

ページビュー上位10%の相関を見る

上位10%のデータの相関を見てみます。

m = merge_pd[merge_pd['pageviews'] >= merge_pd['pageviews'].quantile(0.90)]

pd.plotting.scatter_matrix(m,alpha=0.8, figsize=(12,12), range_padding=0.5)

array([[<matplotlib.axes._subplots.AxesSubplot object at 0x12d6ab320>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12d7e9518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12d7f5780>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12d80f9e8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12da4ad30>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12da79278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12daa27f0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12dacada0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dacadd8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12db23898>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12db4be10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12db7b3c8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dba3940>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dbcceb8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12dbfd470>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dc249e8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dc4ef60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dc80518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dca5a90>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dcd9048>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dcff5c0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12dd28b38>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dd590f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dd80668>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dda6be0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dddb198>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12ddff710>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12de2ac88>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12de5a240>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12de817b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dea8d30>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12deda2e8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12deff860>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12df2cdd8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12df5c390>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12df86908>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dface80>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12dfdd438>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e0039b0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e02ef28>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e05b4e0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e085a58>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x12e0adfd0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e0df588>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e106b00>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e1390b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e15f630>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e188ba8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x12e1b8160>]],
      dtype=object)

20190831_output_39_1.png - Google ドライブ

m.corr()

pageviews20406080100Read rate
pageviews1.0000000.8735500.7918920.8197560.6083730.5905200.154224
200.8735501.0000000.9705740.8573560.2685640.4547550.073183
400.7918920.9705741.0000000.8297290.2189850.5116110.176578
600.8197560.8573560.8297291.0000000.4708880.6039510.283545
800.6083730.2685640.2189850.4708881.0000000.7372930.530483
1000.5905200.4547550.5116110.6039510.7372931.0000000.859617
Read rate0.1542240.0731830.1765780.2835450.5304830.8596171.000000

上位10%だと、ページビューに対する Read rate の相関が正の相関に変わりました。


まとめ

Pandas で Google Analytics の 読了率データを分析してみました。以下、まとめます。

  • 1ページあたり複数回、スクロール率を記録すると、分析時のデータ加工が面倒。
    1回だけ記録するように、Google Analytics の設定を変更するのが良さそうに思いました。
    #GTMTips: Fire Trigger When User Is About To Leave The Page | Simo Ahava’s blog
    記載がありますが、beforeunloadイベントの送付を仕込むと1回だけ記録ができそうなので、これを試してみようかと思います。

  • ページビューと、読了率100%の割合に相関はなさそう。
    当ブログにおいては、基本的にページビューと読了率100%の割合に相関はなさそうです。
    ページ内のリンクの数、記事の文字数などが関係するのかもしれません。

  • 上位10%だと、読了率100%の割合は正の相関になる。
    上位20%のページだと、読了率100%の割合は負の相関ですが、上位10%のページだと、読了率100%の割合は正の相関になります。
    上位20%に入るが、読了率100%の割合が低いページがいるということでどんなページが該当するのか興味を持ちました。


参考

以下、参考にした記事になります。
* pandasで行・列の差分・変化率を取得するdiff, pct_change | note.nkmk.me
* python - How to calculate percentage with Pandas’ DataFrame - Stack Overflow
* Python: PandasのDataFrameを横持ち・縦持ちに変換する - け日記
* python - Pandas: drop a level from a multi-level column index? - Stack Overflow

以上です。

コメント