BigQueryでのデータ分析にARRAYを活用する
こんにちは。
株式会社ココナラのR&Dグループ/検索・推薦グロースチーム所属のエンジニア、Unseoと申します。
本記事では、BigQuery上でのデータ分析にARRAYを活用した分析手法をご紹介いたします。
検索システムのログ分析をテーマに進めていきますが、広告やレコメンドエンジンなど、その他ログの分析にも応用できるテクニックかと思いますので、
- 普段の分析をもう少し精緻にしたい
- BigQueryでできることを増やしたい
- ユーザの行動ログ分析に挑戦したい
といった方々の参考になれば幸いです。
はじめに
ココナラは、[一人ひとりが「自分のストーリー」を生きていく世の中をつくる]をビジョンに掲げ、スキル・知識・経験を売り買いできるサービス(スキル)のマッチングプラットフォームです。現在、ユーザーは300万人を超え、サービスの種類は450種類以上と日本最大級のスキルマーケットに拡大しております。我々R&Dグループは、アルゴリズムとデータを活用し、お客様の良好なマッチングを創出すること・最適化することを使命としています。検索システムはマッチングを実現する方法であるため、我々R&Dグループの注力領域のひとつです。本ブログでは、検索システムの現状分析をテーマとして、BigQueryのARRAY
を活用した分析についてご紹介いたします。
要件
ココナラやその他ECサービスにおいて、検索システムを介したユーザ行動の流れを単純化すると上図のようになります。
検索システムの性能を測るために、下記情報を持つテーブルを実装していくことを目指します。
- どのアイテムを閲覧したか
- どのアイテムをクリックしたか
- クリックしたアイテムが何番目に表示されていたか
といった情報が必要です。これら3つの要件を満たすテーブルを、簡単な例から徐々に複雑にしながら実装していきます。
実装
未加工のログからユーザ行動テーブルを作成
下記のような形式で、ログが集められているところから分析を開始します。
user1
がitem1
とitem2
を閲覧(view
)しています。
[未加工のログテーブル]
user | action | item |
---|---|---|
user1 | view | item1 |
user1 | view | item2 |
user2 | view | item1 |
user2 | view | item2 |
user2 | click | item1 |
user2 | click | item2 |
ユーザ単位での集計を素直に実装すると以下のようなクエリになると思います。
-- このクエリは動きません
SELECT
user,
IF(action = 'view', item, NULL) AS viewed_items,
IF(action = 'click', item, NULL) AS clicked_items,
FROM
raw_log
GROUP BY
user
しかし、viewed_items
とclicked_items
のカラムは、集約関数を適用する必要があるため、このままでは上記クエリは動作しません。
そこで、複数要素をまとめてARRAY
にするのが、ARRAY_AGG
関数です。(参考: Work with arrays)
ARRAY_AGG
関数を使って次のようにitem
を行動別に集約します。
SELECT
user,
ARRAY_AGG(IF(action = 'view', item, NULL) IGNORE NULLS) AS viewed_items,
ARRAY_AGG(IF(action = 'click', item, NULL) IGNORE NULLS) AS clicked_items
FROM
raw_log
GROUP BY
user
このクエリを実行した結果は次のとおりです。
[結果]
user | keyword | viewed_items | clicked_items |
---|---|---|---|
user1 | 'イラスト' | [item1, item2] | [] |
user2 | 'イラスト' | [item1, item2] | [item1, item2] |
これで各item
を、viewed_items
とclicked_items
にそれぞれ格納できました。このテーブルをユーザ行動(user_action
)テーブルと呼ぶことにします。
この結果から、次のような定性的な分析が容易になりました。
-
user1
はitem1
とitem2
を閲覧している。クリックはしていない。 -
user2
はitem1
とitem2
を閲覧している。そしてitem1
とitem2
をクリックした。
検索キーワード別CTRの集計
次に、キーワード毎のインプレッション数と、クリック数、クリック率(CTR)の集計をします。CTRは様々な単位での集計が考えられますが、キーワードとユーザの組み合わせを1単位として集計してみます。この単位は、比較的シンプルなクエリで、大雑把な検索傾向を掴むのに便利です。
さきほどのテーブルに検索キーワードの列を追加した、次のようなテーブルを想定します。
実際には複数のキーワードが存在しますが、1つのキーワードに絞って考えます。
[未加工のログテーブルver.2]
user | keyword | action | item |
---|---|---|---|
user1 | 'イラスト' | view | item1 |
user1 | 'イラスト' | view | item2 |
user2 | 'イラスト' | view | item1 |
user2 | 'イラスト' | view | item2 |
user2 | 'イラスト' | click | item1 |
user2 | 'イラスト' | click | item2 |
キーワードでGROUP BY
してCOUNTIF
で適当な条件のものを集計します。
user_action AS ( ~~ユーザ行動のクエリ~~ ),
SELECT
COUNTIF(ARRAY_LENGTH(viewed_items) > 0) AS impressions,
COUNTIF(ARRAY_LENGTH(clicked_items) > 0) AS clicks,
COUNTIF(ARRAY_LENGTH(clicked_items) > 0) / COUNTIF(ARRAY_LENGTH(viewed_items) > 0) AS CTR
FROM
user_action
GROUP BY
keyword
BigQueryの配列関数のひとつ、ARRAY_LENGTH
によって、各ARRAY
内のアイテムの有無を判定して計上しています。 このように基本的なARRAY
にまつわる操作は標準でサポートされているので、簡単に実装できますね。
[結果]
keyword | impression | clicks | CTR |
---|---|---|---|
'イラスト' | 2 | 1 | 0.5 |
イラストのキーワードが2回検索されていて、1人のユーザによるクリックがあるので、CTRは50%。うまく計算できました。
クリックしたアイテムの順位の取得
ユーザとしては、欲しいアイテムが検索結果の上位に表示されていると便利です。
ユーザに、より便利な検索結果を提供するために、現状を把握する必要があります。
現状把握のために、この記事ではアイテムの表示位置や、クリックされたアイテムの位置を調べます。
次のテーブルを考えてみます。
[ユーザ行動テーブルver.2]
user | keyword | viewed_items | clicked_items |
---|---|---|---|
user1 | イラスト | [風景,人物,アニメ] | [風景,アニメ] |
viewed_items
は検索結果として表示された順番でARRAY
に格納されているとします。
このテーブルに、クリックされたアイテムが何番目か、という列を追加したいです。
これには一工夫必要で、以下のようなやり方が考えられます(他にもたくさん方法はあるかもしれません)。
- 事前に検索結果のアイテムと順位を持つテーブルを別で作っておいて結合する。
-
clicked_items
の要素を1つずつviewed_items
から探索して順位を求める。
本記事では、ARRAY
を活用した手軽な分析として2.の手法をご紹介します。
まずは、2.の手法を前半と後半の2つの手順に分解します。
-
clicked_items
の要素を1つずつ取り出す。 - 取り出した要素を
viewed_items
から探して順位を求める。
手順1.について、はUNNEST
関数によって実現します。
SELECT
item
FROM
UNNEST(clicked_items) AS item
UNNEST
演算子はARRAY
をテーブルに変換する関数です。そのため、上のコードにあるよう、SELECT
文で要素を1つずつ取り出すことができます。
次に、手順2.です。
今はviewed_items
のARRAY
が表示順に並んでいることを仮定しているため、求めるアイテムがARRAY
の何番目に格納されているかわかれば順位がわかります。
例えばPython
言語だと、list
に対してindex
メソッドで実現される機能ですね。
BigQuery
のARRAY
にはこのような関数は実装されていないので自前で書いていきます。
SELECT
offset + 1
FROM
UNNEST(viewed_items) AS item
WITH OFFSET AS offset
WHERE
item = clicked_item
UNNEST
演算子はWITH OFFSET
句をともに用いることで、ARRAY
の要素とインデックスの列を作ります。UNNEST と WITH OFFSET
要素とインデックスの列を、WHERE
句でフィルタリングして、求める要素のインデックスを得ます。
WITH OFFSET
句で得られるインデックスは0から始まるため、1を加算して、クリックされたアイテムの表示順位が得られました。
この処理を一般化してユーザ定義関数(UDF
)にしておきましょう。
後に書くクエリがぐっと読みやすくなります。
CREATE TEMP FUNCTION
GET_INDEX(arr ARRAY<STRING>, target_element STRING)
RETURNS INT64
AS (
(SELECT
offset + 1
FROM
UNNEST(arr) AS element
WITH OFFSET AS offset
WHERE
element = target_element
)
);
仕上げに、手順1.と2.を組み合わせてクエリを完成させます。
SELECT
user,
keyword,
viewed_items,
clicked_items,
ARRAY(
(SELECT GET_INDEX(viewed_items, item) FROM UNNEST(clicked_items) AS item)
) AS clicked_item_ranks
FROM
user_action_v2
さきほど定義したGET_INDEX
を使って、clicked_items
内のアイテムがviewed_items
の何番目にあるか探索し、そのインデックスをARRAY
に集約しています。
このクエリを実行した結果が以下です。
[結果]
user | keyword | viewed_items | clicked_items | clicked_item_ranks |
---|---|---|---|---|
user1 | イラスト | [風景,人物,アニメキャラ] | [風景,アニメキャラ] | [1,3] |
このテーブルから、
- user1が”イラスト”というキーワードを検索
- [風景,人物,アニメキャラ]というアイテムを閲覧
- [風景,アニメキャラ]をクリック
- そして、"風景"は1番目、"アニメキャラ"は3番目に表示されていた。
という行動がわかります。これで当初予定していた要件はすべて満たされました。
おわりに
最後にできあがったテーブルを利用すると、本記事内で集計したCTRよりもさらに詳細な、ユーザ・アイテム単位でのCTRも計算できます。BigQuery SQL力をもう一段階ステップアップする課題としてぜひ挑戦してみてください。
ココナラでは、一緒に事業のグロースを推進していただける様々な領域のエンジニアを募集しています。
ココナラの開発や裏側を知ってみたい! など少しでも興味が湧いたという方も大歓迎です。オンラインでぜひお話ししましょう!
ココナラのエンジニアについてもっと知りたい! という方はこちらもよければご確認ください。
Discussion