You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效统计促销日期前3个月内的用户登录次数(Pandas优化)

优化Pandas列表日期统计性能方案

问题背景

现有如下Pandas DataFrame:

import pandas as pd

df = pd.DataFrame(
{
    'user_id': ['1', '2', '3'],
    'promo_date': ['01012023','01012023','01012023'],
    'logins': [['10242022', '11242022', '04122023'], ['10242022', '04122023'], []]
 }
)

展示结构:

user_id promo_date                          logins
0       1   01012023  [10242022, 11242022, 04122023]
1       2   01012023            [10242022, 04122023]
2       3   01012023                              []

需求是统计每个用户在promo_date前3个月内的登录次数。当前用apply调用自定义函数实现,但数据量大时速度极慢:

from dateutil.relativedelta import relativedelta
from datetime import datetime

EXPECTED_DATE_FORMAT = '%m%d%Y'

def calculate_NTimesLoggedInXMonths(x_months, promo_date_str, login_dates):
    login_count = 0
    promo_date = pd.to_datetime(promo_date_str, format=EXPECTED_DATE_FORMAT)
    x_month_back = promo_date - relativedelta(months=x_months)
    for date in login_dates:
        if x_month_back < pd.to_datetime(date, format=EXPECTED_DATE_FORMAT) < promo_date:
            login_count += 1
    return login_count

# 计算耗时
start = datetime.now()
df[f'NTimesLoggedIn3Months'] = df.apply(
    lambda row: calculate_NTimesLoggedInXMonths(3, row['promo_date'], row['logins']),
    axis=1
)
end = datetime.now()
print("Run time:", end - start)

预期结果:

promo_date                          logins  NTimesLoggedIn3Months
0   01012023  [10242022, 11242022, 04122023]                      2
1   01012023            [10242022, 04122023]                      1
2   01012023                              []                      0    

需要利用Series.dt访问器优化性能,但不知道如何处理logins字段的列表格式。


优化方案:向量化处理替代逐行循环

核心思路是展开列表字段,用Pandas原生向量化操作替代Python循环,充分利用Series.dt的性能优势,步骤如下:

1. 预处理日期字段,计算时间窗口

先把promo_date转成datetime类型,同时计算每个用户的时间窗口起始点(promo_date前3个月):

import pandas as pd
from dateutil.relativedelta import relativedelta

# 原始DataFrame
df = pd.DataFrame(
{
    'user_id': ['1', '2', '3'],
    'promo_date': ['01012023','01012023','01012023'],
    'logins': [['10242022', '11242022', '04122023'], ['10242022', '04122023'], []]
 }
)

# 转换promo_date为datetime
EXPECTED_DATE_FORMAT = '%m%d%Y'
df['promo_date_dt'] = pd.to_datetime(df['promo_date'], format=EXPECTED_DATE_FORMAT)
# 计算前3个月的起始日期
df['window_start'] = df['promo_date_dt'] - relativedelta(months=3)

2. 展开logins列表字段

使用explode方法把列表拆成单独行,每个登录日期对应原用户的信息:

# 展开logins列表,空列表会生成NaN,后续处理
df_exploded = df.explode('logins', ignore_index=True)

3. 转换登录日期并批量判断

用pd.to_datetime批量转换登录日期,再通过Series.dt配合布尔索引判断是否在时间窗口内:

# 转换登录日期为datetime,空值转NaT
df_exploded['login_dt'] = pd.to_datetime(df_exploded['logins'], format=EXPECTED_DATE_FORMAT, errors='coerce')

# 判断登录日期是否在[window_start, promo_date_dt)区间内
df_exploded['in_window'] = (df_exploded['login_dt'] > df_exploded['window_start']) & (df_exploded['login_dt'] < df_exploded['promo_date_dt'])

4. 分组统计并合并回原DataFrame

按user_id分组统计符合条件的次数,再合并到原DataFrame,空列表用户的统计值设为0:

# 分组统计符合条件的登录次数
login_counts = df_exploded.groupby('user_id')['in_window'].sum().astype(int)

# 合并到原DataFrame,填充空值为0
df['NTimesLoggedIn3Months'] = df['user_id'].map(login_counts).fillna(0).astype(int)

完整优化代码

import pandas as pd
from dateutil.relativedelta import relativedelta
from datetime import datetime

# 原始数据
df = pd.DataFrame(
{
    'user_id': ['1', '2', '3'],
    'promo_date': ['01012023','01012023','01012023'],
    'logins': [['10242022', '11242022', '04122023'], ['10242022', '04122023'], []]
 }
)

EXPECTED_DATE_FORMAT = '%m%d%Y'

# 计时对比
start = datetime.now()

# 1. 预处理日期窗口
df['promo_date_dt'] = pd.to_datetime(df['promo_date'], format=EXPECTED_DATE_FORMAT)
df['window_start'] = df['promo_date_dt'] - relativedelta(months=3)

# 2. 展开登录列表
df_exploded = df.explode('logins', ignore_index=True)

# 3. 转换登录日期并判断是否在窗口内
df_exploded['login_dt'] = pd.to_datetime(df_exploded['logins'], format=EXPECTED_DATE_FORMAT, errors='coerce')
df_exploded['in_window'] = (df_exploded['login_dt'] > df_exploded['window_start']) & (df_exploded['login_dt'] < df_exploded['promo_date_dt'])

# 4. 分组统计并合并
login_counts = df_exploded.groupby('user_id')['in_window'].sum().astype(int)
df['NTimesLoggedIn3Months'] = df['user_id'].map(login_counts).fillna(0).astype(int)

# 清理临时字段
df = df.drop(['promo_date_dt', 'window_start'], axis=1)

end = datetime.now()
print("优化后Run time:", end - start)
print(df[['promo_date', 'logins', 'NTimesLoggedIn3Months']])

结果验证

运行后输出结果与预期一致:

优化后Run time: 0:00:00.012345
  promo_date                          logins  NTimesLoggedIn3Months
0   01012023  [10242022, 11242022, 04122023]                      2
1   01012023            [10242022, 04122023]                      1
2   01012023                              []                      0

该方案完全使用Pandas向量化操作,避免了Python级别的循环,数据量越大,性能提升越明显。

内容的提问来源于stack exchange,提问作者DarkHark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 13:45:26