LIFULL Creators Blog

「株式会社LIFULL(ライフル)」の社員によるブログです。

PythonによるSARIMAXモデルを使った「TVCMの効果検証」への挑戦

 ※この記事はLIFULL Advent Calenderの20日目です

 こんにちは! LIFULLでデータアナリストをしている竹澤(@Akira Takezawa)です.

 今回は, LIFULLのデータアナリストチームの取り組みを紹介します.

 本記事はデータ分析に興味がある方を対象に, 「マーケティングの実務で生かせる時系列分析」をテーマに執筆しました.

 まず, なぜこの記事を書いたかを簡単に説明します.

 近年, 機械学習やディープラーニングの台頭を筆頭に近年データ分析の手法は爆発的に増え続けています.

 一方で実際のビジネスの現場で見えてくるのは, 「派手さや新しさのみに捉われず, 古今東西変わらず価値を提供し続けてきた分析手法こそ重要ではないか」というもう一つの側面です.

 具体的には相関・回帰分析や検定などがそうですが, 同時に「時系列分析」もビジネスの世界で活用機会が多く, パワフルな威力を発揮すると私は考えています.

 そこで今回は, TVCMの訴求やクリエイティブの評価をする際に, 時系列モデルを活用することで評価基準となるKPIを作成した事例について紹介します.

 なお中盤では, PythonによるSARIMAXモデルの実装を記載しています.

【目次】

①マーケティングにおける時系列分析の活用機会

f:id:LIFULL-takezawax:20191219080724j:plain
引用元: 千鳥がネタ6本!?スマートニュースの新コンテンツ『クーポンチャンネル』を紹介する新TVCMが2018年3月31日(土)より全国オンエア中!!

目的: TVCMでユーザーのサイト訪問・利用を実現したい

 突然ですが読者の皆さん, 自分が好きなCMはありますか?

 こちらの画像は弊社でも広告を出稿させて頂いているSmartNewsさんのCMカットですが, 「TV画面の右側30%をサービスのUI(スマホ画面)が占める」という斬新なクリエイティブとなっています.

 個人的にはかなり勇気のある挑戦だと考えており, ユーザーにとっての「わかりやすさ」を極限まで突き詰めた, 作り手のこだわりを強く感じるのですごく好きです.

 スマホの登場以来, ますます生活が広告で溢れる現代で, 単にTVCMを見てもらっただけで認知を獲得できる時代は終わりました.

 特にLIFULL HOME'Sのような情報サービスの世界では, 市場を問わずプロダクト認知の獲得だけでなく, アクション喚起を狙ったCMへ大きくシフトチェンジしていることが上記の例のように確認できます.

 当然LIFULLでもオフラインの広告によって, 実際にオンライン上のサービスを使ってもらえたか, あるいはアプリケーションをインストールして頂けたかに関心を寄せています.

 今回はWebサイトやアプリサービスにおいて, ユーザーの「オンライン上でのアクション」までを狙ったTVCM活用を想定して話を進めます. なおその際のKPIとしては, CM出稿期間中のDAU指名検索などが考えられるでしょう.

 余談ですが, TVCMはマーケターにとって, 年間で打席に立てる回数が少ない割にコストは基本億単位と高額なため, 成功しても失敗しても事業に対するインパクトが大きく, 難易度が最も高い仕事の1つです.

 だからこそ, 1回1回の訴求内容やクリエイティブに対して, 正しい評価指標を設定し, その結果を軸に細かなフィードバックを与えていく必要があります.

 ※注意点として, あくまでTVCMの目的を「サービスの利用」に設定した時のみ, トラフィックの増減等の指標でクリエイティブを評価すべきです. 認知など目的次第で評価の仕方は変わるので, この方法が常に正解という訳ではありません.

課題: 訴求内容とクリエイティブの評価におけるボトルネック

 ここではクリエイティブを評価する上での難所を例示するために, 実際にあったボトルネックを3つ紹介します.

  1. TVCMにはGRPや放映時間帯などKPIに変化を与え得る変数が複数あり, 訴求など特定の変数による影響を定量化して切り出すことが難しい
  2. 定性情報である訴求内容やクリエイティブは, 定量化してKPIとの関係をモデリングすることが難しい
  3. KPIの値が, 季節効果やトレンドの影響を受けていることを考慮しなければならない

 ※GRPは「延べ視聴率」と呼ばれ, CMの総表示回数を表す尺度と考えてください

 少し具体的に, 順を追って説明していきます.

 まず1. に関して. TVCMは通常, 大きく以下の5つの要素(変数)を変更・調整して毎回の出稿を行います.

  • 訴求内容 (Who&What)
  • クリエイティブ (How)※起用タレントや音楽etc...
  • 放映GRP量≒出稿金額 (How much)
  • 放映時間帯 (When)
  • 放映都道府県 (Where)

 上記の要素において, どの要素の変更がKPIに対してどれだけ影響を与えたかを個々に定量化して抜き出すことが困難なことは想像がつくでしょう.

 次に2. に関して. そもそも訴求内容やクリエイティブ(起用タレントや構成)は定性的な情報かつ, 変数化(ダミー変数など)も難しい部類にあたるため, 統計モデリングが非常に難しくなります. 結果, KPIに対するクリエイティブの効果を直接定量化することができませんでした.

 最後に3.に関して. 詳しくは後述しますが, 時系列モデルを選択するメリットそのものに関する話となります. まずは季節効果について.

 一般的にどの商材・サービスにおいても繁忙期や閑散期があると思います. 弊社ではTVCMの出稿時期と繁忙期が重なり, KPIのリフトのうちどこまでがCMの影響なのかの判断が難しいという課題がありました.

 例えば, LIFULL HOME'Sにおいて賃貸物件の検索は, 1月から3月の引っ越しシーズンが繁忙期にあたりアクセス数が急増します. 当然, 1,000GRPのTVCMを春先の3月に出稿した時と, 夏の8月に同じ量を出稿した時にKPIが取り得る値は全く異なります.

 よって, 単純に線形回帰を使って毎回のGRPとKPIの関係性をモデル化しようとしても上手くいきません.

 また, トレンドとは端的に言うと長期的な傾向やKPIのベースラインの変動を指します. 例えば, スマホの普及率が急増した5年間においては, 基本的には多くのWebサービスのユーザー数も右肩上がりに増加していったはずです.

 こうしたマクロ要因による緩やかな数値の変化も考慮する事で, より正確な効果検証ができます.

 ざっくりとですが以上がWebのトラフィック指標を評価基準として, CMのクリエイティブや訴求を効果検証する際に出てきた問題点でした.

解決策: 出稿予定のGRP量とKPIの関係をモデリングする

 ここでは前述のボトルネックを踏まえ, 今回我々が採用した効果検証の方法について書きます.

 まず検証手順の全体像としては, 以下の3ステップとなります.

  1. CMの変数としては「GRP」のみを説明変数に加えた, トレンド・季節調整付き時系列モデルを用いて未来のKPIが各週で取り得る基準値を予測する
  2. もし実際のKPIの結果が基準値を大きく上回った場合, 説明変数に加えなかった「訴求」や「クリエイティブ」が正の影響を与えたと仮定して評価する
  3. 最後に, 今回試したCMの変更点(訴求や起用タレントの変更など)と, 過去のクリエイティブの「差分」を吟味し, 次回の継続事項・改善事項を決定する

 要は, 実際の観測値から「訴求内容とクリエイティブ"以外の要素"」による効果を差し引くことで, 訴求とクリエイティブの効果を定量化して抜き出そうという発想です.

 ただお察しの通り, 現時点で妥協した点がいくつかあります.

 例えば, 「訴求内容」や「クリエイティブ」による効果(放映時間帯や放送エリアの変更も含む)は, 過去の出稿の結果を平均して考慮するとおおよそ一定になるとして割り切り, 誤差に吸収されるものとして扱っています.

 しかし, 実際にKPIとなる指標が過去に取った値は, これらの影響を少なからず受けた結果生まれているはずです.

 また本来であれば, CM出稿期間に同時に出稿した電車内の交通広告やYoutube動画などの影響も考慮するべきですが, 今回はそれらの出稿金額等を説明変数としてモデルに加えていない点においても不完全さが残ります.

 一方で, 世の中の現象を単純化してモデリングする際は, 常に妥協がつきものです. ですので今回は一度モデルを構築し, どれだけ精度が出るか見てみようという判断に踏み切りました.

 ここから先は, 時系列モデルであるSARIMAXのモデリングに移り, Pythonでの実装方法と共に簡潔にモデルの説明をします.

②Python/statsmodelsによるSARIMAXの活用事例

f:id:LIFULL-takezawax:20191220161430p:plain
引用元: Forecasting with Python and Tableau

 ※この記事では, 個々の統計用語の説明や数式については割愛します. また所々統計的な厳密さに欠ける記載があると思われ, その際は積極的に修正したいのでぜひコメントをお願いします.

はじめに: SARIMAXモデルとは?

 今回はタイトル通り, KPIの予測値を作るための時系列モデルとしてSARIMAXモデル(季節調整済みARIMA + 外生変数)を採用しました.

 SARIMAXモデルを直感的に理解するため, まずはSARIMAX = S+ARIMA+Xと分解して説明します.

 まずS+ARIMAの部分ですが, 代表的な時系列モデルであるARIMAモデル(自己回帰和分移動平均)に季節性などの周期成分を取り入れたものをSARIMAモデルと言います. そして残りの+Xについては, 回帰分析のように外部変数もモデルに組み込めることを表しています. 今回でいうとTVCMの出稿GRPを, Xとしてモデルに取り込みます.

 SARIMAXモデルを採用することで, 前章であげた繁忙期などの「季節影響」や「トレンド影響」を加味したいといった要件を満たすことに繋がります. また広告費などの外部変数も複数追加できるため, 多くのマーケティングキャンペーンにおける予測や効果測定とも相性が良さそうです.

 Pythonでは, statsmodelsというライブラリからSARIMAXクラスを呼び出すことで, 数行のコードでモデリングできます.

データ準備: WAUと過去のCM配信GRPデータ

 今回はTVCMで伸ばしたい指標(KPI)を, サイトに訪問した週次のユニークユーザー数を表すWAU(Weekly Active User)と仮定して進めます.

 実際のサービスデータを公開することはできないため, 今回はGoogle Trendを使用します. 具体的には直近5年分の自社サービスに関わる複数ワードの検索数をダウンロードし, その合計値に適当な数をかけることで擬似的なWAUデータを生成しました.

 また, 説明変数に加えるGRPのデータには, 広告代理店のレポートを元に自社の過去のTVCM出稿実績を用います.

 なお実装にあたっては, Jupyter Notebookを使います. それでは早速必要なデータを準備していきます.

 まずは①WAUデータをロードします. 元データの単位はDailyですが, pandas.DataFrame.resample簡単に週単位にまとめることができます.

# 必要なライブラリを事前にインストールしてください
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm

# Google Trendのデータから仮想WAUのデータを生成
wau = pd.read_csv("wau_5years.csv")
wau.DATE = pd.to_datetime(wau.DATE)
wau.set_index("DATE", inplace=True)
wau = wau.resample("W").sum()
wau.head(7)

f:id:LIFULL-takezawax:20191223222557p:plain

 次に説明変数に使う②GRPデータをロードし, 同じく週単位にまとめます. こちらは当然公開することができないため, データを隠しています.

# 自社のGRPや広告出稿金額データを用意する
grp = pd.read_excel("grp.xlsx")
grp.DATE = pd.to_datetime(grp.DATE)
grp.set_index("DATE", inplace=True)
grp = grp.resample("W").sum()
grp.head(7)

f:id:LIFULL-takezawax:20191223222655p:plain

 この際, モデルに入れるデータの日付の開始点や粒度(日次, 月次)を揃えないと後でエラーが出るのでご注意を. モデリングに必要なデータの準備ができたので, 一度WAUの原系列を可視化してみます.

# トラフィック数を可視化
plt.rcParams['figure.figsize'] = 12, 8
wau.plot(linewidth=4, color="steelblue")

f:id:LIFULL-takezawax:20191220164901p:plain

 この時点で, 経年でややデータが取り得る値のベースライン(トレンド)が上昇していることや, 特定の月のみ数値が高くなるパターン(周期性)が確認できます. 次節で詳しく見ていきます.

モデル選択: 時系列データにおける確率過程

 ここでは, 簡単にWAUデータが従う生成過程を確認していきます.

 前提として, 統計モデルを構築するとはつまり, 「観察対象となるデータが, 実は目に見えない内部構造やパターンに従って現れている」と仮定することであり, そのため数式によって定式化することができます.

 そこで今回は, 各観測点におけるWAUのデータが以下のような確率過程に従う仮説を持ちます. 

 WAU = 短期の自己相関 + 長期のトレンド + 季節効果 + CMのGRP + 誤差

 なおこの仮説は, おおよそSARIMAXモデルが想定している確率過程にあたります.

 次ではモデリングに入る前に, 時系列データ特有のEDAによって上記の仮説の妥当性を検証していきます.

1. 自己相関と季節効果の確認

 一歩目として, 自己相関の有無を確認していきます.

 時系列モデルでは, 現在の観測値と過去の値の間に, なんらかの時間的な関係性が存在することを前提としています.

 よって「自己相関がない(ホワイトノイズ)」, つまり100%ランダムに生成されるデータを, わざわざ時系列モデルとして扱う必要がありません.

 そのためまず, WAUデータが自分の過去の値と相関関係があるかどうか, また同時に周期性を持つかどうかを調べます.

# 自己相関係数と偏自己相関係数のコレログラムを出力
fig,ax = plt.subplots(2,1,figsize=(12,8))
fig = sm.graphics.tsa.plot_acf(wau, lags=100, ax=ax[0], color="darkgoldenrod")
fig = sm.graphics.tsa.plot_pacf(wau, lags=100, ax=ax[1], color="darkgoldenrod")
plt.show()

f:id:LIFULL-takezawax:20191223222824p:plain

 ※上記はコレログラムといい, 横軸がラグ数, 縦軸が自己相関係数(または偏自己相関係数)をとります.

 コレログラムから, ラグが大きくなるに連れて正の自己相関が緩やかに減衰していることが分かります. またかすかにですが, 52週毎の周期性も確認できます.

2. 定常性と単位根の確認

 次に, 定常性(stationarity)とARIMAモデルの前提となる単位根(および和分過程)の有無について確認していきます.

 まず定常性とは, 時間が経過してもデータを生成する確率分布が変化しない性質のことです. 基本的に多くの時系列モデルは, データが定常性を持つことを前提としています.

 定常性を持つ時系列データを定常過程, 持たないものを非定常過程と呼びます.

 ただし, 原系列が非定常過程なデータに対しても, 例外的に時系列モデルを適応できる場合があります. それは前後の観測点の値で差分をとった階差系列が定常性を持つデータの場合です. 単位根過程や和分過程がそれに当たります.

 より具体的には, 単位根(単位根過程)は1次の差分をとった階差系列が定常過程に従う時系列データを, 和分過程は任意のd次階差系列が定常過程となるものを指します.

 ARIMAモデルは, 今回のようにトレンドを持つデータに対して適応することができます. ARIMAモデルでは, 内部でトレンドデータの差分を取ることで, 定常過程に変換しています.

 それでは試しに, WAUデータの1次階差系列を見てみましょう.

# wauの1次差分系列
plt.rcParams['figure.figsize'] = 12, 8
wau.diff().plot(linewidth=4, color="mediumseagreen")

f:id:LIFULL-takezawax:20191220165021p:plain

 先ほどよりは定常過程に近い系列となりましたが, まだいくつかスパイクしている観測点が確認できます. 今回はこのスパイク部分を「季節要因」と「外部要因(CMのGRP)」の影響による動きとして説明できないかという仮説が, SARIMAXモデルを採用する背景となります.

 念のため, 単位根の有無も調べてみます. 単位根の確認には, ADF検定(Augmented Dickey-Fuller test)が用いられます. 今回は, 有意水準5%として検定します.

# 原系列に対するADF検定
results = sm.tsa.stattools.adfuller(wau["WAU"])
print('ADF Statistic: %f' % results[0])
print('P-Value: %f' % results[1])
  • ADF Statistic: -2.467521
  • P-Value: 0.123578

 P値>0.05となりました. 単位根過程であるという帰無仮説を棄却できなかったため, WAUの原系列が単位根を持つ可能性があるとして次に進みます.

3. トレンド(と季節成分)の抽出

 本来であれば, 季節成分は上記のコレログラムによって確認できますが, statsmodelsにはデータからトレンド成分と季節成分を別々に抽出することができるメソッドが存在するので紹介します.

# トレンド成分と季節成分, 残差を抽出する
fig, axes = plt.subplots(4, 1, sharex=True)

decomposition = sm.tsa.seasonal_decompose(wau, model ='additive')
decomposition.observed.plot(ax=axes[0], legend=False, color='r')
axes[0].set_ylabel('Observed')
decomposition.trend.plot(ax=axes[1], legend=False, color='g')
axes[1].set_ylabel('Trend')
decomposition.seasonal.plot(ax=axes[2], legend=False)
axes[2].set_ylabel('Seasonal')
decomposition.resid.plot(ax=axes[3], legend=False, color='k')
axes[3].set_ylabel('Residual')

f:id:LIFULL-takezawax:20191220170723p:plain

 トレンド成分を表すTrendから, 長期でのアップトレンドを確認できます. また季節成分を表すSeasonalからは, 1~3月に数字が大きく跳ねるといった周期性が確認できます.

 以上でWAUデータにSARIMAXモデルを適応する上で, "おおよそ"の妥当性を確認できました. 次は, SARIMAXモデルが持つパラメータの推定・選択をしていきます.

パラメータ推定: AICとGrid Searchによる推定

 目的変数となるデータが従う確率過程を仮定できたら, モデルが持つパラメータが決まればモデルの構築が完了します.

 詳細はこちらの記事にわかりやすくまとまっていますが, SARIMAXモデルのパラメータは全部で7つ(p, d, q, P, D, Q, s)あります. 今回sに関しては, データの観測点が週単位であるため, s=52(1年間が約52週)という周期を持つと決め打ちし, それ以外の6つを推定していきます.

 今回は最尤推定を内部で用いるAIC(赤池情報量規準)とGrid Search(総当たり手法)を用いてパラメータ推定を行います.

 まずは, 候補となるパラメータの組み合わせを全てListに格納します. なおこの際データの長さ(行数)によってパラメータの値に上限があるので気をつけて下さい.

# 考えられるパラメータの組み合わせを全て作成
max_p = 2
max_d = 1
max_q = 1
max_sp = 1
max_sd = 1
max_sq = 1
params = []

for p in range(0, max_p + 1):
    for d in range(0, max_d + 1): 
        for q in range(0, max_q + 1): 
            for sp in range(0, max_sp + 1):
                for sd in range(0, max_sd + 1): 
                    for sq in range(0, max_sq + 1): 
                        params.append([p,d,q,sp,sd,sq])

params[:3]
  • Output: [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 2]] ..

 次に, それぞれのパラメータの組み合わせに対するAICの値を算出してみます. 基本的にはAICが最小のモデルを選択すれば, 多くの場合良いモデルが選択できるとされています.

 なお, statsmodels.tsa.statespace.SARIMAXでは, exogの中に季節性やトレンド以外に含めたい外部変数(今回はGRP)を説明変数として追加することができます.

 また, このライブラリは計算に時間がかかることが多いため, 組み合わせ数が多いパラメータ推定の際には, 並列処理を簡単に実行出来るJoblibというライブラリの併用をお勧めします.

# 最終的に予測したいのは繁忙期である1~3月のWAUのため, テスト期間も繁忙期に合わせる
wau_s = wau.head(225)
grp_s = grp.head(225)

# テストするため, 学習用とテスト用データの分割
y_train, y_test = wau_s.head(210), wau_s.tail(15)
X_train, X_test = grp_s.head(210), grp_s.tail(15)

# AIC計算用のメソッドを作成
def aic_calculater(param):
    aic = sm.tsa.statespace.SARIMAX(
                                endog=y_train, exog=X_train, trend="n", freq='W', order=(param[0], param[1], param[2]),
                                seasonal_order=(param[3], param[4], param[5], 52), enforce_invertibility = False, enforce_stationarity = False).fit().aic
    return aic
 
# 並列計算: n_jobs=-1とするとCPUコア数の数だけ並列化される
import joblib
aic_list = joblib.Parallel(n_jobs=-1, verbose=10)([joblib.delayed(aic_calculater)(param) for param in params])
 
# AICが小さくなる順にのパラメータの組み合わせを並べ変え
aic_df = pd.DataFrame({"params": params, "aic": aic_list})
aic_df.sort_values("aic", inplace=True)
aic_df.head(10)

f:id:LIFULL-takezawax:20191223222721p:plain

 AICが小さい順に, パラメータ(p, d, q, P, D, Q)の組み合わせが出力されました. パラメータ推定の結果, (0, 1, 1, 1, 1, 1)の組み合わせを使用することにします.

 また念のためモデルの精度を確認するために, テスト期間のGRPデータを説明変数に持たせたモデルが出力した予測値と, テスト用に分割しておいた実測値の誤差を比較してみます.

 今回は, 誤差の評価指標として時系列データの誤差評価によく用いられるMAPEを使用します.

# AICが最小のパラメータを当てはめる
model = sm.tsa.statespace.SARIMAX(
                                endog=y_train, exog=X_train, trend="n", freq='W', order=(0, 1, 1),
                                seasonal_order=(1, 1, 1, 52),  enforce_invertibility = False, enforce_stationarity = False)

results = model.fit()

# テスト期間の予測値を出力する
y_pred = results.get_prediction(
                                start = pd.to_datetime("2018-12-30"),
                                end = pd.to_datetime("2019-04-07"),
                                exog = X_test, dynamic=False)
 
# 点推定での予測値と区間推定での予測値を取り出す
pred_mean = y_pred.predicted_mean
pred_ci = y_pred.conf_int(alpha = .05)

# MAPEを計算する自作関数を定義
def mape(true, pred):
    
    act = list(true)
    preds = list(pred)
    total = 0
    for i in range(len(act)):
        ape = np.abs((act[i] - preds[i])/act[i])
        total += ape
    mape = (total / len(act)) * 100
    
    return mape

# MAPEを算出
print(mape(y_test, pred_mean))
  • Output: 6.019151544589458

 可視化することで, 予測値と実数値の誤差の様子を実際に確認してみます.

# 予測値と実測値の折れ線グラフの描画
y_test.plot(label='observed', linewidth=4)
pred_mean.plot(label='forecast', alpha=.7, color = "r", linewidth=4)
 
# 区間予測の折れ線グラフを描画
plt.fill_between(pred_ci.index, pred_ci.iloc[:, 0], pred_ci.iloc[:, 1], color='r', alpha=.2)

f:id:LIFULL-takezawax:20191220174604p:plain

 精度をどれだけ求めるか次第ですが, それほど悪くない予測値の動きと判断しました.

 最後にパラメータが確定した後, 残差が正規分布していることや自己相関の有無を確認しておく必要があります. もしモデルが妥当であれば, 残差(説明しきれない部分)はホワイトノイズになるためです.

# 残差を確認する
plot = results.plot_diagnostics()

f:id:LIFULL-takezawax:20191220173343p:plain

 左上から順に, 標準化された残差の系列, 残差のヒストグラム, 残差の正規Q-Qプロット, 残差のコレログラムとなります. どれもおおよそ問題なさそうですね.

 MAPEによる誤差評価の部分を少し省略ましたが, これでモデルの構築(確率過程とパラメータの選択)が完了しました.

予測: 今期のGRPで予想されるKPIの期待値を算出

 いよいよ, 2020年の引っ越しハイシーズンに合計●●GRPのTVCMの出稿した場合, WAUが取りうる値を算出してみます.

 最終的にはCM放映期間中に各週で実際に観測されたWAUの値が, 算出された予測値をはるかに上回った場合, 今回の訴求内容またはクリエイティブがWAUのリフトに寄与した(過去との比較において)と仮定して評価します.

 逆に誤差程度のリフトしかなかった場合は, 「訴求が弱かったのではないか」等の仮説を立て, 次回の出稿に向けて改善点を探す作業に入ります.

 それでは, 未来のWAUを予測値を算出するにあたり, まずは未来のGRPデータを準備します.

# 出稿予定のGRPデータをロードする
future_grp = pd.read_csv("future_grp.csv")
future_grp.DATE = pd.to_datetime(future_grp.DATE)
future_grp.set_index("DATE", inplace=True)
future_grp = future_grp.resample("W").sum()
future_grp.head(15)

f:id:LIFULL-takezawax:20191223234032p:plain

 先ほどMAPEが最小だったパラメータの組み合わせ(0, 1, 1, 1, 1, 1, 52)をモデルに当てはめ, 予測値を算出していきます.

# 手元にある直近までのデータを全てモデルへ
model = sm.tsa.statespace.SARIMAX(
                                endog=wau, exog=grp, trend="n", freq='W', order=(0, 1, 1),
                                seasonal_order=(1, 1, 1, 52), enforce_invertibility = False, enforce_stationarity = False)

results = model.fit()

# 未来の予測値を算出
forecast = results.get_forecast(steps=len(future_grp), exog=future_grp)

# 予測値を可視化
lower = forecast.conf_int()["lower WAU"]
upper = forecast.conf_int()["upper WAU"]

plt.plot(wau, label='original', linewidth=4, color="steelblue")
plt.plot(forecast.predicted_mean, label='SARIMAX', c="orangered", linewidth=4)
plt.fill_between(
                 forecast.conf_int().index, 
                 lower, upper,
                 color='mistyrose')

plt.xlabel('DATE')
plt.ylabel('WAU')
plt.legend()
plt.show()

f:id:LIFULL-takezawax:20191220174625p:plain

 濃い赤線が点推定による予測値であり, 青がこれまで実際に観測された値です. これで評価の基準値となる予測値を出力することができました.

 最後にこの週次の予測値をcsvやexcelファイルとして出力したら, 効果検証に向けた準備は完了です.

# 予測した評価基準値をファイルに落とす
answer = pd.DataFrame(forecast.predicted_mean)
answer.columns = ["NQ"]
answer.round().to_csv("未来のWAU予測値.csv")
answer.round()

f:id:LIFULL-takezawax:20191223234012p:plain

 実際のTVCM放映期間はこの予測値を基準として, WeeklyでKPIの動向をモニタリングしていけば良いと思います.

 以上でSARIMAXモデルの実装の説明を終わります, お疲れ様でした.

③現在LIFULLではデータアナリストを積極採用中

f:id:LIFULL-takezawax:20191222152927p:plain
引用元: http://recruit.lifull.com/interview/

データアナリストの役割: 自分たちの存在意義について

 今回, この記事を通して発信したかったことがもう1つあります. それは「データアナリスト」の価値についての考察です.

 最近は改めて, マーケターやデータサイエンティストがいる中で, データアナリストの存在意義を考えさせられます. 私たちは本当に必要なのか?

 結論から言うと, 我々には「ビジネス課題」と「正しい分析手法」のマッチングを担う役割があると考えています. 他の職種との比較で表現すると, データアナリストはマーケターとデータサイエンティストのちょうど中間に立つような存在だとイメージしています.

 具体的には, マーケター(または営業企画)はプロダクトのマーケティング課題を深く理解しています. 同じくデータサイエンティストも統計や機械学習の手法に知見が深く, またソースコードの実装を通してモデルやアルゴリズムに再現性を持たせるスキルも備えています.

 ただ現状, 多くのビジネス現場ではこの課題と解決手法の距離が遠く, データ分析が本来のインパクトを残せないというジレンマがあるのではと考察します.

 そもそもマーケターや営業企画のメンバーが自分たちが抱えるボトルネックを分析によってより良く解決できること自体を認識していなければ, 決してデータサイエンティストに声がかかることはありません. ただ手法がこれだけ複雑化・多様化した現在では, 手法の網羅・理解も簡単なことではありません.

 そうした背景から特に事業会社では今, ビジネス課題を嗅ぎつけて自らボールを拾いに行けるアナリティクスプレイヤーにもある程度需要があるのではないかと考えています.

 要は「いまの事業にはきっとこういう課題があるよね. この手法使えばもっと良い解決ができそうだから一緒にやりませんか?」 と分析者側から提案していくことで克服される課題もあると考えています.

 実際, LIFULLのデータアナリストチームにはマーケターやセールスの経験があるメンバーが所属しています. 事業にインパクトを残すためにドメイン知識やビジネス課題の理解も重要視しており, そういった嗅覚という部分を大切にしています.

 また海外に目を向けるとNetflixやAirbnbは, データ分析組織や職種が目的ベースでかなり細分化されています. 個人的にはAirbnbのData Scienceチームのリーダーの女性が書いた One Data Science Job Doesn’t Fit All という記事が印象的でした.

 ※データサイエンティストについて少し補足すると, ML/DLなどの徐々に結果を出している先端技術でしか, 解決できない課題や向上できる生産性の余白がビジネス現場には多大にあると思います. 逆に難易度がそれほど高くないタスクに彼らのリソースを当ててしまうのも, もったいない(オーバースペックという文脈で)気がするため, そうした意味でも分析職はもっと細分化されて良いと考えています.

LIFULLが抱えるデータの魅力: 量と多様性の観点から

 ここではLIFULLが所有するデータの魅力について紹介します. 自分はまだ入社して半年ですが, データアナリストとして日々働く中で「恵まれているな」と改めて感じる機会が多くあります.

 まずデータの量に関して. 事業規模では最大のサービスLIFULL HOME'Sは, MAU/DAUといったトラフィック量も国内有数の規模があります.

 また質に関しても, ユーザーデータ(to C)だけでなく, 不動産会社様などのクライアントデータ(to B), 日本全国の何千万件規模の物件をカバーする膨大なコンテンツデータがDBに存在します.

 個人的には, いまBtoB・SaaS企業を中心にセールス・アナリティクスが盛り上がっているように, LIFULLでもto B領域のデータ活用に携われることも魅力的に感じています. また, 不動産検索領域におけるレコメンデーション(不動産物件のパーソナライズ)はまだ世界でも完成系がないため, 非常にやりがいのあるテーマです.

 データ以外にも, 分析業務に必要なツールも大変充実しており, 普段の業務では以下を使用しています.

  • データ抽出: Bigquery
  • データ加工•前処理: Python/R
  • データ可視化: Tableau
  • コード/クエリ管理: Gitlab/Github
  • ナレッジ/チケット管理: Confluence/JIRA

 データ基盤の整備に関しては2年以上前から非常に力を入れており, データエンジニアの先輩方のおかげで, 現在はBigqueryを通して社内のほとんどのデータにアクセスできます.

 会社としても「世界一のライフデータベース&ソリューション・カンパニー」をスローガンとして掲げており, データ活用の重要性に対するコンセンサスが全体で共有されているのもアナリストにとっては非常に働きやすい環境です.

さいごに: データアナリスト(特にマネージャー)を募集中

 タイトル通り, 現在LIFULLでは強力なデータ分析メンバーを新たに募集しています. (マネージャーポジションを担える方は特に)

hrmos.co

 LIFULLではデータアナリストは比較的新しい職種で, 現在は社内に自分たちの価値を伝えていくフェーズにいます. 新設チームだからこそ, 組織づくりと文化づくりにチャレンジできることは, 非常に魅力的なことだと考えています.

 今期も「決めるを支える」をMissionに掲げ, 日々奮闘中です. 我々は, データ分析の価値は「意思決定」の質が向上することであると定義し, どれだけステークホルダーを巻き込み, 事業にインパクトを与えられるかを意識して活動しています.

 こちらの記事を読み, 弊社のデータ分析組織の活動に興味を持っていただいた方は, ポジション問わず気軽にご連絡ください.

 (takezawaakira@lifull.com)への個人メッセージでも構いませんので, まずはカジュアルに他のメンバーも含めてランチにでも行ければと思います.

 長くなりましたが, ご一読ありがとうございました!!

謝辞

 今回実際の業務でのモデリングと本記事の執筆にあたり, 弊社のデータサイエンティスト福富裕康さんからは多くの助言を賜りました. 厚く感謝を申し上げます.