You need to enable JavaScript to run this app. pandas时序数据中替代逐行迭代计算行累计值的高效实现方法
优惠活动
大模型
产品
解决方案
定价
更多

pandas时序数据中替代逐行迭代计算行累计值的高效实现方法

问题描述

我有如下代码,用于根据预算计算可购买的商品数量,当前实现用了逐行迭代逻辑,我希望将其优化为更符合pandas风格的高效写法,避免逐行迭代、避免重复计算截至当前行的累计值。
原代码如下:

import math
import pandas as pd

data = [['2021-01-02', 5.5], ['2021-02-02', 10.5], ['2021-03-02', 15.0], ['2021-04-02', 20.0]]
df = pd.DataFrame(data, columns=['Date', 'Current_Price'])

df.Date = pd.to_datetime(df.Date)
mn = df.Date.min()
mx = df.Date.max()
dr = pd.date_range(mn - pd.tseries.offsets.MonthBegin(), mx + pd.tseries.offsets.MonthEnd(), name="Date")
df = df.set_index("Date").reindex(dr).reset_index()
df['Current_Price'] = df.groupby(
    pd.Grouper(key='Date', freq='1M'))['Current_Price'].ffill().bfill()

# 新增Day列标识当前日期是当月的第几天
df['Day'] = pd.to_datetime(df['Date']).dt.day

# 初始化目标列
df[['Deposit', 'Withdrawal', 'Num_of_Products_Bought']] = 0.0

# 每月月初存入100美元
df.loc[df['Day'] == 1, 'Deposit'] = 100.0

原迭代逻辑如下:

for index, row in df.iterrows():
    if df.loc[index, 'Day'] == 1:
        # 购买商品数量 = (截至当前累计存款 - 截至当前累计支出) / 当前商品价格
        df.loc[index, 'Num_of_Products_Bought'] = math.floor(
            (sum(df.iloc[0:(index + 1)]['Deposit'])
             - sum(df.iloc[0:(index + 1)]['Withdrawal']))
            / df.loc[index, 'Current_Price'])
        # 记录对应日期购买商品的总支出
        df.loc[index, 'Withdrawal'] = df.loc[index, 'Num_of_Products_Bought'] * df.loc[index, 'Current_Price']

优化方案

你提到的cumsum不能直接用是因为每一期的支出会影响后续的可用余额,属于带状态的迭代计算,我们可以通过只处理有交易的日期、搭配滚动计算来实现提速,完全避免全表逐行遍历:

纯pandas风格实现

# 筛选出每月1号的交易行,其他行目标列默认是0无需处理
trade_df = df[df['Day'] == 1].reset_index(drop=False).copy()
# 累计存款可以直接用cumsum预计算
trade_df['cum_deposit'] = trade_df['Deposit'].cumsum()

# 自定义滚存计算函数,处理每一期的购买数量和支出
def calc_trade(window):
    # 当前窗口最后一行就是当前待计算的行
    current_row = window.iloc[-1]
    if len(window) == 1:
        prev_withdrawal_sum = 0
    else:
        # 之前所有行的支出总和
        prev_withdrawal_sum = window.iloc[:-1]['Withdrawal'].sum()
    available = current_row['cum_deposit'] - prev_withdrawal_sum
    num = math.floor(available / current_row['Current_Price'])
    withdraw = num * current_row['Current_Price']
    return pd.Series([num, withdraw], index=['Num_of_Products_Bought', 'Withdrawal'])

# 对交易行用expanding窗口计算
trade_df[['Num_of_Products_Bought', 'Withdrawal']] = trade_df.expanding().apply(calc_trade)[['Num_of_Products_Bought', 'Withdrawal']]

# 把计算结果写回原df
df.loc[trade_df['index'], ['Num_of_Products_Bought', 'Withdrawal']] = trade_df[['Num_of_Products_Bought', 'Withdrawal']].values

超高性能精简实现

如果数据量特别大,还可以进一步用变量滚存计算交易行,再合并回df,性能会更好:

trade_df = df[df['Day'] == 1].reset_index(drop=False).copy()
cum_deposit = 0
cum_withdraw = 0
nums = []
withdraws = []
for _, row in trade_df.iterrows():
    cum_deposit += row['Deposit']
    available = cum_deposit - cum_withdraw
    num = math.floor(available / row['Current_Price'])
    withdraw = num * row['Current_Price']
    nums.append(num)
    withdraws.append(withdraw)
    cum_withdraw += withdraw
trade_df['Num_of_Products_Bought'] = nums
trade_df['Withdrawal'] = withdraws
df.loc[trade_df['index'], ['Num_of_Products_Bought', 'Withdrawal']] = trade_df[['Num_of_Products_Bought', 'Withdrawal']].values

两种写法的计算结果和原迭代逻辑完全一致,但性能远高于全表逐行遍历:

  • 第一种是纯pandas风格实现,不需要自己维护状态变量
  • 第二种仅遍历有交易的行(总共有N个月就只遍历N次,远低于原逻辑的总天数次遍历)

内容的提问来源于stack exchange,提问作者user1330974

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.20 11:45:03