kaggleチャレンジ6日目 Googleアナリティクスの顧客収益予測 データの分析

シェアする

  • このエントリーをはてなブックマークに追加

スポンサーリンク

はじめに

  今回は、新しいコンペ Google Analytics Customer Revenue Prediction がはじまったのでデータを見ていこうと思います。今日からのコンペなので、参加しようと思っている方がいたら見ていってください。今回は、コンペ初日ということでデータを見ていきたいと思います。また、今回はGoogle Analyticsのデータが公開されているので、Googleから広告収入を得ているブロガーの方もこのデータは参考になると思います。

前回の記事:

Kaggle チャレンジ 1日目 タイタニックの問題からデータを読み解いてみる

Kaggle チャレンジ 4日目 住宅価格問題を解いていく

今回もGoogleColaboratoryを使って進めていくので、はじめ方などは前回の記事を参考にしてください。

コンペの説明

80/20ルールは、多くの企業にとって実証済みです。僅かな割合の顧客が収益の大部分を占めます。そのため、マーケティングチームは、プロモーション戦略に適切な投資を行うことに挑戦しています。RStudioは、チームが業務を拡大・共有するためのRおよびエンタープライズ対応製品の開発者であり、Google CloudやKaggleと提携して、徹底したデータ分析がもたらすビジネスへの影響を実証しました。このコンペでは、Google Merchandise Store(GStoreとも呼ばれます)の顧客データセットを分析して、顧客あたりの収益を予測することに挑戦しています。うまくいけば、GAデータの上でデータ分析を使用することを選択した企業にとって、より実用的な運用上の変更とマーケティング予算の有効活用が期待されます。

80/20のルールとは、構成要素を大きい順に並べた時、上位20%の要素で全体の80%程度を占めることが多いという経験則の事のようです。ビジネスシーンでは、「上位20%の顧客で売上げの80%を占める」といったものがあるようです。

Gストア

(コンペの説明欄から引用)

評価方法

提出は、平均平方二乗誤差(RMSE)で評価されます。


やること

 顧客あたりの収益を予測することが目的ですが、今回は現在公開されているKernelsを見ながらGoogleColaboratoryを使って、データについての理解を深めたいと思います。次回以降そのデータを使って収益を予測していきたいと思います。

・今回参考にするKernels:

Simple Exploration Notebook – GA Customer Revenue

GoogleColaboratoryの使い方はこちら

対象者

機械学習をKaggleを使って学びたい方、Kaggleに興味がある方、いろいろなデータセットを試してみたい方。

 

Google Analytics Customer Revenue Prediction

データの準備

いつものようにGoogleColaboratory上でやっていくので、コンペのデータを共有しましょう。しかし、今回解凍すると2GB以上あり、時間がかかるので、zipファイルを共有してGoogleColaboratory上で解凍しましょう。

In[1]:

!pip install pydrive

In[2]:

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

In[3]:

id = '*************************' # 共有リンクで取得した id= より後の部分を*の部分に入力
downloaded = drive.CreateFile({'id': id})
downloaded.GetContentFile('all.zip') #ファイルの名前

In[4]:

!unzip all.zip

データセット

データフィールドは次のようになっています。

  • fullVisitorId– Google Merchandise Storeの各ユーザーのID
  • channelGrouping – ユーザーがStoreに来るためのチャンネル
  • date – ユーザーがストアにアクセスした日付
  • device – Storeへのアクセスに使用されるデバイスの仕様
  • geoNetwork – ユーザーの地域に関する情報
  • sessionId – ストアへの訪問の識別子
  • socialEngagementType – エンゲージメントタイプ
  • totals – セッション全体の集計値
  • trafficSource – セッションが発生したトラフィックソースに関する情報
  • visitId – このセッションの識別子
  • visitNumber – ユーザーのセッション番号。これが最初のセッションの場合、これは1に設定されます。
  • visitStartTime – タイムスタンプ(POSIX時間として表されます)

いくつかのフィールドはjson形式なので、注意してください。

こちらのカーネルのおかげで、ファイル内の全てのjsonフィールドを、他のコンペで使用されるような平坦化されたcsvフォーマットに変換することができます。

1 – Quick start: read csv and flatten json fields

In[5]:

import os
import json
import numpy as np
import pandas as pd
from pandas.io.json import json_normalize
import matplotlib.pyplot as plt
import seaborn as sns
color = sns.color_palette()

%matplotlib inline

from plotly import tools
import plotly.offline as py
py.init_notebook_mode(connected=True)
import plotly.graph_objs as go

pd.options.mode.chained_assignment = None
pd.options.display.max_columns = 999

In[6]:
def load_df(csv_path='train.csv', nrows=None):
    JSON_COLUMNS = ['device', 'geoNetwork', 'totals', 'trafficSource']
    
    df = pd.read_csv(csv_path, 
                     converters={column: json.loads for column in JSON_COLUMNS}, 
                     dtype={'fullVisitorId': 'str'}, # Important!!
                     nrows=nrows)
    
    for column in JSON_COLUMNS:
        column_as_df = json_normalize(df[column])
        column_as_df.columns = [f"{column}.{subcolumn}" for subcolumn in column_as_df.columns]
        df = df.drop(column, axis=1).merge(column_as_df, right_index=True, left_index=True)
    print(f"Loaded {os.path.basename(csv_path)}. Shape: {df.shape}")
    return df


GoogleColaboratory上のメモリを圧迫するので今回使わないtest_dfはコメントアウトしてあります。
In[7]:

%%time
train_df = load_df()
#test_df = load_df("test.csv")
Out[7]:
Loaded train.csv. Shape: (903653, 55)
Loaded test.csv. Shape: (804684, 53)
CPU times: user 4min 56s, sys: 11.6 s, total: 5min 8s
Wall time: 5min 8s

In[8]:

train_df.head()
Out[8]:
channelGrouping date ... trafficSource.referralPath trafficSource.source
0 Organic Search 20160902 ... NaN google
1 Organic Search 20160902 ... NaN google
2 Organic Search 20160902 ... NaN google
3 Organic Search 20160902 ... NaN google
4 Organic Search 20160902 ... NaN google

表は、全部出力結果を出すとはみ出すので途中の列を省略しています。

ターゲット変数の探索

ユーザーレベルでトランザクション収益を合計し、ログを取って散布図を作成してみましょう。

In[9]:

train_df["totals.transactionRevenue"] = train_df["totals.transactionRevenue"].astype('float')
gdf = train_df.groupby("fullVisitorId")["totals.transactionRevenue"].sum().reset_index()

plt.figure(figsize=(8,6))
plt.scatter(range(gdf.shape[0]), np.sort(np.log1p(gdf["totals.transactionRevenue"].values)))
plt.xlabel('index', fontsize=12)
plt.ylabel('TransactionRevenue', fontsize=12)
plt.show()
Out[9]:

このグラフを見ると、このコンペの説明に出てきた80/20のルールを思い出しますね。しかしこのグラフを見ると、実際の割合はもっと小さいように思えます。

In[10]:

nzi = pd.notnull(train_df["totals.transactionRevenue"]).sum()
nzr = (gdf["totals.transactionRevenue"]>0).sum()
print("Number of instances in train set with non-zero revenue : ", nzi, " and ratio is : ", nzi / train_df.shape[0])
print("Number of unique customers with non-zero revenue : ", nzr, "and the ratio is : ", nzr / gdf.shape[0])
Out[10]:
Number of instances in train set with non-zero revenue :  11515  and ratio is :  0.0127427231470487
Number of unique customers with non-zero revenue :  9996 and the ratio is :  0.013996726255903731

したがって、収入のない顧客に対する収入を生み出す顧客の割合は1.3%でした。

行の大部分は収益がゼロでないため、以下のプロットでは、変数の各カテゴリの数と、収益がゼロでないインスタンスの数を見てみます。

訪問者数と一般訪問者数

trainとtestのユニーク訪問者数と一般訪問者の数を見てみましょう。

In[11]:

print("Number of unique visitors in train set : ",train_df.fullVisitorId.nunique(), " out of rows : ",train_df.shape[0])
print("Number of unique visitors in train set : ",test_df.fullVisitorId.nunique(), " out of rows : ",test_df.shape[0])
print("Number of common visitors in train and test set : ",len(set(train_df.fullVisitorId.unique()).intersection(set(test_df.fullVisitorId.unique())) ))
Out[11]:
Number of unique visitors in train set :  714167  out of rows :  903653
Number of unique visitors in train set :  617242  out of rows :  804684
Number of common visitors in train and test set :  7679

定数値のカラム

trainの中に一定の価値を持つ特徴がかなりあるように見えます。 これらの特徴のリストを取得しましょう。

In[12]:

[c for c in train_df.columns if train_df[c].nunique()==1]
Out[12]:
['socialEngagementType',
 'device.browserSize',
 'device.browserVersion',
 'device.flashVersion',
 'device.language',
 'device.mobileDeviceBranding',
 'device.mobileDeviceInfo',
 'device.mobileDeviceMarketingName',
 'device.mobileDeviceModel',
 'device.mobileInputSelector',
 'device.operatingSystemVersion',
 'device.screenColors',
 'device.screenResolution',
 'geoNetwork.cityId',
 'geoNetwork.latitude',
 'geoNetwork.longitude',
 'geoNetwork.networkLocation',
 'totals.bounces',
 'totals.newVisits',
 'totals.visits',
 'trafficSource.adwordsClickInfo.criteriaParameters',
 'trafficSource.adwordsClickInfo.isVideoAd',
 'trafficSource.campaignCode',
 'trafficSource.isTrueDirect']

かなりの数です。 値は一定であるため、特徴リストから削除することで、モデリングでのメモリと時間を節約できます。

デバイス情報

In[13]:

def horizontal_bar_chart(cnt_srs, color):
    trace = go.Bar(
        y=cnt_srs.index[::-1],
        x=cnt_srs.values[::-1],
        showlegend=False,
        orientation = 'h',
        marker=dict(
            color=color,
        ),
    )
    return trace

# Device Browser
cnt_srs = train_df.groupby('device.browser')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace1 = horizontal_bar_chart(cnt_srs["count"].head(10), 'rgba(50, 171, 96, 0.6)')
trace2 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"].head(10), 'rgba(50, 171, 96, 0.6)')

# Device Category
cnt_srs = train_df.groupby('device.deviceCategory')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace3 = horizontal_bar_chart(cnt_srs["count"].head(10), 'rgba(71, 58, 131, 0.8)')
trace4 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"].head(10), 'rgba(71, 58, 131, 0.8)')

# Operating system
cnt_srs = train_df.groupby('device.operatingSystem')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace5 = horizontal_bar_chart(cnt_srs["count"].head(10), 'rgba(246, 78, 139, 0.6)')
trace6 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"].head(10),'rgba(246, 78, 139, 0.6)')

# Creating two subplots
fig = tools.make_subplots(rows=3, cols=2, vertical_spacing=0.04, 
                          subplot_titles=["Device Browser - Count", "Device Browser - Non-zero Revenue Count",
                                          "Device Category - Count",  "Device Category - Non-zero Revenue Count",
                                          "Device Operating System - Count", "Device Operating System - Non-zero Revenue Count"])

fig.append_trace(trace1, 1, 1)
fig.append_trace(trace2, 1, 2)
fig.append_trace(trace3, 2, 1)
fig.append_trace(trace4, 2, 2)
fig.append_trace(trace5, 3, 1)
fig.append_trace(trace6, 3, 2)

fig['layout'].update(height=1200, width=800, paper_bgcolor='rgb(233,233,233)', title="Device Plots")
py.iplot(fig, filename='device-plots')
Out[13]:
This is the format of your plot grid:
[ (1,1) x1,y1 ]  [ (1,2) x2,y2 ]
[ (2,1) x3,y3 ]  [ (2,2) x4,y4 ]
[ (3,1) x5,y5 ]  [ (3,2) x6,y6 ]

わかること

  • デバイスブラウザの分布は、収益がゼロではないプロットの数と両方似ている
  • デバイスカテゴリでは、モバイルデバイスと比較してデスクトップの収益率がゼロ以外の割合が高い。
  • デバイスのOSでは、カウントの数はWindowsの数が一番多いが、収益がゼロでない方はMacintoshの方が多くなる。
  • Chrome OSは、収益がゼロではない方の割合も高くなっています
  • モバイルOS側では、Androidに比べてiOSの方が収益がゼロではない割合が高い。

日付探査

In[14]:

import datetime

def scatter_plot(cnt_srs, color):
    trace = go.Scatter(
        x=cnt_srs.index[::-1],
        y=cnt_srs.values[::-1],
        showlegend=False,
        marker=dict(
            color=color,
        ),
    )
    return trace

train_df['date'] = train_df['date'].apply(lambda x: datetime.date(int(str(x)[:4]), int(str(x)[4:6]), int(str(x)[6:])))
cnt_srs = train_df.groupby('date')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_index()
#cnt_srs.index = cnt_srs.index.astype('str')
trace1 = scatter_plot(cnt_srs["count"], 'red')
trace2 = scatter_plot(cnt_srs["count of non-zero revenue"], 'blue')

fig = tools.make_subplots(rows=2, cols=1, vertical_spacing=0.08,
                          subplot_titles=["Date - Count", "Date - Non-zero Revenue count"])
fig.append_trace(trace1, 1, 1)
fig.append_trace(trace2, 2, 1)
fig['layout'].update(height=800, width=800, paper_bgcolor='rgb(233,233,233)', title="Date Plots")
py.iplot(fig, filename='date-plots')
Out[14]:
This is the format of your plot grid:
[ (1,1) x1,y1 ]
[ (2,1) x2,y2 ]

わかること

  • 2016年8月1日から2017年8月1日までの訓練データセットのデータがあります
  • 2016年11月には、訪問ユーザーの数が増加していますが、その期間中の収益がゼロではない人の増加はありません。

地理情報

In[15]:

# Continent
cnt_srs = train_df.groupby('geoNetwork.continent')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace1 = horizontal_bar_chart(cnt_srs["count"].head(10), 'rgba(58, 71, 80, 0.6)')
trace2 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"].head(10), 'rgba(58, 71, 80, 0.6)')

# Sub-continent
cnt_srs = train_df.groupby('geoNetwork.subContinent')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace3 = horizontal_bar_chart(cnt_srs["count"], 'orange')
trace4 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"], 'orange')

# Network domain
cnt_srs = train_df.groupby('geoNetwork.networkDomain')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace5 = horizontal_bar_chart(cnt_srs["count"].head(10), 'blue')
trace6 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"].head(10), 'blue')

# Creating two subplots
fig = tools.make_subplots(rows=3, cols=2, vertical_spacing=0.08, horizontal_spacing=0.15, 
                          subplot_titles=["Continent - Count", "Continent - Non-zero Revenue Count",
                                          "Sub Continent - Count",  "Sub Continent - Non-zero Revenue Count",
                                          "Network Domain - Count", "Network Domain - Non-zero Revenue Count"])

fig.append_trace(trace1, 1, 1)
fig.append_trace(trace2, 1, 2)
fig.append_trace(trace3, 2, 1)
fig.append_trace(trace4, 2, 2)
fig.append_trace(trace5, 3, 1)
fig.append_trace(trace6, 3, 2)

fig['layout'].update(height=1200, width=800, paper_bgcolor='rgb(233,233,233)', title="Geography Plots")
py.iplot(fig, filename='geo-plots')
Out[15]:
This is the format of your plot grid:
[ (1,1) x1,y1 ]  [ (1,2) x2,y2 ]
[ (2,1) x3,y3 ]  [ (2,2) x4,y4 ]
[ (3,1) x5,y5 ]  [ (3,2) x6,y6 ]

わかること

  • 大陸のプロットでは、アメリカは、収益がゼロでないカウント数が多いだけでなく、カウント数も高いことがわかります
  • アジアとヨーロッパでは数が多いものの、これらの大陸からの非ゼロ収益の数は比較的少ないです。
  • サブ大陸(大陸の細かい内訳)のプロットから最初の2つの点も推測できます。
  • ネットワークドメインが「(未設定)」ではなく「unknown.unknown」である場合、収益がゼロでないカウントの数は少なくなる傾向があります。

トラフィックソース

In[16]:

# Continent
cnt_srs = train_df.groupby('trafficSource.source')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace1 = horizontal_bar_chart(cnt_srs["count"].head(10), 'green')
trace2 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"].head(10), 'green')

# Sub-continent
cnt_srs = train_df.groupby('trafficSource.medium')['totals.transactionRevenue'].agg(['size', 'count'])
cnt_srs.columns = ["count", "count of non-zero revenue"]
cnt_srs = cnt_srs.sort_values(by="count", ascending=False)
trace3 = horizontal_bar_chart(cnt_srs["count"], 'purple')
trace4 = horizontal_bar_chart(cnt_srs["count of non-zero revenue"], 'purple')

# Creating two subplots
fig = tools.make_subplots(rows=2, cols=2, vertical_spacing=0.08, horizontal_spacing=0.15, 
                          subplot_titles=["Traffic Source - Count", "Traffic Source - Non-zero Revenue Count",
                                          "Traffic Source Medium - Count",  "Traffic Source Medium - Non-zero Revenue Count"])

fig.append_trace(trace1, 1, 1)
fig.append_trace(trace2, 1, 2)
fig.append_trace(trace3, 2, 1)
fig.append_trace(trace4, 2, 2)

fig['layout'].update(height=1000, width=800, paper_bgcolor='rgb(233,233,233)', title="Traffic Source Plots")
py.iplot(fig, filename='traffic-source-plots')
Out[16]:
This is the format of your plot grid:
[ (1,1) x1,y1 ]  [ (1,2) x2,y2 ]
[ (2,1) x3,y3 ]  [ (2,2) x4,y4 ]


今回は、Google Analyticsのデータを使ってデータ分析をしました。ブログでGoogleAnalyticsを見ている私にとっても興味深いので、このコンペは頑張ってみたいと思います。

最後まで読んでいただきありがとうございました。よろしければこの記事をシェアしていただけると励みになります。よろしくお願いします。

スポンサーリンク
レクタングル広告(大)
レクタングル広告(大)

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

スポンサーリンク
レクタングル広告(大)