6 minutes
用户行为分析实战
项目背景
上一篇文章我们分析了电商订单数据,得到了"是什么"的答案。但订单只是用户行为的终点。要真正理解用户,我们需要追踪和分析用户的完整行为路径:他们从哪来,在站内做了什么,为什么最终选择购买或离开。
本实战项目使用用户事件日志数据,涵盖页面浏览、点击、加购、支付等行为。我们将围绕以下几个核心问题展开分析:
- 用户的留存情况如何?不同渠道获取的用户留存有无差异?
- 从访看到购买的转化漏斗中,哪个环节流失最严重?
- 用户的日活跃度、使用深度如何?
- 如何用 AARRR 框架系统评估用户生命周期?
- 不同用户群体的行为模式有何差异?
- 用户的终身价值该如何估算?
数据加载与预处理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 事件日志数据
events = pd.read_csv('user_events.csv')
print(f'事件总数: {len(events):,}')
print(f'唯一用户数: {events["user_id"].nunique():,}')
events.head()
事件日志数据通常包含以下字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| event_id | 事件编号 | 唯一标识 |
| user_id | 用户编号 | 匿名字段 |
| event_type | 事件类型 | page_view, click, add_to_cart, purchase |
| page_url | 页面地址 | /products/123 |
| timestamp | 事件时间 | 2025-01-15 14:30:00 |
| session_id | 会话编号 | 区分不同访问会话 |
| channel | 来源渠道 | organic, paid, referral, social |
| device | 设备类型 | mobile, desktop, tablet |
# 时间字段处理
events['timestamp'] = pd.to_datetime(events['timestamp'])
events['date'] = events['timestamp'].dt.date
events['hour'] = events['timestamp'].dt.hour
events['weekday'] = events['timestamp'].dt.dayofweek
# 确保事件类型一致
print('事件类型分布:')
print(events['event_type'].value_counts())
同期群分析(Cohort Analysis)
同期群分析是用户分析最有力的工具之一。它追踪同一时间段内注册的用户群体,观察他们在后续时间窗口内的行为变化。
计算留存同期群
# 找到每个用户的首次活跃日期
first_active = events.groupby('user_id')['date'].min().reset_index()
first_active.columns = ['user_id', 'first_active_date']
# 合并到事件数据
events_with_cohort = events.merge(first_active, on='user_id')
# 计算每个事件距离首次活跃的天数
events_with_cohort['days_since_first'] = (
pd.to_datetime(events_with_cohort['date']) -
pd.to_datetime(events_with_cohort['first_active_date'])
).dt.days
# 定义同期群月份
events_with_cohort['cohort_month'] = pd.to_datetime(
events_with_cohort['first_active_date']
).dt.to_period('M')
# 定义期间月份(每个事件发生的月份)
events_with_cohort['period_month'] = pd.to_datetime(
events_with_cohort['date']
).dt.to_period('M')
# 计算月间隔
events_with_cohort['month_offset'] = (
events_with_cohort['period_month'] - events_with_cohort['cohort_month']
).apply(lambda x: x.n)
构建留存矩阵
# 每个同期群在每个月的活跃用户数
cohort_data = events_with_cohort.groupby(
['cohort_month', 'month_offset']
)['user_id'].nunique().reset_index()
# 每个同期群的初始用户数(month_offset=0)
cohort_sizes = cohort_data[cohort_data['month_offset'] == 0][
['cohort_month', 'user_id']
].rename(columns={'user_id': 'cohort_size'})
# 合并并计算留存率
cohort_data = cohort_data.merge(cohort_sizes, on='cohort_month')
cohort_data['retention_rate'] = cohort_data['user_id'] / cohort_data['cohort_size']
# 透视成矩阵表格
cohort_pivot = cohort_data.pivot_table(
index='cohort_month',
columns='month_offset',
values='retention_rate',
aggfunc='mean'
)
# 可视化留存矩阵
fig, ax = plt.subplots(figsize=(14, 8))
sns.heatmap(cohort_pivot, annot=True, fmt='.0%', cmap='YlOrRd',
vmin=0, vmax=0.5, ax=ax, cbar_kws={'label': '留存率'})
ax.set_title('用户月留存同期群矩阵', fontsize=14)
ax.set_ylabel('注册月份(同期群)')
ax.set_xlabel('注册后第 N 个月')
plt.show()
解读留存矩阵
留存矩阵的每一行代表一个同期群,每一列代表该群在后续月份的留存率。观察要点:
- 对角线方向:如果从左下到右上的颜色基本一致,说明不同同期群的留存模式相似
- 第一列(第1个月):通常下降最明显,称为"新用户激活期"
- 尾部稳定值:留存率最终趋于稳定的水平,这个值衡量产品的长期黏性
按渠道的留存对比
# 每个用户的来源渠道(取首次事件的渠道)
user_first_event = events.sort_values('timestamp').groupby('user_id').first().reset_index()
user_channel = user_first_event[['user_id', 'channel']]
events_with_channel = events_with_cohort.merge(user_channel, on='user_id', suffixes=('', '_first'))
# 按渠道计算留存
def cohort_retention_by_channel(df, channel_name):
channel_users = df[df['channel_first'] == channel_name]
cohort = channel_users.groupby(['cohort_month', 'month_offset'])['user_id'].nunique().reset_index()
sizes = cohort[cohort['month_offset'] == 0][['cohort_month', 'user_id']].rename(
columns={'user_id': 'size'})
cohort = cohort.merge(sizes, on='cohort_month')
cohort['retention'] = cohort['user_id'] / cohort['size']
return cohort
channels = events_with_channel['channel_first'].unique()
fig, ax = plt.subplots(figsize=(12, 6))
for ch in channels:
ch_data = cohort_retention_by_channel(events_with_channel, ch)
avg = ch_data.groupby('month_offset')['retention'].mean()
ax.plot(avg.index, avg.values, marker='o', label=ch)
ax.set_title('不同渠道用户留存对比')
ax.set_xlabel('注册后月数')
ax.set_ylabel('留存率')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()
漏斗分析
漏斗分析帮助我们找到转化链条上的薄弱环节。
# 定义漏斗步骤和顺序
funnel_steps = ['page_view', 'product_detail', 'add_to_cart', 'initiate_checkout', 'purchase']
# 每个用户在每个步骤的转化情况
funnel_data = []
for step in funnel_steps:
step_users = events[events['event_type'] == step]['user_id'].nunique()
funnel_data.append({'step': step, 'users': step_users})
funnel_df = pd.DataFrame(funnel_data)
# 计算各步骤转化率
funnel_df['overall_conversion'] = funnel_df['users'] / funnel_df['users'].max()
funnel_df['step_conversion'] = funnel_df['users'] / funnel_df['users'].shift(1)
funnel_df['step_conversion'] = funnel_df['step_conversion'].fillna(1)
# 步骤名称映射
step_labels = {
'page_view': '访问页面',
'product_detail': '查看商品详情',
'add_to_cart': '加入购物车',
'initiate_checkout': '开始结算',
'purchase': '完成购买'
}
funnel_df['step_label'] = funnel_df['step'].map(step_labels)
print(funnel_df[['step_label', 'users', 'overall_conversion', 'step_conversion']])
# 可视化漏斗
fig, ax = plt.subplots(figsize=(10, 6))
# 漏斗图的锥形
max_width = 0.8
bar_colors = ['#2E86AB', '#A23B72', '#F18F01', '#55A868', '#C44E52']
for i, (_, row) in enumerate(funnel_df.iterrows()):
width = row['overall_conversion'] * max_width
left = (1 - width) / 2
ax.barh(i, width, left=left, height=0.6, color=bar_colors[i],
edgecolor='white', linewidth=1.5)
label = f"{row['step_label']}\n{row['users']:,} ({row['overall_conversion']:.1%})"
ax.text(0.5, i, label, ha='center', va='center', fontsize=11, color='white', fontweight='bold')
ax.set_yticks([])
ax.set_title('用户转化漏斗', fontsize=14)
ax.set_xlim(0, 1)
plt.tight_layout()
plt.show()
流失点分析
从每一步到下一步的转化率揭示了流失最严重的环节。最常见的流失点包括:
- 商品详情到加购:可能是价格问题、竞争对比、或产品信息不足
- 加购到结算:可能是有无货提示、运费门槛、或犹豫决策
- 结算到支付:可能是支付流程复杂、支付方式不足、或技术故障
# 按渠道拆解漏斗
for channel in events['channel'].unique():
channel_users = events[events['channel'] == channel]
print(f'\n=== {channel} 渠道漏斗 ===')
for i, step in enumerate(funnel_steps):
if i == 0:
users = channel_users[channel_users['event_type'] == step]['user_id'].nunique()
overall = 1.0
else:
step_users = channel_users[channel_users['event_type'] == step]['user_id'].nunique()
prev_users = channel_users[channel_users['event_type'] == funnel_steps[i-1]]['user_id'].nunique()
step_conv = step_users / prev_users if prev_users > 0 else 0
overall = step_users / funnel_df.iloc[0]['users']
print(f' {step_labels[step]}: {step_users} 人 | 步骤转化率: {step_conv:.1%} | 总体转化率: {overall:.1%}')
用户活跃度与参与度
DAU/MAU 分析
DAU/MAU 比率是衡量用户参与度的黄金指标,通常称为"用户黏性"。比值越高,用户越频繁使用产品。
# 计算 DAU
dau = events.groupby('date')['user_id'].nunique().reset_index()
dau.columns = ['date', 'dau']
# 计算 MAU(滚动30天)
events['month'] = pd.to_datetime(events['date']).dt.to_period('M')
mau = events.groupby('month')['user_id'].nunique().reset_index()
mau.columns = ['month', 'mau']
# 计算周均 DAU
dau['weekday'] = pd.to_datetime(dau['date']).dt.dayofweek
weekly_dau = dau.groupby(pd.to_datetime(dau['date']).dt.isocalendar().week)['dau'].mean()
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# DAU 趋势
axes[0, 0].plot(pd.to_datetime(dau['date']), dau['dau'], color='#2E86AB', linewidth=1)
axes[0, 0].set_title('每日活跃用户 (DAU)')
axes[0, 0].set_ylabel('DAU')
# 星期几的 DAU
dow_avg = dau.groupby('weekday')['dau'].mean()
dow_labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
axes[0, 1].bar(dow_labels, dow_avg, color='#55A868')
axes[0, 1].set_title('各星期平均 DAU')
# MAU 趋势
axes[1, 0].bar(mau['month'].astype(str), mau['mau'], color='#A23B72')
axes[1, 0].set_title('每月活跃用户 (MAU)')
axes[1, 0].tick_params(axis='x', rotation=45)
# DAU/MAU 比率
events['year_month'] = events['date'].apply(lambda x: str(x)[:7])
monthly_dau = dau.groupby(pd.to_datetime(dau['date']).dt.to_period('M'))['dau'].mean()
dau_mau = pd.DataFrame({
'dau': monthly_dau,
'mau': mau.set_index('month')['mau']
}).dropna()
dau_mau['stickiness'] = dau_mau['dau'] / dau_mau['mau']
axes[1, 1].plot(dau_mau.index.astype(str), dau_mau['stickiness'],
marker='o', color='#F18F01')
axes[1, 1].axhline(y=0.2, color='gray', linestyle='--', alpha=0.5, label='优秀线 (20%)')
axes[1, 1].set_title('DAU/MAU 黏性指标')
axes[1, 1].tick_params(axis='x', rotation=45)
axes[1, 1].set_ylabel('DAU/MAU')
axes[1, 1].legend()
plt.tight_layout()
plt.show()
print(f'平均 DAU/MAU: {dau_mau["stickiness"].mean():.2%}')
会话深度分析
# 每个会话的页面浏览数和时长
session_stats = events.groupby('session_id').agg({
'event_id': 'count',
'timestamp': lambda x: (x.max() - x.min()).total_seconds() if len(x) > 1 else 0
}).rename(columns={
'event_id': 'pages_per_session',
'timestamp': 'session_duration'
})
print('会话深度统计:')
print(session_stats.describe())
# 页面浏览量分布
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
session_stats['pages_per_session'].plot(kind='hist', bins=30, ax=axes[0],
color='#2E86AB', edgecolor='white')
axes[0].set_title('每次会话页面浏览量分布')
axes[0].set_xlabel('页面数')
session_stats['session_duration'].plot(kind='hist', bins=30, ax=axes[1],
color='#A23B72', edgecolor='white')
axes[1].set_title('会话时长分布(秒)')
axes[1].set_xlabel('时长(秒)')
plt.tight_layout()
plt.show()
AARRR 框架分析
AARRR(海盗指标)是用户分析的经典框架,覆盖用户完整生命周期。
# 设定分析时间窗口
analysis_start = events['date'].min()
analysis_end = events['date'].max()
print(f'分析周期: {analysis_start} 至 {analysis_end}')
# Acquisition(获取)
acquisition = events.groupby('channel')['user_id'].nunique().sort_values(ascending=False)
# Activation(激活)- 定义为完成关键动作(如浏览3个以上页面)
activation = session_stats[session_stats['pages_per_session'] >= 3]
activated_users = events[events['session_id'].isin(activation.index)]['user_id'].nunique()
total_users = events['user_id'].nunique()
activation_rate = activated_users / total_users
# Retention(留存)- 第7天留存
retention_d7 = events_with_cohort[
(events_with_cohort['days_since_first'] == 7)
]['user_id'].nunique()
retention_d7_rate = retention_d7 / first_active['user_id'].nunique()
# Revenue(收入)
revenue_data = events[events['event_type'] == 'purchase']
total_revenue = revenue_data['amount'].sum() if 'amount' in revenue_data.columns else 0
arpu = total_revenue / total_users
# Referral(推荐)- 有分享行为的用户比例
referral_users = events[events['event_type'] == 'share']['user_id'].nunique()
referral_rate = referral_users / total_users
print('\n===== AARRR 海盗指标 =====')
print(f'获取 (Acquisition): 共 {total_users:,} 用户')
print(f'各渠道:')
for ch, cnt in acquisition.items():
print(f' {ch}: {cnt:,} ({cnt/total_users:.1%})')
print(f'\n激活 (Activation): {activation_rate:.1%}')
print(f'留存 (Retention) 第7天: {retention_d7_rate:.1%}')
print(f'收入 (Revenue): ARPU={arpu:.2f}')
print(f'推荐 (Referral): {referral_rate:.1%}')
# AARRR 雷达图
categories = ['获取', '激活', '留存', '收入', '推荐']
values = [1.0, activation_rate, retention_d7_rate,
min(arpu / 100, 1), referral_rate]
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
angles = [n / float(len(categories)) * 2 * np.pi for n in range(len(categories))]
angles += angles[:1]
values += values[:1]
ax.plot(angles, values, 'o-', linewidth=2, color='#2E86AB')
ax.fill(angles, values, alpha=0.25, color='#2E86AB')
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories)
ax.set_title('AARRR 海盗指标概览', fontsize=14)
plt.show()
用户行为分群
使用聚类方法对用户的行为模式进行分群。
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
# 构建用户行为特征
user_features = events.groupby('user_id').agg({
'event_type': 'count', # 总事件数
'session_id': 'nunique', # 总会话数
'date': lambda x: (x.max() - x.min()).days, # 活跃天数跨度
}).rename(columns={
'event_type': 'total_events',
'session_id': 'total_sessions',
'date': 'active_span'
})
# 添加购买行为
purchase_users = events[events['event_type'] == 'purchase'].groupby('user_id').agg(
purchase_count=('event_id', 'count')
)
user_features = user_features.merge(purchase_users, how='left', on='user_id')
user_features['purchase_count'] = user_features['purchase_count'].fillna(0)
# 添加每小时事件数(活跃时段偏好)
hourly = events.groupby(['user_id', 'hour']).size().unstack(fill_value=0)
for h in range(24):
col = f'active_hour_{h}'
user_features[col] = hourly.get(h, pd.Series(0, index=user_features.index))
# 标准化
features_scaled = StandardScaler().fit_transform(user_features)
# 聚类
kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
user_features['cluster'] = kmeans.fit_predict(features_scaled)
# 分析各群体特征
cluster_profile = user_features.groupby('cluster').agg({
'total_events': 'mean',
'total_sessions': 'mean',
'active_span': 'mean',
'purchase_count': 'mean'
}).round(2)
print('用户行为分群特征:')
print(cluster_profile)
# 各分群大小
cluster_sizes = user_features['cluster'].value_counts().sort_index()
labels_map = {0: '轻度浏览型', 1: '活跃购买型', 2: '潜在观望型', 3: '重度忠诚型'}
for i, size in cluster_sizes.items():
print(f'{labels_map[i]}: {size} 人 ({size/len(user_features):.1%})')
用户终身价值(LTV)估算
LTV 用于预测一个用户在其整个生命周期内能为业务贡献多少价值。
# 简单 LTV 估算方法
# LTV = 平均订单金额 × 购买频率 × 预期生命周期
# 计算每个用户的购买数据
user_ltv = events[events['event_type'] == 'purchase'].groupby('user_id').agg({
'amount': ['sum', 'mean', 'count']
})
user_ltv.columns = ['total_revenue', 'avg_order_value', 'order_count']
# 计算用户的活跃天数
user_days = events.groupby('user_id')['date'].nunique()
user_ltv['active_days'] = user_days
# 历史 LTV
user_ltv['historical_ltv'] = user_ltv['total_revenue']
# 简单预测 LTV(假设未来半年保持当前消费速率)
avg_monthly_spend = user_ltv['total_revenue'] / user_ltv['active_days'] * 30
user_ltv['predicted_ltv_6m'] = user_ltv['total_revenue'] + avg_monthly_spend * 6
print('LTV 统计:')
print(user_ltv[['historical_ltv', 'predicted_ltv_6m']].describe())
# LTV 分布
fig, ax = plt.subplots(figsize=(10, 5))
user_ltv['historical_ltv'].clip(upper=user_ltv['historical_ltv'].quantile(0.95)).hist(
bins=40, ax=ax, color='#C44E52', edgecolor='white')
ax.set_title('用户终身价值分布(截尾 95%)')
ax.set_xlabel('LTV')
ax.set_ylabel('用户数')
plt.show()
LTV 分层运营策略
| LTV 分位 | 特征 | 运营策略 |
|---|---|---|
| 高(前20%) | 高频购买,高客单价 | VIP 服务,个推优先,推荐同品类高客单价商品 |
| 中(20%-60%) | 偶尔购买,中等客单价 | 提高复购率,交叉销售相关品类 |
| 低(后40%) | 极少购买或仅浏览 | 激活策略,首单优惠,个性化推荐引流 |
关键发现总结
完成以上分析后,我们可以给出以下总结性的洞察:
1. 留存洞察
- 首月留存是产品健康度的核心指标
- 自然搜索渠道的用户留存通常高于付费渠道
- 留存曲线在 3-6 个月趋于稳定,这个稳定值代表产品核心价值
2. 漏斗优化机会
- 最大的转化损耗通常发生在"商品详情到加购"环节,说明产品信息和定价是关键
- 移动端的结算转化率可能低于桌面端,需优化移动支付体验
- 不同渠道的漏斗形态差异显著,需要针对性优化
3. 用户分层建议
- 重度忠诚用户(10-20%)贡献了 50-80% 的收入,值得最高级别的运营投入
- 潜在观望用户需要通过个性化推荐和限时优惠来激活
- 轻度浏览用户适合自动化培育流程,逐步建立信任
4. 数据驱动决策
- 建立用户行为指标体系,实时监控 DAU/MAU、核心转化率等指标
- 每周生成 AARRR 看板,跟踪各环节变化趋势
- 用 A/B 测试验证运营策略的有效性
小结
用户行为分析比单纯的订单分析更深入。它让我们看到用户从进入产品到离开的完整旅程,找到产品体验的痛点和增长机会。
本篇文章涉及的核心技能:
- 同期群分析(Cohort Analysis)是理解用户留存的必备工具
- 漏斗分析帮助定位转化瓶颈
- DAU/MAU 等指标量化用户参与度
- AARRR 框架系统化用户生命周期管理
- 行为分群和 LTV 估算指导精细化运营
在下一篇文章中,我们将切换视角,学习如何构建自动化的数据管道,让分析工作更加高效和可持续。