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

Pandas1.4.2中rolling()指定on参数时apply接收序列索引异常

问题描述

自行实现了基于时间偏移量查找DataFrame全量滑动窗口的逻辑,用于对整个DataFrame应用自定义处理函数——pandas原生.rolling()方法默认仅支持逐列运算,无法直接满足该需求,实现思路参考了社区相关优质方案。
该方案在pandas 1.3.0版本的Hex Notebook环境中运行完全符合预期,但在pandas 1.4.2版本的PyCharm环境中运行失败;将PyCharm环境的pandas版本回退至1.3.0后代码恢复正常,初步判断问题由1.3.0至1.4.2版本间的API逻辑变更导致,或为本地安装包损坏引发。

原有实现代码

perform_rolling核心逻辑:

def perform_rolling(df: pd.DataFrame, my_windows: list[tuple]):
    group_id = df[grouping_field_name].unique()[0]
    dfc = df.reset_index(drop=True)
    dfc.drop([grouping_field_name], inplace=True, axis=1)
    dfc.rolling(time_offset, on=time_field_name).apply(assign_windows, kwargs={'my_df': dfc, 'my_windows': my_windows, 'group_id': group_id})

配套assign_windows函数:

def assign_windows(ser: pd.Series, my_df: pd.DataFrame, my_windows: list[tuple], group_id):
    my_uids = list(my_df.loc[ser.index, 'uid'].values)
    # rolling会逐列执行assign_windows,仅在单列触发逻辑避免重复生成窗口
    if -1 in ser.values:
        my_windows.append((group_id, my_uids))
    return 1  # 占位返回,满足rolling.apply对数值返回值的要求

上述逻辑运行于闭包环境中,闭包内维护的my_windows列表最终会返回给上层调用代码。

异常表现

pandas 1.4.2环境下,传入assign_windows()的Series索引被自动替换为on参数指定的time_field_name列值,导致my_df.loc[ser.index, 'uid'].values语句抛出索引错误——因为处理时使用的dfc对象为默认range索引;而pandas 1.3.0环境下,传入apply方法的Series始终保留调用.rolling()时的原始整数索引,无该异常。

原因说明

这是pandas 1.4.0版本对rolling.apply的传参逻辑做的正式调整,不是安装包损坏:当rolling调用指定了on参数时,apply接收的Series索引会固定使用on参数指定的列值,不再保留DataFrame原有的索引,这个变更没有做向下兼容,所以直接依赖传入Series原始索引的写法会在1.4+版本报错。

可行解决方案

不需要修改pandas配置,也不需要锁死1.3.0旧版本,通过显式构建索引映射的方式即可兼容所有pandas版本,稳定性最高:

  • 在执行rolling前,提前构建时间列值到DataFrame原始整数位置的映射字典,不依赖apply传入的Series索引做直接定位
  • 在apply函数中增加索引类型判断,自动适配不同版本传入的索引类型,统一转换为原始整数位置后再取数

修改后兼容代码

def perform_rolling(df: pd.DataFrame, my_windows: list[tuple]):
    group_id = df[grouping_field_name].unique()[0]
    dfc = df.reset_index(drop=True)
    dfc.drop([grouping_field_name], inplace=True, axis=1)
    # 提前构建时间值到原始位置的映射,绕开版本差异
    time_to_pos = dict(zip(dfc[time_field_name], dfc.index))
    dfc.rolling(time_offset, on=time_field_name).apply(
        assign_windows,
        kwargs={
            'my_df': dfc,
            'my_windows': my_windows,
            'group_id': group_id,
            'time_to_pos': time_to_pos,
            'time_col_dtype': dfc[time_field_name].dtype
        }
    )

def assign_windows(ser: pd.Series, my_df: pd.DataFrame, my_windows: list[tuple], group_id, time_to_pos, time_col_dtype):
    # 自动适配不同版本传入的索引类型,统一转成原始整数位置
    if ser.index.dtype == time_col_dtype:
        # 1.4+版本:传入索引是时间列值,通过映射转成原始位置
        target_pos = [time_to_pos[t_val] for t_val in ser.index]
    else:
        # 1.3.x版本:传入索引就是原始整数位置,直接使用
        target_pos = ser.index
    my_uids = list(my_df.loc[target_pos, 'uid'].values)
    # 仅在标记列触发逻辑,避免逐列重复写入窗口
    if -1 in ser.values:
        my_windows.append((group_id, my_uids))
    return 1

额外说明:不要依赖pandas未写入官方文档的内部行为,这类逻辑在小版本迭代中随时可能调整,显式做一层适配映射的维护成本远低于锁版本或者找隐藏配置开关。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 18:09:22