青の統計学-DS Playground-

集計 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_dailyfct_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 点を分離します。

  1. ベース mart … 観測された行動を 忠実に 数える(faithful count)
  2. セグメント mart … free / premium など status 別の内訳
  3. type 別 mart … イベント種別ごとの推移(第5章

ベース mart をセグメントで絞ると、同意前・未知ユーザーの行動が 静かに消えますデータ品質チェック の精神で、消すのではなくタグ付けする 方針が推奨されます。

flowchart TB INT["intermediate
イベント 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 日

特徴

  1. terms 同意会員だけにフィルタしない

    • 観測された activity 行を 忠実に 数える。
    • 通常は signed-in ユーザーだが、analytics は行動記録を優先。
  2. activity type 別の distinct 列

    • practice_problem_saved_users, mock_exam_saved_users など。
    • 1 人が複数 type → active_users は 1、各 type 列はそれぞれカウント。
  3. 前日比列

    • 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;
          
flowchart LR INT["int_user_activity_events"] --> BASE["fct_active_users_daily
会員フィルタなし"] 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_dailyfct_active_users_daily_by_member_status の 2 表で、この分離を実践しています。


関連教材


確認問題

問題 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 の構成比