causalimpactを使ってオフライン施策の効果検証をしてみた
はじめに
こんにちは、NEのデータ事業推進部(DSO)でデータアナリストをしている本岡です。
私は普段、EC業界のデータを用いた売上や顧客行動の分析、当社のデータ基盤の整備なども担当しています。
本日は「ある施策が本当に効果があったのか?」を検証したいときに役立つ手法、Causal Impact(介入効果分析)についてご紹介します。 実際のデータを用いて、どのように施策の効果を検証できるのかを解説します。
なぜ効果測定が難しいのか?
たとえば、あなたがECサイトで大型セールを実施したとします。
セール直後に売上が大きく伸びたとしたら、多くの人は「セールが成功した!」と思うはずです。
しかし、ここで立ち止まって考えてみてください。
本当にその売上の増加はセールの効果だったのでしょうか?
もしかすると、ちょうどテレビ番組で紹介された直後だったのかもしれません。
季節的に購買意欲が高まる時期だった可能性もありますし、競合が値上げをしていたことが影響していたかもしれません。
このように、単純にBefore/Afterの数値を比較するだけでは、「本当にその施策が効果をもたらしたのか」は判断できないのです。
特に、後述するタクシー広告のようなオフライン施策(※いわゆる「OOH:Out-of-Home広告」)では、「何人が見てくれたか」「広告を見た人が実際に興味を持ったか」といった数値を直接追うことは難しいため、成果をそのまま比較するのが困難です。
※OOH(Out-of-Home)広告とは:テレビ・ウェブ広告などの「家庭内」やオンライン空間ではなく、屋外の交通機関・施設・街頭などで接触される広告全般を指す業界用語です。例:駅広告・タクシーサイネージ・街頭ビジョンなど。
Causal Impactとは?
Causal Impact は、「ある施策が実際にどれだけの効果をもたらしたのか?」を推定するための統計手法です。
特に、「もしその施策を打っていなかったら、どうなっていたか?」 という反実仮想の世界を予測し、実際の結果と比較することで効果を定量化します。
前述した施策の成果が単純比較できないときこそ、Causal Impactのような手法が活きてきます。
因果関係(causality)を明らかにするには、相関関係(correlation)だけでは不十分です。
そこで必要になるのが、「施策を打たなかった場合(=反実仮想)」を推定し、それと比較するという考え方です。
ABテストのような実験設計ができれば理想的ですが、実務ではそう簡単にはいきません。そのような制約のある現場で、比較可能な系列(地域・チャネル・類似指標など)を用いて、統計的に「施策がなかった場合の世界」を再現できるのが Causal Impact です。
Google が R パッケージとして最初に開発し、近年では Python 版(tfcausalimpact)も登場し、Web施策や広告効果の検証に広く使われています。
Causal Impact は、ベイズ構造時系列モデル(Bayesian structural time series model)を使って、「変化のなかった場合の予測」 を構築します。
これはざっくり言えば、時系列データの中にあるトレンドや季節性といった構造を捉え、施策の影響が及ばない「対照群(例:別のサイト、他カテゴリなど)」を参考にしながら、介入がなければどうなっていたかをベイズ推定で予測する、という仕組みです。
📌 ベイズ推定とは?
「過去のデータ(事前情報)と現在の観測結果を組み合わせて、最もありそうな未来を推定する」考え方です。Causal Impactではこの手法を使って、未来の売上やアクセス数の「想定される姿」を描きます。
🔗 参考:
Inferring Causal Impact Using Bayesian Structural Time-Series Models (Google Research)
対照群とは?──効果を測るための「もう一つの世界」
Causal Impact の肝は、介入対象(施策を受けたグループ)と、対照群(施策を受けていないグループ)を比べる点にあります。
たとえば、以下のようなシナリオを考えてみてください:
- あなたのECサイトの商品ページAを改修した(介入群)
- 商品ページBは改修していない(対照群)
このとき、商品ページAと商品ページBは、同じ季節性・トレンドの影響を受けている可能性があります。
つまり、商品ページのデータを「影響を受けなかった世界の代表」として活用すれば、トップページが自然な変化だったのか、施策による変化だったのかを見極めるヒントになります。
Causal Impactを使って効果検証してみた
今回は実践例として、2024年7月に当社が実施したタクシー広告 [1] が、当社オーガニックサイトへのアクセス数にどのような影響を与えたのかを検証します。分析には R の causalimpact パッケージを使用しました。
今回のタクシー広告は東京都内のみで配信されたため、
- 東京からのアクセスを 「介入群」
- それ以外の地域からのアクセスを 「対照群」
として設定しました。
この設定には、以下のような前提があります:
- 東京とその他地域で、検索や閲覧行動に大きな違いがないこと
- 季節性や曜日の影響が、両地域に共通して現れること
また、事前に並行トレンドの検証も行い、施策前に両者が類似した動きをしていたことを確認済みです(詳細は後述)。
さらに、分析期間中に東京のみに影響を与える他のマーケティング施策・メディア露出・大規模イベント等はないと仮定しています。(これは100%保証するのは難しい)したがって、本分析では「タクシー広告が唯一の主要な外部要因であった」とみなしています。
🗓 分析対象期間
全体期間:2024年6月30日 ~ 2024年07月31日
施策開始日:2024年7月1日
✅ Step 1. 並行トレンドの検証
Causal Impact を使う前提として、介入群と対照群が「施策前に似た動きをしていたか」=平行トレンドがあるかを確認する必要があります。
今回は、コサイン類似度を使って、並行トレンドを定量化しました。(コサイン類似度は0から1の値を取り、1に近づくほど、類似度=並行トレンドがあると判断されます)
比較期間 | コサイン類似度 |
---|---|
2022年〜2024年6月 | 0.976 |
2024年6月のみ | 0.981 |
どちらの期間でも非常に高い類似度(≧0.97)が確認できたため、介入群・対照群の設定は妥当と判断しました。
🔍 注意点
ただし、コサイン類似度には限界があります。
この指標は時系列の「形(傾向)」には敏感ですが、スケール(絶対的な大きさ)やオフセット(基準値の差)には鈍感です。たとえば、同じ形状でもアクセス数の規模が大きく異なる場合、類似度が高く出る可能性があります。
さらに厳密に比較したい場合は、Dynamic Time Warping(DTW)のように、時系列のずれを考慮して比較できる手法もあります。
今回は、コサイン類似度が十分高かったこと、時系列の差が小さかったことから、手順を簡略化するためにDTWなどの検証は省略しました。
✅ Step 2. データの準備
データは以下の形式のデータフレームとして整形しました:
date | tokyo_not_login_sessions | other_not_login_sessions |
---|---|---|
2024-06-01 | … | … |
2024-06-02 | … | … |
2024-06-03 | … | … |
✅ Step 3. 分析実装
出力結果
デフォルトでは、3つのグラフが出力されます。
- 最初のグラフ(original)は、介入がなかった場合の予測(点線)、介入群の実際のデータ(実線)と95%信頼区間(薄青い区間)を表します。
- 2番目のグラフ(pointwise)は、実際に観察されたデータと反事実予測との差を示します。
- 3番目のグラフ(cumulative)は、2番目のグラフからの寄与を合計し、介入の累積効果を示します。
※グラフ中の縦線は、左側が広告介入開始日(2024年7月1日)、右側が分析期間終了日(2024年7月31日)を示します。
※なお、8月〜9月のデータも参考情報としてグラフ上に表示していますが、今回の効果検証は介入から1ヶ月間(7月1日〜7月31日)に限定して行っています。
グラフを見ると、3番目のグラフ(累積効果)で右肩上がりの傾向が確認できます。
これは、「実測値が予測値を継続的に上回っていた」ことを意味し、広告の効果が一定期間にわたってプラスに作用していたと解釈できます。
特に、累積効果が時間の経過とともに上昇している場合、それは一時的な偶然ではなく、継続的・安定的な因果効果が存在した可能性を示唆しています。
また、介入効果のサマリも以下の形式で表示できます。
ここから読み取れること
実際のアクセス数(45)は、反事実シナリオにおける予測値の95%信頼区間[34, 45]の上限に位置しており、信頼区間に収まっていることから、統計的な有意差があるとは言い切れません。
しかし一方で、Causal Impactが出力する Posterior tail-area probability(p値) は 0.038 であり、これは通常の統計的有意性の判断基準である p < 0.05 を満たしています。このp値は、「広告に効果がなかった場合に、今回のようなアクセス数の上昇が偶然に起こる確率はわずか3.8%である」ということを意味し、広告に統計的な効果があったと解釈できます。
累積アクセス数は予測値の1227に対して実測値は1382であり、155件の増加、すなわち約13%の上昇が確認されました。ただし、この相対的な効果に関する95%信頼区間は[-1.7%, 31%]と幅が広く、0を含んでいるため、効果の大きさには不確実性が残ることも事実です。
結論としては、
- タクシー広告には統計的に有意な効果があったと解釈できます(p=0.038)
- しかし、効果の大きさには幅があり、不確実性もあります(95%CIに0含む)
という結果が得られました。これは、効果の可能性は高いと判断できるものの、より明確な効果を示すには追加の検証が必要であることを示唆しています。
このような不確実性を減らし、より明確な効果を得るための今後の施策案としては、以下が考えられます:
- 広告配信期間を延長する
- → 効果の累積が期待でき、効果量が大きくなりやすい
- 配信エリアを拡大する(例:首都圏全体、関東圏など)
- → サンプルサイズの増加により、統計的な検出力が向上する
- ターゲティング精度を上げる(例:特定の時間帯やエリアでの集中配信)
- → 関心度の高いユーザーへのリーチが増え、効果が顕在化しやすくなる
- 複数チャネルとの組み合わせで相乗効果を狙う(例:Web広告×OOH)
- → ユーザー接触経路が多層化し、記憶や行動への影響が強まる
たとえば、今回のように1ヶ月・限定的地域での広告配信では、効果は出ていても信頼区間が広くなりがちです。次回以降の施策では、「期間」や「量(接触数)」を増やすことで、より明確かつ安定した効果を示すことが可能になると考えられます。
R版とPython版、それぞれの特徴
本記事ではRの CausalImpact パッケージを使用しましたが、Pythonでも同様の分析が可能です。Python版の causalimpact を使えば、既存の分析基盤(BigQuery、Cloud Functions、Slack通知など)とスムーズに連携でき、定期分析の自動化や社内レポートへの組み込みにも適しています。
一方で、R版はGoogleが公式に提供している元祖実装であり、精度・柔軟性・可視化の観点では非常に信頼性が高いです。以下に、R版とPython版の主な特徴を整理します。
観点 | R版 CausalImpact | Python版 causalimpact |
---|---|---|
精度・信頼性 | Google公式。状態空間モデルに基づき、精度・再現性が高い | 実装に差があり、安定性・精度はライブラリごとにばらつきあり |
柔軟性 | トレンドや季節性の追加、共変量の調整などが可能 | 一部制限あり。高度な設定はやや難しい |
可視化 | 分かりやすく洗練されたグラフ出力 | シンプルな可視化。必要に応じて別ライブラリで補完が必要 |
自動化・連携 | R環境の整備や外部連携には一工夫必要 | Pythonエコシステム内で容易に自動化・通知連携が可能 |
導入のしやすさ | Rユーザー向け。やや専門的な環境が必要 | Pythonユーザーにとって扱いやすく、他ライブラリと統合しやすい |
💡 選び方のヒント:
レポートや一度きりの検証 → R版
定期実行・Slack連携などの業務組み込み → Python版
💡 CausalImpactを使う前に意識したい3つの視点
CausalImpactは強力なツールですが、因果推論である以上、前提が重要です。以下のポイントは必ず確認しましょう:
- 適切な対照群を選べているか?
- 並行トレンドがあるかどうかが重要
- 介入期間の開始時点・長さは適切か?
- 遅効性の施策や複数の影響要因に注意
- 外部要因を十分に除外できているか?
- 他施策の影響や自然変動(天候、競合の動き)を想定する
まとめ
CausalImpactは、「もし施策を行わなかったら、どうなっていたか?」 という反実仮想を推定することで、施策の効果を定量的に評価できる手法です。
特に、以下のようなシーンで強力な武器になります:
- ABテストを設計・実施できない状況
- 過去ログデータしか残っていない場合
- 地域やチャネルごとに比較可能な指標があるとき
今回ご紹介したタクシー広告の事例のように、地域差を利用した比較を行うことで、施策の成果を「数字」で語れるようになる点は、マーケティングやプロダクト改善において大きな価値があります。
「変化はあったけど、それって本当に施策の効果?」——そんなとき、CausalImpactは定量的な根拠を持って答えるための一手になります。
因果推論というと難しそうに聞こえますが、ツールとしては非常に実践的です。これからの分析業務や意思決定の場で、ぜひ活用してみてください。
今回使用したコード
タクシー広告介入分析で使用したRコード
##packageのインポート
install.packages("devtools")
library(devtools)
devtools::install_github("google/CausalImpact")
library(CausalImpact)
install.packages('zoo')
library(zoo)
##データのインポート
data = read.csv("dataset.csv")
data
time.points <- seq.Date(as.Date("2024-06-01"), by = 1, length.out = 92)
y = data$tokyo_not_login_sessions
x1 = data$other_not_login_sessions
##日付、都内のセッション数、都外のセッション数を1つにまとめる
data <- zoo(cbind(y,x1), time.points)
head(data)
##キャンペーン前
pre.period <- as.Date(c("2024-06-01", "2024-06-30"))
##キャンペーン後
post.period <- as.Date(c("2024-07-01", "2024-07-31"))
##モデルにFIT
impact <- CausalImpact(data, pre.period, post.period)
##グラフ生成
plot(impact)
##サマリ
summary(impact)
summary(impact, "report")
impact$summary

NE株式会社のエンジニアを中心に更新していくPublicationです。 NEでは、「コマースに熱狂を。」をパーパスに掲げ、ECやその周辺領域の事業に取り組んでいます。 Homepage: ne-inc.jp/
Discussion