広告/統計/アニメ/映画 等に関するブログ

広告/統計/アニメ/映画 等に関するブログ

【備忘録】Rを使いながらバナー広告のA/Bテストについて考えてみる

恥ずかしながら普段はバナー広告のA/Bテストをほぼ行っていないのですが、そろそろ真面目に勉強しようと思って色々と調べてみました。
色んな方のブログがありましたが、僕の中では、この方の考えが合点がいったところがあります。

abrahamcow.hatenablog.com

abrahamcow.hatenablog.com

基本的にはこの方の考えに賛同し、比較についてはクリック数を棒グラフにして信頼区間を見ながら考えよう、というスタンスですが、

今回の記事では、そこに加えて、「では実際のデータでやってみようとしたらどういうことが起きうるか?」を考えてみたいと思います。

まず手元にあるデータってこんな形してません?

実際に自分の手元のデータでA/Bテストをしようとしてみると、色々と簡素化されたデータでは省略されていた問題が出てきて、どうしたらいいものか?と思うことはいでしょうか?(私が今そういう状況です)

適当に作ってみたデータ例です。

Creative    Campaign      Size Device    IMP  CTs
1   TYPE_A targeting_1 300 x 250 PC_Tab   2200    5
2   TYPE_A targeting_1 300 x 250    SMF 487200  820
3   TYPE_A targeting_1 320 x 100 PC_Tab    100    0
4   TYPE_A targeting_1 320 x 100    SMF 103500   40
5   TYPE_A targeting_1  320 x 50 PC_Tab   2100   10
6   TYPE_A targeting_1  320 x 50    SMF 550600 7500

例えば、GDNでバナーのデザイン別のレポートって、だいたいこんな感じではないでしょうか?
つまり、入稿したサイズ別*1、配信デバイス別、ターゲティングも”リマーケティング”、”アフィニティカテゴリー”、”トピックターゲット”等、「ん?これは一概に扱っていいのかね?」というデータで出てくると思います。

先ずは、データをプロットしてみる

先ほどのブログの通り、クリック率で比較するとして、データからクリック率(CTR)を求め、かつ二項分布と仮定して信頼区間をだします。
インプレッション数がサンプルサイズということになります。

#データの読込
df <- read.csv("test.csv",header=T)
#CTRの計算
df$CTR <- df$CTs/df$IMP*100
#負の二項分布だと仮定して信頼区間を入れてみる
df$lower <- qbinom(0.025,size=df$IMP,prob=df$CTR)/df$IMP
df$upper <- qbinom(0.975,size=df$IMP,prob=df$CTR)/df$IMP
head(df)

残念ながら信頼区間が出せないレベルのIMP数のものがあります。

 Creative    Campaign      Size Device    IMP  CTs        CTR      lower      upper
1   TYPE_A targeting_1 300 x 250 PC_Tab   2200    5 0.22727273 0.21000000 0.24500000
2   TYPE_A targeting_1 300 x 250    SMF 487200  820 0.16830870 0.16725780 0.16935961
3   TYPE_A targeting_1 320 x 100 PC_Tab    100    0 0.00000000 0.00000000 0.00000000
4   TYPE_A targeting_1 320 x 100    SMF 103500   40 0.03864734 0.03747826 0.03982609
5   TYPE_A targeting_1  320 x 50 PC_Tab   2100   10 0.47619048 0.45476190 0.49761905
6   TYPE_A targeting_1  320 x 50    SMF 550600 7500 1.36215038        NaN        NaN

欠損値のあるレコードを除くことにします

df <- na.omit(df)

ggplot2を使ってクリック率を棒グラフにします。

library("ggplot2", lib.loc="C:/R-3.2.3/library")
g <- ggplot(df,aes(x=Creative,y=CTR,fill=Creative))
g <- g + theme_gray(base_family="Japan1GothicBBB")
g <- g + geom_bar(stat="identity",position="dodge")
g <- g + facet_grid(Size~Campaign+Device)
g <- g + geom_errorbar(aes(ymin=lower,ymax=upper,width = 0.3))
g <- g + geom_text(aes(x=Creative,y=0,label=round(CTR,2)),vjust=-1.2,size=8)
g <- g + theme(axis.text.x=element_text(angle=0,size=8,color="black"))
plot(g)
ggsave(file="df.png",plot=g,height=8.27,width=11.69,scale=1)

facet_gridを使って、縦方向をサイズ別、横方向をターゲティング別+デバイス別、としました。
geom_errorbarを使ってエラーバーとして信頼区間を加えています。

f:id:yyhhyy:20160507221755p:plain

しかしここで、「クリック率が高いこの施策の組み合わせが一番いいのだ!!」と早合点してはいけないと思います。

インプレッション数も一緒に考えてみたい*2

先ほどのデータでは、IMPがやたら多いものと少ないものとがあったと思います。
そもそも余り配信されない組み合わせは強化してもインパクトが少ない筈です。

g <- ggplot(df,aes(x=Creative,y=CTR,fill=Creative))
g <- g + theme_gray(base_family="Japan1GothicBBB")
g <- g + geom_bar(stat="identity",position="dodge")
g <- g + facet_grid(Size~Campaign+Device)
g <- g + geom_errorbar(aes(ymin=lower,ymax=upper,width = 0.3))
g <- g + geom_text(aes(x=Creative,y=0,label=round(CTR,2)),vjust=-1.2,size=8)
g <- g + theme(axis.text.x=element_text(angle=0,size=8,color="black"))
g <- g + geom_point(aes(x=Creative,y=0.4,size=sqrt(IMP)),color="blue",alpha=0.5) +
  scale_size_continuous(range = c(min(sqrt(df$IMP))/50,max(sqrt(df$IMP)))/50)
g <- g + geom_text(aes(x=Creative,y=0.4,label=IMP),alpha=0.8,size=4)
plot(g)
ggsave(file="df2.png",plot=g,height=8.27,width=11.69,scale=1)

ということで、geom_pointを使ってインプレッション数をバブルチャートで加えてみました。
バブルチャートの場合、面積が大きさとなりますので、サイズ=半径については、平方根(SquareRoot)をとる方がよいのかなと思いました。

f:id:yyhhyy:20160507222105p:plain

この中で、IMP数が多いターゲティングのうち、明らかにクリエイティブ別のクリック率に差があるものがあれば、そこにクリエイティブを併せていく、というのが良いのではないかと思います。
ターゲティング1でスマートフォンでの配信、ターゲティング2でスマートフォンでの配信が配信数が多そうです。その場合タイプAの方がクリック率が高いようですが、積極的にAだけにしよう!とする程の差でもない気がします。*3

ところでデバイスやサイズについての評価は?

A/Bテストといっても、そもそもクリエイティブの影響とターゲティングの影響とデバイス別の影響と、どれが一番影響しているの?クリエイティブを確認することに意味はあるの??

という疑問もあると思います。

そこで、回帰分析をしてみます。

#使わない値を外す
df <- df[,-which (colnames(df) %in% c("IMP","CTs","lower","upper"))]
#カウントデータなので二項分布を使ったロジスティック回帰
df_lm <- glm(CTR ~.,family=binomial,df)
summary(df_lm)

結果

Coefficients:
                    Estimate Std. Error z value Pr(>|z|)
(Intercept)          -2.1547     1.4890  -1.447    0.148
CreativeTYPE_B       -0.1318     1.0718  -0.123    0.902
Campaigntargeting_2   0.2168     1.0730   0.202    0.840
Size320 x 100        -1.9741     2.7654  -0.714    0.475
Size320 x 50          1.3091     1.6717   0.783    0.434
Size468 x 60          1.0124     1.3297   0.761    0.446
DeviceSMF             0.3110     1.2376   0.251    0.802

Estimateが影響の指数になるわけですが、このままではみにくいので、図示してくれる便利なパッケージを使いグラフで見ます。

library("coefplot", lib.loc="C:/R-3.2.3/library")
g <- coefplot(df_lm)
g <- g + theme_gray(base_family="Japan1GothicBBB")
plot(g)
ggsave(file="df_lm.png",plot=g,height=8.27,width=11.69,scale=1)

こんな感じです

f:id:yyhhyy:20160507222833p:plain

ところでサイズって4つありましたよね?

一つ減っているのは、Rの優秀さの現れです。

サイズ、クリエイティブのタイプ、デバイス というのは数値ではなくカテゴリカルなデータです。本来は、0,1のデータに変換しないと回帰分析はできません。
例えば、クリエイティブの列は、クリエイティブAを「1」、クリエイティブBを「0」とする列に変換しないといけません。サイズだと、サイズAかどうか?で1列、サイズBかどうか?で1列、サイズCかどうか?で1列、サイズDかどうか?で1列、と作ります。
ただし、そのままだと情報が各カテゴリーで1つずつ余分になります。例えば、サイズAでもBでもCでもないとわかればDであることは明白ですのでサイズDに関する列は情報として余分です。

どれか一列を基準としてはずさないといけないのですが、Rのlmやglmは、勝手に一つ減らしてくれるのです。
逆に言うと、表やグラフを見るとき、それを思い出しつつ注意してみないといけません。

因みにこのグラフを見ると、クリエイティブタイプやターゲティングより、バナーのサイズの方の影響の方が多そうですね。

結論

残念ながら今回のテストデータでは、クリエイティブをどちらにすべきか?については、余り判断に足る情報は得られませんでした。
今回はテストデータでしたが、実際の施策でも結構そんなものじゃないかと思います。最初からちゃんとターゲティングができていれば無理にPDCAを回す必要なんてないからです。PDCAを回して何か小さな改善をしていくことよりも、事業へのインパクトの有無を判断軸にしておきたいと思います。

----------------------------

その他参考にしたサイト

■カテゴリカルデータの分析について

Rとカテゴリカルデータのモデリング(1)

Rとカテゴリカルデータのモデリング(2)

■バブルチャートについてsutchy.cocolog-nifty.com

■信頼区間について

検定と区間推定

http://cse.naro.affrc.go.jp/takezawa/r-tips/r/60.html

■エラーバーの出し方について

geom_bar | ggplot2で棒グラフや積み上げ棒グラフを描く方法

■coefプロットについて

みんなのR データ分析と統計解析の新しい教科書

みんなのR データ分析と統計解析の新しい教科書

 

 

 

*1:GDNはもっと色々入れられますが露出量の多い6タイプ位をよくいれます

*2:クリック数の方がいいかもしれませんが取り急ぎ

*3:ここで”効果量”を考えるべきなのでしょう。今は僕はまだ知識が足りません