基于Python实现对电商客户数据进行RFM分析

1. 数据集准备

使用 Kaggle 公开电商数据集,这里为了方便找了一个 Olist 巴西电商数据(可能需要代理访问),下载数据后通过Python 加载数据:

1
2
3
4
5
6
7
8
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 加载数据集(需提前下载)
orders = pd.read_csv('olist_orders_dataset.csv')
order_items = pd.read_csv('olist_order_items_dataset.csv')
products = pd.read_csv('olist_products_dataset.csv')

2. 数据清洗与 RFM 计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 合并数据集时保持日期为Timestamp类型
merged_data = pd.merge(
orders[['order_id', 'customer_id', 'order_purchase_timestamp']],
order_items[['order_id', 'product_id', 'price']],
on='order_id'
)
merged_data = pd.merge(merged_data, products[['product_id', 'product_category_name']], on='product_id')

# 修正日期处理(不转换为datetime.date)
merged_data['order_date'] = pd.to_datetime(merged_data['order_purchase_timestamp']).dt.tz_localize(None) # 移除时区信息
merged_data['month'] = merged_data['order_date'].dt.to_period('M')

# 计算snapshot_date时保持Timestamp类型
snapshot_date = merged_data['order_date'].max() + pd.DateOffset(days=1)

# RFM计算(使用pd.Timestamp类型)
rfm = merged_data.groupby('customer_id').agg({
'order_date': lambda x: (snapshot_date - x.max()).days, # 计算距离最新订单日期次日的天数
'order_id': 'nunique', # 消费频率
'price': 'sum' # 消费总金额
}).reset_index()
rfm.columns = ['客户_id', 'recency', 'frequency', 'monetary']

为何需要 snapshot_date

在 RFM 分析中:

  • Recency(最近一次消费时间)定义为:
    客户最后一次购买日期到 snapshot_date 的天数
  • 若直接使用数据中的最新订单日期作为 snapshot_date,可能导致以下问题:
    • 对于在最新日期下单的客户,Recency = 0(但业务上可能需要最小间隔为 1 天)。
    • 若数据仅包含历史记录(如截止到 2024-12-31),而实际分析时间是 2025-01-01,需手动指定未来的 snapshot_date

通过将 snapshot_date 设为 最新订单日期的次日

  • 确保所有客户的最后一次消费均在 snapshot_date 之前,避免 Recency 为 0。

3. 关键指标分析

通过分析 merged_data 中的产品销售数据,找出 销售额最高的前5个商品品类,并生成可视化图表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 基础指标
print(f"总销售额:{merged_data['price'].sum():.2f}")
print(f"月均增长率:{merged_data.groupby('month')['price'].sum().pct_change().mean():.2%}")

# 设置中文字体(兼容Windows、Mac、Linux)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei'] # 指定常用中文字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题

# 计算前5品类
top_categories = merged_data.groupby('product_category_name')['price'].sum().nlargest(5)

# 绘制图表
plt.figure(figsize=(10, 6))
sns.barplot(x=top_categories.values, y=top_categories.index, palette='viridis')

# 设置中文标题和标签
plt.title('销售额最高的前5个产品类别', fontsize=14, pad=20) # pad调整标题与图表的间距
plt.xlabel('销售额', fontsize=12) # X轴标签
plt.ylabel('产品类别', fontsize=12) # Y轴标签

# 调整布局并保存
plt.tight_layout() # 自动优化布局
plt.savefig('top_categories.png', dpi=300, bbox_inches='tight')
plt.close()

生成条形图如下:

top_categories.png

4. RFM 用户分层

通过对客户的 Recency(近度)Frequency(频度)Monetary(金额) 指标进行评分,将客户划分为 High Value(高价值)Mid Value(中价值)Low Value(低价值) 三个群体。

1
2
3
4
5
6
7
8
9
10
11
12
13
# RFM评分
rfm['R_rank'] = pd.qcut(rfm['recency'], 5, labels=False, duplicates='drop') + 1
rfm['F_rank'] = pd.cut(rfm['frequency'], 5, labels=False, include_lowest=True) + 1
rfm['M_rank'] = pd.qcut(rfm['monetary'], 5, labels=False, duplicates='drop') + 1

# 用户分群
rfm['RFM_Score'] = rfm[['R_rank', 'F_rank', 'M_rank']].sum(axis=1)
rfm['Segment'] = 'Low Value'
rfm.loc[rfm['RFM_Score'] > 9, 'Segment'] = 'High Value'
rfm.loc[rfm['RFM_Score'].between(6,9), 'Segment'] = 'Mid Value'

# 保存结果
rfm.to_csv('rfm_analysis.csv', index=False, encoding='utf-8-sig')
  • pd.qcut()
    将每个 RFM 指标的值按 分位数 划分为 5 个等宽的区间(每区间占 20% 的数据量)。

    • labels=False:返回区间编号(0~4),而非区间标签。
    • duplicates='drop':如果数据有重复值导致无法划分唯一分位数,自动删除重复分位点。
    • include_lowest=True :确保最小值包含在第一个区间。
    • +1:将区间编号从 0-4 调整为 1-5,使评分更直观(1分最低,5分最高)。
  • 评分逻辑

    • Recency(R_rank)recency 值越小(客户最近购买),得分越高。
      (例如:recency 最小的 20% 客户得 5 分,最大的 20% 得 1 分)
    • Frequency(F_rank)Monetary(M_rank):值越大(消费次数/金额越多),得分越高。
  • sum(axis=1)

    将每个客户的 R、F、M 三个得分相加,得到 RFM 总分

    • 总分范围:

      最小值 = 1 + 1 + 1 = 3

      最大值 = 5 + 5 + 5 = 15

  • 客户分群逻辑

    • High Value(高价值):总分 >9 → 至少有两个维度得高分(如 5+5+1=11)。
    • Mid Value(中价值):总分 6~9 → 各维度中等或部分维度较高。
    • Low Value(低价值):总分 <6 → 各维度均较低。

最终得到通过RFM对客户进行分析后的数据:

image.png