Ghostwriting-Service Dr. Rainer Hastedt

Fachtexte, White Papers, statistische Auswertungen

Erfolgsmessung im Marketing - Folge 31: Googles CausalImpact-Package (Teil 1)

Ich hatte mich in Folge 15 mit Vorher-Nachher-Vergleichen beschäftigt. Bei diesem Verfahren vergleichen Sie den Erfolg im Nachher-Zeitraum mit dem Erfolg im Vorher-Zeitraum. Dies kann sinnvoll sein, wenn Sie Grund zu der Annahme haben, dass die beobachtete Veränderung durch eine im Nachher-Zeitraum ergriffene Marketing-Maßnahme verursacht wurde.

Ein Vorher-Nachher-Vergleich wäre für Fragen der folgenden Art unzureichend:

Sie messen die wöchentlichen Visits auf Ihrer Website, die Ihnen Google gebracht hat. Seit einiger Zeit schalten Sie auch Textanzeigen mit Google Ads und wollen wissen, ob sich dies für Sie lohnt. Im Vorher-Zeitraum hatten Sie Visits durch Klicks auf Google-Suchergebnisse, im Nachher-Zeitraum Visits durch Klicks auf Google-Suchergebnisse und Klicks auf Textanzeigen. In diesem Fall könnte das Schalten der Textanzeigen die Anzahl der Visits durch Klicks auf Google-Suchergebnisse reduziert haben.

Ein Ansatz zur Untersuchung derartiger Fragen besteht in einer modifizierten Form des Vorher-Nachher-Vergleichs:

Das von Google verwendete CausalImpact-Package vergleicht den Erfolg im Nachher-Zeitraum mit einem prognostizierten Wert, dem Erfolg im Nachher-Zeitraum, der sich ohne die Marketing-Maßnahme ergeben wäre. Die Differenz zwischen den beiden Größen dient dann als Schätzwert für den Effekt der Marketing-Maßnahme, den Causal Impact.

Ich erläutere das Verfahren anhand von Beispieldaten, indem ich zunächst einen Vorher-Nachher-Vergleich anstelle, dann den Effekt mit der Methode der kleinsten Quadrate schätze und schließlich das CausalImpact-Package verwende und die Ergebnisse vergleiche.

Vorher-Nachher-Vergleich

Als Ausgangspunkt dient mein Datensatz eins, den Sie in der folgenden Grafik sehen und am Ende dieses Beitrags in Form einer HTML-Tabelle finden. Der Vorher-Zeitraum umfasst 70 Beobachtungswerte, der Nachher-Zeitraum 30.

Vermutung:

Eine seit Beginn des Nachher-Zeitraums laufende Marketing-Maßnahme hat die höheren Werte im Nachher-Zeitraum verursacht.

Datensatz eins

library(ggplot2)
svg("emim-31-1.svg", width=5.3, height=3.6, bg="transparent")
ggplot(daten,aes(x=x,y=y)) +
geom_line(colour="#dddddd") +
geom_vline(xintercept=70.5,colour="darkgrey",
size=0.8,linetype="dashed") +
geom_point() +
annotate("text",x=69.5, y=97.2,
label="Vorher-Zeitraum",hjust=1,vjust=0, size=4) +
annotate("text",x=71.5, y=97.2,
label="Nachher-Zeitraum",hjust=0,vjust=0, size=4) +
labs(x="Zeit",
y="Visits",
title="Datensatz eins") +
theme_bw() +
theme(plot.background=
element_rect(colour=NA,fill="transparent"))
dev.off()

Für einen Vorher-Nachher-Vergleich Art berechne ich für jeden der beiden Zeiträume den Mittelwert. Dann subtrahiere ich vom Mittelwert für den Nachher-Zeitraum den Mittelwert für den Vorher-Zeitraum. Als Ergebnis erhalte ich 6,36 (gerundet).

Die folgende Grafik zeigt die beiden Mittelwerte und den sich hieraus ergebenden Schätzwert für den Effekt der Marketing-Maßnahme:

Vorher-Nachher-Vergleich

mw1 <- mean(daten[71:100,"y"]) # 106.5426
mw0 <- mean(daten[1:70,"y"]) # 100.1793
mw1-mw0 # Effekt = 6.363274

svg("emim-31-2.svg", width=5.3, height=3.6, bg="transparent")
ggplot(daten,aes(x=x,y=y)) +
geom_line(colour="#dddddd") +
geom_vline(xintercept=70.5,colour="darkgrey",
size=0.8,linetype="dashed") +
geom_point(colour="#bbbbbb") +
geom_segment(aes(x=70.5,xend=100,y=mw1,yend=mw1),
size=1,colour="darkblue",linetype="solid") +
geom_segment(aes(x=1,xend=70.5,y=mw0,yend=mw0),
size=1,colour="darkblue",linetype="solid") +
geom_segment(aes(x=72.5,xend=72.5,y=mw0,yend=mw1),
size=1,colour="#dd0000",
arrow=arrow(ends="last",length=unit(0.3,"cm"))) +
annotate("text",x=69.5, y=97.2,label="Vorher-Zeitraum",hjust=1,vjust=0, size=4) +
annotate("text",x=71.5, y=97.2,label="Nachher-Zeitraum",hjust=0,vjust=0, size=4) +
annotate("text",x=75.5, y=103,label="Effekt = 6,36",
hjust=0,vjust=0, size=4,col="#dd0000",fontface=1) +
labs(x="Zeit",
y="Visits",
title="Vorher-Nachher-Vergleich") +
theme_bw() +
theme(plot.background=
element_rect(colour=NA,fill="transparent"))
dev.off()

Beim Vorher-Nachher-Vergleich wird vorausgesetzt, dass sich bei einem Verzicht auf die Marketing-Maßnahme im Nachher-Zeitraum der gleiche Mittelwert ergeben hätte wie im Vorher-Zeitraum. Dies ist hier allein schon deshalb fragwürdig, weil in den Daten des Vorher-Zeitraums ein leichter Aufwärtstrend erkennbar ist.

Eine naheliegende Verbesserung besteht darin, einen Trend in den Daten des Vorher-Zeitraums bei der Schätzung des Effekts zu berücksichtigen.

Vorher-Nachher-Vergleich mit Zeitreihenprognose

Als Datenbasis für meine Zeitreihenprognose verwende ich die 70 Beobachtungswerte des Vorher-Zeitraums aus der Datensatz eins.

Der Ansatz lautet:

Visitst = α + β*t + εt für t = 1, …, 70

reg <- lm(y~x,daten[1:70,]) # y = Visits, x = t (Zeit)
summary(reg)

Als Schätzwerte erhalte ich 99,64 für die Niveaukonstante α und 0,02 für die Steigung β (jeweils gerundet). Beide Koeffizienten sind signifikant (p=0,000 für die Niveaukonstante und p=0,033 für den Einfluss der Variablen x).

Die folgende Grafik zeigt die Daten des Vorher-Zeitraums und die Regressionsgerade, die den Aufwärtstrend in den Daten verdeutlicht:

Regressionsanalyse für den Vorher-Zeitraum

svg("emim-31-3.svg", width=5.3, height=3.6, bg="transparent")
ggplot(fortify(reg),aes(x=x,y=y)) +
geom_point(colour="#bbbbbb") +
labs(x="Zeit",
y="Visits",
title="Regressionsanalyse für den Vorher-Zeitraum") +
geom_line(aes(y=.fitted)) +
theme_bw() +
theme(plot.background=
element_rect(colour=NA,fill="transparent"))
dev.off()

Ich verwende jetzt die Schätzergebnisse für meine Regressionsgleichung, um die Anzahl der Visits für den Nachher-Zeitraum (t = 71 bis 100) vorherzusagen:

Visitst = 99,64 + 0,02*t für t = 71, …, 100

Ich hatte die Regressionsgleichung ursprünglich für den Vorher-Zeitraum geschätzt (t = 1 bis 70) und verwende sie jetzt für den Nachher-Zeitraum (t = 71 bis 100). Ich unterstelle demnach für den Vorher- und den Nachher-Zeitraum eine einheitliche Regressionsgleichung, die ich jedoch nur anhand meiner Daten für den Vorher-Zeitraum geschätzt habe.

Wie hätte sich die Anzahl der Visits im Nachher-Zeitraum entwickelt, wenn die Marketing-Maßnahme im Nachher-Zeitraum unterblieben wäre?

Zur Beantwortung dieser Frage berechne ich die prognostizierten Werte (t = 71 bis 100) und bilde hiervon den Mittelwert:

prog <- predict(reg,daten[71:100,])
mwp <- mean(prog) # 100.9319

Als Mittelwert der prognostizierten Visits mwp erhalte ich 100,93 (gerundet). Dies ist etwas mehr als der für den Vorher-Zeitraum ermittelte Wert (mw0 = 100,18).

Beim Vorher-Nachher-Vergleich hatte ich den Effekt nach der Formel mw1-mw0 berechnet (durchschnittliche Visits im Nachher-Zeitraum minus durchschnittliche Visits im Vorher-Zeitraum). Ich ersetze jetzt den Wert mw0 durch den Wert mwp und berücksichtige hiermit den für den Vorher-Zeitraum gefundenen Aufwärtstrend.

Der Schätzwert für die Größe des Effekts ist daher gleich mw1-mwp = 5,61 (gerundet).

Ich zeichne dies:

daten2 <- data.frame(x=daten$x,
fittedreg=c(reg$fitted,rep(NA,30)),
prognose=c(rep(NA,70),prog))

svg("emim-31-4.svg", width=5.3, height=3.6, bg="transparent")
ggplot(daten2,aes(x=x,y=y)) +
geom_point(colour="#bbbbbb") +
geom_line(aes(y=fittedreg),size=0.6,colour="darkblue") +
geom_point(aes(y=prognose),size=0.6,colour="red") +
geom_vline(xintercept=70.5,colour="darkgrey",
size=0.8,linetype="dashed") +
geom_segment(aes(x=70.5,xend=100,y=mwp,yend=mwp),
size=1,colour="darkblue",linetype="solid") +
geom_segment(aes(x=70.5,xend=100,y=mw1,yend=mw1),
size=1,colour="darkblue",linetype="solid") +
geom_segment(aes(x=72.5,xend=72.5,y=mwp,yend=mw1),
size=1,colour="#dd0000",
arrow=arrow(ends="last",length=unit(0.3,"cm"))) +
annotate("text",x=69.5, y=97.2,
label="Vorher-Zeitraum",hjust=1,vjust=0, size=4) +
annotate("text",x=71.5, y=97.2,
label="Nachher-Zeitraum",hjust=0,vjust=0, size=4) +
annotate("text",x=75.5, y=103.3,
label="Effekt = 5,61",hjust=0,vjust=0,
size=4,col="#dd0000",fontface=1) +
labs(x="Zeit",
y="Visits",
title="Vergleich mit Zeitreihenprognose") +
theme_bw() +
theme(plot.background=
element_rect(colour=NA,fill="transparent"))
dev.off()

Vorher-Nachher-Vergleich mit Zeitreihenprognose

Sie sehen in der unteren Hälfte der Grafik die Regressionsgerade (ansteigende dunkelblaue Linie) und die für den Nachher-Zeitraum prognostizierten Visits (rote Punkte). Die Mittelwerte mw1 und mwp sind durch die waagerechten Linien dargestellt.

Für meinen Vergleich mit dem CausalImpact-Package schätze ich zusätzlich ein Konfidenzintervall für den Effekt. Hierzu schätze ich eine Regressionsgerade von der Art y = α + βx, deren Koeffizient β der Effekt ist.

Grundlage für meine Schätzung sind die Daten für den Nachher-Zeitraum und die für den Nachher-Zeitraum prognostizierten Werte. Außerdem verwende ich eine (0,1)-Variable.

Y <- c(daten2[71:100,"prognose"],daten[71:100,"y"])
D <- c(rep(0,30),rep(1,30))

Der Vektor Y enthält die prognostizierten und die tatsächlichen Visits, jeweils für den Nachher-Zeitraum. Vektor D besteht aus 30 Nullen und 30 Einsen.

Meine Regressionsgleichung lautet:

YT = α + β*DT + εT mit T = 1, …, 60

reg2 <- lm(Y~D)
summary(reg2)

Der Schätzwert für den Koeffizienten der (0,1)-Variablen ist signifikant (p=0,000) und stimmt mit dem Effekt überein (5,6107). Der Schätzwert für die Niveaukonstante ist ebenfalls signifikant (p=0,000) und ist gleich mwp (100,9319).

confint(reg2)

Als 0,95-Konfidenzintervall erhalte ich (5,29926; 5,922049). Der Effekt ist demnach gleich 5,61 ± 0,31 (0,95-Konfidenzintervall, Werte gerundet).

Schätzung des Effekts mit CausalImpact

Google ermittelt den Return on Investment von Google-Ads-Kampagnen mit Hilfe von R und dem CausalImpact-Package (http://blog.revolutionanalytics.com/2014/09/google-uses-r-to-calculate-roi-on-advertising-campaigns.html).

Informationen zum CausalImpact-Package finden Sie auf den Seiten https://cran.r-project.org/package=CausalImpact und http://google.github.io/CausalImpact/. Es gibt dort eine Einführung in das CausalImpact-Package und einen Aufsatz über die verwendete Methode (»Inferring Causal Impact Using Bayesian Structural Time-Series Models«).

Die Anwendung von CausalImpact ist einfach. Ich definiere zunächst den Vorher- und den Nachher-Zeitraum:

pre.period <- c(1,70) # Vorher
post.period <- c(71,100) # Nachher

Jetzt kommt die Auswertung:

library(CausalImpact)
impact <- CausalImpact(daten[,"y",drop=FALSE],
pre.period, post.period)

Sie können sich das Ergebnis in Form eines Texts ausgeben lassen:

summary(impact,"report")

Die von CausalImpact berechneten Werte:

impact$summary

Sie sehen unter anderem:

impact$summary$AbsEffect[1] # Effekt = 6.347309

Der Effekt ist die Differenz zwischen dem Mittelwert im Nachher-Zeitraum mw1 und dem für den Nachher-Zeitraum prognostizierten Mittelwert, der sich bei einem Verzicht auf die Kampagne ergeben hätte. Ich bezeichne diesen prognostizierten Mittelwert mit mwp2.

impact$summary$Actual[1] # 106.5426 = mw1
mwp2 <- impact$summary$Pred[1] # Prognose = 100.1953

Effekt = mw1-mwp2 = 6,347309

Das CausalImpact-Package bietet hierzu eine Grafik, die ich mit zusätzlichen Beschriftungen versehen habe:

svg("emim-31-5.svg", width=5.3, height=4.1, bg="transparent")
plot(impact, "original") +
geom_segment(aes(x=1,xend=100,y=mw1,yend=mw1),
size=0.4,linetype="dashed") +
geom_segment(aes(x=78.1,xend=78.1,y=mwp2,yend=mw1),
size=1,colour="#dd0000",
arrow=arrow(ends="last", length=unit(0.3,"cm"))) +
annotate("text",x=80.1, y=103.1,
label="Effekt = 6,35",hjust=0,vjust=0,
size=4,col="#dd0000",fontface=1) +
annotate("text", x=67, y=mw1+0.1,
label="mw[1]", hjust=1, vjust=0,
size=4, parse=TRUE) +
annotate("text", x=80.1, y=mwp2-0.2,
label="mw[p2]", hjust=0, vjust=1,
size=4,parse=TRUE) +
labs(x="Zeit",
y="Visits",
title="CausalImpact:",
subtitle="Effekt = Mittelwert Nachher - Mittelwert Nachher prognostiziert") +
theme(plot.background=
element_rect(colour=NA,fill="transparent")) +
theme(axis.text=element_text(size=8),
axis.title=element_text(size=12),
plot.title=element_text(size=14),
plot.subtitle=element_text(size=12))
dev.off()

Effekt laut Google-CausalImpact

Ich hatte im vorherigen Abschnitt die Methode der kleinsten Quadrate für meine Zeitreihenprognose verwendet. Das CausalImpact-Package basiert auf einem anderen Verfahren der Regressionsanalyse und liefert daher andere Ergebnisse.

Ein Vorteil des CausalImpact-Packages liegt in der einfachen Anwendung. Bei der Methode der kleinsten Quadrate (OLS) ist dies anders. Meine Gleichung

Visitst = α + β*t + εt für t = 1, …, 70

ist nur sinnvoll, wenn sich meine Daten hierdurch hinreichend gut beschreiben lassen. Andernfalls müsste ich die Kurvenform ändern oder meine Daten transformieren. Die Methode der kleinsten Quadrate erfordert daher immer, dass ich mein Regressionsmodell vor der Schätzung des Effekts prüfe und bei Bedarf modifiziere.

Vergleich der Schätzergebnisse

Ich erstelle eine Tabelle mit den Schätzwerten für die Konfidenzintervalle und den Effekt:

ci_ols <- c(confint(reg2)[2,1],
mw1-mwp,
confint(reg2)[2,2])
ci_CI <- c(impact$summary$AbsEffect.lower[1],
impact$summary$AbsEffect[1],
impact$summary$AbsEffect.upper[1])
daten3 <- data.frame(
x=c("OLS","CausalImpact"),
yu=c(ci_ols[1],ci_CI[1]),
ym=c(ci_ols[2],ci_CI[2]),
yo=c(ci_ols[3],ci_CI[3]),
stringsAsFactors=FALSE)
x yu ym yo
OLS 5,29926022 5,61065481 5,92204939
CausalImpact 5,81640654 6,34730861 6,90512468

Ich erstelle hieraus eine Grafik:

svg("emim-31-6.svg", width=5.3, height=3.6, bg="transparent")
ggplot(daten3,aes(x=x,y=ym)) +
geom_hline(yintercept=mw1-mw0, size=0.4, linetype="dashed") +
geom_errorbar(aes(ymin=yu, ymax=yo),width=0.2) +
geom_point(shape=4,size=5) +
annotate("text", x=1.25, y=mw1-mw0+0.03,
label="Effekt laut Vorher-Nachher-Vergleich", hjust=0, vjust=0, size=4) +
labs(x="Schätzverfahren",
y="Visits (Mittelwerte)",
title="Schätzwerte für den Effekt: 0,95-Konfidenzintervalle") +
theme_bw() +
theme(plot.background=
element_rect(colour=NA,fill="transparent"))
dev.off()

Konfidenzintervalle CausalImpact vs. Regression (OLS)

Mit dem CausalImpact-Package ergibt sich für den Effekt ein Schätzwert, der fast so hoch ist wie das Ergebnis eines Vorher-Nachher-Vergleichs. Mit der Methode der kleinsten Quadrate (OLS) erhalte ich einen deutlich geringeren Schätzwert. Die 0,95-Konfidenzintervalle überschneiden sich jedoch.

Datensatz eins

x y
1 99,5015543
2 98,9813896
3 100,741802
4 99,7982887
5 98,8711229
6 101,616058
7 98,325559
8 98,3667855
9 99,2337765
10 99,4636658
11 100,324057
12 97,8719502
13 99,1075943
14 98,4932543
15 99,216314
16 101,213564
17 100,774714
18 100,058527
19 100,104992
20 99,8198409
21 100,109563
22 100,489431
23 99,4666881
24 101,820151
25 101,093173
26 99,8306003
27 99,0590092
28 99,7600472
29 100,958284
30 101,766345
31 100,323126
32 99,8555863
33 100,512612
34 100,002685
35 98,4797086
36 100,683796
37 101,364484
38 101,677215
39 101,117848
40 101,372664
41 99,9401036
42 100,207955
43 99,7585705
44 99,4495427
45 99,9927617
46 102,95352
47 99,9741758
48 100,592913
49 100,070976
50 101,04321
51 100,71251
52 99,8241224
53 100,069161
54 100,443498
55 102,632423
56 100,821653
57 102,488252
58 98,5524819
59 99,0693248
60 96,8541086
61 98,2336541
62 99,8574657
63 101,043623
64 101,304247
65 101,656343
66 100,784154
67 101,314706
68 99,3770573
69 99,1705215
70 102,731858
71 107,39714
72 106,664968
73 106,7075
74 105,510513
75 107,915939
76 106,798159
77 105,34848
78 105,703675
79 106,208017
80 106,443917
81 105,33143
82 106,278507
83 107,558315
84 106,579954
85 106,558143
86 106,5903
87 107,465578
88 106,229093
89 106,224054
90 106,182379
91 106,308635
92 106,956843
93 106,971453
94 105,75681
95 104,435021
96 106,904138
97 107,002995
98 107,120277
99 106,4397
100 108,686009