使用Pandas计算各维度过往项目成功数及成功率(排除当前项目)
按多维度计算过往项目成功指标
原始数据
import pandas as pd data = { 'project_id': [11, 22, 31, 45, 52, 61], 'user_id': [10001, 10002, 10001, 10003, 10002, 10004], 'customer_id': [20001, 20002, 20001, 20001, 20003, 20002], 'project_start_date': ['2023-01-01', '2023-02-01', '2023-03-01', '2023-04-01', '2023-05-01', '2023-06-01'], 'project_end_date': ['2023-01-31', '2023-02-28', '2023-03-31', '2023-04-30', '2023-05-31', '2023-05-10'], 'product_name': ['Product A', 'Product B', 'Product C', 'Product A', 'Product B', 'Product C'], 'success_flag': [0, 1, 1, 1, 0, 1] } df = pd.DataFrame(data)
计算需求
- a)按
user_id分组计算两个指标:past_project_success_count: 统计该用户当前项目之前的成功项目(success_flag=1)数量past_project_success_ratio: 该用户过往成功项目数 / 过往项目总数
- b)按
customer_id分组计算上述相同指标 - c)按
user_id与customer_id的组合分组计算上述相同指标 - d)所有计算仅包含当前项目之前的过往项目,每组首条记录的指标值为0
用户尝试的错误代码
t1 = t1.sort_values('project_end_date') t1['user_past_project_success_count'] = t1.groupby('user_id')['success_flag'].cumsum() / t1.groupby('user_id').cumcount() t1['user_past_project_success_count'] = t1.groupby('customer_id')['success_flag'].cumsum() / t1.groupby('customer_id').cumcount() t1['user_past_project_success_count'] = t1.groupby(['user_id','customer_id'])['success_flag'].cumsum() / t1.groupby(['user_id','customer_id']).cumcount()
问题分析
这段代码存在三个核心问题:
- 重复赋值同一个列名,导致前面的计算结果被覆盖;
cumsum()会包含当前行的success_flag,不符合“仅统计过往项目”的要求;cumcount()从0开始计数,首行会出现除以0的错误,且无法直接得到过往项目总数。
解决方案
先按项目结束时间排序,确保时间顺序正确,再通过分组计算并偏移累计值来排除当前项目,最后处理除以0的情况:
# 按项目结束日期排序,保证过往项目的时间顺序 df_sorted = df.sort_values('project_end_date').reset_index(drop=True) # 定义通用计算函数,处理单个分组的过往指标 def compute_past_metrics(group): # 过往成功数:累计成功数下移一行,首行填充0 group['past_success_count'] = group['success_flag'].cumsum().shift(1).fillna(0).astype(int) # 过往项目总数:分组内累计行数(从0开始),正好对应当前项目之前的项目数 group['past_total_count'] = group.cumcount() # 成功率:避免除以0,总数为0时返回0 group['past_success_ratio'] = group.apply( lambda row: row['past_success_count'] / row['past_total_count'] if row['past_total_count'] != 0 else 0, axis=1 ) return group # 按user_id计算指标并重命名列 df_sorted = df_sorted.groupby('user_id', group_keys=False).apply(compute_past_metrics) df_sorted.rename(columns={ 'past_success_count': 'user_past_success_count', 'past_total_count': 'user_past_total_count', 'past_success_ratio': 'user_past_success_ratio' }, inplace=True) # 按customer_id计算指标并重命名列 df_sorted = df_sorted.groupby('customer_id', group_keys=False).apply(compute_past_metrics) df_sorted.rename(columns={ 'past_success_count': 'customer_past_success_count', 'past_total_count': 'customer_past_total_count', 'past_success_ratio': 'customer_past_success_ratio' }, inplace=True) # 按user_id+customer_id组合计算指标并重命名列 df_sorted = df_sorted.groupby(['user_id', 'customer_id'], group_keys=False).apply(compute_past_metrics) df_sorted.rename(columns={ 'past_success_count': 'user_customer_past_success_count', 'past_total_count': 'user_customer_past_total_count', 'past_success_ratio': 'user_customer_past_success_ratio' }, inplace=True) # 打印最终结果 print(df_sorted)
关键逻辑说明
- 排序:必须先按
project_end_date排序,否则无法正确判断“过往项目”的时间顺序; - 偏移累计值:
cumsum().shift(1)实现了“仅统计当前项目之前的成功数”,首行shift后为NaN,用fillna(0)填充符合需求; - 过往项目总数:
cumcount()在分组内从0开始计数,正好对应当前项目之前的项目数量(首行0个过往项目,第二行1个,以此类推); - 除以0处理:通过
lambda函数判断,当过往项目总数为0时直接返回0,避免运算错误。
输出结果示例
| project_id | user_id | customer_id | project_start_date | project_end_date | product_name | success_flag | user_past_success_count | user_past_total_count | user_past_success_ratio | customer_past_success_count | customer_past_total_count | customer_past_success_ratio | user_customer_past_success_count | user_customer_past_total_count | user_customer_past_success_ratio |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11 | 10001 | 20001 | 2023-01-01 | 2023-01-31 | Product A | 0 | 0 | 0 | 0.0 | 0 | 0 | 0.0 | 0 | 0 | 0.0 |
| 22 | 10002 | 20002 | 2023-02-01 | 2023-02-28 | Product B | 1 | 0 | 0 | 0.0 | 0 | 0 | 0.0 | 0 | 0 | 0.0 |
| 61 | 10004 | 20002 | 2023-06-01 | 2023-05-10 | Product C | 1 | 0 | 0 | 0.0 | 1 | 1 | 1.0 | 0 | 0 | 0.0 |
| 31 | 10001 | 20001 | 2023-03-01 | 2023-03-31 | Product C | 1 | 0 | 1 | 0.0 | 0 | 1 | 0.0 | 0 | 1 | 0.0 |
| 45 | 10003 | 20001 | 2023-04-01 | 2023-04-30 | Product A | 1 | 0 | 0 | 0.0 | 1 | 2 | 0.5 | 0 | 0 | 0.0 |
| 52 | 10002 | 20003 | 2023-05-01 | 2023-05-31 | Product B | 0 | 1 | 1 | 1.0 | 0 | 0 | 0.0 | 0 | 0 | 0.0 |
内容的提问来源于stack exchange,提问作者The Great
相关产品推荐
相关产品推荐

