こんにちは。 オープンワーク株式会社アナリストグループ所属の佐々木といいます。
弊社では、アナリストがA/Bテストを集計する際にはBigQueryでクエリを書いています。 その際に差が有意かどうかを判断の参考にするためにp値を算出していますが、p値を計算するためだけに、 いったんスプレッドシートやエクセルに集計結果を貼り付けたりPythonで計算したりなどして二度手間になっていました。 そこでBigQuery上で集計するときに、2つの群に対して比率の差を検定したいときに、p値まで算出できる方法を他社事例も参考にして、工夫しました。
具体的には、JavaScriptの統計ライブラリjStatをGoogle Cloud Storage(GCS)上に配置し、 (1) Zスコアを計算する (2) jStatの関数を呼び出してp値を計算する のように2つの簡単なUDFを書くだけでBigQuery上でp値が求められます。 UDFの中身をみると分かりますが、今回は、Zスコアを用いて両側検定をやっています。
例題
以下のような集計をしていて比率の差に有意差があるかをみたいケースを考えましょう。
| ユーザー数 | コンバージョンしたユーザー数 | コンバージョン率 | |
|---|---|---|---|
| パターンA | 3203 | 604 | 18.9% |
| パターンB | 3018 | 673 | 22.3% |
https://unpkg.com/jstat/dist/jstat.js から最新のjStatをダウンロードしてローカル端末に jstat.js と名前をつけて保存し、
GCS上の gs://custom-tools/statistics/jstat.js (一例) にアップロードします。
この場合のp値の計算の仕方は以下のようなクエリをコピペしてBigQuery上で実行すればOKです。
-- Zスコアを求める CREATE TEMPORARY FUNCTION get_zscore(usersA FLOAT64, convA FLOAT64, usersB FLOAT64, convB FLOAT64) RETURNS FLOAT64 LANGUAGE js AS """ let pA = convA / usersA; let pB = convB / usersB; // Pooled probability let pp = (convA + convB) / (usersA + usersB); // Standard Error let se = Math.sqrt(pp * (1 - pp) * (1 / usersA + 1 / usersB)); // Z-score return (pB - pA) / se; """; -- jStatの関数を利用してp値を求める CREATE TEMPORARY FUNCTION get_pvalue(z FLOAT64) RETURNS FLOAT64 LANGUAGE js AS """ return 2 * (1 - jStat.normal.cdf(Math.abs(z), 0, 1)) """ /* OPTIONSでGCSに保存したJavaScript Libraryを指定する */ OPTIONS ( library=["gs://custom-tools/statistics/jstat.js"] ); -- BigQuery上では正しく表示されるも、redash上ではNUMERICにcastしないと0と表示されてしまったのでこのように修正する SELECT CAST(get_pvalue(get_zscore(3203, 604, 3018, 673)) AS NUMERIC) AS p_value SELECT get_pvalue(get_zscore(3203, 604, 3018, 673)) AS p_value;
無事、p値が求められました。

最後に
どんどん進化しつつある弊社のデータ利活用に興味がある方は、ぜひ採用ページもご覧ください。 www.openwork.co.jp
参考資料
この集計を実施するにあたり、 BigQuery で統計処理を完結させる - GO Tech Blog のt検定のUDFを作る部分を参考にさせていただきました。