python - 如何加快这个嵌套循环(按日期索引?)
问题描述
数据集如下所示:
email event_date event_type
0 4867784685125632 2015-10-26 21:38:03.911350 delivered
1 5352432066363392 2015-10-26 21:37:57.871980 delivered
2 6024938649550848 2015-10-26 21:37:57.853210 purchase
3 6191500064980992 2015-10-26 21:37:58.867800 delivered
4 4867784685125632 2015-10-28 21:37:56.331130 purchase
本质上,有许多行共享一个散列的电子邮件(电子邮件)值。对于 event type = 已交付的每一行,我需要计算 event_type = purchase 共享相同电子邮件地址的行数,并在原始行中的日期之后的 5 天内发生。
我已经找到了一种方法来做到这一点,我将这个 df 拆分为单独的交付和购买的数据帧,然后使用嵌套循环来搜索两者——但它确实效率低下并且需要永远。
attributed_purchases = []
count = 0
for idx_e, row_e in delivered.iterrows():
purch = 0
rev = 0
for idx_p, row_p, in purchased.iterrows():
if delivered.loc[idx_e, 'email'] != purchased.loc[idx_p, 'email']:
pass
elif (purchased.loc[idx_p, 'event_date'] >= delivered.loc[idx_e, 'event_date']) and purchased.loc[idx_p, 'event_date'] <= (delivered.loc[idx_e, 'event_date'] + timedelta(days=5)):
purch += 1
print('I just found a purchase')
attributed_purchases.append(purch)
count += 1
print(f'Completed iteration {count}')
delivered['attributed_purchases'] = attributed_purchases
第一个循环遍历传递的数据帧中的行。对于每一行,它会遍历购买的数据框并首先检查电子邮件是否匹配。如果有,它会检查日期是否在 5 天之内,并增加计数器。遍历购买的数据帧后,它将计数附加到列表并跳转到第一个 for 循环中的下一次迭代。
这行得通,但是我正在处理大量数据并且花费的时间太长了。
我相信有很多方法可以加快速度。也许如果我根据日期索引?任何帮助表示赞赏!
如果您需要更多信息,请与我们联系。
解决方案
很难不知道更具体的要求,但是一些高级建议-
- 在内部循环中找到购买数据后使用
break
,这样您就不会不必要地处理所有剩余的项目。 - 在交付完成时清理购买列表,以便它随着时间的推移而缩小,并且外部循环的未来迭代不会处理已经归属的项目。
不过,根据我的评论,我仍然认为简单的单循环方法会更有效,并且要处理单个电子邮件的多个可能重叠的购买,您只需将它们分别存储为一个列表(defaultdict(list)
为方便起见),并且随时随地管理这些列表。这也确保了一次交付不会满足多次购买,尽管如果需要,只需将整个try
块更改为类似count += bool(pending[ehash])
import datetime
from collections import defaultdict
emails = ((e[0], datetime.datetime.strptime(e[1], '%Y-%m-%d'), e[2])
for e in (
(1, '2019-01-01', 'delivered'), # ignored, no prior purchase
(1, '2019-01-02', 'purchase'),
(1, '2019-01-03', 'purchase'),
(1, '2019-01-04', 'delivered'), # matches [1], count == 1
(1, '2019-01-05', 'purchase'),
(1, '2019-01-06', 'delivered'), # matches [2], count == 2
(1, '2019-01-20', 'delivered') # ignored, too long since last purchase
)
)
count = 0
pending = defaultdict(list)
for (ehash, date, status) in sorted(emails, key=lambda e: e[1]):
# record a purchase awaiting delivery
if status == 'purchase':
pending[ehash].append(date)
elif status == 'delivered':
# purge any purchases for this email > 5days old
pending[ehash] = [p_date for p_date in pending[ehash]
if p_date > date - datetime.timedelta(days=5)]
# then the next oldest (<5days) also deleted, and increments the count
try:
del pending[ehash][0]
count += 1
except IndexError:
pass # No valid purchase for this delivery
print(count)
推荐阅读
- django - django runserver 不显示启动开发服务器消息
- vuejs2 - How to run a function with a delay from inside axios then
- java - 爪哇。无法从导入 java.security.Signature 导入 SHA256withRSA 签名
- leaflet - 无法让 dashArray 在 Leaflet 1.0.3 中的画布上工作
- redis - 如何获取 Redis 中键集的大小(以字节为单位)?
- java - 无法将 Premium_Fragment 转换为 Activity
- wso2 - WSO2IS-5.3.0 的 OIDC SSO 中缺少声明
- javascript - 如果已经激活 JS,则停止切换
- css - 将除一类以外的所有字体设置为特定字体
- c# - 有没有更有效的方法来禁用多个脚本,同时在 Unity 中的单个游戏对象上保持一些活动?