Categories
最適化テスト

コンバージョン最適化に向けた仮説づくりのためのフレームワーク:LIFTモデル

A/Bテストに向けての仮説づくり

時々勘違いしている人もいるようですが、A/Bテストは仮説を検証するための手段です。A/Bテストを通してコンバージョンを向上させるためには、効果的な仮説づくりが不可欠です。コンバージョンを大きく向上させそうな仮説を生み出せさえすれば、後は実装して、テストを行うだけです。

しかし、この仮説づくりこそがA/Bテストを実施するうえで最も難しい部分です。コンバージョン最適化の経験がない人にとっては、何を変更すればコンバージョンが向上するのか検討がつかないかもしれないです。経験がある人にとっても結果が汎用的かどうかの判断は難しく、過去の経験に基いて自信を持って作った仮説が逆の結果になってしまう、ということはよくあることです。

もちろん、仮説が間違っていたということ自体には特に問題はなく、間違ったという結果が確認できた分、テストの価値があったといえるでしょう。しかし、適切な仮説を作れなければ、いくらテストをしても意味がありません。やみくもにCTA(Call to Action)のボタンの色を変える。ちょっとした文章を手直ししただけのテストをする。このようなテストを繰り返して有意差が全然出ない結果が続く。そして何度テストしても一向にCVRが向上しない。こうならないためにも、適切な仮説づくりがコンバージョン最適化のキモとなります(もっとも最悪なのは、適切な検定を行わず見かけの数字だけで結果を判断し、文脈を無視して過度な一般化を行い、正しいものとしてしまうことですが。。。)

では、どうすれば効果的な仮説が作れるのか。まずは改善の「ベクトル」を間違えないことです。単純にCTAボタンの色を変えても効果は出ません。仮に結果が出たとしてもそれは「色が変わったこと」自体の結果ではなく、多くは「視認性の向上」によるものです。どのような仮説に基いて最適化をするとコンバージョンが向上しやすいか、というポイントを抑えておく必要があります。

アメリカのコンバージョン最適化を専門に行っているコンサルティング会社「WiderFunnel社」はA/Bテストの仮説構築化のためのフレームワーク「LIFTモデル」を提唱しています。仮説を考えるための枠組みとしては非常に優れている考え方なので、紹介したいと思います。

LIFTモデルとは

正式には「Landing Page Influence Function for Tests Model」と言うようです。コンバージョン向上に効果的な仮説構築のためのフレームワークとしてWiderFunnel社が作り出しました。WiderFunnelのChris Goward氏は「You should Test That」という素敵なタイトルの書籍も出しており、そちらにもLIFTモデルが詳しく説明されています。

LIFTモデルは図中にあるような6つの要因によって構成されています。それぞれの要因を改善していくことによって、コンバージョン数を向上させていくということが基本的な考えです。

lift_475px2.gif

1. Value Proposition(価値提案)

商品やサービスの価値を提供する部分です。どのような内容でユーザーに商品を訴求するか、どのような見せ方にするのか、どのような追加オファーをするか、などが含まれます。当然といえば当然ですが、Value Propositionは6つのコンバージョン要因の中で最も重要という位置づけで、ここでどのような価値提案をするのかが他の要因にも大きく影響してきます。

2. Relevance (関連性)

Relevanceで特に考慮すべきものはユーザーの考えとLPとの「関連性」です。LPの流入元となる広告では「価格」を訴求しているのに、LPでは全く違う「機能」を訴求したりすると、ユーザーに混乱を招きます。ユーザーが混乱しないように、流入元とLPでメインの訴求内容を合わせたり、用語を統一することがコンバージョン向上へと繋がります。

3. Clarity(明瞭さ)

訴求内容は曖昧さを持たずにシンプルになっているのか、用語はわかりにくくないか、CTAボタンは見落とされていないか、などLPにおける「明瞭さ」には多くの改善の余地があり、コンバージョンの向上にも役立ちます。「明瞭さ」はWeb制作担当が気づきやすい点であり、テストもしやすいので、ここに注力してしまいがちです。しかし、「明瞭さ」はあくまでもワンオブゼムであり、改善の中心は「価値提案」であることを忘れないようにしておくことです。

4. Urgency(緊急性)

Chris GowardはInternal UrgencyとExternal Urgencyの両方を紹介していますが、B2C向けのECサイトではExternal Urgency(外的緊急性)の方が重要でしょう。これは一言で言えば「煽り」です。「今だけ○○円」であるとか「期間限定」などの訴求で「いま買わなくてはいけない理由」を与えます。「いつ買っても同じ」ではなく、「いま買わないと損しますよ」とユーザーにお訴求することでユーザーに購入を促します。TVショッピングとかでよくある手法ですね。Webでも効果的です。

5. Anxiety(不安)

これもよく言われることです。ユーザーの不安感を取り除くことは重要です。Verisignやウィルス対策ソフトのロゴを掲載しているECサイトはよく見られます。しかし、これも文脈によって異なることがある点に注意してください。単純にロゴを貼れば良いというわけではありません。実際に「You Should Test That」の中でもセキュリティシールを貼ったことでコンバージョンが悪化した事例が掲載されています。十分に信頼のある企業でユーザーのITリテラシーが高くない場合は、セキュリティシールそれ自体がセキュリティに対する不安感を想起してしまうこともあります。

6. Distraction(注意をそらすもの)

ゴールに向かって一直線である方がコンバージョンへの確率は高まります。あれもこれも訴求したいばかりに、無関係なリンクを置いたり、別の商品の訴求をしたりすると、ユーザーはそちらに気を取られてしまい当初の目的を忘れてしまいます。全ての訴求内容はコンバージョンというゴールに向かって一貫性を持っているものでなくてはなりません。

 

Value Propositonについて

LIFTモデルは仮説のアイデア出しの考え方としては非常に有用だと思います。一つ文句があるとすると、「Value Propositon」って範囲広すぎないですか?ということです。漠然と「価値提案」が大事だと言われても、そりゃそうですよねとしか言えないです。。。

上述の本やWiderFunnelのブログではもっと詳細に説明されているので、そちらも参照してもらえれば良いと思うのですが、僕なりに「Value Propositon」をもうちょっと分解して考えます。

1. 商品のメインオファー

メインとなるキャッチコピーです。この商品の最大の売りは何かという部分になります。メインオファーを変えるということは商品の売り方を変えるということです。見方によってはモノは同じでも全く違う意味を持った商品となることもあります。旧来のマーケティングでいうところのリポジショニングに当たるとも言えます。メインオファーを変えることによって、想定していなかったターゲット層に響き、爆発的なヒットを生む可能性もあります(もちろん全く反応しない可能性の方が遥かに高いですが)。訴求ポイントがいくつかある場合は、訴求点を見せる順番を変えるだけで効果が出ることもあります。

2. 商品外のオファー

商品とは直接関係の無い部分でのオファーも重要な価値提案要素です。具体的に言うと「ここで買う理由」がこれにあたります。値引きをはじめ、クーポンやポイントなどの金銭的なオファー、配達の早さやサポートの充実などのオファーも重要なコンバージョン要因となります。

3. 訴求イメージ

コピーだけではなく、コピーを効果的に伝えるためのイメージ画像もコンバージョン要因となりえます。モノだけではなく人を使ったほうが良い、であるとか、人が商品を見ている絵だと商品に注目が集まるとか、まことしやかな言説もありますが、仮説として検証するには良い材料ではあります。

4. オファーの量

オファーの質が最も大切なのは間違いありませんが、「量」もテスト対象となりうります。日本のECサイトでは楽天式の長くてストーリーのあるLPが受け入れられていますが、英語圏ではほとんど見られません。どれだけ詳細に(=しつこく)オファーを行うことがコンバージョンへと繋がるのかを検証することは重要なことでしょう。

 

LIFTモデルはコンバージョン改善手法ではない

LIFTモデルはあくまでもA/Bテストに向けた仮説構築のための考え方(=フレームワーク)です。LIFTモデルに基いてLPを作り直せば、コンバージョンが改善します、というものではありません。この点はChris Gowardも強く強調しています。LIFTモデルに基いて作った仮説をA/Bテストで検証することが絶対に必要です。LPを作り直し終わりではありません。A/Bテストをして検証して初めて価値が出るものです。

巷には「こうやってLPを作れば絶対にCVRが改善します」という人がたくさんいますが、100%インチキです。そしてこういう人こそ、CTAボタンの色は赤が一番であるとか、ファーストビューがどうだこうだとか、小手先の適当なことを言います。A/Bテストを何百回やった結果だと言っても、たいていその人が関わっている業界が偏っており、汎用的であるとは言い切れません。

重要なことは自分のサイトで検証していないことは全て「仮説」として考えて、A/Bテストで検証することです。思いつきは直感でも判断はデータに基いて行う。そして、一つ一つ地道に改善をしていく。このような姿勢が大事です。

Categories
Web分析一般

ランダムウォークによる広告効果予測モデル

広告効果の予測モデルを作成する

アトリビューション分析 – マルコフ連鎖モデルの分析方法の続きです。

いくら素敵なアトリビューション分析をしたところで、実際に活用できなければ意味がありません。
小難しい理論を駆使して、アトリビューションモデリングを行い、広告の評価を行っても、「ふーん」で終わってしまうこともよくあります。アトリビューション分析によって、どのようなビジネスインパクトがあるのかを示さない限り、分析をする価値はありません。

以前述べたように、分析には検証型調査と探索型調査の2種類があります。
検証型調査では、アトリビューション効果を踏まえた上での目標設定を行った上で、分析をすることで、目標に対する達成度という視点で評価することができます。
探索型調査において、アトリビューション分析によって分かるようになることはいくつかのパターンがありますが、ビジネスに対するインパクトが大きいのは、「広告効果の予測」ではないかと思います。

広告効果の予測とはどういうことか?一番役に立ちそうな予測は間違いなく、
・「どの広告にいくら投資したら、いくらリターンがあるのか」
ということです。
ただ、広告の価格については、みずものなところがありますので、
・「どの広告からどれだけ流入があれば、何件CVが上がるのか」
について、考えたいと思います。

random_001_R
例えば、「バナー広告からの流入を1万件あげると、CVはどれだけ上がるのか」がわかるということです。流入数さえ分かれば、ある程度の広告価格も推測が着くので、投資対効果も間接的にわかるようになります。

 

ラストクリックによる予測モデル

ラストクリックだけ考えれば、予測モデルは超簡単です。
予想流入数に単純に該当の広告のCVRを掛けるだけです。

(流入数)X CVR = CV数

*過去のデータは当てにならないということもありますが、統計的な分析を行う上では過去のデータに頼らざるを得ません。統計モデルであれば、どのモデルも過去の実績に依存します。逆を言うと、過去のデータがない限り、予測モデルは作れません。これまで起こったことのないことに対する予測については、統計は無力です。

これで良いと言えば良いのですが、純広告のような認知拡大効果が大きそうな広告であったり、価格が高く、すぐにCVしないような商材であれば、ラストクリックのみだと「アシスト効果」が見えなくなってしまいます。アシスト効果を加味した上で広告を評価しましょう、というのがアトリビューション分析ですので、もうちょっと考えてみたいと思います。
*線形回帰による予測モデル
一番シンプルな多変量解析予測モデリングは重回帰分析によるモデル化です。
単純化して数式にすると、

y = a1*x1 + a2*x2 + a3*x3 + a4*x4 + a0

となります。広告モデルに置き換えると、

CV = (流入元Aの係数)*(Aの流入数)+(流入元Bの係数)*(Bの流入数)+(流入元Cの係数)*(Cの流入数)+(流入元Dの係数)*(Dの流入数)+切片
みたいなモデルが作れれば、予測が可能となります。
ただし、このモデルの係数を算出しても恐らく誤差が大きくなりすぎて、使い物にならないモデルになると思います。ロジスティック回帰であるとか、階層ベイジアンモデルなどを使って、モデル化すれば、ある程度役に立つモデルができそうですが、実装がなかなか難しそうな上に、大量のデータを扱うには計算量も大きくなりすぎてしまいます。
なので、もうちょっとシンプルでわかりやすいモデルができれば理想的です。

 

 ランダムウォークによる予測モデル

ここからが本題です。できるだけシンプルで計算量も少ないモデルを作成したいと思います。
以前取り上げたマルコフ連鎖モデルと同じランダムウォークを用いた予測モデル化を検討します。

まず、ログデータを分析して、下記のような遷移行列を作成します(簡単に書きましたが、このデータの作成はすごい大変です。。。後述)。

random_002_R

 

表の見方ですが、表側が流入元、表頭が次回来訪時の流入元もしくはCV/離脱です。
例えば、「ad」から流入した件数が1,132件あって、CVは60件、離脱は675件。次回来訪時に「org」から来た人が300件という見方です。

この表の横%表も作成しておきます。この表は確率を表しているので、「ad」からの「cv」する確率(CVR)は5.30%と読みます。

random_003_R

これらのデータさえできれば、後は簡単です。

・初回ループ

仮に「ad」から5,000件のユーザーを連れてくると仮定します。
その場合、すぐにCVする件数はCVRが5.3%なので、265件となります。
離脱してしまって、2度とサイトに来ない人は59.6%なので、2,981件となります。
残りの1,754件は、再度サイトに来てくれるユーザーです。
このうち、
・また「ad」から来る人は8.8件
・「sem」経由で来る人は282.7件
・「org」経由で来る人は1,325件
・「mail」経由で来る人は265件
となります。

・2回目ループ

この1,754件は、サイトに来たユーザーなので、この人達もCVするかもしれません。
「org」から来た人は1,325件いて、「org」のCVRがは6.01%なので、「ad → org」という流れで、79.6件のCVが生まれます。同様に、他の流入元も足し合わせると、2回目の来訪で全体として、86.9件のCVが発生します。
当然離脱も多く発生して、1,168件が離脱します。残りの499件はCVしなかったけど、またサイトに来てくれるユーザーたちです。

・3回目以降のループ

この499件からはCV22.6件、離脱302件、残り279件はまたループします。と、いうのを繰り返していくと、離脱・CVは次に繰り越さないため、段々とサイトに来る数が収束していきます。もういいや、というくらい数が少なくなったら、ループ終了です。
繰り返した結果は下記のような表となります。

 

random_004_R

 

結果の解釈:このモデルは何を表しているのか?

初回来訪時のCV数(=ラストクリック評価でのCV数)は265件でしたが、このモデルでのCV数合計は386.2件とかなり膨らみます。次回来訪への波及効果によるCV発生が差分である121.2件と解釈できます。
この方法を使えば、簡単にwhat-ifモデルによる予測モデルが構築できます。
例えば、ad 5,000件に加えて、semを2,000件追加すると、ループは下記の表のように変化して、
CV数は482件となります。このように簡易的な予測がすぐにできます。

random_005_R

この予測モデルは論理的にわかりやすく、説明しやすい上に、計算量も少なくてすみます。ちょっと面倒ですが、Excelでも余裕で計算できます。
ただ、Excelで計算すると、対象の流入元の種類が増えた時に大変すぎるので、rubyで実装しました。たくさんループさせてもしょうがないので、10回ループもしくは、1ループあたりのCV数が1件以下になったら、ループ終了としています。

 

 

このモデルでは「ad→sem」や「ad→org」などの流入順序を基に構成されています。この流入順序は「1対のみ」の流入順序です。「ad→sem→org」という複数の遷移情報を持ったデータはこのモデルでは考慮されていません。
例えば、「広告A→広告B→自然検索→CV」という流れを想定したキャンペーンなどを実施した場合は、「広告A→広告B」という流れは評価されていますが、「広告B→自然検索」という流れの時には、事前に広告Aを踏んでいる場合と、広告Aを踏んでいない場合の両方が同じものとして評価されてしまいます。このように複数の施策を組み合わせたキャンペーンを評価する場合には向いていないモデルです。

 

ランダムウォークによる予測モデルの欠点

複数組み合わせのキャンペーンを評価していないという点も欠点といえば、欠点ですが、多くの場合、それほど大きな問題にはならないと思います。このモデルの欠点は、事前に準備しなくてはいけない「遷移行列」を作るのが非常に大変という点です。。。。
流入元情報のデータさえあれば、簡単に作れそうな気がするのですが、なかなか一筋縄ではいきません。
特に、「離脱」の扱いが非常に厄介です。「離脱」の定義をどう置くのか。仮に「3ヶ月サイト来訪なし」を離脱と置くと、3ヶ月前までのデータしか対象にすることができません。直近3ヶ月のデータは再度サイトへ来るかもしれない人も含まれたデータだからです。
その上、3ヶ月以上前のデータを対象にして集計しようとしても、集計期間後のデータをルックアップしないと離脱かどうかの判別ができないため、データ件数が多くなると、処理が大変になります。
直帰率などの代替のデータに係数をかけて離脱とみなす、という方法が現実的かもしれません。

また、「ad→sem」のような流れは、ユーザー(ブラウザ)単位で起きるので、ユーザーレベルでの集約が必要となります。アクセス解析ツールでうまく実装すれば、流入元履歴のデータを取得することができますが、基本的にセッション単位のデータとして集計されて出てくるので、ユーザー単位への集約は必要となります。この作業もかなり大変です。

また、「CVは初回CVだけを対象にするのか」や初回以外も対象にする場合、「一度、CVした人は流入元を一旦クリアするのか」など、離脱以外にも細かなデータ定義が必要となってきて、このデータ定義によって、計算結果も大きく変わってきます。

ランダムウォークによる予測モデル自体はシンプルなのですが、事前作業である遷移行列作りが大変なのが、大きな欠点です。

 

とはいえ、一旦システムを構築してしまえば、後は自動的に遷移行列作りは可能です。
シンプルな予測モデルとしては、結構良さそうな気がするのですが、いかがでしょうか?

 

Categories
Web分析一般

Pythonデータ解析ライブラリpandasと遊ぶ:クロス集計~検定・残差分析まで

PythonでRっぽいことができるpandas

Pythonにはpandasという素敵なライブラリがあります。pandasを簡単に言うと、PythonにRのデータフレームのような型を持たせるライブラリです。これによって、行列計算が劇的に楽になり、これまでRでやっていたような集計作業がPythonでも楽にできるようになります。
詳しくは公式ページをどうぞ

http://pandas.pydata.org

RでできるならRでやればいいじゃん、という意見はもっともですが、Pythonを使うことによるメリットも当然あります。
最も大きなメリットは、データクリーニングから集計・分析までPythonのみで実施できるという点です。データクリーニングに必要なテキスト処理などの地道な作業はPythonの方が得意です。当然、データベースからデータを抽出し、加工することもPythonなら普通にできます。もちろん、Rでもできることはできますが、Pythonほど便利ではありません。
地味なメリットとしては計算が早いという点です。ベンチマークをとったわけじゃないですが、体感的にはpandasで集計した方がかなり早いです。というのも、pandasでは裏でnumpyを利用しており、このnumpyはC言語で書かれています。また、公式サイトやオライリー本を読む限り、かなり最適化を行なっているようです。
計算処理が早いということは、古いPCでも大量のデータを扱えるということでもあります。SQLのJoinのようなデータの結合処理をした時は10万件以上のデータ同士でも、それほど時間がかからずに完了しました(ちなみに同じ作業をRで行ったら、PCがフリーズしました)。データ分析をする時は、速いが正義となります。
ただ、pandasにも弱点は当然あります。一番大きな問題はpandasには統計解析処理が「無い」という点です。。。集計まではできるのですが、統計処理を行うのは、scipyという別のライブラリでやれというスタンスです。scipyやその関連ライブラリも非常に充実しているのですが、Rと比べると弱かったり、ドキュメントが少なかったりして困ることが多いです。今回のお題のカイ2乗検定のやり方も中々見つからなくて困りました。

 

pandasでクロス集計

pandasの機能は膨大で全部紹介するのは無理ですので、今回はアンケート調査などでよくやるクロス集計表とその検定を行おうと思います。データはこちらです。男女別に好きな動物を聞きました的なイメージです。

データを読み込みます

#関連ライブラリの読み込み
import numpy as np
import pandas as pd
import scipy as sp
import scipy.stats
from pandas import DataFrame, Series

#データ読み込み
data = pd.read_csv("data.txt", sep="\t")

#データのサマリ
data.describe()
#=>
#   gender  animal
#count  38  38
#unique 2   3
#top    f   dog
#freq   25  15

#単純集計
for col in data:
    print pd.value_counts(data[col])
    print "\n"
#=>
#f  25
#m  13
#dtype: int64
#
#dog    15
#cat    14
#usagi  9
#dtype: int64

 

こんな感じで簡単にデータを読み込んで、サマリを見ることができます。

 

pandasでクロス集計

クロス集計も簡単です。ついでに行%表も作っちゃいましょう。

 

#クロス集計
crossed = pd.crosstab(data.gender, data.animal)
print crossed
#=>
#gender cat dog usagi
#f  3   13  9
#m  6   2   5


#行%表の作成
arr = []
for row in crossed.T:
    arr.append(crossed.T[row] / float(crossed.T[row].sum()))
crossed_per = DataFrame(arr)
print crossed_per
#=>
#animal cat dog usagi
#f  0.120000    0.520000    0.360000
#m  0.461538    0.153846    0.384615

 

ここまではpandasのみでサクッと作れちゃいます。
このクロス集計表に対して、カイ2乗検定を行います。

 

scipyでカイ2乗検定

クロス集計表のカイ2乗検定はscipy.stats.chi2_contingencyを使います。
scipy.stats.chi2_contingencyの戻り値は、tuple型で上から
・カイ2乗値
・P値
・自由度
・期待値
となっています。
ではカイ2乗検定を行います。

 

res = sp.stats.chi2_contingency(crossed)
#p-value
print res[1]
#=> 0.028280095

P値が0.05以下なので、有意差ありという結果です。

 

地道に残差分析

ここからが本番です。
カイ2乗検定で有意差が認められた場合、残差分析を行なってクロス表のどのセルが効いて違いを作ったのかを探ります。

Rであればchisq.test()の戻り値に調整済み標準化残差が含まれているので簡単なのですが、scipyには探した限りありません。
自分で作るしかありません。。。

def chi_sq_test(df):
    res = {}
    #カイ2乗検定の実施→カイ2乗値、p値、自由度、期待値が戻り値
    df_chi = sp.stats.chi2_contingency(df)

    res = {
        "data" : df,
        #p値
        "p_val" : df_chi[1],
        #期待値
        "df_exp": DataFrame(df_chi[3])
    }

    #期待値のカラム名とインデックス名を基データに合わせる
    res["df_exp"].columns = df.columns
    res["df_exp"].index = df.index

    #残差
    res["df_res"] = df - res["df_exp"]
    res["df_res"].columns = df.columns
    res["df_res"].index = df.index

    #行%の計算
    arr = []
    for row in df.T:
        arr.append(df.T[row] / float(df.T[row].sum()))

    res["df_per"] = DataFrame(arr)
    res["df_per"].columns = df.columns
    res["df_per"].index = df.index
   
   
    #残差分析用前処理
    row_sum = df.T.sum()
    col_sum = df.sum()
    full_sum = float(row_sum.sum())
   
    #残差分散を算出
    arr_all = []
    for r in row_sum:
        arr = []
        for c in col_sum:
            arr.append((1-(r/full_sum))*(1-(c/full_sum)))
        arr_all.append(arr)

    res["df_res_var"] =  DataFrame(arr_all)
    res["df_res_var"].columns = df.columns
    res["df_res_var"].index = df.index

    col_size = df.columns.size
    row_size = df.index.size

    #調整済み標準化残差を算出
    arr_all = []
    for r in np.arange(row_size):
        arr = []
        for c in np.arange(col_size):
            arr.append(res["df_res"].iloc[r].iloc[c] / np.sqrt(res["df_exp"].iloc[r].iloc[c] * res["df_res_var"].iloc[r].iloc[c]))
        arr_all.append(arr)

    res["df_res_final"] = DataFrame(arr_all)
    res["df_res_final"].columns = df.columns
    res["df_res_final"].index = df.index
       
    return res

#関数の実行
res = chi_sq_test(crossed)

#p値の出力
print(res["p_val"])
#=> 0.028280095

#調整済み標準化残差の出力
print(res["df_res_final"])
#=>
#gender cat dog usagi
#f  -2.349377704162738  2.190723321954562   -0.14923492309463762
#m  2.349377704162738   -2.1907233219545614 0.149234923094637

 

catとdogが1.96を超えています。この2つが影響を与えて有意差が出たと言えそうです。
あまりPythonに慣れていないので、残差分散とか調整済み標準化残差とかの計算がもっとエレガントにできそうな気はしますが、一応これで残差分析までできました。
データ集計までは非常に楽できるのですが、そこからscipy使うのに大きなハードルがあるという感じですね。単純にインタラクティブな分析をするのであれば、Rの方が使いやすいですが、アプリケーションとして実行させるならPythonのみで作った方が楽そうです。

Categories
Web分析一般

アトリビューション分析 – マルコフ連鎖モデルの分析方法

広告効果測定におけるアトリビューション分析では様々なモデルが提案されています。通常よく利用されるのは、「均等分配モデル」、「U字型モデル」、「ファーストクリック重視モデル」、「ラストクリック重視モデル」の4つのモデルです。これらの分析は非常にシンプルで誰でもすぐにわかります。通常はこれらの分析で十分ですが、機械学習の考え方を応用したモデルも最近使われるようになってきました。

特によく使われるのは、「マルコフ連鎖モデル」と「ベイジアンネットワークモデル」です。どちらも確率論をベースとしたモデルです。
これらの分析の方法論を調べようと思ったのですが、これといった分析方法はさすがにネット上には掲載されておらず、ちょっと困っていました。マルコフ連鎖モデルであれば、以前勉強したことがあるので、できそうな気がします。なので、自分でテキトーにアトリビューション分析のマルコフ連鎖モデル作っちゃえというのが今回のエントリーです。

*注意:専門家ではない人間が思いつきで作ったモデルですので、あくまでも参考程度にしておいてください。
専門的にマルコフ連鎖ソリューションを提供している企業は、同じマルコフ連鎖を用いて全く違う分析をしている可能性もあります(少なくとも本エントリの分析方法と同じではないと思います)。

 

マルコフ連鎖とモデル化の考え方

マルコフ連鎖とは、を調べるとそこそこ難しいので、Wikipediaでも参照してください。
http://ja.wikipedia.org/wiki/マルコフ連鎖

Wikipediaにも記載されているように、Googleのページランクもマルコフ連鎖によって定義されています。アトリビューションモデルとしてマルコフ連鎖を用いる場合は、ページランクの考え方とほぼほぼ同じと考えても良いと思います。

Googleのページランクでは

  • より多くのリンクを集めているページは良いページ
  • 良いページからリンクを集めているページは良いページ

と考えます。
(*現在のGoogleはもっと複雑なロジックを採用しています)

この考え方をアトリビューション分析のモデル化に応用します。
どういうことかというと、流入状況の順番をリンクとして捉えて、リンク構造を分析するというイメージです。

例えば、
Yahoo!バナー>Googleリスティング>アドネットワークバナー>CV
という流れでCVまで到達したユーザーを考えます。
前後の流入元を組み合わせて、

 

  • 初回>Yahoo!バナー
  • Yahoo!バナー>Googleリスティング
  • Googleリスティング>アドネットワーク
  • アドネットワーク>CV

 

という風に分解します。
初回流入も評価するために、最初のYahoo!バナーは初回と組み合わせています。

これをページランクで言うところの、「リンク元>リンク先」と考えて分析します。
つまり、一番上のものは、「初回」というページから、「Yahoo!バナー」というページへリンクが貼られていると考えます。そうすることで、ページランクと同じロジックでの分析が可能となります。

つまり、この分析はどういうことかというと、
「流入媒体の前後の繋がりを考慮した上での効果測定モデル」
と考えられます。

 

マルコフ連鎖モデルの計算方法

ここから実際の計算に入ります。元のデータはこれを使用します。
(ちなみにアクセスログからこのデータを作るのは結構しんどいです)

First>Affi>CV
First>Google>Yahoo>Affi>CV
First>Yahoo>CV
First>Google>Affi>CV
First>Yahoo>Affi>Affi>Google>CV
First>Affi>Google>Affi>Yahoo>CV
First>Affi>Yahoo>Affi>CV
First>Google>Affi>Affi>Google>CV
First>Yahoo>Google>Google>Google>CV
First>Yahoo>Affi>CV
First>Google>Yahoo>Yahoo>Yahoo>CV
First>Affi>Google>Yahoo>Yahoo>CV
First>Affi>Affi>Yahoo>Affi>CV
First>Affi>Affi>Affi>CV
First>Google>Affi>Affi>Affi>CV
First>Affi>Affi>Affi>Affi>CV
First>Google>Affi>Google>Affi>CV
First>Yahoo>CV
First>Affi>Yahoo>Yahoo>CV
First>Affi>Affi>Google>Affi>CV
First>Yahoo>Yahoo>Yahoo>Affi>CV
First>Google>CV
First>Yahoo>Yahoo>Affi>Affi>CV
First>Google>Yahoo>Affi>Affi>CV
First>Affi>Affi>Yahoo>Affi>CV
First>Google>Affi>Affi>Affi>CV
First>Affi>Google>Google>Google>CV
First>Yahoo>CV
First>Yahoo>Yahoo>Yahoo>Yahoo>CV
First>Google>Yahoo>Google>Google>CV
First>Affi>Affi>Google>Yahoo>CV
First>Yahoo>Affi>Google>Affi>CV
First>Yahoo>Affi>Affi>Affi>CV
First>Yahoo>Google>Google>Affi>CV
First>Affi>Yahoo>CV
First>Google>Yahoo>Google>Google>CV
First>Affi>Yahoo>CV
First>Google>Google>Yahoo>Affi>CV
First>Yahoo>Affi>CV
First>Yahoo>CV
First>Google>Affi>Google>Affi>CV
First>Affi>Google>Affi>Google>CV
First>Google>Affi>Google>Affi>CV
First>Affi>Yahoo>Yahoo>Affi>CV
First>Yahoo>CV

このデータから、遷移行列を作成します。
上記のように、データを分解して件数を数えると、下記のような行列を作ることができます。

matrix1

この遷移行列からページランクを生成します。
ページランクの計算方法は下記のエントリーを参照してください。

サイトの導線を評価する:応用編2

ページランクを計算すると下記のようになります。
 

  • First → 0.05
  • Affi → 0.30
  • Google → 0.19
  • Yahoo → 0.20
  • CV → 0.26

 

ページランクが高いほど、重要な媒体と考えれれます。FirstとCVは媒体ではないので除外すると、Affiが圧倒的に高く、次いで、Yahoo!、Googleとなります。
これで完成です。と言いたいところですが、このモデルだと都合が悪いことがあります。全てCVに繋がっているため、CVのページランクもそこそこ高くなっているのですが、このCVからの還元がありません。ページの前後のみで計算しているため、最終ゴールであるCVからのリンクはゼロという状態です。つまりCVへの貢献度が全く評価されず、単純に「他の媒体からの流入直後に流入する媒体が高く評価されている」という状態です。
これではまずいので、CVからの逆流も遷移行列に組み込みます。
CVからの逆流を組み込んだ遷移行列が下記です。

matrix2

 
ページランクを計算すると、こうなります。

  • First → 0.00
  • Affi → 0.38
  • Google → 0.18
  • Yahoo → 0.21
  • CV → 0.24

 

コンバージョンとのリンクが多いAffiのページランクが大きく増加しました。これだけでも良いといえば良いのですが、まだ評価していないイベントがあります。ファーストクリックです。
ファーストクリックからの遷移は組み込んでいますが、ファーストクリックへの遷移は行列に組み込まれていません。そのため、ファーストクリックからのリンクがいくら多くても評価は全く上がらない状態となっています。CVと同じようにファーストクリックからの逆流遷移も遷移行列に組み込みます。

matrix3

 
ページランクを計算すると下記のようになります。

  • First → 0.16
  • Affi → 0.31
  • Google → 0.17
  • Yahoo → 0.19
  • CV → 0.16

ファーストクリックは3媒体ともに大差なかったので、相対的にはそれほど評価は変わっていませんが、Firstクリックのページランクが上昇したことがわかるかと思います。これが最終的な評価となります。
これを見ると、アフィリエイトが最も評価が高い媒体なので、もう少しアフィリエイトの予算を増やしても良いと言えます(実際に分析する際はCPAや引き上げ率、LTVなどを加味しながら予算分配を決定していきます)。
もし、ラストクリックを高く評価したいのであれば、「各媒体>CV」の数に係数を掛ければCVのページランクが上がるため、CVからの逆流が多い媒体の評価も上がります。

 

マルコフ連鎖モデルの優れている点

今回のマルコフ連鎖モデルの優れている点は、

  • 各媒体の流入順序も考慮した分析が可能

という点です。
均等分配モデルや、U字型モデルなどの単純モデルはファーストクリックとラストクリック以外の媒体の順番を考慮していません。マルコフ連鎖モデルは各媒体の順番をリンクとして見ることで、媒体間の関連性も評価軸として組み込んでいます。
また、遷移行列を作ることで各媒体間の関連性も評価できます。例えば、遷移行列を見ると、ファーストクリックで多く入ってきた媒体、ラストクリックとなったことが多い媒体、アフィリエイトの次に入ってくることが多い媒体などがひと目でわかります。

マルコフ連鎖モデルの問題点は直感的ではないという点です。均等分配モデルやU字型モデルであれば、話を聞けばすぐに理解できるのに対して、マルコフ連鎖モデルを理解するのには、ちょっとした基礎知識が必要です。また、「媒体流入の流入順序を考慮した分析」によって、精度がどれだけ上がるのかも疑問です。ベイジアンネットワークモデルも事後確率を用いるだけで基本的な考えは、マルコフ連鎖モデルと大きく変わりません。データソースの作り方をもっと工夫すれば、より精度の高い分析/予測モデルができるかもしれません。

Categories
Web分析一般

なぜそのECサイトでモノを買うのか

ECサイトで購入する理由

なぜ人はECサイトでモノを買うのか?ここでは、あえてECサイトである必要もありません。なぜ、通信販売でモノを買うのか?
その理由は単純です。以前、アンケートをとったことがありますが、以下の3つに集約されました。

  • 価格が安いから
  • そこで買った方が便利だから
  • そこでしか買えないから

この中で最も多い意見は圧倒的に「価格が安いから」です。大部分のユーザーは流通の仕組みを(中途半端に)理解していて、ECサイトでは、人件費・店舗の地代・在庫管理などでコストカットできるとわかっています。そのため、ECサイトでは価格が安いことを強く期待します。その上、価格.comのような価格比較サイトまでチェックして、最安値のサイトでモノを購入しようとします。逆に言うと、他より価格が安くないとユーザーは商品を購入しません。それくらい、ECサイトにおいては、価格はシビアです。
次の理由「そこで買った方が便利だから」もわかりやすいと思います。例えば、低価格の大型家具などは、実際モノをみる必要もないし、店舗に行ったところで、どうせ配送をお願いしないと自宅まで持って帰ることができません。このような商品はECサイトでの販売(通信販売)に向いており、製品力で勝負することができます。
3つ目の理由は「そこでしか買えないから」です。これは純粋に流通チャネルがECでしかない場合です。どこの店舗でも取り扱っていない商品、Web限定の商品、など「ここでしか買えない」というプレミア感が重要です。これは純粋に製品力の勝負となります。魅力のある製品であれば、多少高くても購買してもらうことができるでしょう。

 

この3つで勝負できない場合はどうすれば良いのか

ECサイトで売り上げをあげるためには、この3つの理由のどれかに応えることが一番の方法です。「価格を安くする」、「ECの方が便利な製品を取り扱う」、「このサイトでしか買えない商品を取り扱う」。
しかし、この3つの戦術を採用できる場合の方が稀です。「価格を安く」しようとすると、利益を大きく圧迫します。価格.comで最安値常連の店舗が倒産したことは記憶に新しいです。また、その他小売店や卸売店との関係上、なかなか値引きできないことも多いです。このことは、ユーザーは理解してくれません。「EC向きの製品をを取り扱う」にしても、そもそもそのような商品は取り扱っていない場合も多いです。また、仮に取り扱っていたとしても、競合が同じ商品を取り扱っていれば、すぐに価格競争となります。3つ目の「ここでしか買えない商品を取り扱う」にしても、その商品自体に魅力がなければ購入してもらえません。そんな商品を開発できるのであれば、誰も苦労しません。
では、どうすれば売り上げをあげることができるのか。ユーザーにアンケートで聞いても答えは出てきません。ユーザーの行動を考えてみましょう。
一つ考えられるのが、「ついで買い」です。ECサイトでは何らかの商品を買う「ついでに」、何か別の商品を買うことがよくあります。デジタルカメラを買うついでに、SDカードや液晶保護シールを買うなど、よくあります。これにはレコメンデーションエンジンが大きな力を発揮します。
しかし、「ついで買い」にしても、「ついで」になる前の商品を購入してもらわなくてはいけません。そもそも何らかの商品を購入する意思がある人しか、「ついで」で商品を購入してもらうことはできません。
次に考えられるのは、ユーザー行動の「シェア」を独占することです。大部分のユーザーは検索結果の1ページ目しか閲覧しません。3ページ目に表示されるサイトに最安値の商品があったとしても、ユーザーは気づきません。1ページ目に表示される数件のECサイトの中でもっともバリューを感じられるサイトで商品を購入して行きます。仮に検索結果ページの1ページ目をすべて自社のECサイトで占有してしまえば、ユーザーの選択の余地はありません。「そこでしか買えない」状況を作りあげることができます(実際は当然無理ですが)。
この方法は強力です。うまくやれば、効果を発揮するでしょう。ただ、少し本質的でないような気もします。他に方法はあるのでしょうか。
確かに「特別安くない」、「特別ECで便利ではない」、「ここでしか買えないというわけではない」にも関わらず、大きな売り上げをあげているサイトがあります。そのようなサイトは何をやっているのでしょうか。このようなサイトに共通して言えることは、「ECサイト自体に魅力がある」ということです。「このサイトで買いたい」と思わせる何かが、このようなサイトにはあります。それは何なんでしょうか。

 

ECサイトもメディアである

マクルーハンは「メディアはメッセージである」と言いましたが、ECサイトは店舗であると同時に、メディアでもあります。ECサイトそれ自体にメッセージが含まれています(そういう意味では実店舗も同じですが)。ECサイトでも「ここで買いたい」と思わせることができるのではないでしょうか。
一つヒントになることがあります。北欧、暮らしの道具店を運営するクラシコムさんがWebSig会議でECサイトのメディア化について講演していました。僕は実際に参加したわけではないので、詳しくはわかりませんが、資料を見ると、「メディアとしての魅力を高め」、「最愛のポジションを獲得する」とあります。恐らくこれが、3つの基本戦術が使えない場合の最高の戦術ではないかと思います。
ECサイト上で有益な情報を提供する、魅力的なユーザーの経験を提供するなどして、メディアの価値を高めることで売り上げにも繋げることができます。
ECサイトで有益な情報を提供したところで、ユーザーはそれを見て他の安いサイトで購入するのではないか?という意見もあるでしょう。恐らく当然、それはあります。しかし、「他より多少高くても」ここで買おうという気持ちになることもあります。有益な体験をした「見返り」として、「ここで買う」という行動をとります。心理学では「代償行動」という用語で説明できると思います。代償行動とは、「何らかの目標が達成できない場合、別の類似な方法で代替しようとする行動」です。ECサイトで有益な情報を得て、感謝を示したいという「借り」ができます。その「借り」を返す方法は、直接会って感謝を示すことができない以上、サイト上で買い物をして返そうとします。これで売り上げをあげることができます。
もちろん、全てのECサイトで同じ方法をとることはできないかもしれませんが、この方法であればかなり普遍的に利用できるのではないかと思います。

 

メディアとしてのECサイトを評価する

ECサイトでメディアとしての価値が重要であるならば、それをきちんと計測し、KPIに組み入れていきたいところです。該当ページのPV数・訪問数は「メディアが見られていた」ことの証明となるので、KPIとして必要になります。それに加え、ある程度「ロイヤリティ」や「エンゲージメント」を評価する仕組みが必要になります。これは、ページ内に満足度などの簡易アンケートを設けて評価する、「Facebookのイイね数」や「はてなブックマーク」の数で評価するなどの方法が考えられます。また、1ページ内に多くの情報を詰め込む場合は、ページの「スクロール率」なども評価項目として考えられるでしょう。

 

最愛のポジションを築くために

ECサイトのメディア化が重要なのはわかった。測定方法も考えた。では、最愛のポジションを築くために何をすればいいんでしょうか。それは、サイトごとに違うんだと思います。まずはユーザーをよく知ることから始めるべきです。ユーザーが望んでいること、それは多くの場合、潜在的なものです。ユーザーに聞いても答えてくれません。ユーザーの行動を観察するか、ユーザーとコミュニケーションをとって分かり合うか、の方法で知るしかありません。まずは、ユーザー理解が何よりも重要なんだと思います。
(あえて今回は触れませんでしたが、マイナスの要因を排除することも当然重要です。cwf. FUD

Categories
GoogleAnalytics

Googleアナリティクス 複数ドメイン計測でのtaget=”_blank”

Googleアナリティクスで複数ドメインを跨いで計測するには、ちょっと工夫が必要です。
まず、

_gaq.push(['_setAllowLinker', true]);

として、複数ドメイン計測を可能にした上に、
onClick属性に

 _gaq.push(['_link', "[リンク先URL]"]); return false;

を追加してあげればOKです。
これで、URLパラメータにcookie情報を付与して、リンク先に遷移します。このURLパラメータを読み込むことでセッション情報を維持しています。

しかし、aタグの属性でtarget=”_blank”を指定して、
別ウィンドウで開こうとするとうまく機能しません。

たとえば、

<a href="http://www.yahoo.co.jp/" target="_blank" onclick="_gaq.push(['_link', this.href]); return false;" rel="noopener noreferrer">Yahoo1</a>

このようなコードで計測しようとすると、新規ウィンドウが開かずに、
普通にページが遷移してしまいます。
「target=”_blank”」の位置を変えても全く動作は変わりません。
「return false;」を削除すると、新規ウィンドウが開きますが、同一ウィンドウ上でもページ遷移が行われてしまいます。

これは、”_link”メソッドにリダイレクトが含まれているからだと思われます。”_link”を受け取ると、リンク先URLにcookie情報を付与して、新しいURLを作り、そのURLへリダイレクトするという作りになっているようです。
なので、”_link”メソッドを使って、新規ウィンドウを開くことは諦めた方がよさそうです。その代わりに、「window.open(「リンク先URL」)」でリンク先のURLにパラメータを付与したものを記載できれば良いと思われます。

そこで、GAのトラッキングAPIを見てみると、「_getLinkerUrl」というものがあり、cookie情報をパラメータとして付与したURLを返してくれそうな気がします。
http://code.google.com/intl/ja-JP/apis/analytics/docs/gaJS/gaJSApiDomainDirectory.html#_gat.GA_Tracker_._getLinkerUrl

これを使って、

<a href="http://www.yahoo.co.jp/" target="_blank" onclick="window.open(_gaq.push(['_getLinkerUrl', this.href])); return false;" rel="noopener noreferrer">Yahoo1</a>

とすれば、新規ウィンドウでパラメータ付きのURLのページが開くはずです。。。

しかし、これがうまく動かないんです。。。
実はこの「_getLinkerUrl」は非同期コードでは動きません!ひどいです!(他にもいくつか_get~というメソッドが非同期では動かないのがあります。。。)

かといって、自分でcookie情報を読み込んで、色々と作業するのは面倒すぎます。

色々調べたのですが、ここだけ同期用のトラッキングコードを使ってしまうのが一番楽そうです。

_gat._getTrackers()[0]._getLinkerUrl([リンク先URL])

こんな感じで同期用コードの_getLinkerUrlを使うことができます。
これをwindow.open()中で使えば、うまくいきそうです。
こんな感じになります。

<a href="http://www.yahoo.co.jp/" target="_blank" onclick="javascript:window.open(_gat._getTrackers()[0]._getLinkerUrl(this.href))" rel="noopener noreferrer">Yahoo2</a>

これでcookie情報をパラメータに付与した形で、新しいウィンドウが立ち上がります。
 
 

面倒なので一括で指定したい

いちいち、aタグにonclick属性をシコシコ書いていくのは面倒なので、1つのJSファイルで一気に指定できるようにしたいですよね。
jQueryを利用して一気にやってしまいましょう。
 

作戦

  • 設定で測定対象ドメインを決める
  • クリックしたリンクのリンク先URLが測定対象ドメインで、かつ自ドメインではない場合、_link(またはwindow.open(~~))を走らせる
  • デフォルトの操作をストップさせる(preventDefault)

という流れでいこうと思います。
aタグのonclickイベントにバインドさせる場合、preventDefault()を付けてないと普通のリンククリックのイベントが動いてしまうので、ちゃんと動作をストップさせないとうまく動作しません。

こんな感じのソースコードになります。

$(function(){
    var _gaConf = {
        internalDomain :
            //計測対象のドメインをココに記載
            [
            ".livedoor.com",
            ".yahoo.co.jp",
            "localhost"
            ]
    }

    //target="_blank"だったらwindow.open()。そうでなかったら'_link'を利用。
    var makeGaLink = function(atag){
        var targetAttri = $(atag).attr("target");
        if(targetAttri === "_blank"){
            window.open(_gat._getTrackers()[0]._getLinkerUrl($(atag).attr("href")));
        }else{
            _gaq.push(['_link', $(atag).attr("href")]);
        }
        return false;
    }

    //aタグにバインド
    $("a").click(function(evt){
        var targetUrl, i;
        targetUrl = $(this).attr("href");
        for(i=0; i<_gaConf.internalDomain.length; i++){
            if(targetUrl.indexOf(_gaConf.internalDomain[i]) !== -1 && targetUrl.indexOf(location.host) === -1){
                //パラメータを付けてリンク先へ移動
                makeGaLink($(this));
                //デフォルトの動作をストップ
                evt.preventDefault();
            }
        }
    });
});

「_gaq.push([‘_setAllowLinker’, true]);」を設定したうえで、jQueryとこのJavaScriptを読みこめば、うまく動くと思います!
上の例だと「yahoo.co.jp」と「livedoor.com」へのリンクには全部cookie情報を付与した形でのリンクになります。

IE6/7ではテストしてないので、動くかどうかわかりません!

Categories
javascript

[js]サーバーログ型ツールでもイベントトラッキングがしたい

ビーコン型ツールでは当たり前のイベントトラッキングですが、サーバーログ型でやろうとすると中々難しかったりします。
そこで、ちょっとだけ簡単にイベントトラッキングをできるようにJSを書いてみました。
ものすごいニッチなニーズだとは思いますが。。。

作戦としては単純で、
・GAの真似をして4階層でイベントを定義
・URLパラメータに変換
・透明gif画像をパラメータ付きで呼び出す
(・解析ツール側で頑張って解析)
です。

 

使い方

1.まずはビーコン画像をサーバーにアップします。
1×1 pixelのgifがいいと思います。
wikipediaとかにあったりします。
http://en.wikipedia.org/wiki/File:Transparent.gif

2.次にJSファイルのURLの箇所をビーコン画像をおいたパスに修正します。

3.HTMLからJSファイルを呼び出してください。

4.「_evTrac.track([category,action,label,value])」で、パラメータ付きのビーコン画像を呼び出すようにしています(valueのみ省略可です)。

aタグのクリック回数を取得したい場合は、aタグにonClick属性を追加して下記のようにしてみてください。

<a onClick="javascript:_evTrac.track(['clcik_data','click','click01']);" href="/test.thml">Aタグ</a>

外部サイトへのリンクを一括設定で取得したい場合は、jQueryを使ってこんな感じで書けばいいと思います。

$(function(){
  $("a").click(function(){
    var href = ($(this).attr("href")).replace(/http($)?:///, "")
    _evTrac.track(["outbound",href,location.pathname]);
  });
})

 

 

ソースコード(coffee-script版)

ソースはcoffee-scriptで書いたので、coffee-scriptの方のソースコードを載せておきます。
JS版は、github(gist)に置いてます。
https://gist.github.com/2156661

coffee-script版

 

 

注意

たいていのサーバーログ型解析ツールでは、gif画像の解析は行わないように設定されています。
フィルター設定で、ビーコン画像だけ解析対象にするようにうまく設定してください。

Categories
javascript

[JS]同期ロード・非同期ロードの両方が可能なJavaScriptローダー

ちょっと必要があって、JSローダーを書きました。
ワンタグで複数のJSを読み込めるようにします。

こういうのは他にも結構あるんですけど、
同期と非同期がどっちも選べて使いやすそうなのが無かったので、自分で書いてみました。
ついでにjQueryとGAの読み込みは簡単にできるようにしています。

HTMLファイルのどこかに

<script type="text/javascript" src="loadingMachine.js"></script>

こんな感じで仕込めばよいかと思います。

読み込むファイルは、
loadingMachine.jsの最初の方にある
var files = [ …
のところでサンプルを参考にして設定しちゃってください。

Categories
Web分析一般

探索型調査、検証型調査、そしてWeb分析

データ分析と制約条件

企業がデータ分析に基づいた意思決定を行うにあたって、制約条件となっているものは何か。多くの場合、それは「人」です。データはある、解析するためのツールもある、でもデータを分析することができるアナリストがいない。または、アナリストがいないため何が必要なデータなのかがわからない、エンジニアがいないため必要なデータが取得できない。これが多くの企業の現状だと思います。
では、仮に十分な「人」がいるとします。その場合、次に制約条件となるのは何でしょうか。それは分析の目的やデータの種類によって変わってくるでしょう。「データ」が制約条件となる場合もあれば、「解析ツール/分析インフラ」が制約条件となる場合もあるし、「組織」が制約条件となる場合もあるでしょう。最近は、インフラの整備が進んできていて、分析しなくてはいけないデータが溢れているという状況も多くあります(バズワードである「ビッグデータ」はこのことでしょう)。つまり、場合によってはデータが制約条件となっていない場合もあります。
このように、データが豊富にある状況であれば、データマイニングをすることができるようになります。「今、そこにある膨大なデータの中から意味のある発見をする」、それがデータマイニングです。
データマイニングを含めて、「データ」を分析の出発点とし、仮説を見つけ出す方法を「探索型調査(仮説探索型調査)」と呼びます。

 

仮説探索型調査と仮説検証型調査

仮説探索型調査は非常に魅力的なアプローチ方法です。データを分析していく中で、当初思いもしなかった発見があるかもしれません。企業に蓄積された膨大な量のデータや、そのデータを分析するためのインフラ/ソフトウェアによって、仮説探索型調査であるデータマイニングも特別なことではなく、一般ユーザーでも使いこなせるようになってきました。
当然、定量的なデータ(量的データ)だけではなく、定性的なデータ(質的データ)も探索型調査には使われます。というよりも、質的調査の場合は探索型が中心です。特定のユーザーの行動を観察して、観察した結果から一般的な仮説な導きだす。これが質的調査の通常の方法です。できるだけ事前のバイアスを排除し、「マーケターの想い」ではなく、「ユーザーの考え・行動」を知る。これができるのは探索型調査のみです。
とはいえ、量的データを扱う場合、事前のバイアス(=仮説)をとことん排除していたら、いつまで経っても結果は出て来ません。特にデータマイニングの場合は、「小さな仮説の構築とその検証」の繰り返しです。「この製品とこの製品は関連がありそうなのではないか」、「この変数を用いれば正確な予想ができるのではないか」、「この軸でセグメンテーションをすると効率的なプロモーションができるのではないか」など小さな仮説と検証を積み上げていって分析を行う。これがデータマイニングの方法論です。

一方で、データ分析に対する別のアプローチ方法があります。それが仮説検証型調査です。まずは、「仮説」を作り出し、仮説を検証するためのデータを収集して、分析する。仮説検証型調査とは、このようなアプローチ方法です。アンケート調査を中心とした「レガシーな」調査手法の多くは仮説検証型調査です。まずは仮説を立て、仮説を検証するための「調査設計」を行い、調査設計に基づいてデータを収集し分析を行う。(正しい)アンケート調査はこのような流れで行います(余談ですが、このことがわかっていない人が非常に多く、とりあえずアンケートをしてみて何も見つけられないというパターンが非常に多いです)。
検証型調査は着実に結果を出すことができます。しかし、マーケターの仮説立案能力の幅を超えることはありません。つまり、検証型調査から新たな発見を導き出すことは難しい、と言えます。

 

理論的サンプリングという方法

このような仮説検証型の欠点を克服する方法があります。それは繰り返し繰り返し、仮説検証を実施することです。データマイニングは小さな仮説の構築とその検証と上で書きました。同じことを検証型調査でも実施すれば良いのです。
本題に入る前に、もう少し理論的な話をします。社会学では調査方法論について、多くの議論がなされてきました。社会学における質的調査の方法論の1つである、「グラウンデッド・セオリー・アプローチ(GTA)」について紹介します(一応、リンクは貼っておきますが、Wikipediaの記述は偏見に満ちているのでおすすめしません)。
GTAはグレイザーとストラウスという社会学者が考案した質的調査の方法論です。看護学でよく利用されているようです。GTAの理論的背景や詳細な方法論を紹介しようとすると、一冊の本になってしまうので、ここでは簡潔に分析の流れだけ説明します。

  • テーマを決める(ここでは細かい仮説を立てない)
  • テーマに基づいて、必要と思われる人に対してインタビューを行う
  • インタビューの発言データを細かく分断し、データを「コード化」する
  • データをカテゴリ化・階層化して、データ同士の関連性を見つけ出し、そこから仮説を導き出す
  • 出てきた仮説を基に再度必要なインタビュイーを選定し、インタビューを実施し、データを分析する
  • インタビューとデータ分析の繰り返しを結果が収束するまで行う

ここで問題にしたいのは、最後の2つです。GTAでは、データを分析した後に、分析結果に基づいて再度インタビューを行い分析をします、そして、それを繰り返します。この方法を「理論的サンプリング」と呼びます。バイアスの掛かっていない状態から出発し、データを分析することで道筋をつけ、検証しながら仮説を精緻化していく。この方法を用いることで新たな発見を導き出すことができる、それがGTAの考えです。
しかし、実際のマーケティングリサーチにGTAを応用するときに問題となるのは、この理論的サンプリングの部分です。マーケティングの現場では、結果の正当性と同時に、「スピード」と「コスト」も求められます。GTAは通常、質的調査(インタビュー)で行いますが、まともにやろうとすると少なくとも半年くらいかかります。理論的サンプリングはアンケートにも応用できる考えですが、理論的サンプリングを使って、アンケート調査をしようとすると、いくらお金があっても足りませんし、時間も相当かかります。結果が出る頃には、市場が変わっている可能性すらあります。
マーケティングリサーチの実務において、GTAの考えを応用することは不可能でした。レガシーな調査方法に頼っている限り。

 

そしてWeb分析

ようやく本題です。Webはこれまで不可能だった理論的サンプリングに頼った手法を可能にします。僕はこれがWebにおけるデータ分析で決定的に重要なことだと考えています。
WebではA/Bテストなどの実験的調査を低コストで実施することができます。このような手法を使えば、仮説の検証を素早く、しかも低コストで行うことができます。最初はあまりいい仮説が思いつかなったとしても、仮説を検証している中で、新たな仮説を導き出すことができます。そして、その仮説を検証していき、再度別の仮説を立てる。よく言われるPDCAの実施です。高速なPDCAです。あえて、A/BテストやMVTなどの実験的調査をしなくても、単純に施策実施前後の比較で十分な場合もあります。Webでは調査設計と実施を短期間・低コストで実施することができます。それが大きな特長です。

一方で、アクセスログ関連だけでも膨大なデータが蓄積されていることもまた事実です。さらに広告の配信データや顧客データなどと結びつけようとすると、データは倍増します。この膨大に蓄積されたデータを「マイニング」することもアナリストには求められます。

ここからはあくまでも経験則です。アクセスログを中心とした膨大なデータを後から分析しようとしても求める結果、「つまりコンバージョンを上げるためにはどうすれば良いのか」は中々出て来ません。効率性の観点から見ると、非効率極まりない方法です。そんなことをするくらいであれば、「とりあえず何らかの施策を実施してみる→結果を検証する」という方法を採った方が遥かに効率的に必要な分析/改善ができます。
つまり、Webにおいてはビッグデータのマイニングよりも、「仮説の構築」と「データ取得のデザイン」の方が遥かに重要です。データ分析の制約条件となっているのは、(人ではない場合)データです。正確にいうと、「データの蓄積」ではなく、「データの取得」です。仮に膨大なデータが蓄積されていたとしても、必要なデータが無い場合がほとんどです。膨大なゴミの中から、あるかどうかわからない宝石を見つけ出す作業は不毛です。それよりも、必要なデータを取得するための「デザイン」をした方が効果的・効率的です。

 

Web分析の中心は仮説検証

つまり、何が言いたいのかというと、Web分析の中心的な作業は「仮説検証」であるべきということです。なんとなく収集してみた膨大なデータを分析しても、意味のある結果が出るかどうかわかりません。あまりにも非効率な方法です。それはコンテンツの更新が容易であるというWebの特長を無視した方法です。何らかの施策を実施するのと同時に、施策の効果を検証するために
「いかにしてデータを取得するのか」
というデザインを行う、これこそが最も重要なアナリストのタスクです。
仮説と評価指標を予め持った上で施策を実施することで、効率的に仮説の検証ができ、次の施策に繋げることができます。探索型調査ではワンショットの調査で膨大な工数がかかってしまい、何度も実施することができません。

アトリビューション分析が微妙なのはこの点です。アトリビューションモデリングの多くは探索型調査を前提にしています。後付け後付けで、この広告のビュースルーがどうだこうだ言われても、分析の苦労の割に得るものは少ないです。
もちろん全てのアトリビューション分析を否定しているわけではなく、「探索型」のアトリビューション分析は多くの場合、不要だと言っているだけです。例えば、広告の配信前に、
「バナー広告はビュースルー効果があり、他の刈り取り型広告や自然検索と組み合わせることで、効果が発生する」
という仮説を立てて、必要なデータ取得をデザインし、その効果を検証すれば良いのです。ただデータがあるだけの状態から分析するよりも、遥かに分析工数が少なくて済み、次の施策へ活かすことができます。

もちろん、探索型調査も有効な場合もあります。長い期間、データを貯めるだけ貯めてきて全く分析をしてこなかった、という場合は、一度探索型調査をした方が良いでしょう。リスティング広告や第3社配信広告をしている場合は、特に設定しなくても膨大な量のデータが自動で収集できてしまいます。費用をかけてでも、一度このような膨大なデータを分析してから次の戦略を練った方が結果的に低コストになります。

しかし、通常の場合は、「仮説構築→データ取得デザイン→データ検証→仮説構築→・・・」というサイクルを回していく方がうまくいきます。そして、それができるのがWebの特長です。膨大なデータが蓄積されているのがWebの特長ではなく、データ取得のデザインが自在にできるという点がWebの特長です。

大事なことなので、もう一度言います。探索型調査は時間のムダです。仮説構築→検証を繰り返し、最適な解を見つけていく。これがWeb分析の中心となるべきアプローチ方法です。その中で最も重要なアナリストのタスクはデータ取得のデザインを行うことです。ごりごりとデータマイニングをすることではありません。ゴミからはゴミしか出て来ません。GIGO(garbage in, garbage out)です。

 

ちょっとした補足

仮説→検証をパラレルで数本走らせて、ちょっとした「突然変異」を入れてみる。そうすると、遺伝的アルゴリズムの考え方と一致します。贅沢な方法ですが、一度やってみたい気がしないでもないです。遺伝的アルゴリズム(Genetic Algorithm)、略してGAだし。

Categories
GoogleAnalytics ruby

rubyのGoogleアナリティクスAPI用ラッパーライブラリ「garb」が面白い

Googleのサイトにruby用のGoogleアナリティクスAPIラッパーは「garb」と「Gattica」の2種類が紹介されています。

そのうちの「Garb」を使ってみたら、案外面白かったので紹介します。

https://github.com/vigetlabs/garb

*ここに記載されているrubyのコードは基本的にruby1.9系のものを利用しています

 

簡単な使い方

Garbのgithubのページには、READMEやWikiなどがあるのですが、結構不親切な感じになっているので、まず簡単な使い方を紹介します。

インストール

インストールは、たぶん下記コマンドでできます

gem install garb

実行時に色んなエラーが出てくる場合は、githubからGemfileを持ってきて、

bundle install

とすれば、必要なgemファイルがインストールされると思います。

 

ログイン

OAuthを使ったログインもできますが、通常はシングルユーザーログインで十分だと思います。
こんな感じで簡単にログインできます。

Garb::Session.login('sample@gmail.com', 'password')

 

プロファイルを選択する

エクスポートしたいデータが入っているプロファイルを選択します。

Web Property ID (UA-XXXXXX-Xってやつ)だけで簡単に選択できます。

profile = Garb::Management::Profile.all.detect {|p| p.web_property_id == 'UA-XXXXXXX-X'}

1つのWeb Property IDで複数のレポート(テーブル)を作成した場合は、最初に表示されるものが選択されてしまいます。

そのような場合、個別のレポートID(table ID)で指定することもできます。

profile = Garb::Management::Profile.all.detect {|p| p.table_id == 'ga:xxxxxxxxx'}

table_idはData Feed Query Explorerで調べると楽です。

http://code.google.com/intl/ja/apis/analytics/docs/gdata/gdataExplorer.html

こんな感じのコードでtable_idを出すこともできます。

profile.each do |x|
    puts "UA Account: #{x.web_property_id}ttitle: #{x.title}ttable_id: #{x.table_id}"
end

 

必要なデータを定義する

次に、どのディメンションとメトリクスを使うのかを定義します。

定義方法は簡単です。
「metrics」のところに、使いたいメトリクスを並べて、
「dimensions」のところに、使いたいディメンションを並べるだけです。
ページごとのセッション数、PVが知りたい場合、次のようなコードになります。

class Upvs
    extend Garb::Model

    metrics :unique_pageviews, :pageviews
    dimensions :page_path
end

利用できるディメンションとメトリクスの種類は Data Export APIのページを参考にしましょう。

http://code.google.com/intl/ja/apis/analytics/docs/gdata/dimsmets/dimsmets.html

ただ、少し注意が必要です。
ga:visitsやga:pageviewsなどは、そのまま単純に「ga:」を削除して、
:visitsや:pageviewsにすればOKです。
しかし、ga:unique_pageviewsやga:pagePathなどの大文字が含まれている場合は、
大文字を「アンダースコア+小文字」にする必要があります。
ga:pagePathの場合は、「:page_path」となります。
めんどうな人は、active_supportを利用して、

str = ga:pagePath
str.underscore.gsub(/^ga:/,"")

みたいな形で、変換させてあげれば良いと思います。

 

データを取り出す

データを取り出す方法も簡単です。
とりあえず取るという場合は、これで十分です。

res = profile.upvs
res.each do |x|
    puts "#{x.page_path},#{x.unique_pageviews},#{x.pageviews}}"
end

これだけで、ページ別のPVとセッション数が取れます。
コード中にある「profile」は上で指定したレポートのプロファイルです。
「upvs」というのは、上で作ったClass「Upvs」のUを小文字にしたものです。
「profile.upvs」とするだけで、Googleアナリティクスのサーバーからデータを引っ張ってくることができます。

ちなみに、「Upvs.results(profile)」でも同様のデータを出すことが可能です。
あとは、rubyらしくeachでイテレーションをかけて、フォーマットを整えて吐き出せば完了です。

 

フィルター・期間・ソートの設定

このままじゃ、使い物にデータなので、期間を指定したり、フィルターを掛けたりしましょう。
これらの設定は、「profile.upvs」の引数で指定します。

res = profile.upvs(
    :filters => {:page_path.eql => '/'},
    :limit => 1000,
    :offset => 1001,
    :sort => :unique_pageviews.desc,
    :start_date => Time.parse("2011-11-01"),
    :end_date => Time.parse("2011-11-02")
)

res.each do |x|
    puts "#{x.page_path},#{x.unique_pageviews},#{x.pageviews}}"
end

フィルターは、:filters => {フィルター}のようにして記述します。

上の例では、”/”とURLが一致したデータのみ抽出としています。

フィルターで利用できるオペレーターは以下の通りです。

【メトリクス用】
eql => ‘==’(完全一致),
not_eql => ‘!=’(完全不一致),
gt => ‘>’(大なり),
gte => ‘>=’(大なりイコール),
lt => ‘ ‘ ‘==’(完全一致),

【ディメンション用】
does_not_match => ‘!=’(完全不一致),
contains => ‘=~’(正規表現一致),
does_not_contain => ‘!~’(正規表現不一致),
substring => ‘=@’(文字列が含まれる),
not_substring => ‘!@(文字列が含まれない)’

次に期間の設定です。
期間はrubyの日付オブジェクトで指定します。
Timeクラスをrequireして、

require 'time'
Time.parse("2011-11-01")

のようなやり方が簡単です。後述しますが、active_supportを使うとより便利になります。

次に、ソートです。
ディメンション・メトリクスのどれかを基にデータを並び替えることができます。
昇順の場合は、並べ替えをしたい指標を:sortの項目に入れればOKです。
:sort => :unique_pageviews
降順の場合は、指標のあとに、.descを付けてください。
:sort => :unique_pageviews.desc
でユニークページビューの値が大きい順に並び替えられてデータが出てきます。

最後にリミットとオフセットです。
リミットとは、一回のリクエストで取得するデータの数です。API経由の場合は、1回あたり1,000件までと制限されています(確か)。
オフセットはデータを取得するときの始める位置です。「100」と指定すると100番目からのデータを取得します。
最初からデータを取りたい場合は、オフセットの項目を指定しないか、「nil」を指定しておきます。
設定の仕方は簡単で、

:limit => 1000,
:offset => 1001

こんな感じで指定すれば、OKです。
これで必要なデータを取得できる準備ができました。

ここまでのコードをまとめておくと、こんな感じになります。

require 'garb'

Garb::Session.login('sample@gmail.com', 'password')
profile = Garb::Management::Profile.all.detect {|p| p.web_property_id == 'UA-XXXXXXXX-1'}

class Upvs
    extend Garb::Model

    metrics :unique_pageviews, :pageviews
    dimensions :page_path
end

res = profile.upvs(
    :filters => {:page_path.eql => '/'},
    :limit => 1000,
    :sort => :unique_pageviews.desc,
    :start_date => Time.parse("2011-11-01"),
    :end_date => Time.parse("2011-11-30")
)

res.each do |x|
    puts "#{x.page_path},#{x.unique_pageviews},#{x.pageviews}"
end

これだけのコードで、”/”のセッション数とPV数を取得することができちゃいます。

 

 

ちょっと応用編

ここまでわかれば、後はrubyの知識を駆使して、色んなことができます。
Garb使ってみて気づいた、ちょっとしたTipsを書いておきます。
 

1000件以上のデータを取る

API経由でデータを引っ張ってくることで、特に嬉しいことは、大量のデータをぼーっとしている間に取得することが出来る点です。
API経由だと、1回のリクエストでの取得件数が1000件と制限されているはずですが、何度かに分けてデータをリクエストすれば問題ありません。

具体的には、データを取得する時の設定を関数化して、offset値を引数にして、イテレータで回してしまうのがいいでしょう。
データの総数は、返り値のres.total_resultsで取得することができます。
(ちなみに、データがサンプリングされているかどうかは、res.sampledで取得することができます)

あとは、rubyらしく、timesのイテレータを使って、関数にバシバシ設定値を入れてあげて、リクエストを送れば、全部のデータを抽出されます。

↓こんな感じのコードで、簡単に1,000件以上のデータが取得できます。

require 'garb'

Garb::Session.login('sample@gmail.com', 'password')
profile = Garb::Management::Profile.all.detect {|p| p.web_property_id == 'UA-XXXXXXXX-1'}

class Upvs
    extend Garb::Model

    metrics :unique_pageviews, :pageviews
    dimensions :page_path
end

def get_data(profile, offset_val)
    res = profile.upvs(
        :limit => 1000,
        :offset => offset_val,
        :sort => :unique_pageviews.desc,
        :start_date => Time.parse("2011-11-01"),
        :end_date => Time.parse("2011-11-30")
    )

    return res
end

#初回リクエスト
res = get_data(profile, nil)

#全部の件数から繰り返し回数を算出
repeat_times = res.total_results / 1000

#サンプリングの有無を調査
puts "Sampled? => #{res.sampled}"

puts "---we have #{res.total_results} results---"

res.each do |x|
    puts "#{x.page_path},#{x.unique_pageviews},#{x.pageviews}"
end

#2回目以降
if(repeat_times != 0)
    repeat_times.times do |x|
        res = get_data(profile, (x+1)*10)
        puts "---processing #{x+2}th iteration---"

        res.each do |x|
            puts "#{x.page_path},#{x.unique_pageviews},#{x.pageviews}"
        end
    end

end

 

アクティブサポートを有効に使う

Garbは色んなところで、active_supportを利用しているようです。
なので、こちらもactive_supportを利用しちゃいましょう。特に日付の操作が超絶便利です。
(active_support3以降を利用している場合は、active_support/timeをrequireする必要があります)

よく使いそうなところで、先月一ヶ月のデータを取得したいという場合は、こんな感じでできます。

require 'active_support/time'
#先月の初め
start_date = (1.month.ago Time.now).beginning_of_month
#先月の終わり
end_date = (1.month.ago Time.now).end_of_month

これだけで、先月の初めと先月の終わりの日付が取得できちゃいます。

これをフィルター設定のところの期間に、入れて上げれば期間設定せずとも、自動的に先月分のデータを取得できます。

 

設定はYAMLで

アカウントIDやパスワードなどをプログラム内に書いてしまうと、あまり美しくないので、切り出せるところは切り出してしまいましょう。
rubyでは、YAMLという美しい設定ファイルが使えます。人間にもコンピューターにも優しい構文で、さくっと設定ファイルがかけます。

詳しくは、↓を参照してください。

http://jp.rubyist.net/magazine/?0009-YAML

例えば、

date:
 start_date : 2011-11-01
 end_date : 2011-11-01
account:
 mail_address : sample@gmail.com
 pass_word : password
table_id :
 first : ga:xxxxxxxxx
 second : ga:xxxxxxxxx

こんな感じでconf.ymlを作成し、

本文の中で、

require 'yaml'
#read conf file
f = File.open('conf.yml')
conf_str = f.read
conf = YAML.load(conf_str)

こうするだけで、confオブジェクトの中に、設定内容が格納されます。

 

 

Garbはrubyらしくて非常に面白いので、ぜひ使ってみてください。
Googleチャートなどを使って、簡単にブラウザ上でグラフ化することもできます。
(そんなことするんだったら、Googleアナリティクスの画面を見ても同じですが。。。)

 

2012/1/13:自分用備忘録追記

garbは、アドバンスセグメントにも対応しているみたいです。
使い方は、フィルターや日付などを設定する箇所で、
:segment_id => xxxxxx
とするだけです(xxxxxxはセグメントのid。gaid::xxxxxのxxxxx部分)
セグメントIDは、Data Feed Query Explorerで見つけるのが一番楽だと思います。

ダイナミックセグメントのやり方はちょっと見つけられませんでした。
この人が提案しているみたいですけど、まだコードには入っていないみたいです。
https://github.com/stevenwilkin/garb/commit/d7c5b1c5fbde702a1b2f06edcebc44bfa555b0e6

ソースコードをいじっちゃえば、いいんですけどね。