如何加速Python中计算小时损失的嵌套双循环?
优化小时级损失计算的高效方案
原嵌套双循环的时间复杂度为O(N*M)(N=8760小时数据,M为故障记录数),当M较大时执行效率极低。以下是几种按效率排序的优化方案:
方法一:事件流累加(最优,O(M+N)复杂度)
核心思路是将每个故障的开始/结束转换为"增减事件",通过重采样累加得到小时级损失,避免逐行匹配:
步骤与代码:
- 统一转换时间列为
datetime类型(处理字符串格式的时间) - 生成事件数据:故障开始时加对应损失,结束时减损失(结束时间向上取整到下一小时,确保当前小时的损失被正确统计)
- 合并事件并计算累计损失,再重采样到小时粒度
- 与原始小时时间数据对齐得到结果
import pandas as pd import numpy as np # 1. 转换时间列类型(注意Forced的日期格式是日/月/年) Forced['Start'] = pd.to_datetime(Forced['Start'], format='%d/%m/%Y %H:%M') Forced['End'] = pd.to_datetime(Forced['End'], format='%d/%m/%Y %H:%M') DF_time['datetime'] = pd.to_datetime(DF_time['datetime']) # 2. 生成增减事件:开始事件加损失,结束事件减损失 start_events = Forced[['Start', 'Lost']].rename(columns={'Start': 'datetime', 'Lost': 'delta'}) end_events = Forced[['End', 'Lost']].rename(columns={'End': 'datetime', 'Lost': 'delta'}) end_events['delta'] *= -1 # 结束时间向上取整到下一小时,保证当前小时的损失被统计 end_events['datetime'] = end_events['datetime'].dt.ceil('H') # 3. 合并事件并计算累计损失 events = pd.concat([start_events, end_events]).sort_values('datetime') events['cum_loss'] = events['delta'].cumsum() # 4. 重采样到小时粒度,填充缺失值 hourly_loss = events.set_index('datetime').resample('H').ffill().fillna(0) # 5. 与DF_time对齐,得到最终结果 LOST = DF_time.merge(hourly_loss[['cum_loss']], on='datetime', how='left').rename(columns={'cum_loss': 'perd'}) LOST['perd'] = LOST['perd'].fillna(0)
方法二:向量化区间匹配(O(N*logM)复杂度)
利用numpy广播或pandas区间索引,替代显式循环:
方案A:numpy广播计算
# 先转换时间类型(同方法一) Forced['Start'] = pd.to_datetime(Forced['Start'], format='%d/%m/%Y %H:%M') Forced['End'] = pd.to_datetime(Forced['End'], format='%d/%m/%Y %H:%M') DF_time['datetime'] = pd.to_datetime(DF_time['datetime']) # 转换为时间戳数组,实现广播比较 hour_stamps = DF_time['datetime'].values[:, np.newaxis] start_stamps = Forced['Start'].values end_stamps = Forced['End'].dt.ceil('H').values # 生成匹配掩码,计算每个小时的总损失 mask = (hour_stamps >= start_stamps) & (hour_stamps < end_stamps) LOST = DF_time.copy() LOST['perd'] = np.dot(mask, Forced['Lost'].values)
方案B:pandas区间索引
# 转换时间类型(同方法一) Forced['Start'] = pd.to_datetime(Forced['Start'], format='%d/%m/%Y %H:%M') Forced['End'] = pd.to_datetime(Forced['End'], format='%d/%m/%Y %H:%M') DF_time['datetime'] = pd.to_datetime(DF_time['datetime']) # 创建区间索引 intervals = pd.IntervalIndex.from_arrays(Forced['Start'], Forced['End'].dt.ceil('H'), closed='left') # 计算每个小时对应的损失和 LOST = DF_time.copy() LOST['perd'] = LOST['datetime'].apply(lambda x: Forced.loc[intervals.contains(x), 'Lost'].sum())
原逻辑的最小优化(适合快速调整)
如果要保留逐小时匹配的思路,可通过提前提取数组减少索引开销:
import pandas as pd import numpy as np # 转换时间类型(同方法一) Forced['Start'] = pd.to_datetime(Forced['Start'], format='%d/%m/%Y %H:%M') Forced['End'] = pd.to_datetime(Forced['End'], format='%d/%m/%Y %H:%M') DF_time['datetime'] = pd.to_datetime(DF_time['datetime']) LOST = DF_time.copy() # 提前提取数组,避免循环内重复索引 hours = DF_time['datetime'].values starts = Forced['Start'].values ends = Forced['End'].values losts = Forced['Lost'].values # 用列表推导+向量化求和替代双循环 LOST['perd'] = [losts[(h >= starts) & (h <= ends)].sum() for h in hours]
内容的提问来源于stack exchange,提问作者Santi
相关产品推荐
相关产品推荐

