2015-09-04
二項テストとフロンティア曲線を用いた広告配信の最適化
久々のポスト。
広告配信やサイトのグロースハックなどにおいて複数の施策を試すとき、どの施策がどのくらい効果がありそれぞれバラツキがどの程度なのかを分析しながら施策の組み合わせを試したくなります。
その際に、お手軽でそこそこ理論的な裏付けも把握しやすい方法として
という方法を考案してみました。
試しながらやりたい方は、ソースがこちら(Chiral’s gist)に置いてあります。
二項テスト:途中実績から効果の程度とバラツキを分析
広告がA,B,Cと3つあり、それぞれ以下のように配信実績(Imp=インプレッション数,Click=クリック数)が得られているとします。
広告 | Imp | Click |
---|---|---|
A | 300 | 6 |
B | 500 | 14 |
C | 150 | 5 |
よくあるのがこの表のように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"というオプションを使います。
結果はこんな感じ。
広告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だけから現実的に目標を達成可能な配分比率)だけをプロットします。
結果はこんな感じ。
プロットした点の予算配分比は以下のようになります。(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 までご連絡ください。
- 2015-08-30 日本橋浜町Weblog別館(日々酔亭) 4/71 5%