一个平均 Firebase 活跃用户指标(DAU、WAU、MAU)应该是多少?

Should one average Firebase Active User Metrics (DAU, WAU, MAU)?

我正在尝试了解在当前 Firebase "Active" 用户指标报告(查看下图)中按月报告是否更好,或者自行计算并报告 在特定时期内每个指标的平均值

乍一看,仪表板显示了 2018 年 12 月的 1 天、7 天和 28 天活跃用户,但实际上它只是所选日期范围值的最后一天显示(在右侧)。很高兴知道这一点,但在我的逐月分析中仅比较最后日期的值有点误导。另一种方法是自行计算选定时间段内的平均值

应用于Firebase Demo数据集,得到如下数字:

Firebase 仪表板:

我计算的平均值:

此处的增量差异很小,但我发现我们的应用程序存在一些显着差异,该应用程序每月有数百万活跃用户。

问题:

为了回答这个问题,我想先回顾一下 Google 的适用定义,然后 运行 进行计算(最后一次回顾:2021 年 7 月)。


Google 给出了以下定义:

GA4 - Automatically collected events

  • session_start(应用程序、网络)- 当用户使用应用程序或网站时
  • user_engagement (app, web) - 定期,当应用程序在前台或网页处于焦点时 带参数:engagement_time_msec

GA4 - How the number of sessions is calculated

  • Sessions:在您的网站或应用程序上开始的会话数(触发了 session_start 事件)。
  • App session timeout duration:当应用程序移至后台时,应用程序会话开始超时,但您可以选择通过包含 extend_session 参数(值为1) 应用程序在后台时发送的事件。如果您的应用经常在后台使用(例如导航和音乐应用),这将非常有用。通过 setSessionTimeoutDuration 方法更改应用会话的默认超时 30 分钟。
  • Engaged sessions:持续 10 秒或更长时间,或具有 1 次或更多转化事件或 2 次或更多页面浏览的会话数。

GA4 Dashboard

  • Monthly (28-day), Weekly (7-day), and Daily (1-day) Active Users 日期范围,包括相对于上一个日期范围的百分比波动。一位活跃用户在设备前台使用了一个应用程序,并记录了一个 user_engagement 事件。
  • Daily user engagement - 日期范围内每位用户的平均每日参与度,包括与上一个日期范围相比的百分比波动。

我对定义的看法:

根据支持性 GA4/Firebase 文档,我(重新)总结了以下每个指标的定义。非常重要的一点是,只有 唯一身份用户 才应计入每个指标(给定选定的日期范围)。不需要 UNNEST 因为我们已经在 event_name 级别查询,而不是 event_parameter 级别。

  • 1 天活跃用户: 1 天 唯一 活跃用户参与在设备前台有一个应用 AND 在过去 1 天内(给定选定的日期范围)记录了一个 user_engagement 事件。
  • 7 天活跃用户: 7 天 唯一 活跃用户参与在设备前台有一个应用 AND 在过去 7 天内(给定选定的日期范围)记录了一个 user_engagement 事件。
  • 28 天活跃用户: 28 天 唯一 活跃用户参与在设备前台有一个应用 AND 在过去 28 天内(给定选定的日期范围)记录了一个 user_engagement 事件。

在下面的单元格中,您可以看到 12 月指标的计算方式:

计算每个指标/受众的方法:

  • 使用以下公式计算特定月份的 DAU:Average 1-day active user metric
  • 使用以下公式计算特定月份的 WAU:Average 7-day active user metric。我通过对 12 月 7 日、14 日、21 日和 28 日的快照求平均值来计算的。
  • 使用以下公式计算特定月份的 MAU:Non-averaged 28-day active user metric。不对这个指标的值进行平均的主要原因是,因为我只想获得整个月的一个快照。如果我在这里使用平均值,我也会考虑上个月的活跃用户。

1.a) 平均 1 天唯一活跃用户指标

# StandardSQL
SELECT
  ROUND(AVG(users),0) AS users
FROM 
(
  SELECT
  event_date,
  COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX BETWEEN '20181201' AND '20181231'
  AND platform = "ANDROID"
GROUP BY 1
) table

# or you could also use code below, but you will have to add in the remaining days' code to query against the entire month. 

-- Set your variables here
WITH timeframe AS (SELECT DATE("2018-12-01") AS start_date, DATE("2018-12-31") AS end_date)

-- Query your variables here
SELECT ROUND(AVG(users),0) AS users
FROM
(
SELECT event_date, COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`AS z, timeframe AS t
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX > FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 1 DAY))
  AND _TABLE_SUFFIX <= FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL 0 DAY))
  AND platform = "ANDROID"
GROUP BY 1

UNION ALL 

SELECT event_date, COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`AS z, timeframe AS t
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX > FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 2 DAY))
  AND _TABLE_SUFFIX <= FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 1 DAY))
  AND platform = "ANDROID"
GROUP BY 1
... 
...
...
...
) avg_1_day_active_users

1.b) 平均 1 天唯一活跃用户指标

更新的版本计划 daily 到 BQ 目标 table daus_android_{run_time|"%Y%m%d"} 并具有写入首选项 WRITE_APPEND,如下所示。我之前进行了深入研究,确定日内 -table 事件最多可能需要 48 小时才能传播到永久 BQ tables(因此查询中有 - 3 天)。

with base AS (
    SELECT *
    FROM `<id>.analytics_<number>.events_*`
    WHERE (_TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 3 DAY)) AND _TABLE_SUFFIX < FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)))
        AND platform = "ANDROID"
        AND event_name = 'user_engagement'
), app AS (
    SELECT
      FORMAT_DATE('%Y%m%d', @run_date) AS _currentdate,
      FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 3 DAY)) AS _begindate,
      FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)) AS _enddate,
      TIMESTAMP_DIFF(TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 2 DAY)), TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 3 DAY)), HOUR) AS _hoursdiff,
      COUNT(DISTINCT user_pseudo_id) AS _uniqusers
    FROM base
    )
SELECT 
    app._currentdate,
    app._begindate,
    app._enddate,
    app._hoursdiff,
    app._uniqusers
FROM app;

1.c) 平均 1 天唯一活跃用户指标

WITH app as (
SELECT
  FORMAT_DATE('%Y%m%d', @run_date) AS _currentdate,
  FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 3 DAY)) AS _begindate,
  FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)) AS _enddate,
  TIMESTAMP_DIFF(TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 2 DAY)), TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 3 DAY)), HOUR) AS _hoursdiff,
  COUNT(DISTINCT user_pseudo_id) AS _uniqusers
FROM `<gcp-project>.analytics_<id>.events_*`
WHERE
  platform = "ANDROID"
  AND event_name = 'user_engagement'
  AND _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 3 DAY))
  AND _TABLE_SUFFIX < FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY))
)

SELECT 
  app._currentdate,
  app._begindate,
  app._enddate,
  app._hoursdiff,
  app._uniqusers
FROM app

2.a) 平均 7 天唯一活跃用户指标

-- Set your variables here
WITH timeframe AS (SELECT DATE("2018-12-01") AS start_date, DATE("2018-12-31") AS end_date)

-- Query your variables here
SELECT ROUND(AVG(users),0) AS users
FROM
(
SELECT COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`AS z, timeframe AS t
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX > FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 7 DAY))
  AND _TABLE_SUFFIX <= FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL 0 DAY))
  AND platform = "ANDROID"

UNION ALL

SELECT COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`AS z, timeframe AS t
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX > FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 14 DAY))
  AND _TABLE_SUFFIX <= FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 7 DAY))
  AND platform = "ANDROID"
  
UNION ALL

SELECT COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`AS z, timeframe AS t
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX > FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 21 DAY))
  AND _TABLE_SUFFIX <= FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 14 DAY))
  AND platform = "ANDROID"

UNION ALL

SELECT COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`AS z, timeframe AS t
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX > FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 28 DAY))
  AND _TABLE_SUFFIX <= FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 21 DAY))
  AND platform = "ANDROID"
) avg_7_day_active_users

2.b) 平均 7 天唯一活跃用户指标

更新的版本计划 daily 到 BQ 目标 table waus_android_{run_time|"%Y%m%d"},写入首选项 WRITE_APPEND,可能看起来像:

with base AS (
    SELECT *
    FROM `<id>.analytics_<number>.events_*`
    WHERE (_TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 9 DAY)) AND _TABLE_SUFFIX < FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)))
        AND platform = "ANDROID"
        AND event_name = 'user_engagement'
), app AS (
    SELECT
      FORMAT_DATE('%Y%m%d', @run_date) AS _currentdate,
      FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 9 DAY)) AS _begindate,
      FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)) AS _enddate,
      TIMESTAMP_DIFF(TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 2 DAY)), TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 9 DAY)), HOUR) AS _hoursdiff,
      COUNT(DISTINCT user_pseudo_id) AS _uniqusers
    FROM base
    )
SELECT 
    app._currentdate,
    app._begindate,
    app._enddate,
    app._hoursdiff,
    app._uniqusers
FROM app;

3.a) 非平均 28 天唯一活跃用户指标

# StandardSQL
-- Set your variables here
WITH timeframe AS (SELECT DATE("2018-12-01") AS start_date, DATE("2018-12-31") AS end_date)

-- Query your variables here
SELECT COUNT(DISTINCT user_pseudo_id) AS users
FROM `<id>.events_*`AS z, timeframe AS t
WHERE
  event_name = 'user_engagement'
  AND _TABLE_SUFFIX > FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL - 28 DAY))
  AND _TABLE_SUFFIX <= FORMAT_DATE('%Y%m%d', DATE_ADD(t.end_date, INTERVAL 0 DAY))
  AND platform = "ANDROID"

3.b) 非平均 28 天唯一活跃用户指标

更新的版本计划 daily 到 BQ 目的地 table maus_android_{run_time|"%Y%m%d"},写入首选项 WRITE_APPEND,可能看起来像:

with base AS (
        SELECT *
        FROM `<id>.analytics_<number>.events_*`
        WHERE (_TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 30 DAY)) AND _TABLE_SUFFIX < FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)))
            AND platform = "ANDROID"
            AND event_name = 'user_engagement'
    ), app AS (
        SELECT
            FORMAT_DATE('%Y%m%d', @run_date) AS _currentdate,
            FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 30 DAY)) AS _begindate,
            FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)) AS _enddate,
            TIMESTAMP_DIFF(TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 2 DAY)), TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 30 DAY)), HOUR) AS _hoursdiff,
            COUNT(DISTINCT user_pseudo_id) AS _uniqusers
        FROM base
        )
    SELECT 
        app._currentdate,
        app._begindate,
        app._enddate,
        app._hoursdiff,
        app._uniqusers
    FROM app;

旁注:

  • 我知道有些公司仍在计算他们 30 天的 MAU。因此,您必须进行测试,看看哪种方法最适合您的公司。
  • 您可以根据上述示例计算自己的 DAU-to-MAU 比率或 WAU-to-MAU 比率,以确定您应用的 stickiness
  • 我对 MAU 计算的唯一问题是它还没有考虑每个月的开始日期。也许可以取第 31 天 - 28 天、第 30 天 - 28 天、第 29 天 - 28 天、第 28 天 - 28 天的平均值 ...
  • 我发现 Firebase 团队的示例 queries 也很有帮助,但他们的活跃指标仅涉及执行查询时的活跃用户数(查看下面的示例):
SELECT
  COUNT(DISTINCT user_id)
FROM
  /* PLEASE REPLACE WITH YOUR TABLE NAME */
  `YOUR_TABLE.events_*`
WHERE
  event_name = 'user_engagement'
  /* Pick events in the last N = 20 days */
  AND event_timestamp > UNIX_MICROS(TIMESTAMP_SUB(CURRENT_TIMESTAMP, INTERVAL 20 DAY))
  /* PLEASE REPLACE WITH YOUR DESIRED DATE RANGE */
  AND _TABLE_SUFFIX BETWEEN '20180521' AND '20240131';

忍者秘诀

要将您的团队“s/company”s/product 的关注点从 Vanity Metrics 转移到 Actionable Metrics,请考虑将您的主要转化事件之一添加为上面的查询(例如 in_app_purchase for e-commerce companies):

with base AS (
    SELECT *
    FROM `<id>.analytics_<number>.events_*`
    WHERE (_TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 3 DAY)) AND _TABLE_SUFFIX < FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)))
        AND platform = "ANDROID"
        # AND event_name = 'user_engagement' 
        AND event_name = 'in_app_purchase'
), app AS (
    SELECT
      FORMAT_DATE('%Y%m%d', @run_date) AS _currentdate,
      FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 3 DAY)) AS _begindate,
      FORMAT_DATE('%Y%m%d', DATE_ADD(@run_date, INTERVAL - 2 DAY)) AS _enddate,
      TIMESTAMP_DIFF(TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 2 DAY)), TIMESTAMP(DATE_ADD(@run_date, INTERVAL - 3 DAY)), HOUR) AS _hoursdiff,
      COUNT(DISTINCT user_pseudo_id) AS _uniqusers
    FROM base
    )
SELECT 
    app._currentdate,
    app._begindate,
    app._enddate,
    app._hoursdiff,
    app._uniqusers
FROM app;