github star gitee star atomgit star PyPI Downloads AI 编程 AI 交流群

大家好,我是正在实战各种AI项目的程序员晚枫。

欢迎来到第二个实战项目!这次我们要聊的是用户行为分析,核心工具是经典的RFM模型

做运营的朋友都知道,用户不是铁板一块。有的用户天天买,有的买完就跑;有的出手阔绰,有的只薅羊毛。RFM模型就是帮你把用户分层的利器,让你知道该对谁重点投入,该放弃谁。


项目背景

需求场景

你是某电商平台的用户运营负责人,面临这些问题:

  1. 用户数量庞大,不知道谁是核心用户
  2. 营销预算有限,想把钱花在刀刃上
  3. 想针对不同用户制定差异化策略

目标产出

用RFM模型对用户进行分层,输出可执行的运营策略建议。


什么是RFM模型?

RFM是三个英文单词的首字母:

指标含义业务意义
R (Recency)最近一次消费距今天数R越小越活跃
F (Frequency)消费频次F越大越忠诚
M (Monetary)消费金额M越大价值越高

核心逻辑:最近买过、经常买、花得多的用户,就是最值钱的用户。


准备数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# 生成模拟用户交易数据
np.random.seed(42)

def generate_user_transactions(n_users=1000, days=365):
"""生成用户交易记录"""
end_date = datetime(2024, 12, 31)

data = []
for user_id in range(1, n_users + 1):
# 随机决定这个用户的活跃度类型
user_type = np.random.choice(
['高频高价值', '高频低价值', '低频高价值', '流失风险', '新用户'],
p=[0.15, 0.25, 0.15, 0.30, 0.15]
)

if user_type == '高频高价值':
n_orders = np.random.randint(20, 50)
avg_amount = np.random.randint(500, 2000)
elif user_type == '高频低价值':
n_orders = np.random.randint(15, 40)
avg_amount = np.random.randint(50, 200)
elif user_type == '低频高价值':
n_orders = np.random.randint(3, 8)
avg_amount = np.random.randint(1000, 5000)
elif user_type == '流失风险':
n_orders = np.random.randint(1, 5)
avg_amount = np.random.randint(100, 800)
else: # 新用户
n_orders = np.random.randint(1, 3)
avg_amount = np.random.randint(200, 1000)

# 生成订单日期(集中在不同时间段)
if user_type == '流失风险':
# 流失用户:订单都在早期
dates = pd.date_range(end=end_date - timedelta(days=180), periods=n_orders, freq='D')
elif user_type == '新用户':
# 新用户:订单都在近期
dates = pd.date_range(end=end_date, periods=n_orders, freq='D')
else:
# 其他用户:分散在全年
dates = pd.date_range(end=end_date, periods=n_orders, freq=f'{365//n_orders}D')

for date in dates[:n_orders]:
data.append({
'用户ID': f'USER{user_id:05d}',
'订单日期': date,
'订单金额': max(10, int(np.random.normal(avg_amount, avg_amount * 0.3))),
'用户类型': user_type
})

return pd.DataFrame(data)

# 生成数据
df = generate_user_transactions(n_users=1000)
df.to_csv('user_transactions.csv', index=False, encoding='utf-8-sig')
print(f"生成了 {len(df)} 条交易记录,涉及 {df['用户ID'].nunique()} 个用户")

计算RFM指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class RFMAnalyzer:
def __init__(self, data_path, analysis_date=None):
self.df = pd.read_csv(data_path, encoding='utf-8-sig')
self.df['订单日期'] = pd.to_datetime(self.df['订单日期'])
self.analysis_date = analysis_date or self.df['订单日期'].max()
print(f"分析基准日期: {self.analysis_date.strftime('%Y-%m-%d')}")
print(f"共 {self.df['用户ID'].nunique()} 个用户,{len(self.df)} 笔交易")

def calculate_rfm(self):
"""计算每个用户的RFM指标"""
rfm = self.df.groupby('用户ID').agg({
'订单日期': lambda x: (self.analysis_date - x.max()).days, # Recency
'订单金额': ['count', 'sum'] # Frequency, Monetary
}).reset_index()

# 重命名列
rfm.columns = ['用户ID', 'R', 'F', 'M']

# 确保数据类型正确
rfm['R'] = rfm['R'].astype(int)
rfm['F'] = rfm['F'].astype(int)
rfm['M'] = rfm['M'].astype(float)

self.rfm = rfm
return rfm

def rfm_summary(self):
"""RFM指标统计摘要"""
return self.rfm[['R', 'F', 'M']].describe()

RFM评分与分层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def score_rfm(self, r_bins=5, f_bins=5, m_bins=5):
"""
给RFM打分(1-5分)
注意:R越小越好,所以分数要倒序
"""
# 使用分位数进行打分
self.rfm['R_score'] = pd.qcut(self.rfm['R'], q=r_bins, labels=[5,4,3,2,1])
self.rfm['F_score'] = pd.qcut(self.rfm['F'].rank(method='first'), q=f_bins, labels=[1,2,3,4,5])
self.rfm['M_score'] = pd.qcut(self.rfm['M'], q=m_bins, labels=[1,2,3,4,5])

# 转换为数值型
self.rfm['R_score'] = self.rfm['R_score'].astype(int)
self.rfm['F_score'] = self.rfm['F_score'].astype(int)
self.rfm['M_score'] = self.rfm['M_score'].astype(int)

# 综合RFM得分
self.rfm['RFM_score'] = (self.rfm['R_score'].astype(str) +
self.rfm['F_score'].astype(str) +
self.rfm['M_score'].astype(str))

return self.rfm

def segment_users(self):
"""根据RFM进行用户分层"""
def get_segment(row):
r, f, m = row['R_score'], row['F_score'], row['M_score']

# 定义分层规则
if r >= 4 and f >= 4 and m >= 4:
return '重要价值客户'
elif r >= 4 and f >= 4 and m <= 2:
return '重要保持客户'
elif r >= 4 and f <= 2 and m >= 4:
return '重要发展客户'
elif r <= 2 and f >= 4 and m >= 4:
return '重要挽留客户'
elif r >= 4 and f <= 2 and m <= 2:
return '新客户'
elif r <= 2 and f >= 4 and m <= 2:
return '一般保持客户'
elif r <= 2 and f <= 2 and m >= 4:
return '潜力客户'
elif r <= 2 and f <= 2 and m <= 2:
return '流失客户'
else:
return '一般客户'

self.rfm['客户分层'] = self.rfm.apply(get_segment, axis=1)
return self.rfm

可视化分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import matplotlib.pyplot as plt
import seaborn as sns

class RFMVisualizer:
def __init__(self, analyzer):
self.analyzer = analyzer
plt.style.use('seaborn-v0_8')

def plot_rfm_distribution(self):
"""RFM指标分布图"""
fig, axes = plt.subplots(1, 3, figsize=(15, 4))

# R分布
axes[0].hist(self.analyzer.rfm['R'], bins=30, color='skyblue', edgecolor='black')
axes[0].set_title('Recency Distribution\n(Last Purchase Days)', fontsize=12)
axes[0].set_xlabel('Days Since Last Purchase')
axes[0].set_ylabel('Number of Users')
axes[0].axvline(self.analyzer.rfm['R'].mean(), color='red', linestyle='--',
label=f'Mean: {self.analyzer.rfm["R"].mean():.0f}')
axes[0].legend()

# F分布
axes[1].hist(self.analyzer.rfm['F'], bins=30, color='lightgreen', edgecolor='black')
axes[1].set_title('Frequency Distribution\n(Purchase Count)', fontsize=12)
axes[1].set_xlabel('Number of Purchases')
axes[1].set_ylabel('Number of Users')
axes[1].axvline(self.analyzer.rfm['F'].mean(), color='red', linestyle='--',
label=f'Mean: {self.analyzer.rfm["F"].mean():.1f}')
axes[1].legend()

# M分布
axes[2].hist(self.analyzer.rfm['M'], bins=30, color='salmon', edgecolor='black')
axes[2].set_title('Monetary Distribution\n(Total Spent)', fontsize=12)
axes[2].set_xlabel('Total Amount')
axes[2].set_ylabel('Number of Users')
axes[2].axvline(self.analyzer.rfm['M'].mean(), color='red', linestyle='--',
label=f'Mean: ¥{self.analyzer.rfm["M"].mean():.0f}')
axes[2].legend()

plt.tight_layout()
plt.savefig('rfm_distribution.png', dpi=300, bbox_inches='tight')
plt.show()

def plot_segment_pie(self):
"""客户分层饼图"""
segment_counts = self.analyzer.rfm['客户分层'].value_counts()

colors = plt.cm.Set3(np.linspace(0, 1, len(segment_counts)))

plt.figure(figsize=(10, 8))
wedges, texts, autotexts = plt.pie(
segment_counts,
labels=segment_counts.index,
autopct='%1.1f%%',
colors=colors,
startangle=90
)

plt.title('User Segmentation Distribution', fontsize=16, fontweight='bold')
plt.axis('equal')
plt.savefig('segment_pie.png', dpi=300, bbox_inches='tight')
plt.show()

return segment_counts

def plot_segment_comparison(self):
"""各分层对比柱状图"""
segment_stats = self.analyzer.rfm.groupby('客户分层').agg({
'R': 'mean',
'F': 'mean',
'M': 'mean',
'用户ID': 'count'
}).rename(columns={'用户ID': '用户数'}).round(2)

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 用户数
axes[0,0].barh(segment_stats.index, segment_stats['用户数'], color='steelblue')
axes[0,0].set_title('User Count by Segment', fontsize=12)
axes[0,0].set_xlabel('Number of Users')

# 平均R值
axes[0,1].barh(segment_stats.index, segment_stats['R'], color='coral')
axes[0,1].set_title('Average Recency by Segment', fontsize=12)
axes[0,1].set_xlabel('Days Since Last Purchase')

# 平均F值
axes[1,0].barh(segment_stats.index, segment_stats['F'], color='lightgreen')
axes[1,0].set_title('Average Frequency by Segment', fontsize=12)
axes[1,0].set_xlabel('Average Purchase Count')

# 平均M值
axes[1,1].barh(segment_stats.index, segment_stats['M'], color='gold')
axes[1,1].set_title('Average Monetary by Segment', fontsize=12)
axes[1,1].set_xlabel('Average Total Spent')

plt.tight_layout()
plt.savefig('segment_comparison.png', dpi=300, bbox_inches='tight')
plt.show()

return segment_stats

运营策略建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
def generate_strategy_report(analyzer):
"""生成运营策略报告"""

strategies = {
'重要价值客户': {
'特征': '最近购买、高频、高消费',
'占比': '',
'策略': [
'VIP专属服务,提供1对1客服',
'新品优先体验权',
'积分翻倍、生日特权',
'邀请成为品牌大使'
]
},
'重要保持客户': {
'特征': '最近购买、高频、但消费低',
'占比': '',
'策略': [
'推送高客单价商品',
'满减券刺激消费升级',
'组合套餐推荐',
'会员等级激励'
]
},
'重要发展客户': {
'特征': '最近购买、消费高、但频次低',
'占比': '',
'策略': [
'定期复购提醒',
'订阅制服务推荐',
'个性化推荐算法',
'限时优惠促活'
]
},
'重要挽留客户': {
'特征': '曾经高频高消费,但很久没来了',
'占比': '',
'策略': [
'大额优惠券召回',
'短信/电话关怀',
'老客专属活动',
'了解流失原因'
]
},
'新客户': {
'特征': '最近才来,消费少',
'占比': '',
'策略': [
'新手引导教程',
'首单优惠',
'新人专享礼包',
'快速建立信任'
]
},
'一般保持客户': {
'特征': '以前常来但消费低,现在不来了',
'占比': '',
'策略': [
'低成本触达(邮件/推送)',
'小额优惠券',
'随缘维护'
]
},
'潜力客户': {
'特征': '消费高但很久没来,且次数少',
'占比': '',
'策略': [
'分析首次购买体验',
'针对性改进产品',
'定向广告投放'
]
},
'流失客户': {
'特征': '很久没来、消费少、次数少',
'占比': '',
'策略': [
'放弃治疗,减少投入',
'极低成本的偶尔触达',
'资源投入到其他群体'
]
}
}

# 计算各层占比
segment_counts = analyzer.rfm['客户分层'].value_counts()
total_users = len(analyzer.rfm)

print("="*60)
print("【RFM用户分层运营策略报告】")
print("="*60)

for segment, info in strategies.items():
count = segment_counts.get(segment, 0)
percentage = count / total_users * 100
info['占比'] = f"{percentage:.1f}%"

print(f"\n📊 {segment} ({count}人, {percentage:.1f}%)")
print(f" 特征: {info['特征']}")
print(f" 策略:")
for i, strategy in enumerate(info['策略'], 1):
print(f" {i}. {strategy}")

return strategies

主程序运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def main():
"""主程序"""
print("="*60)
print("RFM用户行为分析系统")
print("="*60)

# 1. 初始化分析器
print("\n[1/5] 加载数据...")
analyzer = RFMAnalyzer('user_transactions.csv')

# 2. 计算RFM
print("[2/5] 计算RFM指标...")
rfm = analyzer.calculate_rfm()
print("\nRFM指标统计:")
print(analyzer.rfm_summary())

# 3. 评分与分层
print("\n[3/5] RFM评分与用户分层...")
analyzer.score_rfm()
analyzer.segment_users()
print("\n各分层人数:")
print(analyzer.rfm['客户分层'].value_counts())

# 4. 可视化
print("\n[4/5] 生成可视化图表...")
visualizer = RFMVisualizer(analyzer)
visualizer.plot_rfm_distribution()
visualizer.plot_segment_pie()
visualizer.plot_segment_comparison()
print(" ✓ 图表已保存")

# 5. 策略报告
print("\n[5/5] 生成运营策略...")
generate_strategy_report(analyzer)

# 导出结果
output_file = 'rfm_analysis_result.csv'
analyzer.rfm.to_csv(output_file, index=False, encoding='utf-8-sig')
print(f"\n ✓ 详细结果已保存: {output_file}")

print("\n" + "="*60)
print("分析完成!")
print("="*60)

if __name__ == "__main__":
main()

项目总结

学到的技能

  • ✅ RFM模型的原理与应用
  • ✅ 用户分层的量化方法
  • ✅ 基于数据的运营决策
  • ✅ 多维度数据可视化

RFM的价值

  1. 精准营销:钱花在刀刃上
  2. 资源优化:识别最有价值的用户
  3. 策略差异化:不同用户不同对待
  4. 效果可衡量:分层后追踪转化率

扩展方向

  • 结合机器学习预测用户LTV
  • 构建用户流失预警模型
  • A/B测试验证策略效果
  • 实时RFM看板开发

进阶:用户生命周期价值(LTV)计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 计算用户LTV(Lifetime Value)
import pandas as pd
import numpy as np

# 假设已有订单数据
ltv = orders.groupby('user_id').agg(
first_order=('order_date', 'min'),
last_order=('order_date', 'max'),
total_spend=('amount', 'sum'),
order_count=('order_id', 'count')
)

# 生命周期天数
ltv['lifetime_days'] = (ltv['last_order'] - ltv['first_order']).dt.days + 1

# 平均购买间隔
ltv['avg_interval'] = ltv['lifetime_days'] / ltv['order_count']

# LTV预测(简单法:总消费 / 生命周期 × 365)
ltv['predicted_ltv'] = ltv['total_spend'] / ltv['lifetime_days'] * 365

# LTV分布
print(ltv['predicted_ltv'].describe())
print(f"\n高LTV用户(>1000): {(ltv['predicted_ltv'] > 1000).sum()}")

留存分析详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 计算N日留存率
def retention_analysis(df, user_col='user_id', date_col='date'):
df[date_col] = pd.to_datetime(df[date_col])
df['cohort'] = df.groupby(user_col)[date_col].transform('min').dt.to_period('M')
df['order_period'] = df[date_col].dt.to_period('M')

cohort_data = df.groupby(['cohort', 'order_period'])[user_col].nunique().reset_index()
cohort_data = cohort_data.pivot(index='cohort', columns='order_period', values=user_col)

# 留存率(除以首月用户数)
retention = cohort_data.divide(cohort_data.iloc[:, 0], axis=0) * 100
return retention.round(1)

retention = retention_analysis(orders)
print(retention)

避坑指南

❌ 坑1:RFM分群不均衡

1
2
3
4
5
# qcut在某些情况下分群不均
rfm['R_score'] = pd.qcut(rfm['R'], 5, labels=[5,4,3,2,1], duplicates='drop')

# 更好的方式:自定义分界点
rfm['R_score'] = pd.cut(rfm['R'], bins=[0, 7, 14, 30, 90, 365], labels=[5,4,3,2,1])

RFM模型实战代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import pandas as pd
import numpy as np

# 模拟用户订单数据
np.random.seed(42)
n_orders = 50000
n_users = 5000

orders = pd.DataFrame({
'user_id': np.random.randint(1, n_users+1, n_orders),
'order_date': pd.date_range('2024-01-01', periods=n_orders, freq='10min'),
'amount': np.random.exponential(200, n_orders).round(2)
})

# RFM计算
today = orders['order_date'].max() + pd.Timedelta(days=1)

rfm = orders.groupby('user_id').agg(
Recency=('order_date', lambda x: (today - x.max()).days),
Frequency=('order_id', 'count'),
Monetary=('amount', 'sum')
)

# RFM评分(5分制)
rfm['R_score'] = pd.qcut(rfm['Recency'], 5, labels=[5,4,3,2,1], duplicates='drop').astype(int)
rfm['F_score'] = pd.qcut(rfm['Frequency'].rank(method='first'), 5, labels=[1,2,3,4,5], duplicates='drop').astype(int)
rfm['M_score'] = pd.qcut(rfm['Monetary'].rank(method='first'), 5, labels=[1,2,3,4,5], duplicates='drop').astype(int)

# RFM总分
rfm['RFM_score'] = rfm['R_score'] * 100 + rfm['F_score'] * 10 + rfm['M_score']

# 用户分类
def rfm_segment(row):
if row['R_score'] >= 4 and row['F_score'] >= 4 and row['M_score'] >= 4:
return '重要价值用户'
elif row['R_score'] >= 4 and row['F_score'] >= 4:
return '重要发展用户'
elif row['R_score'] >= 4 and row['M_score'] >= 4:
return '重要保持用户'
elif row['R_score'] >= 4:
return '一般发展用户'
elif row['F_score'] >= 4 and row['M_score'] >= 4:
return '重要挽留用户'
else:
return '一般用户'

rfm['segment'] = rfm.apply(rfm_segment, axis=1)

# 输出分群结果
print("=== RFM用户分群结果 ===")
segment_summary = rfm.groupby('segment').agg(
user_count=('RFM_score', 'count'),
avg_monetary=('Monetary', 'mean'),
avg_frequency=('Frequency', 'mean')
).round(1)
print(segment_summary)

留存分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 计算N日留存率
def calculate_retention(orders, retention_days=[1, 7, 14, 30]):
'''计算用户留存率'''
first_order = orders.groupby('user_id')['order_date'].min().reset_index()
first_order.columns = ['user_id', 'first_date']

results = {}
for day in retention_days:
target_date = first_order['first_date'] + pd.Timedelta(days=day)
# 检查这些用户在目标日之后是否有订单
retained = orders.groupby('user_id')['order_date'].max()
# 简化计算
rate = len(orders[orders['order_date'] >= first_order.set_index('user_id')['first_date'] + pd.Timedelta(days=day)]['user_id'].unique()) / len(first_order)
results[f'D+{day}'] = rate

return results

retention = calculate_retention(orders)
print("用户留存率:")
for k, v in retention.items():
print(f" {k}: {v*100:.1f}%")

用户画像构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 完整的用户画像构建流程
import pandas as pd
import numpy as np

def build_user_profile(orders, users):
'''构建用户画像'''
# 1. 基本信息维度
profile = users[['user_id', 'age', 'gender', 'city']].copy()

# 2. 消费维度
consumption = orders.groupby('user_id').agg(
total_spend=('amount', 'sum'),
avg_spend=('amount', 'mean'),
max_spend=('amount', 'max'),
order_count=('order_id', 'count'),
first_order=('order_date', 'min'),
last_order=('order_date', 'max')
)

# 3. RFM维度
today = orders['order_date'].max() + pd.Timedelta(days=1)
consumption['recency'] = (today - consumption['last_order']).dt.days
consumption['lifetime'] = (consumption['last_order'] - consumption['first_order']).dt.days + 1

# 4. 合并画像
profile = profile.merge(consumption, on='user_id', how='left')

# 5. 添加标签
profile['spending_level'] = pd.qcut(profile['total_spend'].fillna(0),
4, labels=['低', '中低', '中高', '高'])
profile['activity_level'] = pd.qcut(profile['recency'].fillna(999),
4, labels=['活跃', '一般', '沉默', '流失'])

return profile

# 输出画像摘要
profile = build_user_profile(orders, users)
print("=== 用户画像摘要 ===")
print(f"总用户数: {len(profile)}")
print(f"
消费等级分布:")
print(profile['spending_level'].value_counts())
print(f"
活跃度分布:")
print(profile['activity_level'].value_counts())

完整RFM分析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import pandas as pd
import numpy as np

np.random.seed(42)
n = 30000
orders = pd.DataFrame({
'user_id': np.random.randint(1, 5001, n),
'order_date': pd.date_range('2024-01-01', periods=n, freq='30min'),
'amount': np.random.exponential(200, n).round(2),
'product': np.random.choice(['A', 'B', 'C', 'D'], n)
})

# ===== RFM计算 =====
today = orders['order_date'].max() + pd.Timedelta(days=1)

rfm = orders.groupby('user_id').agg(
R=('order_date', lambda x: (today - x.max()).days),
F=('order_id', 'count'),
M=('amount', 'sum')
)

# ===== RFM评分 =====
rfm['R_score'] = pd.qcut(rfm['R'], 5, labels=[5,4,3,2,1], duplicates='drop').astype(int)
rfm['F_score'] = pd.qcut(rfm['F'].rank(method='first'), 5, labels=[1,2,3,4,5], duplicates='drop').astype(int)
rfm['M_score'] = pd.qcut(rfm['M'].rank(method='first'), 5, labels=[1,2,3,4,5], duplicates='drop').astype(int)

rfm['RFM'] = rfm['R_score'] * 100 + rfm['F_score'] * 10 + rfm['M_score']

# ===== 用户分类 =====
def classify(r, f, m):
if r >= 4 and f >= 4 and m >= 4: return '重要价值'
if r >= 4 and f >= 4: return '重要发展'
if r >= 4 and m >= 4: return '重要保持'
if f >= 4 and m >= 4: return '重要挽留'
if r >= 4: return '一般发展'
if m >= 4: return '一般保持'
return '一般用户'

rfm['segment'] = rfm.apply(lambda x: classify(x['R_score'], x['F_score'], x['M_score']), axis=1)

# ===== 输出结果 =====
print('=== RFM用户分群结果 ===')
seg = rfm.groupby('segment').agg(
人数=('RFM', 'count'),
平均消费=('M', 'mean'),
平均频次=('F', 'mean')
).round(1)
seg['占比'] = (seg['人数'] / seg['人数'].sum() * 100).round(1)
print(seg)

# ===== 可视化 =====
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
seg['人数'].plot.pie(ax=axes[0], autopct='%1.1f%%', title='用户分群占比')
rfm.boxplot(column='M', by='segment', ax=axes[1])
axes[1].set_title('各群体消费金额分布')
plt.suptitle('')
plt.tight_layout()
plt.savefig('rfm_analysis.png', dpi=150)

用户行为分析进阶:同期群分析

同期群分析(Cohort Analysis)是用户行为分析的核心方法,它跟踪同一时期加入的用户群体的行为变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False

np.random.seed(42)

# 模拟用户行为数据
n_users = 2000
n_orders = 50000

users = pd.DataFrame({
'user_id': range(1, n_users + 1),
'signup_date': pd.date_range('2024-01-01', periods=n_users, freq='6H'),
})

orders = pd.DataFrame({
'user_id': np.random.randint(1, n_users + 1, n_orders),
'order_date': pd.date_range('2024-01-01', periods=n_orders, freq='10min'),
'amount': np.random.exponential(200, n_orders).round(2)
})

# ===== 同期群分析 =====
# 1. 找到每个用户的注册月份
signup_month = users.set_index('user_id')['signup_date'].dt.to_period('M')

# 2. 计算每笔订单的月份和注册月份
orders['order_month'] = orders['order_date'].dt.to_period('M')
orders['signup_month'] = orders['user_id'].map(signup_month)

# 3. 计算订单月份与注册月份的差(第N个月)
orders['cohort_index'] = (orders['order_month'] - orders['signup_month']).astype(int)

# 4. 计算留存用户数
cohort_data = orders.groupby(['signup_month', 'cohort_index'])['user_id'].nunique().reset_index()
cohort_data.columns = ['signup_month', 'cohort_index', 'user_count']

# 5. 计算留存率
cohort_pivot = cohort_data.pivot(index='signup_month', columns='cohort_index', values='user_count')
cohort_size = cohort_pivot.iloc[:, 0]
retention = cohort_pivot.divide(cohort_size, axis=0) * 100

# 6. 可视化留存率热力图
plt.figure(figsize=(14, 8))
sns.heatmap(retention.iloc[:12, :12], annot=True, fmt='.1f',
cmap='RdYlGn', vmin=0, vmax=100)
plt.title('用户留存率热力图(%)')
plt.xlabel('注册后第N个月')
plt.ylabel('注册月份')
plt.tight_layout()
plt.savefig('cohort_retention.png', dpi=150)
plt.show()

用户行为漏斗分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 漏斗分析:从访问到购买的转化率
funnel_data = {
'步骤': ['访问首页', '浏览商品', '加入购物车', '提交订单', '完成支付'],
'用户数': [100000, 65000, 28000, 18000, 15000]
}
funnel = pd.DataFrame(funnel_data)
funnel['转化率'] = (funnel['用户数'] / funnel['用户数'].iloc[0] * 100).round(1)
funnel['步骤转化率'] = (funnel['用户数'] / funnel['用户数'].shift(1) * 100).fillna(100).round(1)

print('=== 转化漏斗 ===')
print(funnel)

# 找出流失最严重的步骤
worst_step = funnel.loc[funnel['步骤转化率'].idxmin(), '步骤']
print(f'\n⚠️ 流失最严重的步骤: {worst_step}')

用户生命周期价值(LTV)预测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# LTV计算
ltv = orders.groupby('user_id').agg(
total_spend=('amount', 'sum'),
first_order=('order_date', 'min'),
last_order=('order_date', 'max'),
order_count=('order_id', 'count')
)

ltv['lifetime_days'] = (ltv['last_order'] - ltv['first_order']).dt.days + 1
ltv['avg_daily_spend'] = ltv['total_spend'] / ltv['lifetime_days']
ltv['predicted_annual'] = ltv['avg_daily_spend'] * 365

# LTV分群
ltv['ltv_tier'] = pd.qcut(ltv['predicted_annual'], 4, labels=['低', '中低', '中高', '高'])

print('=== LTV分群 ===')
print(ltv.groupby('ltv_tier')['predicted_annual'].agg(['mean', 'count']).round(0))
print(f'\n高LTV用户平均年消费: ¥{ltv[ltv["ltv_tier"]=="高"]["predicted_annual"].mean():,.0f}')
print(f'低LTV用户平均年消费: ¥{ltv[ltv["ltv_tier"]=="低"]["predicted_annual"].mean():,.0f}')

下节预告

下一项目是库存分析与预测,学习如何用时间序列方法预测商品销量,优化库存管理。

👉 继续阅读:项目3-库存分析与销量预测


💬 加入学习交流群

扫码加入Python学习交流群,和数千名同学一起进步:

👉 点击加入交流群

群里不定期分享:

  • 数据分析实战案例
  • Python学习资料
  • 求职面试经验
  • 行业最新动态

推荐:AI Python数据分析实战营

🎁 限时福利:送《利用Python进行数据分析》实体书

👉 点击了解详情


课程导航

上一篇: 项目1-销售数据分析报表自动化

下一篇: 项目3-库存分析与销量预测


PS:RFM模型是用户运营的核武器。掌握它,你就掌握了精细化运营的核心能力。



📚 推荐教材

主教材《Excel+Python 飞速搞定数据分析与处理(图灵出品)》

💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!