BigQuery でランダムサンプリング

  • 6
    Like
  • 12
    Comment

やりたいこと

タイトルの通り BigQuery でランダムサンプリングをする方法を調べたり考えたりしたのでメモを残しておきます。

方法その 1

乱数生成結果を入れただけの列を追加して、その列についてソートするという作業をサブクエリで記述します。

SELECT
  word
FROM (
  SELECT word, RAND() AS rand
  FROM [publicdata:samples.shakespeare]
)
ORDER BY
  rand
LIMIT
  100

RAND() 関数が真の乱数を生成するという仮定の下でランダムサンプリングが実現できており、最も素直で確実な方法だと思います。

欠点

ただ、この方法には 1 つ大きな欠点があります。

BigQuery では乱数の seed を固定できないので、再現性がないのです。
どう頑張っても実行するたびに結果が変わってしまいます。
コメントで seed を固定が可能との指摘を頂きました。
ただ、 LIMIT を使った場合、大抵は同じ結果が返ってくるけれど、保証は無いようです。

再現性のない実験や分析なんて、トトロやラピュタの目撃情報と大差ありません。

方法その 2

最近見かけて頭良いなと思った方法を改良したものです。

SELECT
  word
FROM (
  SELECT word, ROW_NUMBER() OVER() AS rand
  FROM [publicdata:samples.shakespeare]
)
WHERE
  ABS(HASH(STRING(rand))) % 1600 = 0

以下のような処理を行っています:

  • RAND() ROW_NUMBER() を使って重複が無い列を追加して、その列を HASH() に突っ込む
    • HASH() は STRING か INTEGER しか受け取れないので STRING に変換している
  • 負の値を除くために HASH() の結果を ABS() に突っ込む
  • それを適当な整数で割った余りを指定して行を抽出する

HASH() が良い感じにランダムな整数値を返すという仮定の下に成り立っている方法なので、乱数としての質はイマイチだと思います。
ですが、この方法の素晴らしいところは何度やっても同じ結果が返ってくるという再現性です。

LIMIT を使って抽出する個数を制御すると BigQuery の機嫌次第で毎回結果が変わるので、何で割り切れるものを抽出するか (上記の例では 1600 になっているところ) を変えて、おおよその個数を制御しています。
今回は元のデータが 164656 行あって、大体 100 行ほどランダムサンプリングしたかったので 1600 で割り切れるものを抽出しました。

欠点

もう書きましたが、個数の制御がアバウトにしかできません。
LIMIT を使って個数を指定すると再現性が失われてしまいます。

まとめ

紹介した 2 種類の方法で大抵の場合は何とかなりそうですが、それぞれ一長一短なので状況によって使い分ければ良いかなと思っています。

もちろん再現性と個数の正確な指定を両立できる方法があればベストですが、残念ながらそれは思いつきませんでした。
どなたか知っている人がいたらコメントで教えてください。