Hatena::ブログ(Diary)

アドファイブ日記

2015-09-04

二項テストとフロンティア曲線を用いた広告配信の最適化

久々のポスト。

広告配信やサイトのグロースハックなどにおいて複数の施策を試すとき、どの施策がどのくらい効果がありそれぞれバラツキがどの程度なのかを分析しながら施策の組み合わせを試したくなります。

その際に、お手軽でそこそこ理論的な裏付けも把握しやすい方法として

  • 途中経過から効果の程度とそのバラツキを分析するために二項テスト
  • 二項テストの結果から正規分布を仮定してポートフォリオ理論によって次の打ち手を決める

という方法を考案してみました。

試しながらやりたい方は、ソースがこちら(Chiral’s gist)に置いてあります。

二項テスト:途中実績から効果の程度とバラツキを分析

広告がA,B,Cと3つあり、それぞれ以下のように配信実績(Imp=インプレッション数,Click=クリック数)が得られているとします。

広告ImpClick
A3006
B50014
C1505

よくあるのがこの表のようにImpのボリュームがそろってなくて、CはA,BよりCTRは高そうだがまだ数打ってないので未知数、といった状況です。それぞれの真の効果の程度とバラツキを知りたいところです。

こういうときに統計で便利なのが二項テストです。広告をクリックするかしないかは二項分布だと仮定して、分析ができます。Rだとbinom.testという関数で使えます。例えば、300impで5クリックだったら「真のCTR」は

> binom.test(5,300)

	Exact binomial test

data:  5 and 300
number of successes = 5, number of trials = 300, p-value < 2.2e-16
alternative hypothesis: true probability of success is not equal to 0.5
95 percent confidence interval:
 0.005433205 0.038463745
sample estimates:
probability of success 
            0.01666667 

という感じで95%信頼区間で0.54%〜3.84%という値が出てきます。binom.test(5,300,conf.level=0.80)みたいにすると80%信頼区間を求めることができます。

これを上表の配信実績データに適用してggplot2で箱ヒゲ図を描いてみます。
箱の境界は信頼区間80%、ヒゲの境界は信頼区間95%という設定とします。

input <- read.csv("input.csv",header=T)

ctr <- data.frame()
for (i in 1:nrow(input)) {
  r<-input[i,]
  bt1 <- binom.test(r$Clk,r$Imp,conf.level=0.80)
  bt2 <- binom.test(r$Clk,r$Imp,conf.level=0.95)
  p <- bt1$estimate
  conf1 <- bt1$conf.int
  conf2 <- bt2$conf.int
  tmp <- data.frame(CTR=p,CTRvar=p*(1-p),
                    CTR11=conf1[1],CTR12=conf1[2],
                    CTR21=conf2[1],CTR22=conf2[2])
  ctr <- rbind(ctr,tmp)
}
input <- cbind(input,ctr)

g <- ggplot(
  input,
  aes (
    x = Ad,
    middle = CTR,
    ymin = CTR21,
    ymax = CTR22,
    lower = CTR11,
    upper = CTR12,
    color = Ad
  )
)

ymin <- min(input$CTR21)
ymax <- max(input$CTR22)

# relaxing ylim
margin <- (ymax-ymin)/5
ymin <- max(0,ymin-margin)
ymax <- min(1,ymax+margin)

g <- g+geom_boxplot(stat="identity")
g <- g+coord_cartesian(ylim=c(ymin,ymax))

png("bar.png")
plot(g)
dev.off()

geom_boxplotはデフォルトで集計までやってくれちゃいますが、今はbinom.testの結果を表示したいだけなのでstat="identity"というオプションを使います。

結果はこんな感じ。
f:id:isobe1978:20150904232443p:image
広告Aがベースラインだとして、広告Bはimp数が出ているぶんbinom.testによってバラツキが小さく推定されてます。広告Cは効果は良さそうであるもののまだ広告Bと比べてimp数が少ないのでバラツキが広いです。

じゃあこの状況をどう次のアクションにフィードバックしましょう?というところでポートフォリオ理論のフロンティア曲線というのが使えます。

フロンティア曲線:二項分布を正規分布とみなしちゃってポートフォリオ最適化

ポートフォリオ理論を使うと、複数の広告に予算を配分したい際に「所望の平均CTRをもっとも小さいバラツキで得る配分比率」を求めることができます。

ハイリターンを狙ってCTRの目標値を高く掲げると、そのぶん広告Cのようなバラツキの大きな広告に予算を多めに投下する必要が生じる、そういった状況を数学的に定量化する手法とも言えます。

正規分布を仮定する必要があるのですが、ここでは二項分布の推定値pから決まる分散p*(1-p)を正規分布分散とみなして適用してみます。

R言語でそのフロンティア曲線を求めるにはfPortfolioというパッケージがあるのですが、これは入力が時系列(timeSeries型)じゃないといけない仕様になっていて、今回のデータの場合は配信実績を時系列データを集計した後の値なので直接使えません。

そこで、自前で求めてみます。この資料が非常にわかりやすく、その通りにコーディングしてみます。

V <- diag(input$CTRvar)
V1 <- V
V1 <- rbind(V1,0)
V1 <- rbind(V1,0)
V1 <- cbind(V1,0)
V1 <- cbind(V1,0)

N <- nrow(input)
V1[N+1,1:N] <- input$CTR
V1[N+2,1:N] <- 1
V1[1:N,N+1] <- -input$CTR
V1[1:N,N+2] <- -1

V1_inv <- inv(V1)

x <- c()
y <- c()
for (r in seq(ymin,ymax,length.out=100)) {
  bvec <- c(rep(0,N),r,1)
  wvec <- V1_inv %*% bvec
  w <- wvec[1:N]
  if (all(w>=0)) {
    x <- c(x,t(w) %*% V %*% w)
    y <- c(y,r)
    names(w) <- paste(input$Ad,'_weight(%)',sep='')
    print(w*100)
  }
} 

png("frontier.png")
plot(x,y,type="p",xlab="variance",ylab="CTR")
lines(x,y)
dev.off()

分散行列のところが対角行列になってるのは、本来は複数の時系列の相関を集計した値が入れるべきなのですが、今は広告A,B,Cそれぞれ独立に配信した結果の集計データが得られているという想定なので、共分散成分はとりあえずゼロとします。

また、フロンティア曲線の計算式をそのまま使って配分比を出すと配分比がマイナスのものが出てきたりするので、探索の刻みを細かく(上のソースだと100)して配分比が全てプラスになる点(広告A,B,Cだけから現実的に目標を達成可能な配分比率)だけをプロットします。

結果はこんな感じ。
f:id:isobe1978:20150904232444p:image

プロットした点の予算配分比は以下のようになります。(Rからの出力をブログ用に整形)


CTR分散A_weight(%)B_weight(%)C_weight(%)
0.02267979 0.01135905 70.223897 24.194260 5.581843
0.023586978 0.009956157 62.67435 26.05829 11.26736
0.024494169 0.009003916 55.12480 27.92233 16.95288
0.025401361 0.008502329 47.57525 29.78636 22.63839
0.026308552 0.008451394 40.02570 31.65039 28.32391
0.027215744 0.008851113 32.47615 33.51442 34.00942
0.028122935 0.009701486 24.92660 35.37846 39.69494
0.02903013 0.01100251 17.37705 37.24249 45.38046
0.02993732 0.01275419 9.827504 39.106522 51.065973
0.03084451 0.01495652 2.277955 40.970555 56.751489

分散が一番小さくなる値はCTR=2.63%, σ=p*(1-p)=0.008451394で、解くと*1p=0.85%なのでCTR=2.63±0.85%という感じです(ただし二項分布を正規分布と仮定してるのでこれは目安値です)。またこのとき広告A,B,Cにそれぞれ40%,31.7%,28.3%という配分比率になります。

CTRがもっとも大きくなるのはCTR=3.08%, σ=p*(1-p)=0.01495652で、解くとp=1.52%なのでCTR=3.08±1.52%になり、配分比率は2.3%, 41.0%, 56.8%という感じでアグレッシブに広告Cを攻める感じになってますね。

というわけで

二項テストとポートフォリオ理論を組み合わせて広告配信戦略を考えてみました。

二項分布はデータがたくさん溜まれば正規分布に近づきますがデータが少ない状況では怖い仮定ではあります。

また、本エントリでは共分散を考慮してませんでしたが広告配信の生ログを使って時刻を揃えた時系列の束にして共分散成分を求めてやるという改良も考えられます。広告A,B,Cが同一インプレッションで配信されて独立ではなくなる状況ではそのほうがより現実に即したモデルになるでしょう。

最後に宣伝ですが、アドファイブ(株)ではデータ分析の企画・実施からシステム機能実装まで単独で行えることを強みとし仕事をお受けしておりますのでお仕事のご依頼ご相談はisobe あっと adfive.net までご連絡ください。

*1:中学校で習った二次方程式の解の公式より(1-sqrt(1-4*σ) )/2で求まります

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/isobe1978/20150904/1441380352
おとなり日記