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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 21:07:46