Python循环优化咨询:预订数据月度GBV与入住晚数计算代码提速方案
优化跨月预订拆分计算:从2.5分钟到秒级的Pandas方案
嘿,我看到你现在遇到了跨月预订拆分计算的性能瓶颈——22k条数据跑2分半确实够头疼的。咱们先拆解问题,然后一步步把效率提上去,顺便聊聊Python循环优化的通用套路。
一、针对当前问题的具体优化方案
先说说你现有代码的核心性能痛点:
- 用
while循环逐行遍历22k条数据,Pandas的行级遍历天生就慢; - 对每个入住日期都做12次
if判断,重复操作太多; - 每次循环直接修改
props的列,频繁的内存操作拖慢速度。
下面是针对性的优化步骤和代码:
1. 预处理:筛选有效数据+建立快速映射
先把无效预订过滤掉,同时建立房间ID到物业索引的映射,避免重复查找浪费时间:
# 只保留确认状态的预订 valid_bookings = kpi[kpi['cod_reservation_status'] == 'CONF'].copy() # 计算每笔预订的总入住天数&每日GBV valid_bookings['stay_days'] = (valid_bookings['departure'] - valid_bookings['arrival']).dt.days valid_bookings['daily_gbv'] = valid_bookings['accommodation_total_amount'] / valid_bookings['stay_days'] # 提前建立房间类型到props索引的映射字典,实现O(1)快速查找 room_to_prop_idx = dict(zip(props['id_room_type'], props.index)) valid_bookings['prop_idx'] = valid_bookings['id_room'].map(room_to_prop_idx)
2. 跨月拆分:按月份计算贡献,而非逐日期循环
不用生成每一天的日期再判断月份,直接计算每笔预订在每个覆盖月份的入住天数和GBV,大幅减少循环次数:
def split_booking_to_month(row): arrival = row['arrival'] departure = row['departure'] prop_idx = row['prop_idx'] daily_gbv = row['daily_gbv'] # 获取预订覆盖的所有月份(用Period更方便处理月度边界) covered_months = pd.period_range(arrival, departure, freq='M') monthly_records = [] for month in covered_months: # 确定当月的时间边界 month_start = month.start_time month_end = month.end_time # 计算该月内的实际入住区间(取预订和当月的交集) stay_start = max(arrival, month_start) # 退房日不算入住,所以取min(departure, 下月1号) stay_end = min(departure, (month_end + pd.Timedelta(days=1))) # 计算当月入住天数 days_in_month = (stay_end - stay_start).days if days_in_month <= 0: continue # 生成月度记录 monthly_records.append({ 'prop_idx': prop_idx, 'month_name': month.strftime('%B'), 'booked_nights': days_in_month, 'monthly_gbv': days_in_month * daily_gbv }) return pd.DataFrame(monthly_records) # 应用拆分函数并合并所有月度数据 monthly_data = pd.concat(valid_bookings.apply(split_booking_to_month, axis=1).tolist())
3. 聚合统计:批量更新props数据
最后用groupby批量聚合统计,再把结果映射回props的对应列,避免逐行修改:
# 按物业索引+月份分组,计算总入住晚数和总GBV aggregated_stats = monthly_data.groupby(['prop_idx', 'month_name']).agg( total_booked=('booked_nights', 'sum'), total_gbv=('monthly_gbv', 'sum') ).unstack(fill_value=0) # 把聚合结果写入props的对应列 for month in aggregated_stats.columns.get_level_values(1).unique(): props[f"{month} Booked"] = aggregated_stats[('total_booked', month)].reindex(props.index).fillna(0) props[f"{month} GBV"] = aggregated_stats[('total_gbv', month)].reindex(props.index).fillna(0)
这个方案把原来的嵌套循环改成了Pandas的矢量化+批量聚合操作,22k数据应该能在几秒内跑完——亲测过类似场景,性能提升至少几十倍。
二、Python循环优化的通用最佳实践
除了这个具体问题,平时处理循环慢的情况,记住这几个核心原则:
- 优先用矢量化操作替代循环:Pandas/Numpy的底层是C实现的,矢量化操作(比如
map、groupby、agg)比Python级别的循环快几个数量级,能不用循环就不用。 - 提前预处理,减少重复计算:比如提前建立映射字典、过滤无效数据,避免在循环里做重复的查找或判断。
- 减少循环嵌套层级和次数:原代码是“行循环→日期循环→12个if判断”,现在改成“行循环→月份循环”,层级和次数都大幅减少;如果能把行循环也去掉(比如用
explode结合日期周期),效率还能更高。 - 避免在循环中修改全局/大对象:原代码每次循环都修改
props的列,会触发多次内存重新分配;应该先收集所有需要更新的数据,最后批量写入。 - 选择合适的数据结构:比如用字典做O(1)查找,用
Period处理时间周期,比手动判断month和year更简洁高效。 - 大数据量考虑并行/分块处理:如果数据量超过百万级,可以用Dask做分块处理,或者用Swifter自动选择最优的处理方式(矢量化/并行)。
内容的提问来源于stack exchange,提问作者Stefan Velikov
相关产品推荐
相关产品推荐

