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

Python识别3000万行订单数据年内连续购买留存年数的高效方法

问题描述

我现有约3000万行数据,共包含6个字段:

DISTINCT_IRECIPIENTID | ORDERNUMBER | ORDERDATE | ORDERDATE_OF_NEXT_ORDER | RETAINED_OR_NOT

其中RETAINED_OR_NOT字段有三类取值:

  • "Retained for one year":ORDERDATE与ORDERDATE_OF_NEXT_ORDER的差值≤365天
  • Next_purchase_but_not_retained:ORDERDATE与ORDERDATE_OF_NEXT_ORDER的差值>365天
  • "Only one lifetime purchase":该用户终身仅产生1笔订单
    我需要计算每个消费者连续留存的年数:例如某消费者下单4次,前3笔订单相邻间隔均≤1年,第3笔与第4笔间隔>1年,则前3笔对应的计数为3,最后1笔计数为0。
    当前数据已按DISTINCT_IRECIPIENTID、ORDERDATE降序排序,我编写了如下代码,但执行速度极慢,请问有什么优化方案可以提升运行效率?
def find_consecutive_purchases_in_a_year(input):
    count = 0
    exit_loop = 0
    sub_data = prepared_main_data_backup[prepared_main_data_backup['DISTINCT_IRECIPIENTID']
                                         == input]

    for index, row in sub_data.iterrows():

        if exit_loop == 1:
            return count
        if exit_loop == 0:
            if row['RETAINED_OR_NOT'] == 'retained_for_one_year':
                count += 1
            else:
                exit_loop = 1
    return count


data_test = prepared_main_data_backup

data_test['retain_counter'] = data_test['DISTINCT_IRECIPIENTID'].apply(
    find_consecutive_purchases_in_a_year)

参考样例数据如下:

DISTINCT_IRECIPIENTID   TSORDERDATETIME FIRST_TRANS_DATE    ORDER_DATE_AFTER    DIFFERENCE_BETWEEN_ORDERS   RETAINED_OR_NOT Output
    1   2017-04-24-09.33.21.000000  2017-04-24-09.33.21.000000          only one lifetime purchase  0
    2   2017-04-24-09.35.16.000000  2017-04-24-09.35.16.000000          only one lifetime purchase  0
    3   2017-04-27-14.45.48.000000  2017-04-27-14.45.48.000000  2017-04-29-14.53.46.000000  2   retained_for_one_year   2
    3   2017-04-29-14.53.46.000000  2017-04-27-14.45.48.000000  2017-05-10-09.06.25.000000  11  retained_for_one_year   2
    3   2017-05-10-09.06.25.000000  2017-04-27-14.45.48.000000  2018-09-22-05.54.07.000000  500 next_purchase_but_not_retained  0
    3   2018-09-22-05.54.07.000000  2017-04-27-14.45.48.000000  2020-09-12-19.12.59.000000  721 next_purchase_but_not_retained  0
    3   2020-09-12-19.12.59.000000  2017-04-27-14.45.48.000000  2020-09-14-11.49.33.000000  2   retained_for_one_year   2
    3   2020-09-14-11.49.33.000000  2017-04-27-14.45.48.000000  2021-06-08-07.18.42.000000  267 retained_for_one_year   2
    4   2017-04-24-09.35.27.000000  2017-04-24-09.35.27.000000  2017-04-30-12.00.14.000000  6   retained_for_one_year   1
    4   2017-04-30-12.00.14.000000  2017-04-24-09.35.27.000000  2018-06-18-09.15.23.000000  414 next_purchase_but_not_retained  0
优化方案

原始代码性能差有两个核心原因:

  1. 每次apply调用都全表筛选对应用户的数据,时间复杂度达到O(N²),3000万行量级下完全不可行
  2. iterrows迭代本身速度极慢,进一步放大了耗时

直接用pandas向量化分组操作即可,时间复杂度为O(N),3000万行数据常规配置下几分钟即可跑完:

import pandas as pd

# 按用户分组,给非留存行打分割标记,区分不同的连续留存序列
prepared_main_data_backup['break_point'] = (prepared_main_data_backup['RETAINED_OR_NOT'] != 'retained_for_one_year').groupby(prepared_main_data_backup['DISTINCT_IRECIPIENTID']).cumsum()

# 按用户+分割点分组,计算每个连续留存序列的长度
grouped = prepared_main_data_backup.groupby(['DISTINCT_IRECIPIENTID', 'break_point'])
prepared_main_data_backup['retain_counter'] = grouped['RETAINED_OR_NOT'].transform('count')

# 非留存行的计数直接置0
prepared_main_data_backup.loc[prepared_main_data_backup['RETAINED_OR_NOT'] != 'retained_for_one_year', 'retain_counter'] = 0

# 清理临时辅助列
prepared_main_data_backup.drop('break_point', axis=1, inplace=True)

如果内存不足以一次性加载全量数据,可以按DISTINCT_IRECIPIENTID做哈希分块,逐块处理后再合并结果即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 20:54:04