如何让Pandas仅在有效数据区间计算pct_change且保留完整索引?
解决Pandas中pct_change在NaN区域无效计算的问题
问题场景
现有带时间索引的DataFrame,部分行存在NaN值,且索引超出最后一个有效数据点:
2022-01-31 96.210 21649.6 2022-02-28 96.390 21708.4 2022-03-31 97.410 21739.7 2022-04-30 98.630 21644.3 2022-05-31 103.744 21649.2 2022-06-30 102.498 21607.4 2022-07-31 105.138 21636.1 2022-08-31 105.450 21631.8 2022-09-30 109.691 21503.1 2022-10-31 111.745 21414.8 2022-11-30 111.481 21351.6 2022-12-31 104.728 NaN 2023-01-31 103.522 NaN 2023-02-28 NaN 2023-03-31 NaN 2023-04-30 NaN 2023-05-31 NaN 2023-06-30 NaN 2023-07-31 NaN 2023-08-31 NaN
计算pct_change(12)时,Pandas会在最后一个有效数据点之后继续计算(将NaN视为参与计算的有效值),导致结果不符合预期。需要实现:
- 保留所有原始索引
- 仅在每列的非NaN有效范围内计算pct_change
- 无需手动指定索引范围,自动适配后续新增数据
- 不能使用
dropna方法
解决方案
方法1:基于最后有效索引过滤结果
先正常计算pct_change,再通过每列的最后有效索引,将该索引之后的结果设为NaN:
import pandas as pd import numpy as np # 构造示例DataFrame(实际使用时替换为你的数据) data = { 'col1': [96.210, 96.390, 97.410, 98.630, 103.744, 102.498, 105.138, 105.450, 109.691, 111.745, 111.481, 104.728, 103.522, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], 'col2': [21649.6, 21708.4, 21739.7, 21644.3, 21649.2, 21607.4, 21636.1, 21631.8, 21503.1, 21414.8, 21351.6, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan] } index = pd.date_range(start='2022-01-31', end='2023-08-31', freq='M') df = pd.DataFrame(data, index=index) # 计算百分比变化 pct_result = df.pct_change(12) # 获取每列最后一个非NaN值的索引 last_valid_indices = df.apply(lambda col: col.last_valid_index()) # 遍历每列,将最后有效索引之后的结果设为NaN for col, idx in last_valid_indices.items(): if idx is not None: # 找到最后有效索引的下一个月份,之后的行全部设为NaN next_month = idx + pd.DateOffset(months=1) pct_result.loc[next_month:, col] = np.nan print(pct_result)
方法2:基于有效范围掩码过滤
通过生成布尔掩码标记每列的有效数据范围(从第一个非NaN到最后一个非NaN),再用掩码过滤pct_change结果:
import pandas as pd import numpy as np # 构造示例DataFrame(实际使用时替换为你的数据) data = { 'col1': [96.210, 96.390, 97.410, 98.630, 103.744, 102.498, 105.138, 105.450, 109.691, 111.745, 111.481, 104.728, 103.522, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan], 'col2': [21649.6, 21708.4, 21739.7, 21644.3, 21649.2, 21607.4, 21636.1, 21631.8, 21503.1, 21414.8, 21351.6, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan] } index = pd.date_range(start='2022-01-31', end='2023-08-31', freq='M') df = pd.DataFrame(data, index=index) # 生成有效范围掩码:标记每列从第一个非NaN到最后一个非NaN的行 # 从顶部向下累积标记有效行 top_mask = df.notna().cummax(axis=0) # 从底部向上累积标记有效行,再反转回来 bottom_mask = df.notna()[::-1].cummax(axis=0)[::-1] # 两者取交集得到最终有效掩码 valid_mask = top_mask & bottom_mask # 用掩码过滤pct_change结果,无效区域设为NaN pct_result = df.pct_change(12).where(valid_mask) print(pct_result)
效果验证
两种方法都能得到预期结果:
2022-01-31 0.069713 0.117543 2022-02-28 0.059464 0.106713 2022-03-31 0.069969 0.094989 2022-04-30 0.061336 0.076258 2022-05-31 0.140671 0.060263 2022-06-30 0.141022 0.056075 2022-07-31 0.135400 0.049517 2022-08-31 0.145573 0.038228 2022-09-30 0.186490 0.025632 2022-10-31 0.188271 0.012812 2022-11-30 0.187484 0.000112 2022-12-31 0.090576 NaN 2023-01-31 0.076000 NaN 2023-02-28 NaN NaN 2023-03-31 NaN NaN 2023-04-30 NaN NaN 2023-05-31 NaN NaN 2023-06-30 NaN NaN 2023-07-31 NaN NaN 2023-08-31 NaN NaN
内容的提问来源于stack exchange,提问作者user20856754
相关产品推荐
相关产品推荐

