CausalImpactで実践する 施策の効果検証(第0回)
CausalImpact(コーザルインパクト)とは何か(前後比較との違い)

CausalImpactで実践する 施策の効果検証(第0回)CausalImpact(コーザルインパクト)とは何か(前後比較との違い)

5月の1か月間、自店舗で販促キャンペーンを打った。キャンペーン中の1日あたり売上は、前の月より14.7%多かった。

この数字を見て「キャンペーンで売上が14.7%伸びた」と報告する。

よくある報告ですが、この14.7%にはキャンペーンがなくても増えていた分が混ざっています。

結論を先に3行で書きます。

  1. CausalImpact(コーザルインパクト)は、「キャンペーンがなかったら」の売上を予測し、実際の売上との差を効果とみなす手法です。Googleの研究者が2015年に発表しました。
  2. 正解(売上+10%)を仕込んだ練習用データで動かすと、推定値は9.6%(95%区間 8.17〜11.13%)。前後比較の14.7%と違い、正解を区間の中に捉えます。
  3. ただし結果が正しいかは、比較に使う店舗(対照系列)の選び方と、前提の確認で決まります。

この記事を読み終えると、次のことができるようになります。

  • CausalImpactが「何を」「どうやって」測る手法か、人に説明できる
  • 前後比較の数字が、そのまま効果にならない理由が分かる
  • Python(tfcausalimpact)の出力から、効果の大きさと確からしさを拾える
  • 連載全11回のうち、自分がどこから読めばよいかが分かる

この記事と連載で使うのは、架空の店舗の日次売上データ campaign_sales.csv です。自店舗の売上(sales_own)と、キャンペーンをしていない近隣2店舗の売上(store_a、store_b)が入っています。キャンペーン期間(2026年5月1日〜31日)の自店舗の売上には、効果として+10%を仕込んであります。正解が分かっているので、手法の答え合わせができます。

以下からダウンロードできます。

campaign_sales.csv

まず、単純に比べてみる

キャンペーンをした5月と、その前の4月の売上を比べてみます。

以下、コードです。

import pandas as pd

data = pd.read_csv(
    "campaign_sales.csv", 
    index_col="date", 
    parse_dates=True
)

april = data.loc["2026-04", "sales_own"].mean()
may = data.loc["2026-05", "sales_own"].mean()

print(f"4月の平均売上:{april:.1f}千円")
print(f"5月の平均売上:{may:.1f}千円")
print(f"前後比較の伸び率:{may / april - 1:.1%}")

 

以下、実行結果です。

4月の平均売上:458.8千円
5月の平均売上:526.3千円
前後比較の伸び率:14.7%

 

1日あたりの売上は458.8千円から526.3千円へ。14.7%の増加です。

ただ、この14.7%には、キャンペーンと関係なく売上を動かすものが混ざっています。

  1. 祝日と曜日:5月にはゴールデンウィーク(GW)があり、土日も4月より2日多い
  2. トレンド:この店の売上は、もともと少しずつ伸びている
  3. 偶然のゆらぎ:たまたま良い日・悪い日が続くことがある

では、キャンペーンがなかったら、5月の売上はいくらだったのか。前後比較では、ここが分かりません。これを予測するのが、CausalImpactです。

 

CausalImpact(コーザルインパクト)とは何か

CausalImpactは、施策の効果を時系列データから推定する手法です。Googleの研究者Brodersenらが2015年に論文で発表し、R言語のパッケージとして公開しました(公式ページ、論文)。

考え方は1行で書けます。

効果 = 実際の売上 − 「キャンペーンがなかったら」の売上(予測)

「キャンペーンがなかったら」の世界は、現実には観測できません。この仮想の世界を反実仮想(はんじつかそう、counterfactual)と呼びます。CausalImpactは、反実仮想を予測で作り出す手法だ、と言い換えられます。

手順は4つです。

  1. 介入前期間で「ふだんの動き」を学ぶ:キャンペーン前の期間(介入前期間)で、自店舗の売上の傾向と、他店舗の売上との連動を学ぶ
  2. 介入後期間を予測する:キャンペーン中の期間(介入後期間)の他店舗の売上から、「キャンペーンがなかった自店舗」の売上を予測する
  3. 実測値と予測値の差を取る:この差が効果
  4. 差の「幅」と「確からしさ」を出す:予測には誤差があるので、効果を1つの数字ではなく、95%区間と確率つきで示す

図にすると、次のようになります。

CausalImpactの考え方を示すグラフ。自店舗の日次売上の実測値と、キャンペーンがなかった場合の予測(反実仮想)を比べ、その差を効果として塗りつぶしている(架空の売上データ)
実測値と「キャンペーンがなかったら」の予測の差が効果

2026年3〜5月の自店舗の日次売上(架空の例)。オレンジの帯は予測の95%区間。4月29日(昭和の日)やGWの山では予測も一緒に上がっており、祝日の分は効果に数えられていない。キャンペーン前は実測値がおおむね帯の中に収まっていることが、予測を信頼できる根拠になる。

 

ここで効いているのが、キャンペーンをしていない他店舗の売上です。これを対照系列と呼びます。

他店舗も、GWや土日には売上が増えます。介入前期間で「自店舗の売上は、他店舗の売上とこう連動する」と学んでおけば、5月の他店舗の売上から、GWの分も含めて「キャンペーンがなかった自店舗の売上」を予測できます。

A/Bテストのように「施策をする店」と「しない店」をランダムに分けられないときに使える手法です。たとえば、1店舗だけでキャンペーンをした、特定の地域だけでテレビCMを流した、自社サイトをリニューアルした、といった場面です。

 

まず、動かしてみる

Python版のCausalImpactの1つ、tfcausalimpactで動かします(ライブラリの選び方は後述)。データと2つの期間を渡すだけです。

以下、コードです。

import pandas as pd
import tensorflow as tf
from causalimpact import CausalImpact

tf.random.set_seed(42)  # 推定に使う乱数を固定する

# 1列目が目的変数(自店舗の売上)、2列目以降が対照系列(他店舗の売上)
data = pd.read_csv(
    "campaign_sales.csv", 
    index_col="date", 
    parse_dates=True
)
data = data.sort_index()
data = data.asfreq("D")

pre_period = ["2025-01-01", "2026-04-30"]   # 介入前期間
post_period = ["2026-05-01", "2026-05-31"]  # 介入後期間(キャンペーン中)

ci = CausalImpact(data, pre_period, post_period)
print(ci.summary())
ci.plot()

 

読み込むCSVは、1列目が効果を測りたい数値(目的変数、ここでは自店舗の売上)、2列目以降が対照系列(他店舗の売上)という並びにしておきます。

tf.random.set_seed(42) は、推定に使う乱数を固定する1行です。CausalImpactは推定の途中で乱数を使うため、これがないと実行のたびに数値が少し変わります。実行にはこの記事の環境で約1分かかりました。

以下、実行結果です。

Posterior Inference {Causal Impact}
                          Average            Cumulative
Actual                    526.35             16316.8
Prediction (s.d.)         480.25 (3.63)      14887.88 (112.39)
95% CI                    [472.92, 487.13]   [14660.48, 15101.05]

Absolute effect (s.d.)    46.09 (3.63)       1428.92 (112.39)
95% CI                    [39.22, 53.43]     [1215.75, 1656.32]

Relative effect (s.d.)    9.6% (0.75%)       9.6% (0.75%)
95% CI                    [8.17%, 11.13%]    [8.17%, 11.13%]

Posterior tail-area probability p: 0.0
Posterior prob. of a causal effect: 100.0%

For more details run the command: print(impact.summary('report'))

 

英語の表が出てきました。1行ずつの読み方は第5回で扱います。ここでは3か所だけ拾います。

出力の行 値 意味
Relative effect 9.6%(95%区間 8.17%〜11.13%) 売上は、キャンペーンがなかった場合より約9.6%多かった
Absolute effect(Cumulativeの列) 1428.92(95%区間 1215.75〜1656.32) キャンペーン期間の合計で約143万円の上乗せ(単位は千円)
Posterior prob. of a causal effect 100.0% 効果があったと言える確からしさ

パソコンの環境(OSやライブラリのバージョン)が違うと、小数点以下の数値が少し変わることがあります。

 

答え合わせ:仕込んだ10%を捉えたか

3つの数字を並べます。

方法 効果の推定値
前後比較(4月→5月) 14.7%
CausalImpact 9.6%(95%区間 8.17%〜11.13%)
正解(データに仕込んだ値) 10%

CausalImpactの95%区間は、正解の10%を含んでいます。前後比較の14.7%は、区間の外です。

差の正体も、出力から読めます。キャンペーンがなかった場合の5月の予測は、1日あたり480.25千円(Predictionの行)。4月の実績458.8千円より約4.7%多い値です。

つまりCausalImpactは「GWや土日の多さ、伸びのトレンドで、5月はキャンペーンがなくても4.7%ほど増えていたはず」と見ています。前後比較の14.7%から、この分を取り除いたのが9.6%です。

金額でも確かめます。5月の実際の売上合計16316.8千円のうち、仕込んだ10%の上乗せ分は約1483千円です。CausalImpactの推定1428.92千円の95%区間(1215.75〜1656.32)に入っています。

 

グラフで見る:3段のパネルが示すもの

ci.plot() は、次の3段のグラフを出します。

tfcausalimpactのplot()で出力したCausalImpactの3段グラフ。上段が実測値と予測、中段が日ごとの効果、下段が累積効果(架空の売上データ)
ci.plot() の出力:上から「実測値と予測」「日ごとの効果」「効果の累積」

2025年のGWの山(上段)でも、中段の効果は0のまわりのまま。モデルが祝日の売上増を効果と取り違えていないことが分かる。下段の右端が、キャンペーン期間全体の効果の合計(架空の例)。

 

  • 上段(original):実測値(y、黒線)と予測(Predicted、オレンジの点線)。帯は予測の95%区間
  • 中段(pointwise):日ごとの効果。実測値から予測を引いたもの
  • 下段(cumulative):日ごとの効果を足し上げたもの。右端の値が、期間全体の効果の合計

読みどころは2つ。

1つ目は、キャンペーン前は中段が0のまわりにあること。予測がふだんの売上をよく再現できている証拠です。2025年のGWの山でも、中段は0のまわりから外れていません。

2つ目は、キャンペーン開始後に中段が0より上に張り付くこと。効果が一時的なものではなく、期間中ずっと続いていたと読めます。

 

CausalImpactが向いている場面・向いていない場面

向いている 向いていない
施策の開始日がはっきりしている 施策が徐々に浸透し、いつ始まったか曖昧
介入前のデータが十分にある(季節の周期1回分以上) 介入前のデータが数週間しかない
施策の影響を受けていない対照系列がある(なくても動く) 対照系列も施策の影響を受けている
施策以外に大きな変化がなかった 施策と同時に値上げや改装など、別の変化があった
A/Bテストができない(施策をした対象が1つしかない) A/Bテストができる(それならA/Bテストのほうが確実)

「向いていない」に当てはまるときの対処や、結果を信じてよいかを確かめる方法(プラセボテスト)は、連載の後半で扱います。

 

PythonでCausalImpactを使うなら、どのライブラリか

CausalImpactの本家はR版です。Python版は複数あり、中身と更新状況が違います。

名前 言語 開発元 状況(2026年9月26日時点)
CausalImpact R Google 本家。論文のアルゴリズムをそのまま実装
tfcausalimpact Python WillianFuks氏(個人) R版に近い実装。0.0.19(2026年9月20日公開)でPython 3.8〜3.13に対応
tfp-causalimpact Python GoogleのTensorFlow Probabilityチーム 0.2.0(2023年5月)以降、更新なし。Googleの公式サポート製品ではない旨の注記あり
pycausalimpact Python WillianFuks氏ら 0.1.1(2020年5月)で更新停止。Python 3.8まで

この連載ではtfcausalimpactを使います。理由は3つです。

  • R版と同じく、ベイズ推定でモデルを当てはめる実装である
  • 2026年9月にも新しい版が出ており、更新が続いている
  • pip install tfcausalimpact の1行で、必要なもの(TensorFlow、TensorFlow Probability、tf-keras)がそろう

なお、tfcausalimpact 0.0.19は pandas の版を2.2.3以下に制限しています。新しいpandas(3.x)が入った環境にインストールすると、pandasが入れ替わります。仮想環境を作ってからインストールするのがおすすめです。手順は第2回でまとめます。

 

ありがちな失敗:キャンペーンの影響を受けた店を比較相手にする

CausalImpactの予測は、対照系列(他店舗の売上)を頼りにしています。その対照系列が、キャンペーンの影響を受けていたらどうなるか。

たとえば、キャンペーンにつられて、近くの店舗Aのお客さんが自店舗に流れたとします。店舗Aの売上は下がり、それを手がかりにした予測も下がります。実測値と予測の差、つまり効果は本当より大きく出ます。

逆に、キャンペーンの告知を見たお客さんが、同じチェーンの店舗Aにも来ていたらどうか。店舗Aの売上が伸びて予測も上がり、効果は本当より小さく出ます。

どちらの場合も、出力は何事もなかったように数字を返します。対照系列が施策の影響を受けていないかは、データではなく現場の事情で確かめる必要があります。対照系列の選び方は第8回、結果を検証する方法(プラセボテスト)は第10回で扱います。

 

この連載で学ぶこと(全11回)

回 タイトル 学べること
第1回 施策の効果測定で「前後比較」が危ない理由と反実仮想の考え方 前後比較が誤る3つのパターンを図で確かめる
第2回 tfcausalimpactのインストール手順(Windows)とtf-kerasエラーの直し方 環境をつまずかずに作る
第3回 CausalImpactの練習用データをPythonで作る(正解の効果を仕込む) この記事で使った売上データを自分で作る
第4回 PythonでCausalImpactを動かす最小コード(pre_period・post_periodの指定) データの形と期間の指定方法
第5回 CausalImpactのsummary()の見方(相対効果・95%区間・事後確率) 出力の数字を1行ずつ読めるようになる
第6回 CausalImpactのplot()の見方(original・pointwise・cumulative) 3段グラフの意味と使い方
第7回 CausalImpactの仕組み:ベイズ構造時系列モデルをやさしく解説 「予測」の中で何が起きているか
第8回 CausalImpactの共変量(対照系列)の選び方と入れてはいけない系列 精度を上げる系列、結果を壊す系列
第9回 CausalImpactのmodel_args設定:季節性(nseasons)と介入前期間で結果はどう変わる? 設定による結果の違い
第10回 CausalImpactの前提条件と注意点:プラセボテストで結果を検証する 出た結果を信じてよいかの確かめ方
第11回 CausalImpactの結果をビジネスレポートにまとめる方法(効果額・区間・確率の伝え方) 上司やクライアントへの伝え方

読む順番の目安です。

  • まず自分のPCで動かしたい:第2回 → 第3回 → 第4回 → 第5回
  • 考え方から順に理解したい:第1回から順番に
  • すでに動かしていて、結果を信じてよいか不安:第8回 → 第10回

 

まとめ

今回のポイントです。

  • 前後比較の14.7%には、GW・土日の多さ・伸びのトレンドなど、キャンペーンがなくても増えた分が混ざる
  • CausalImpactは「キャンペーンがなかったら」の売上を予測し、実測値との差を効果とみなす
  • 練習用データでは推定9.6%(95%区間 8.17〜11.13%)。仕込んだ正解の10%を区間の中に捉えた
  • 予測の精度を支えるのは、施策の影響を受けていない対照系列(他店舗の売上)
  • 対照系列が施策の影響を受けると、効果を大きくも小さくも見誤る。出力は何も警告しない

 

よくある質問

対照系列(他店舗のデータ)がなくても使えますか?

使えます。自店舗の過去の売上だけで「ふだんの動き」を学び、予測します。

ただし、GWのような特別な日を予測に反映しにくくなり、予測の精度が落ちやすくなります。

対照系列あり・なしの比較は第8回で行います。

A/Bテストと何が違いますか?

A/Bテストは、同じ期間に「施策あり」と「施策なし」のグループをランダムに分けて比べます。

CausalImpactは、施策をした対象が1つしかなく、ランダムに分けられなかった施策を、施策前の動きと対照系列から評価します。

A/Bテストができるなら、そちらのほうが確実です。

「Posterior prob. of a causal effect」は、p値と同じものですか?

同じではありません。

1行上の「Posterior tail-area probability p」は、この大きさの効果が偶然だけで生じる確率を表し、p値と似た読み方をします。

ただし、ベイズ統計の考え方で計算した値です。「Posterior prob. of a causal effect」は、1からこのpを引いて%で表したものです。詳しくは第5回で扱います。

日次以外(週次・月次)のデータでも使えますか?

CausalImpactで実践する 施策の効果検証(第1回)前後比較で施策の効果を測ると何がまずいのか(反実仮想とは)

使えます。ただし、データの点数が減るので、介入前期間を長めに取る必要があります。

目安は季節の周期1回分以上です。たとえば、年ごとの季節性がある月次データなら、2年分以上あると安心です。

Screenshot

【月1 特定テーマ講座(10月)】
Python で学ぶ 明日からできる「欠損値処理」超入門

【開催日時】 全2回(土)2026/10/17,10/31(13:30〜18:00)
【受講形式】 当日Zoom( or 復習用に後日動画視聴)
【参加費用】 2万2千円(税込み)/人