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

如何用向量化替代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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 21:15:40