在Pandas中计算接触特定课程后剩余的唯一用户数
高效处理千万级数据:计算课程接触后的学生流失与剩余统计
首先得说,1300万行用for循环肯定行不通——Python的循环在大数据量下效率太低了,得用pandas的向量化分组操作来解决,这能把速度提升几个数量级。下面直接给你可运行的方案,分两种常见的业务场景:
场景1:接触课程后完全不再有任何行为(即最后一次记录就是该课程)
这种场景下,流失学生指的是:某个学生的最后一次平台行为就是接触该课程,之后再也没出现过。
代码实现
import pandas as pd # 第一步:确保timestamp是datetime类型(如果不是的话先转换) df['timestamp'] = pd.to_datetime(df['timestamp']) # 第二步:按用户和时间戳排序,保证行为的时间顺序正确 df_sorted = df.sort_values(by=['userid', 'timestamp']).reset_index(drop=True) # 第三步:标记每个用户的最后一条行为记录 df_sorted['is_last_behavior'] = df_sorted.groupby('userid')['timestamp'].transform( lambda x: x == x.max() ) # 第四步:统计核心指标 # 1. 每个课程的总接触学生数(去重) total_users_per_lecture = df_sorted.groupby('lecture_id')['userid'].nunique() # 2. 每个课程对应的流失学生数(最后一次行为是该课程的学生) churned_users_per_lecture = df_sorted[df_sorted['is_last_behavior']]['lecture_id'].value_counts() # 第五步:合并成统计DataFrame,填充缺失值(有些课程没有流失学生) stats_df = pd.DataFrame({ 'total_users': total_users_per_lecture, 'churned_users': churned_users_per_lecture.reindex(total_users_per_lecture.index, fill_value=0) }) # 计算剩余学生数 stats_df['remaining_users'] = stats_df['total_users'] - stats_df['churned_users'] # 重置索引,让lecture_id成为普通列 stats_df = stats_df.reset_index()
场景2:接触课程后不再接触其他课程(但可能重复刷该课程)
如果你的“流失”定义是:学生接触某课程后,再也没接触过其他课程(但可以继续刷该课程),那需要调整逻辑,关注学生的「首次接触课程顺序」:
代码实现
import pandas as pd # 预处理:转换时间戳+排序 df['timestamp'] = pd.to_datetime(df['timestamp']) df_sorted = df.sort_values(by=['userid', 'timestamp']) # 获取每个用户首次接触各课程的时间 user_first_contact = df_sorted.groupby(['userid', 'lecture_id'])['timestamp'].min().reset_index() # 对每个用户,按首次接触时间排序,标记最后一个首次接触的课程 user_first_contact_sorted = user_first_contact.sort_values(by=['userid', 'timestamp']) user_first_contact_sorted['is_last_new_lecture'] = user_first_contact_sorted.groupby('userid')['timestamp'].transform( lambda x: x == x.max() ) # 统计指标 total_users = user_first_contact_sorted.groupby('lecture_id')['userid'].nunique() churned_users = user_first_contact_sorted[user_first_contact_sorted['is_last_new_lecture']].groupby('lecture_id')['userid'].nunique() # 合并统计结果 stats_df = pd.DataFrame({ 'total_users': total_users, 'churned_users': churned_users.reindex(total_users.index, fill_value=0) }) stats_df['remaining_users'] = stats_df['total_users'] - stats_df['churned_users'] stats_df = stats_df.reset_index()
为什么这比for循环快?
所有操作都是pandas的向量化分组/变换,底层用C语言实现,避开了Python循环的性能损耗。1300万行三列的数据,只要你的内存足够(大概几百MB以内),几分钟就能跑完,完全不会出现循环卡死的情况。
如果内存实在吃紧,可以考虑用dask.dataframe做分块处理,但上面的方案对大部分普通机器已经够用了。
内容的提问来源于stack exchange,提问作者Chad Coleman
相关产品推荐
相关产品推荐

