Featured image of post DuckDB 时间序列分析完全指南:从同比环比到用户留存

DuckDB 时间序列分析完全指南:从同比环比到用户留存

用 DuckDB 的窗口函数和 time_bucket 完成所有时间序列分析任务——同比环比计算、移动平均线、用户留存分析、Gap&Island 问题,比 Pandas 快 10 倍。附完整代码和变现建议。

DuckDB 时间序列分析完全指南:从同比环比到用户留存

DuckDB 时间序列分析架构图

你肯定遇到过这种需求:

  • “上个月销售额环比增长了多少?”
  • “今年每个季度的同比变化趋势”
  • “过去 7 天的移动平均值”
  • “每个用户最近一次下单是什么时候?”

传统做法是用 pandas 写一堆 groupby + shift + rolling,代码又长又容易出错。而用 DuckDB,一条 SQL 就能搞定所有时间序列操作,而且速度更快。

一、同比 & 环比计算

假设有一张销售表 sales(date, amount),你要计算每个月相对于上月的环比增长,以及相对于去年同月的同比增长。

SELECT
    date_trunc('month', sale_date) AS month,
    SUM(amount) AS monthly_revenue,
    -- 环比:与上月比较
    LAG(SUM(amount)) OVER (ORDER BY date_trunc('month', sale_date)) AS prev_month_revenue,
    ROUND(
        100.0 * (SUM(amount) - LAG(SUM(amount)) OVER (ORDER BY date_trunc('month', sale_date)))
        / NULLIF(LAG(SUM(amount)) OVER (ORDER BY date_trunc('month', sale_date)), 0),
        2
    ) AS mom_growth_pct,
    -- 同比:与去年同月比较
    LAG(SUM(amount), 12) OVER (ORDER BY date_trunc('month', sale_date)) AS same_month_last_year,
    ROUND(
        100.0 * (SUM(amount) - LAG(SUM(amount), 12) OVER (ORDER BY date_trunc('month', sale_date)))
        / NULLIF(LAG(SUM(amount), 12) OVER (ORDER BY date_trunc('month', sale_date)), 0),
        2
    ) AS yoy_growth_pct
FROM sales
GROUP BY date_trunc('month', sale_date)
ORDER BY month;

关键点:

  • LAG(col, n) — 取前面第 n 行的值。n=1 是上月,n=12 是去年同月
  • NULLIF(..., 0) — 防止除以零报错
  • date_trunc('month', ...) — 按月聚合,把日期截断到月初

在 pandas 中实现同样的功能需要多步操作:先 groupby 聚合,再用 shift() 移位,最后计算增长率。而在 DuckDB 中,这些操作都在一条 SQL 中完成。

二、移动平均线(MA)

做技术分析的朋友最熟悉这个了。用 DuckDB 的窗口函数,几行代码就能算出 MA5、MA20、MA60。

SELECT
    sale_date,
    amount AS daily_revenue,
    -- 5日移动平均
    AVG(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
    ) AS ma5,
    -- 20日移动平均
    AVG(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
    ) AS ma20,
    -- 60日移动平均
    AVG(amount) OVER (
        ORDER BY sale_date
        ROWS BETWEEN 59 PRECEDING AND CURRENT ROW
    ) AS ma60
FROM sales
ORDER BY sale_date;

核心语法:ROWS BETWEEN n PRECEDING AND CURRENT ROW 表示"当前行 + 前面 n 行",正好是一个滑动窗口。

三、时间桶聚合(time_bucket)

DuckDB v1.0+ 引入了 time_bucket 函数,可以按任意时间间隔聚合数据。非常适合做实时看板。

-- 每 15 分钟聚合一次(适合高频交易数据)
SELECT
    time_bucket(INTERVAL '15 minutes', timestamp) AS time_slot,
    COUNT(*) AS trade_count,
    AVG(price) AS avg_price,
    MAX(price) - MIN(price) AS price_range,
    SUM(volume) AS total_volume
FROM trades
WHERE timestamp >= TIMESTAMP '2026-07-01'
GROUP BY time_bucket(INTERVAL '15 minutes', timestamp)
ORDER BY time_slot;

-- 每天聚合
SELECT
    time_bucket(INTERVAL '1 day', sale_date) AS day,
    SUM(amount) AS daily_revenue,
    COUNT(DISTINCT customer_id) AS active_customers
FROM orders
GROUP BY day
ORDER BY day;

time_bucket 的优势:

  • 自动处理不规则的时间间隔
  • 比手动 date_trunc 更灵活,支持任意间隔(15 分钟、30 天、1 季度等)
  • 输出结果是标准 timestamp,方便后续 JOIN

四、连续区间分析 — 用户留存

这是互联网产品最核心的指标之一。用 DuckDB 算用户次日留存、7 日留存、30 日留存。

WITH first_login AS (
    -- 每个用户的首次登录日期
    SELECT
        user_id,
        MIN(login_date) AS first_date
    FROM user_logins
    GROUP BY user_id
),
login_after_first AS (
    -- 用户在首次登录之后的每次登录
    SELECT
        f.user_id,
        f.first_date,
        l.login_date,
        DATEDIFF('day', f.first_date, l.login_date) AS days_since_first
    FROM first_login f
    JOIN user_logins l ON f.user_id = l.user_id
    WHERE l.login_date > f.first_date
)
SELECT
    days_since_first AS retention_day,
    COUNT(DISTINCT user_id) AS retained_users,
    ROUND(100.0 * COUNT(DISTINCT user_id) / (
        SELECT COUNT(DISTINCT user_id) FROM first_login
    ), 2) AS retention_rate_pct
FROM login_after_first
WHERE days_since_first BETWEEN 0 AND 30
GROUP BY days_since_first
ORDER BY retention_day;

五、Gap & Island 问题 — 识别用户活跃期

真实场景:你的 SaaS 客户有活跃期和沉寂期,你想自动识别每个客户的"活跃阶段"。

WITH groups AS (
    SELECT
        user_id,
        login_date,
        -- 连续登录的日期会形成相同的分组
        login_date - INTERVAL '1 day' * ROW_NUMBER() OVER (
            PARTITION BY user_id ORDER BY login_date
        ) AS grp
    FROM user_logins
)
SELECT
    user_id,
    MIN(login_date) AS period_start,
    MAX(login_date) AS period_end,
    COUNT(*) AS consecutive_days
FROM groups
GROUP BY user_id, grp
HAVING consecutive_days >= 3
ORDER BY user_id, period_start;

这个模式叫"Gap & Island",是数据分析中最经典的问题之一。核心思路:用 日期 - 行号 来标记连续区间,相同值的就是一组。

六、Python 完整实战:从 CSV 到分析报告

import duckdb
import pandas as pd

con = duckdb.connect()

# 创建示例数据
con.execute("""
CREATE TABLE sales AS
SELECT
    DATE '2026-01-01' + (random() * 365)::INT AS sale_date,
    (random() * 1000)::DECIMAL(10,2) AS amount,
    ('北京','上海','广州','深圳','杭州')[1 + (random()*4)::INT] AS city,
    ('电子产品','服装','食品','家居','图书')[1 + (random()*4)::INT] AS category
FROM range(100000)
""")

# 一键生成月度销售报告
report = con.execute("""
WITH monthly AS (
    SELECT
        date_trunc('month', sale_date) AS month,
        SUM(amount) AS revenue,
        COUNT(*) AS order_count,
        AVG(amount) AS avg_order_value
    FROM sales
    GROUP BY month
)
SELECT
    month,
    revenue,
    order_count,
    avg_order_value,
    LAG(revenue) OVER w AS prev_month_revenue,
    ROUND(100.0 * (revenue - LAG(revenue) OVER w) / NULLIF(LAG(revenue) OVER w, 0), 2) AS mom_growth,
    ROUND(AVG(revenue) OVER (
        ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ), 2) AS ma3_revenue
FROM monthly
WINDOW w AS (ORDER BY month)
ORDER BY month
""").fetchdf()

print(report.to_string(index=False))

七、与传统工具对比

功能DuckDBPandasExcelClickHouse
环比/同比LAG 窗口函数shift() + 合并复杂公式类似窗口函数
移动平均ROWS BETWEENrolling()无内置类似窗口函数
时间桶聚合time_bucket()resample()数据透视表不支持
用户留存CTE + JOIN多步操作复杂公式类似窗口函数
Gap & Island日期 - 行号复杂逻辑几乎不可能类似窗口函数
100 万行速度<100ms~2s卡顿<100ms
学习成本低(SQL)中(Python)

八、容易被忽略的技巧:WINDOW 子句复用

当你在同一个查询中多次用到相同的窗口定义时,用 WINDOW 子句避免重复:

SELECT
    month,
    revenue,
    LAG(revenue) OVER w AS prev_month,
    AVG(revenue) OVER w AS ma3,
    SUM(revenue) OVER w AS cumulative
FROM monthly_sales
WINDOW w AS (ORDER BY month);

这样只需要写一次窗口定义,查询更简洁、性能也更好(优化器会复用计算)。

九、变现建议

掌握这些时间序列分析技巧后,你可以直接变现:

  1. 自动化报表服务:为中小企业搭建每日/每周销售看板,收取订阅费(500-2000 元/月)
  2. 数据产品即服务:将用户留存分析做成 SaaS 工具,按用户数收费
  3. 金融数据产品:用移动平均线 + 时间序列分析做量化策略回测工具
  4. 电商数据分析报告:为商家提供月度销售趋势分析,按报告收费

DuckDB 的核心优势在于:窗口函数强大time_bucket 灵活Gap & Island 解法优雅Python 无缝集成。掌握这些技巧,你处理任何时间序列报表的需求都能从容应对。

💡 更多 DuckDB 时间序列实战案例 → duckdblab.org

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。