1. 项目概述:从两个核心指标看产品健康度
做产品运营或者数据分析的朋友,对DNU和DAU这两个指标肯定不陌生。每天看报表,这两个数字几乎是必看的。但说实话,光看两个孤零零的数字,很多时候感觉就像隔靴搔痒。你知道今天DAU涨了,但为什么涨?是新用户带动的,还是老用户更活跃了?你知道DNU降了,但降了多少,对大盘的影响有多大?这些问题,单看日环比、周同比的百分比变化,往往不够直观。
我自己在带团队做一款内容社区APP的数据分析时,就深有体会。老板每天晨会必问:“我们日活怎么样?新增怎么样?”一开始,我们就把两个折线图往PPT上一贴。时间长了,我发现一个问题:这两个指标是强相关的,但又彼此独立。新增用户(DNU)是DAU的“新鲜血液”,而DAU的构成里又包含了新增和留存的老用户。把两条线分开看,很难一眼就看出它们之间的动态关系和此消彼长的贡献度。直到我开始用“面积图”来可视化这两个指标,整个局面才清晰起来。这张图不仅能回答“是多少”,更能回答“为什么是这么多”。
简单来说,这个项目就是教你如何用Python,将DNU和DAU这两个核心运营指标,绘制成一张信息量巨大的堆叠面积图。通过这张图,你可以直观地看到:
- DAU的整体规模和趋势:面积图的总高度代表了DAU。
- DNU对DAU的每日贡献:图中最底层、通常用不同颜色表示的面积部分,代表了当日新增用户。
- 老用户留存(或回流)的活跃情况:总高度减去底层新增部分,剩下的面积就代表了当日活跃的老用户(即DAU - DNU)。这部分可以理解为“留存活跃用户”或“活跃存量用户”。
- 结构变化的动态过程:观察底层(新增)和上层(老用户)面积随时间变宽或变窄,你能立刻判断DAU的增长是依靠“拉新”还是“促活”,以及策略调整后的效果。
无论你是刚入门的数据分析师,还是需要经常复盘产品数据的运营、产品经理,掌握这个分析方法,都能让你的数据洞察力提升一个档次。它把枯燥的数字变成了有故事、有结构的画面。接下来,我就手把手带你从理解指标开始,到用Python实现这个分析可视化案例。
2. 核心指标深度解析:DNU与DAU究竟是什么?
在动手画图之前,我们必须把基础概念夯扎实。很多人对DNU和DAU的理解停留在表面,这会导致后续分析走偏。
2.1 DAU:产品每日活力的“总刻度”
DAU,日活跃用户数。定义很简单:在统计当日,启动或使用了你产品的独立用户数。注意关键词是“独立”,同一个用户一天内打开十次,也只算一个DAU。它衡量的是产品在一天内的用户覆盖面和基础热度。
但DAU是一个“结果性”指标,也是一个“黑箱”。一个100万DAU的产品,可能是由90万老用户和10万新用户构成,也可能是由50万老用户和50万新用户构成。这两种构成,对应的产品健康状况、运营策略重心和未来风险是天差地别的。前者可能增长乏力但用户稳定,后者可能处于烧钱拉新阶段,留存堪忧。只看DAU总量,我们无法进行诊断。
2.2 DNU:增长引擎的“每日燃料”
DNU,每日新增用户数。通常指统计当日,首次启动应用或完成核心注册流程的独立用户数。它是产品增长的源头活水,是市场、渠道、投放等部门工作成果最直接的数字化体现。
然而,DNU同样是一个需要谨慎看待的指标。高DNU值得庆祝,但更要追问:
- 来源质量:新增用户来自哪里?自然增长、渠道投放还是活动裂变?
- 转化成本:获取这些新增用户的成本(CAC)是多少?
- 次日留存:这些新增用户,有多少在第二天还会回来?(即DNU的次日留存率)
孤立地追求高DNU没有意义,甚至危险。如果新增用户留存极差,那么高DNU就像往一个漏水的池子里拼命灌水,DAU这个“水位”很难持续上涨,且营销费用浪费严重。
2.3 关系的本质:DAU = 新增 + 留存活跃
这才是最关键的公式,也是我们做面积图的理论基础。从定义上,我们可以推导出:
今日DAU = 今日DNU + 今日活跃的老用户
这里的“今日活跃的老用户”,指的是在今日之前(含昨日)新增,并且在今日依然活跃的用户。他们可能是昨日新增今日留存的,也可能是更早之前新增今日回流的。
因此,DAU的变化可以拆解为两个动力:
- 增长动力(拉力):来自今日DNU。这是外生变量,受市场、投放影响大。
- 留存动力(推力):来自老用户的持续活跃。这是内生变量,主要取决于产品本身的价值、用户体验和运营手段。
面积图的美妙之处在于,它将这个公式视觉化了。总面积(DAU)被清晰地分割为两部分,你可以一眼看出在任何一天,拉力和推力各自贡献了多少“力量”。当总面积上升时,你能立刻分辨是底层(新增)变厚了,还是上层(老用户)变厚了,亦或是两者都变厚了。这种直观性,是任何数字报表都无法比拟的。
注意:这里有一个重要的统计口径问题。在实际业务中,有时“老用户”可能指“去除今日新增后的所有活跃用户”,这是一个较宽的口径。更精细的分析可能会区分“次留用户”、“7日内留存用户”、“7日以上留存用户”等。我们当前案例采用最基础的二分法(新增 vs. 非今日新增活跃),这是最通用也最直观的起点。掌握了这个方法后,你可以将其扩展为更精细的多层堆叠面积图,来分析不同生命周期用户的活跃贡献。
3. 案例实战:用Python绘制DNU/DAU面积图
理解了原理,我们进入实战环节。我将用一个模拟的数据集,展示从数据准备到图形绘制的完整流程。你会看到,用Python实现这一切,既专业又高效。
3.1 环境准备与数据模拟
首先,确保你的Python环境安装了必要的库。我们将主要使用pandas进行数据处理,matplotlib进行绘图。在Jupyter Notebook或你的Python脚本中,先导入它们。
import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import datetime, timedelta # 设置中文显示和图形样式(可选,让图表更美观) plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 plt.style.use('seaborn-v0_8-whitegrid') # 使用seaborn的白色网格风格,更清晰因为真实业务数据涉及隐私,我们这里模拟一份30天的数据。在现实中,你的数据可能来自数据仓库、数据库或统计平台导出。
# 模拟生成30天的日期序列 date_today = datetime.now().date() dates = [(date_today - timedelta(days=i)).strftime('%Y-%m-%d') for i in range(29, -1, -1)] # 最近30天,从旧到新 # 模拟DAU数据:假设有一个缓慢上升的趋势,并加上一些日常波动 np.random.seed(42) # 固定随机种子,确保结果可复现 base_dau = 100000 trend = np.linspace(0, 20000, 30) # 线性增长趋势 fluctuation = np.random.randn(30) * 5000 # 随机波动 dau_values = (base_dau + trend + fluctuation).astype(int) # 模拟DNU数据:假设新增有周期性(如周末低,工作日高),且整体趋势平稳 # 先创建一个周期性的基础值 weekday_effect = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 0.7, 0.6]) # 周一到周日的相对系数 weekday_idx = [pd.Timestamp(d).dayofweek for d in dates] base_nu = 8000 nu_values = (base_nu * np.array([weekday_effect[i] for i in weekday_idx]) + np.random.randn(30) * 1000).astype(int) # 计算每日活跃的老用户数 = DAU - DNU retained_dau_values = dau_values - nu_values # 确保老用户数不为负(虽然模拟数据一般不会,但真实数据清洗时需注意) retained_dau_values = np.maximum(retained_dau_values, 0) # 创建DataFrame df = pd.DataFrame({ 'date': dates, 'dau': dau_values, 'dnu': nu_values, 'retained_dau': retained_dau_values }) df['date'] = pd.to_datetime(df['date']) # 转换为datetime类型,便于绘图 print(df.head()) print(df.tail())运行这段代码,你会得到一个包含四列(日期、DAU、DNU、留存活跃DAU)的DataFrame。打印头尾可以查看数据概况。
3.2 绘制基础堆叠面积图
核心的绘图部分其实非常简洁。matplotlib的stackplot函数是专门为此类图表设计的。
# 创建图形和坐标轴 fig, ax = plt.subplots(figsize=(14, 8)) # 绘制堆叠面积图 # 参数说明: # x: 日期序列 # y: 一个列表,包含要堆叠的Y值序列。注意堆叠顺序:先绘制的在底部。 # labels: 对应每一层的图例标签 # colors: 自定义颜色,通常用对比色区分新增和老用户 ax.stackplot(df['date'], [df['dnu'], df['retained_dau']], # 底层是新增,上层是老用户 labels=['每日新增用户 (DNU)', '活跃老用户 (DAU - DNU)'], colors=['#FF6B6B', '#4ECDC4'], # 红色代表新增(醒目),青色代表老用户(稳定) alpha=0.8) # 设置一定透明度,避免过于厚重 # 在面积图上方,绘制DAU的总量折线,用于对比和精确读数 ax.plot(df['date'], df['dau'], color='#2C3E50', linewidth=3, label='日活跃用户 (DAU)', marker='o') # 设置图形标题和坐标轴标签 ax.set_title('DNU与DAU构成分析面积图 (模拟数据)', fontsize=16, fontweight='bold', pad=20) ax.set_xlabel('日期', fontsize=12) ax.set_ylabel('用户数', fontsize=12) # 优化X轴日期显示,避免重叠 ax.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d')) # 只显示月-日 ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO)) # 每周一显示一个刻度 fig.autofmt_xdate(rotation=45) # 旋转日期标签 # 添加网格和图例 ax.grid(True, which='major', linestyle='--', linewidth=0.5, alpha=0.7) ax.legend(loc='upper left', fontsize=11) # 显示图形 plt.tight_layout() plt.show()执行代码后,一张清晰的堆叠面积图就生成了。你应该能看到:
- 总面积(从X轴到最顶部的轮廓)代表了每日的DAU。
- 底部红色的区域代表了每日的DNU。
- 上部青色的区域代表了每日活跃的老用户。
- 一条深蓝色的折线穿行在面积图顶部,清晰地勾勒出DAU的每日具体数值。
实操心得:颜色选择至关重要。我习惯用暖色、亮色(如红、橙)代表“输入型”、“变动大”的指标(如DNU),用冷色、暗色(如青、蓝、绿)代表“存量型”、“较稳定”的指标(如留存用户)。这符合大多数人的认知习惯。另外,一定要加上DAU的总量折线。因为面积图堆叠后,顶部轮廓的纵坐标值就是DAU,加上折线可以更精确地定位每日数值,尤其是在进行不同日期间对比时,折线的指引作用非常明显。
3.3 图表进阶优化与信息增强
基础的图表已经能说明问题,但我们可以做得更好,让它承载更多信息,也更易于解读。
1. 添加关键数据点标注:在DAU的峰值、谷值,或者DNU有特殊变化(如活动开始日)的点上,添加文字标注,说明当时可能发生的事件。
# 接续上面的绘图代码,在plt.show()之前添加 # 示例:标注DAU最高点和最低点 max_dau_idx = df['dau'].idxmax() min_dau_idx = df['dau'].idxmin() ax.annotate(f"峰值: {df.loc[max_dau_idx, 'dau']:,}", xy=(df.loc[max_dau_idx, 'date'], df.loc[max_dau_idx, 'dau']), xytext=(10, 20), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=.2'), fontsize=9, bbox=dict(boxstyle='round,pad=0.3', facecolor='wheat', alpha=0.8)) ax.annotate(f"谷值: {df.loc[min_dau_idx, 'dau']:,}", xy=(df.loc[min_dau_idx, 'date'], df.loc[min_dau_idx, 'dau']), xytext=(-40, -30), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=-.2'), fontsize=9, bbox=dict(boxstyle='round,pad=0.3', facecolor='lightblue', alpha=0.8)) # 示例:如果第15天有一个拉新活动,标注DNU # 假设我们模拟数据中第15天(索引14)是活动开始日 event_day_idx = 14 ax.axvline(x=df.loc[event_day_idx, 'date'], color='gray', linestyle=':', linewidth=1, alpha=0.7) ax.text(df.loc[event_day_idx, 'date'], ax.get_ylim()[1]*0.9, ' 活动开始', rotation=90, verticalalignment='top', fontsize=9, color='gray')2. 计算并可视化贡献度百分比:有时我们更关心结构比例而非绝对数值。可以在图中添加一个辅助Y轴,显示DNU占DAU的百分比。
# 方法:创建一个双Y轴图,主Y轴是用户数,次Y轴是百分比 fig, ax1 = plt.subplots(figsize=(14, 8)) # 主Y轴:绘制堆叠面积图(用户数) ax1.stackplot(df['date'], [df['dnu'], df['retained_dau']], labels=['每日新增用户 (DNU)', '活跃老用户 (DAU - DNU)'], colors=['#FF6B6B', '#4ECDC4'], alpha=0.8) ax1.plot(df['date'], df['dau'], color='#2C3E50', linewidth=3, label='日活跃用户 (DAU)', marker='o') ax1.set_xlabel('日期', fontsize=12) ax1.set_ylabel('用户数', fontsize=12, color='black') ax1.tick_params(axis='y', labelcolor='black') ax1.set_title('DNU/DAU构成分析与新增占比趋势', fontsize=16, fontweight='bold', pad=20) # 格式化X轴 ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d')) ax1.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO)) fig.autofmt_xdate(rotation=45) # 创建次Y轴,用于显示DNU占比 ax2 = ax1.twinx() dnu_ratio = (df['dnu'] / df['dau'] * 100).round(1) # 计算每日新增占比百分比 ax2.plot(df['date'], dnu_ratio, color='#9B59B6', linewidth=2, linestyle='--', label='DNU占比 (%)', marker='s', markersize=4) ax2.set_ylabel('DNU占比 (%)', fontsize=12, color='#9B59B6') ax2.tick_params(axis='y', labelcolor='#9B59B6') ax2.set_ylim(0, max(dnu_ratio)*1.2) # 设置合适的Y轴范围 # 合并图例(需要将两个轴的图例线条合并到一起) lines1, labels1 = ax1.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left', fontsize=10) ax1.grid(True, which='major', linestyle='--', linewidth=0.5, alpha=0.7) plt.tight_layout() plt.show()这张图的信息量就更大了。你不仅能看到绝对数量的构成,还能通过紫色的虚线追踪新增用户占比的变化趋势。例如,如果总面积(DAU)在增长,但紫色虚线在下滑,那就明确表示增长主要靠老用户拉动,新增的贡献在减弱。
4. 从图表到洞察:如何分析面积图?
图画出来了,怎么用它来指导业务呢?这才是关键。下面我结合几个典型的图形模式,分享我的分析思路。
4.1 健康增长模式
特征:总面积(DAU)稳步上升,且红色底层(DNU)和青色上层(老用户)的面积都在协同增长,或者至少有一方在稳定增长,另一方保持平稳。解读:这是最理想的状态。说明产品不仅有能力持续获取新用户(市场拉力足),还能让老用户保持或提升活跃度(产品推力强)。新增用户占比曲线可能在一个健康的区间内(例如10%-30%,视产品阶段而定)平稳波动。行动建议:保持当前策略的连贯性。可以进一步细分DNU渠道和老用户分层,找出效率最高的增长点和最忠实的用户群,进行资源倾斜。
4.2 依赖拉新的脆弱增长
特征:总面积(DAU)在增长,但增长几乎完全由底层红色面积(DNU)的增厚驱动,上层青色面积(老用户)非常薄甚至萎缩。新增占比曲线持续处于高位(如>50%)。解读:这是一种“流量依赖症”。DAU的增长是靠不断“输血”(买量、拉新)维持的,产品自身的“造血能力”(用户留存和活跃)很弱。一旦市场预算收缩或渠道竞争加剧,DNU下降,DAU就会立刻崩塌。这在很多工具类或靠补贴拉新的产品初期很常见。行动建议:立即将运营重心从“拉新”转向“留存”和“促活”。需要深入分析用户流失原因,优化新用户引导流程(Onboarding),提升产品核心功能的使用体验和粘性。同时,审视新增用户质量,是否目标用户定位有偏差。
4.3 存量驱动的稳健模式
特征:总面积(DAU)平稳或缓慢增长,底层红色面积(DNU)很小且稳定,上层青色面积(老用户)构成了DAU的绝对主体。新增占比曲线处于低位(如<10%)。解读:常见于成熟期产品或垂直社区。市场增长见顶,新增有限,但产品拥有非常稳固的核心用户群。DAU的波动主要取决于老用户的活跃情况。这种模式增长潜力有限,但基本盘稳固,抗风险能力强。行动建议:深耕现有用户价值。专注于提升用户生命周期价值(LTV),通过精细化运营、社区建设、增值服务等方式,从存量用户中挖掘更多价值。同时,可以尝试探索新的、更精准的细分市场来获取高质量新增,而不是盲目追求数量。
4.4 衰退预警模式
特征:总面积(DAU)持续下滑。此时需要看结构:
- 情况A:红、青两层面积同步萎缩。说明产品可能遇到了系统性风险(如竞品强力冲击、政策影响、重大版本失误),导致新老用户同时流失。
- 情况B:红色面积(DNU)萎缩,青色面积(老用户)暂时稳定。说明增长渠道出了问题,但产品基本盘还在。这是修复增长渠道的窗口期。
- 情况C:红色面积稳定,青色面积(老用户)萎缩。说明新增渠道没问题,但产品留不住用户,问题出在留存和活跃体系上。行动建议:根据不同的结构衰退特征,采取不同措施。A情况需全面危机应对;B情况需复盘渠道策略和投放素材;C情况需紧急检查产品近期的改版、运营活动是否伤害了用户体验。
实操心得:看面积图,一定要有“对比”的思维。不要只看一天,要看一个周期(周、月)的趋势。同时,要结合业务事件(版本上线、大型活动、节假日)来看。例如,一次大型营销活动后,图中会出现一个红色的“脉冲”,紧接着要看后续几天青色部分是否也能被带动起来,这能评估活动的长期价值。另外,建议将这张图与“留存率曲线图”、“用户活跃时长趋势图”放在一起看,能获得更立体的产品健康度诊断。
5. 常见问题与实战避坑指南
在实际操作中,你可能会遇到一些疑问和坑。这里我整理了几个最常见的问题。
5.1 数据口径不一致怎么办?
问题:数据团队给出的DNU口径是“新增注册用户”,而DAU的口径是“启动用户”。一个用户当天注册但未启动,会计入DNU但不会计入DAU,这就导致DAU - DNU可能为负数,面积图会出现错误。解决方案:这是最需要警惕的一点!在绘图前,必须与数据部门或BI确认指标口径。确保DNU是DAU的子集。通常,一个更安全的口径是使用“新增且活跃用户”作为DNU(即当日既是新增又是活跃的用户),这样DAU = 当日新增且活跃用户 + 当日活跃的老用户恒成立。如果暂时无法统一,可以在计算“活跃老用户”时,使用max(0, DAU - DNU)来避免负值,但这只是技术修补,根本还是要推动口径对齐。
5.2 图表波动太大,看不清趋势?
问题:尤其是DNU,可能受单日渠道投放影响剧烈波动,导致面积图锯齿严重,长期趋势难以观察。解决方案:可以引入移动平均线来平滑数据。例如,计算DAU和DNU的7日移动平均,然后用移动平均后的数据来绘制面积图。这能有效过滤噪音,突出宏观趋势。但要注意,在汇报时需说明使用的是平滑后的数据。
# 计算7日移动平均 df['dau_7d_avg'] = df['dau'].rolling(window=7, center=False).mean() df['dnu_7d_avg'] = df['dnu'].rolling(window=7, center=False).mean() df['retained_dau_7d_avg'] = df['dau_7d_avg'] - df['dnu_7d_avg'] # 然后用 *_7d_avg 列去绘制面积图5.3 想分析更细分的用户构成?
问题:基础的二分法(新/老)不够用了,想分析比如“次留用户”、“7日内新用户”、“沉默回流用户”等更多层次。解决方案:堆叠面积图支持多层堆叠。你只需要准备多个数据序列,分别代表不同分层的用户数,确保它们之和等于DAU(或活跃用户总数)。然后按顺序传入stackplot函数即可。颜色搭配上,建议使用同一色系的不同深浅来代表同一大类(如新用户),用对比色系代表不同大类,以保证图表的可读性。
5.4 如何将这份分析自动化、产品化?
问题:每天手动跑脚本太麻烦,希望能做成定时报表或集成到数据看板中。解决方案:
- 脚本自动化:将上述Python脚本封装成函数或类,接受日期范围等参数。使用任务调度工具(如Linux的cron,Windows的任务计划程序,或Apache Airflow等)每日定时运行,将生成的图表自动保存到指定目录或发送邮件。
- 集成到BI工具:更专业的做法是,在数据仓库层就建好“每日用户分层活跃”的中间表。然后直接在Tableau、FineBI、Superset等BI工具中,利用其内置的“堆叠面积图”功能进行可视化配置。这样业务人员可以自己筛选日期、维度进行交互式分析。
- Web应用:使用Dash、Streamlit等Python框架,可以快速搭建一个简单的内部数据看板,将绘图逻辑嵌入,实现交互式查询和图表展示。
5.5 面积图与瀑布图有什么区别?
这是一个很好的延伸问题。两者都用于展示构成和变化,但侧重点不同:
- 堆叠面积图:强调随时间变化的整体规模及各部分的贡献。它展示的是在每一个时间点上,总量的构成情况,并且可以看到各部分随时间的趋势。核心是“构成”和“趋势”。
- 瀑布图:强调从一个初始值到最终值的变化过程,展示各个正负因素是如何逐步累加导致结果变化的。它常用于分析月度收入变化、利润构成等。核心是“变化过程”和“因素分解”。
在我们的场景中,关注的是每日活跃用户构成的连续趋势,所以面积图是更合适的选择。如果你想分析“本月DAU相比上月净增了10万,这10万分别由哪些因素(新用户、回流用户、留存用户变化)贡献”,那么用瀑布图会更清晰。
掌握DNU/DAU面积图的分析方法,就像是获得了一副观察产品生命体征的“X光眼镜”。它不能直接给你答案,但能帮你快速、直观地定位问题所在,让你问出更正确的问题。下次再被问到“我们日活怎么样”时,试着不再只是抛出一个数字,而是展示这张图,并讲述数字背后的故事——新老用户是如何共同谱写产品每日的活跃曲线的。这才是数据驱动决策真正开始的地方。