Pandas:日期范围求和与重复uID的用户活跃订阅统计问题
高效处理用户活跃频次统计的方案
嘿,你的问题我太懂了——处理大数据集的时候,循环和重复ID确实是坑!原代码的问题主要是效率低(iterrows循环在大数据下慢到离谱),而且没法正确处理同一个uID的多条订阅记录,我给你两个高效的解决方案,都是用pandas的向量化操作,比循环快得多,还能完美处理重复ID的情况:
方案一:用apply+explode快速展开日期范围(易读性高)
这个方法代码简洁,容易理解,适合中等规模的数据集:
步骤1:先统一日期格式
首先把输入的字符串日期转成pandas的datetime类型,方便后续处理:
import pandas as pd # 假设input_df是你的原始输入DataFrame input_df['startdate'] = pd.to_datetime(input_df['startdate'], format='%d-%m-%Y') input_df['stopdate'] = pd.to_datetime(input_df['stopdate'], format='%d-%m-%Y')
步骤2:展开每条订阅的日期范围
为每条订阅记录生成它覆盖的所有日期,然后把日期序列展开成单独的行:
# 为每条订阅生成对应的日期序列 input_df['date_range'] = input_df.apply( lambda row: pd.date_range(row['startdate'], row['stopdate'], freq='D'), axis=1 ) # 把日期序列展开,每条日期对应一条订阅记录 expanded_df = input_df.explode('date_range')
步骤3:统计并转成目标格式
统计每个日期、每个uID的活跃订阅数,再转成你需要的宽表格式:
# 按日期和uID分组统计活跃数 count_df = expanded_df.groupby(['date_range', 'uID']).size().reset_index(name='count') # 转成宽表,列是uID,行是日期,缺失值填充0 result_df = count_df.pivot(index='date_range', columns='uID', values='count').fillna(0).astype(int) # 把列名改成你要的"uID:X"格式 result_df.columns = [f'uID:{col}' for col in result_df.columns] # 重置索引,把日期转成字符串格式 result_df = result_df.reset_index().rename(columns={'date_range': 'Date'}) result_df['Date'] = result_df['Date'].dt.strftime('%d-%m-%Y')
方案二:用numpy广播优化(超大数据集首选)
如果你的数据集特别大(百万级以上),apply可能还是有点慢,用numpy的向量化操作可以进一步提升效率:
import pandas as pd import numpy as np # 先统一日期格式(和方案一一样) input_df['startdate'] = pd.to_datetime(input_df['startdate'], format='%d-%m-%Y') input_df['stopdate'] = pd.to_datetime(input_df['stopdate'], format='%d-%m-%Y') # 计算每条订阅覆盖的天数 start_dates = input_df['startdate'].values stop_dates = input_df['stopdate'].values days = (stop_dates - start_dates).astype('timedelta64[D]').astype(int) + 1 # 生成所有订阅对应的日期和uID dates = np.concatenate([pd.date_range(s, e, freq='D') for s, e in zip(start_dates, stop_dates)]) uids = np.repeat(input_df['uID'].values, days) # 构建展开后的DataFrame expanded_df = pd.DataFrame({'Date': dates, 'uID': uids}) # 统计并转成目标格式(和方案一后续步骤一致) count_df = expanded_df.groupby(['Date', 'uID']).size().reset_index(name='count') result_df = count_df.pivot(index='Date', columns='uID', values='count').fillna(0).astype(int) result_df.columns = [f'uID:{col}' for col in result_df.columns] result_df = result_df.reset_index() result_df['Date'] = result_df['Date'].dt.strftime('%d-%m-%Y')
为什么这两个方案比原代码好?
- 效率高:完全避免了
iterrows循环,用pandas/numpy的向量化操作,处理大数据集的速度能提升几十甚至上百倍 - 自动处理重复uID:不管同一个uID有多少条订阅记录,分组统计都会自动累加当日的活跃数(比如uID:1在15-01-2019的两条订阅会被统计为2)
- 格式完全匹配需求:最终输出的DataFrame和你期望的结构完全一致,日期格式也符合要求
内容的提问来源于stack exchange,提问作者Jelmer
相关产品推荐
相关产品推荐

