集計 mart:日次 KPI を安定して返す
Stage 3 — 第4章 | dbt入門カリキュラム 推定学習時間:40〜50分 | 難易度:★★★☆☆
この章で学ぶこと
第3章 で Membership mart の proxy 定義を学びました。 次は Activity mart です。intermediate の SSOT から 日次 KPI を安定して返す設計を読み解きます。
第1章 の int_user_activity_events が 「何を学習行動と呼ぶか」 を固定したら、mart では 何人が・どのセグメントで 学習したかを grain ごとに集計します。
まず dbt の作法を整理し、続けて DS Playground の fct_active_users_daily と fct_active_users_daily_by_member_status を読み解きます。
この章を終えると、こんなことができるようになります:
- DAU が「パッシブログイン」ではなく Learning Action ベースである理由を説明できる
- ベース mart と status 分割 mart の 使い分け を説明できる
unknown行を削除しない設計判断を説明できる- activity type 別ユーザー数列の読み方を理解できる
集計 mart
intermediate がイベント行の SSOT なら、Activity mart の典型パターンは 日次 distinct user 数 です。
-- 概念形
select
date_day,
count(distinct user_id) as active_users
from {{ ref('int_..._events') }}
group by 1;
ファネル分析 の「アクティブ」と同様、分子の定義 が KPI の半分です。 mart 設計では次の 3 点を分離します。
- ベース mart … 観測された行動を 忠実に 数える(faithful count)
- セグメント mart … free / premium など status 別の内訳
- type 別 mart … イベント種別ごとの推移(第5章)
ベース mart をセグメントで絞ると、同意前・未知ユーザーの行動が 静かに消えます。 データ品質チェック の精神で、消すのではなくタグ付けする 方針が推奨されます。
イベント SSOT"] BASE["ベース mart
faithful count"] SPLIT["セグメント mart
status 別 + unknown"] TYPE["type 別 mart
日 × activity_type"] INT --> BASE INT --> SPLIT INT --> TYPE
ベース mart とセグメント mart の分離
| 設計案 | メリット | デメリット |
|---|---|---|
| ベース DAU を会員のみ | 会員母数と見た目が揃う | 同意前・未知ユーザーの行動が 静かに消える |
| ベースは faithful、分割 mart で status | データ品質問題を unknown で可視化 |
ダッシュボードで 2 表の使い分けが必要 |
セグメント mart では LEFT JOIN 後に coalesce(..., 'unknown') を使い、マッチしなかった行を明示 します。
unknown が急増したら、staging または upstream マスタの 品質インシデント を疑います(品質インシデント)。
date spine により ゼロ activity の日も 0 として表示 するのが日次 KPI mart の定番です。 前日比列(delta / pct)を付けると、トレンド説明が容易になります。
Active User の定義(再確認)
Active User = 指定期間に 1 つ以上の Learning Action を起こした distinct user_id。
select
date_day,
count(distinct user_id) as active_users
from {{ ref('int_user_activity_events') }}
group by 1;
パッシブなログインやページビューは含めません。 「ログインしただけ」では DAU は増えず、明示的な保存・完了・送信が SSOT に載ったとき初めてカウントされます。
fct_active_users_daily
Learning Action SSOT int_user_activity_events から、何人が学習したか を日次で見る mart が 2 種類あります。
fct_active_users_daily— 全体の DAU(Daily Active Users)fct_active_users_daily_by_member_status— free / premium / former_premium / unknown 別
business question と grain
business question: 「日別に、明示的学習行動を起こしたユーザーは何人か」
grain: 1 行 = 1 日
特徴
terms 同意会員だけにフィルタしない
- 観測された activity 行を 忠実に 数える。
- 通常は signed-in ユーザーだが、analytics は行動記録を優先。
activity type 別の distinct 列
practice_problem_saved_users,mock_exam_saved_usersなど。- 1 人が複数 type →
active_usersは 1、各 type 列はそれぞれカウント。
前日比列
active_users_delta_vs_previous_day,active_users_pct_vs_previous_day
-- 概念: type 別 distinct
count(distinct case
when activity_type = 'curriculum_page_completed'
then user_id
end) as curriculum_page_completed_users
date spine で ゼロ activity の日も 0 として表示 します。
fct_active_users_daily_by_member_status
business question: 「会員ステータス別に、学習行動ユーザーは何人か」
grain: 1 行 = 1 日 × 1 member_status
with activity_with_member_status as (
select
activity_events.date_day,
coalesce(member_status.member_status, 'unknown') as member_status,
activity_events.user_id,
activity_events.activity_type
from {{ ref('int_user_activity_events') }} as activity_events
left join {{ ref('int_member_status_current') }} as member_status
on activity_events.user_id = member_status.user_id
)
select
date_day,
member_status,
count(distinct user_id) as active_users,
safe_ratio(active_users, total_active_users) as active_user_share
from daily_by_status;
会員フィルタなし"] INT --> JOIN["LEFT JOIN int_member_status_current"] JOIN --> SPLIT["fct_active_users_daily_by_member_status
unknown 保持"]
なぜベース mart を会員だけに絞らないか
DS Playground は ベースは faithful、分割 mart で status を採用します。
unknown になる典型例
- terms 同意前にテスト環境で保存された行(稀)
- 同意レコード欠落・同期遅延
- 将来の新ソース追加時の JOIN 漏れ
unknown が急増したら、staging または consent 記録の 品質インシデント を疑います。
2 つの mart の使い分け
| 見たいこと | 使う mart |
|---|---|
| プロダクト全体の学習行動トレンド | fct_active_users_daily |
| 無料 vs 有料の構成比 | fct_active_users_daily_by_member_status |
| 行動タイプのミックス(日次) | fct_activity_events_daily_by_type(第5章) |
| DAU/WAU/MAU 期間推移 | fct_active_users_period_summary |
Metabase exposure metabase_product_health_dashboard は 両方 を depends_on に含みます。
よくある誤解
| 誤解 | 正しい理解 |
|---|---|
| 「DAU ≤ 累計会員数のはず」 | ベース DAU は会員フィルタしない。unknown 含む |
| 「status 別の合計 = ベース DAU」 | 理論上は近いが、定義差・JOIN 粒度で完全一致は要確認 |
| 「ログイン数と DAU が同じ」 | Learning Action 定義のため 一致しない |
体系コラム:カリキュラム上の位置づけ
| 項目 | 内容 |
|---|---|
| Stage / 章 | Stage 3 — 第4章 |
| 今回の論点 | faithful DAU、status 分割、unknown、type 別列 |
| 前章との接続 | Membership mart |
| 次章への伏線 | Incremental mart |
まとめ
DAU は「ログイン」ではなく 学習行動 です。 ベース mart は観測の忠実さを守り、セグメント分割が必要なときだけ status mart を使います。
unknown を残す設計は、データ品質問題を 消すのではなく可視化する 判断です。
DS Playground では fct_active_users_daily と fct_active_users_daily_by_member_status の 2 表で、この分離を実践しています。
関連教材
- 前章: Membership mart
- 次章: Incremental mart
- 関連(Analytics): ウィンドウ関数
- 関連(DM): 分析前チェックリスト
確認問題
問題 1
ベースの DAU mart が 全観測ユーザー を含む設計の主な理由はどれですか。
A. SQL が簡単だから
B. 観測された行動を忠実に数え、セグメント分割は別 mart で行うため
C. Auth テーブルが存在しないから
D. incremental だから
正解: B
問題 2
セグメント mart で unknown カテゴリを残す 主な理由 はどれですか。
A. 必ず bot ユーザーだから
B. ベース mart の行がセグメント定義にマッチしなかったことを明示するため
C. 有料会員の別名だから
D. 集計エラーで必ず除外すべき行だから
正解: B
問題 3
セグメント別の active user share を見るとき、一般的に 適切な設計 はどれですか。
A. ベース DAU mart に WHERE で 1 セグメントだけ残す
B. セグメント別 mart を別途用意し、ベース mart は faithful count のまま保つ
C. staging でセグメント列を作らず BI で都度 CASE する
D. source() を mart から直接読む
正解: B
用語メモ(この章)
| 用語 | 意味(この章での使い方) |
|---|---|
| DAU | Daily Active Users。日次の distinct active user |
| faithful count | 観測データをセグメント都合で落とさない数え方 |
| Learning Action | 明示的な保存・完了・送信など、SSOT に載る学習行動 |
unknown |
セグメント JOIN にマッチしなかった status |
| date spine | 全日付行を生成しゼロ activity 日も 0 表示 |
| active_user_share | セグメント別 active_users の構成比 |