如何用向量化替代iterrows实现邮箱订阅状态批量计算?
问题
我有两个DataFrame:
- 第一个仅包含
email列,是完整的邮箱列表; - 第二个包含
email、subscribe_or_unsubscribe、date三列,是用户订阅/退订邮件系统的历史记录,已按日期从旧到新排序,且可能存在未订阅就退订的异常数据。
我需要生成包含email和is_subscribed两列的第三个DataFrame,标记各邮箱当前的订阅状态。目前用iterrows遍历实现,但速度极慢,请问如何用向量化方式实现?
示例代码如下:
import numpy as np import pandas as pd first_dataframe = pd.DataFrame(["test1@gmail.com", "test2@gmail.com", "test3@gmail.com", "test4@gmail.com", "test5@gmail.com", "test6@gmail.com", "test7@gmail.com" , "test8@gmail.com", "test9@gmail.com"], columns=['email']) # 格式:年-月-日 second_dataframe = pd.DataFrame([['test3@gmail.com', 'subscribe', '2020-12-26'], ['test5@gmail.com', 'subscribe', '2021-06-06'], \ ['test7@gmail.com', 'unsubscribe', '2021-02-18'], ['test5@gmail.com', 'unsubscribe', '2020-08-17'], \ ['test9@gmail.com', 'subscribe', '2022-01-08'], ['test9@gmail.com', 'unsubscribe', '2022-03-10'], \ ['test9@gmail.com', 'subscribe', '2022-05-26']], columns=['email', "subscribe_or_unsubscribe", "date"]) second_dataframe['date'] = pd.to_datetime(second_dataframe['date']) second_dataframe.sort_values(by='date', inplace=True)
当前iterrows实现代码:
third_dataframe = first_dataframe.copy(deep=True) third_dataframe['is_subscribed'] = "unsubscribed" # 数据已按从旧到新排序 for i, third_dataframe_row in third_dataframe.iterrows(): for j, second_dataframe_row in second_dataframe.iterrows(): if third_dataframe_row['email'] == second_dataframe_row['email']: third_dataframe.at[i,'is_subscribed'] = second_dataframe_row['subscribe_or_unsubscribe'] + "d"
解决方案
核心思路是取每个邮箱的最新操作记录(状态由最后一次操作决定),同时处理异常情况,全程用Pandas向量化操作替代循环,大幅提升效率。
完整实现代码
import numpy as np import pandas as pd # 初始化数据(复用示例代码) first_dataframe = pd.DataFrame(["test1@gmail.com", "test2@gmail.com", "test3@gmail.com", "test4@gmail.com", "test5@gmail.com", "test6@gmail.com", "test7@gmail.com" , "test8@gmail.com", "test9@gmail.com"], columns=['email']) second_dataframe = pd.DataFrame([['test3@gmail.com', 'subscribe', '2020-12-26'], ['test5@gmail.com', 'subscribe', '2021-06-06'], \ ['test7@gmail.com', 'unsubscribe', '2021-02-18'], ['test5@gmail.com', 'unsubscribe', '2020-08-17'], \ ['test9@gmail.com', 'subscribe', '2022-01-08'], ['test9@gmail.com', 'unsubscribe', '2022-03-10'], \ ['test9@gmail.com', 'subscribe', '2022-05-26']], columns=['email', "subscribe_or_unsubscribe", "date"]) second_dataframe['date'] = pd.to_datetime(second_dataframe['date']) second_dataframe.sort_values(by='date', inplace=True) # 1. 获取每个邮箱的最新操作记录 # 按email分组,筛选出每组中date最大的行索引,直接提取对应行 latest_actions = second_dataframe.loc[second_dataframe.groupby('email')['date'].idxmax()] # 2. 转换操作类型为最终订阅状态,同时处理异常 latest_actions['is_subscribed'] = latest_actions['subscribe_or_unsubscribe'].map({ 'subscribe': 'subscribed', 'unsubscribe': 'unsubscribed' }) # 3. 与完整邮箱列表合并,未出现在历史记录的邮箱默认状态为unsubscribed third_dataframe = first_dataframe.merge( latest_actions[['email', 'is_subscribed']], on='email', how='left' ).fillna('unsubscribed') print(third_dataframe)
关键步骤说明
groupby('email')['date'].idxmax():向量化方式快速定位每个邮箱的最新操作行,比循环效率提升几个数量级;map函数:批量转换操作类型为订阅状态,避免逐行判断;merge+fillna:一次性完成所有邮箱的状态匹配,默认未参与过订阅/退订的邮箱状态为unsubscribed;- 异常处理:若邮箱的最新操作是
unsubscribe(哪怕无前置订阅记录),最终状态仍为unsubscribed,符合业务逻辑。
运行结果
email is_subscribed 0 test1@gmail.com unsubscribed 1 test2@gmail.com unsubscribed 2 test3@gmail.com subscribed 3 test4@gmail.com unsubscribed 4 test5@gmail.com subscribed 5 test6@gmail.com unsubscribed 6 test7@gmail.com unsubscribed 7 test8@gmail.com unsubscribed 8 test9@gmail.com subscribed
内容的提问来源于stack exchange,提问作者LLL
相关产品推荐
相关产品推荐

