dbt Semantic layer conversion metrics を試してみる
はじめに
dbt Semantic Layer(MetricFlow)の Conversion Metrics を試した際の内容を記事としました。
Conversion Metrics の概要
Conversion Metrics は、時間的な順序とウィンドウを考慮してエンティティを追跡するメトリクスタイプです。
より具体的には、「あるイベントを経験したエンティティが、一定期間内に別のイベントに至った割合」を測るメトリクスタイプとなります。
例えば「サイトを訪問したユーザーが、7日以内に購入に至った割合」などです。この例では、訪問というベースイベントと購入というコンバージョンイベントをユーザー ID 等で紐付け、一定のタイムウィンドウ内に両方が発生したかどうかを追跡します。
他にも以下のようなケースで使える指標と思います。
- 広告クリック → 会員登録(登録コンバージョン率)
- 会員登録 → 有料プランへのアップグレード(30日以内)
- 注文 → 支払い完了(7日以内)
本機能については以下に記載があります。
他のメトリクスタイプとの主な違いは以下の通りです。
| タイプ | 計算方法 | 特徴 |
|---|---|---|
| ratio | 分子 ÷ 分母(単純割り算) | 同時点の集計値を割る |
| derived | 既存メトリクスの組み合わせ | 計算式を自由に定義 |
| conversion | ベース → コンバージョンをエンティティで追跡 | 時間的な順序とウィンドウを考慮できる |
例として「返品率」は ratio で計算できますが、「注文から30日以内の返品率」の計算には Conversion Metrics が必要です(ratio では同時点の集計値を割るだけであるため、タイムウィンドウ内のエンティティ追跡ができない)。
内部処理
こちらも公式ドキュメントに記載がありますが、MetricFlow では以下のステップで Conversion Metrics を計算します。
- ベースとコンバージョンを JOIN:エンティティが一致し、かつコンバージョンがウィンドウ内に発生した組み合わせをすべて取得する。ここで1件のコンバージョンイベントに対して複数のベースイベントが条件を満たす場合、その数だけ行が生成される(ファンアウト)。
- 各コンバージョンを「最も近いベースイベント」に割り当て:ウィンドウ関数(
FIRST_VALUE ... ORDER BY base_date DESC)で、ファンアウトが発生している行群に対して最も近いベースイベントの値を書き込み - DISTINCT で重複を除去:ファンアウトが発生していた行群は、全て同じ内容(最近傍のベースイベント)に書き換わっているため、
SELECT DISTINCTで1行に集約 - 集計して conversion_rate を計算:ベース側(opportunities)とコンバージョン側(conversions)を別々に集計し、
FULL OUTER JOINで合算
前提条件
Conversion Metrics を使用するには以下が必要です。
- 2つのイベントテーブル:Conversion Metrics で指定が必要な
base_metricとconversion_metricオプションには、それぞれ COUNT 系のシンプルメトリクスを指定します。この2つのメトリクスは異なるセマンティックモデル(異なるテーブル)に定義されている必要があります - 共通のエンティティ:両テーブルを結ぶ共通のエンティティ(例:
user_id、order_id)が必要です。また、両セマンティックモデルで指定するnameが一致している必要があります- 名前が異なると MetricFlow が join path を見つけられず、エラーとなります
YAML 設定
Conversion Metrics は、どのイベントを起点やコンバージョンとみなすか、何で紐付けるかなどを定義します。
代表的なオプションは以下の通りです。
| オプション | 内容 | 説明 |
|---|---|---|
| entity | 必須 | 2つのイベントを結ぶエンティティ名。両セマンティックモデルに同名のエンティティが必要 |
| base_metric | 必須 | ベースイベントを表す COUNT 系のシンプルメトリクス。分母となる起点イベント数を表す |
| conversion_metric | 必須 | コンバージョンイベントを表す COUNT 系のシンプルメトリクス。分子となる変換イベント数を表す |
| window | 任意 | コンバージョンとみなすタイムウィンドウ(例:7 days)。省略すると無制限となる |
| calculation | 任意 | conversion_rate(割合、デフォルト)または conversions(件数)を指定 |
前提条件
検証環境
以下の環境を使用しています。
- DWH:Snowflake
- dbt platform
- セマンティックレイヤーの記法は dbt Fusion エンジンおよび dbt platform Latest リリーストラックで利用可能な新しい YAML 記法を使用しています
事前準備
検証用 Seed データを使います。Seeds に直接セマンティックモデルを定義できないため、Seeds を参照するモデルも用意します。
サンプルデータで動作を確認
ここでは「サイト訪問(sample_visits)から購入(sample_purchases)への7日以内のコンバージョン」を追跡する設定とします。
entity に user(ユーザー軸)と visit(訪問軸)の2パターンを定義し、結果の違いを比較します。
サンプルデータの用意
以下の CSV をseeds/に配置します。
visit_id,user_id,visit_date
1,u001,2024-01-01
2,u001,2024-01-03
3,u002,2024-01-01
4,u003,2024-01-01
5,u004,2024-01-07
purchase_id,user_id,visit_id,purchase_date
1,u001,2,2024-01-05
2,u002,3,2024-01-04
3,u004,5,2024-01-15
sample_purchasesにはvisit_id(訪問との直接紐付け)とuser_id(ユーザー単位の紐付け)の両方を持たせています。これにより、entityオプションの挙動の違いを確認します。
各モデルとエンティティの対応関係は以下の通りです。
| モデル | エンティティ種別 | 名前 | カラム |
|---|---|---|---|
smp_visits |
primary | visit |
visit_id |
smp_visits |
foreign | user |
user_id |
smp_purchases |
primary | purchase |
purchase_id |
smp_purchases |
foreign | visit |
visit_id |
smp_purchases |
foreign | user |
user_id |
Seed からテーブルを作成しておきます。
dbt seed --select "sample_visits sample_purchases"
また、Seeds に直接セマンティックモデルを定義できないため、Seeds を参照するモデルを作成します。
select * from {{ ref('sample_visits') }}
select * from {{ ref('sample_purchases') }}
dbt build --select "smp_visits smp_purchases"
セマンティックモデルと Conversion Metrics の定義
異なる entity(entity: userとentity: visit)の2パターンを同時に定義し、結果を比較できるようにします。
models:
- name: smp_visits
semantic_model:
enabled: true
agg_time_dimension: visit_date
columns:
- name: visit_id
entity:
type: primary
name: visit
- name: user_id
entity:
type: foreign
name: user # smp_purchases の foreign entity と対応
- name: visit_date
granularity: day
dimension:
type: time
metrics:
# base_metric として使用:conversion の「分母(起点イベント数)」になる
- name: visit_count
type: simple
label: 訪問数
agg: count
expr: 1
# --- entity: user を軸にした Conversion(ファンアウトあり)---
# JOIN キー = user_id。同一ユーザーの複数訪問が同一購入に紐付く → ファンアウト発生
- name: visit_to_purchase_conversion_rate_by_user
type: conversion
label: 訪問→購入コンバージョン率・ユーザー軸(7日)
entity: user # JOIN キー:両テーブルの user_id(foreign entity)を軸に結合
calculation: conversion_rate
base_metric: visit_count # smp_visits で定義したメトリクスを参照
conversion_metric: purchase_count # smp_purchases で定義したメトリクスを参照
window: 7 days
- name: visit_to_purchase_conversions_by_user
type: conversion
label: 訪問→購入コンバージョン件数・ユーザー軸(7日)
entity: user
calculation: conversions
base_metric: visit_count
conversion_metric: purchase_count
window: 7 days
# --- entity: visit を軸にした Conversion(1対1対応・ファンアウトなし)---
# JOIN キー = visit_id。purchases.visit_id が直接指定した訪問のみが対象
- name: visit_to_purchase_conversion_rate_by_visit
type: conversion
label: 訪問→購入コンバージョン率・訪問軸(7日)
entity: visit # JOIN キー:visit_id(primary/foreign entity)を軸に結合
calculation: conversion_rate
base_metric: visit_count
conversion_metric: purchase_count
window: 7 days
- name: visit_to_purchase_conversions_by_visit
type: conversion
label: 訪問→購入コンバージョン件数・訪問軸(7日)
entity: visit
calculation: conversions
base_metric: visit_count
conversion_metric: purchase_count
window: 7 days
- name: smp_purchases
semantic_model:
enabled: true
agg_time_dimension: purchase_date
columns:
- name: purchase_id
entity:
type: primary
name: purchase
- name: user_id
entity:
type: foreign
name: user # smp_visits の foreign entity と対応
- name: visit_id
entity:
type: foreign
name: visit # smp_visits の primary entity と対応 → entity: visit での JOIN に使用
- name: purchase_date
granularity: day
dimension:
type: time
metrics:
# conversion_metric として使用:conversion の「分子(変換イベント数)」になる
- name: purchase_count
type: simple
label: 購入数
agg: count
expr: 1
conversion メトリクス内のentity オプション
conversion メトリクスのentityは「何を追跡対象とするか」を決めるオプションとなります。
entity: userとentity: visitによって、「訪問したユーザーが購入したか(ユーザー単位の追跡)」なのか「その訪問が直接購入につながったか(訪問単位の追跡)」なのかを指定できます。
calculation オプション
YAML 内のcalculationオプションにはconversion_rateとconversionsのいずれかを指定できます。それぞれ以下の通りです。
| calculation | 返す値 | 計算式 |
|---|---|---|
| conversion_rate | コンバージョン率(0〜1以上の小数) | conversions ÷ opportunities |
| conversions | コンバージョン件数(整数) | ウィンドウ内に変換されたベースイベントの件数 |
どちらも同じ base/conversion/window の設定を使い、集計の最終ステップでの出力形式だけが異なります。用途に応じて使い分けるか、上記のように両方定義しておくことで割合と件数を同時に取得できます。
クエリ実行と結果確認
entity: user(ユーザー軸)と entity: visit(訪問軸)の4メトリクスを同時にクエリして比較します。
dbt sl query \
--metrics visit_to_purchase_conversion_rate_by_user,visit_to_purchase_conversions_by_user,visit_to_purchase_conversion_rate_by_visit,visit_to_purchase_conversions_by_visit \
--group-by metric_time__day
実行結果は以下の通りです。
$ dbt sl query \
--metrics visit_to_purchase_conversion_rate_by_user,visit_to_purchase_conversions_by_user,visit_to_purchase_conversion_rate_by_visit,visit_to_purchase_conversions_by_visit \
--group-by metric_time__day
+-------------------------+-------------------------------------------+--------------------------------------------+---------------------------------------+----------------------------------------+
| METRIC_TIME__DAY | VISIT_TO_PURCHASE_CONVERSION_RATE_BY_USER | VISIT_TO_PURCHASE_CONVERSION_RATE_BY_VISIT | VISIT_TO_PURCHASE_CONVERSIONS_BY_USER | VISIT_TO_PURCHASE_CONVERSIONS_BY_VISIT |
+-------------------------+-------------------------------------------+--------------------------------------------+---------------------------------------+----------------------------------------+
| 2024-01-07T00:00:00.000 | <nil> | <nil> | <nil> | <nil> |
| 2024-01-03T00:00:00.000 | 1 | 1 | 1 | 1 |
| 2024-01-01T00:00:00.000 | 0.6666666667 | 0.3333333333 | 2 | 1 |
+-------------------------+-------------------------------------------+--------------------------------------------+---------------------------------------+----------------------------------------+
2つの entity による結果の違いをまとめると以下の通りです。
| 日付 | by_user rate | by_user conversions | by_visit rate | by_visit conversions | 差異の理由 |
|---|---|---|---|---|---|
| Jan 1 | 0.667 | 2 | 0.333 | 1 | Jan 1 の訪問は3件(u001・u002・u003)。user 軸では u001(visit 1)と u002(visit 3)の2件がコンバージョン → 2/3。visit 軸では purchases.visit_id が存在する visit 3(u002)の1件のみ → 1/3 |
| Jan 3 | 1.0 | 1 | 1.0 | 1 | visit 2(u001)→ purchases.visit_id=2(Jan 5, 2日後)。両軸で一致 |
| Jan 7 | nil | nil | nil | nil | visit 5(u004)→ purchase(Jan 15)は8日後でウィンドウ外 |
また、参考までに、もう少し詳細に結果がどのように計算されるか以下にまとめます。
ベース側(分母)
どちらの entity でも、分母となる opportunities は「その日の訪問件数」となります。
| 日付 | 訪問 | opportunities |
|---|---|---|
| Jan 1 | visit 1(u001)・visit 3(u002)・visit 4(u003) | 3 |
| Jan 3 | visit 2(u001) | 1 |
| Jan 7 | visit 5(u004) | 1 |
コンバージョン側(分子)
こちらは entity によって変わります。
entity: visitはvisit_idで直接 JOIN するため、purchases テーブルにvisit_idが存在する訪問だけがコンバージョン済みとみなされます。
visit 1(u001, Jan 1)→ purchases に visit_id=1 の行なし → 未コンバージョン
visit 2(u001, Jan 3)→ purchases に visit_id=2(Jan 5, 2日後)→ コンバージョン
visit 3(u002, Jan 1)→ purchases に visit_id=3(Jan 4, 3日後)→ コンバージョン
visit 4(u003, Jan 1)→ purchases に visit_id=4 の行なし → 未コンバージョン
visit 5(u004, Jan 7)→ purchases に visit_id=5(Jan 15, 8日後)→ ウィンドウ外
entity: userはuser_idで JOIN するため、「その訪問時点から7日以内にそのユーザーが購入したか」を各訪問に対して独立して評価します。
visit 1(u001, Jan 1)→ u001 が Jan 5 に購入(4日後) → コンバージョン ← entity:visit と差異が出る
visit 2(u001, Jan 3)→ u001 が Jan 5 に購入(2日後) → コンバージョン
visit 3(u002, Jan 1)→ u002 が Jan 4 に購入(3日後) → コンバージョン
visit 4(u003, Jan 1)→ 購入なし → 未コンバージョン
visit 5(u004, Jan 7)→ u004 が Jan 15 に購入(8日後) → ウィンドウ外
opportunities と conversions を日付別に集計して割る
最終的には、opportunities と conversions を日付別に集計して割っています。
| entity | Jan 1 conversions | Jan 1 opportunities | rate |
|---|---|---|---|
| visit | 1(visit 3 のみ) | 3 | 0.333 |
| user | 2(visit 1 + visit 3) | 3 | 0.667 |
visit 1(u001, Jan 1)が entity: user ではコンバージョン済み、entity: visitでは未コンバージョンとなることが、Jan 1 の値が異なる原因です。
上記より、entity: visitでは「その訪問に直接紐づく購入が7日以内に発生したか」を厳密に測りたい場合に適しています。一方で、entity: userはvisit_idがなくユーザー単位でしか紐付けられない場合や、訪問単位の厳密な紐付けが不要な場合に適しています。
さいごに
dbt Semantic Layer の Conversion Metrics の動作を確認してみました。
こちらの内容がどなたかの参考になれば幸いです。







