使用循环填充NaN时出错:2023年6月数据被意外外推
问题分析与解决
背景与问题
用户拥有如下DataFrame:
import numpy as np import pandas as pd from numpy import rec,nan df=pd.DataFrame.from_records(rec.array([('Jan-22', 200., 100., 325., nan, nan, nan), ('Feb-22', 210., 101., 328., nan, nan, nan), ('Mar-22', 212., 102., 329., nan, nan, nan), ('Apr-22', 211., 104., 330., nan, nan, nan), ('May-22', 208., 105., 329., nan, nan, nan), ('Jun-22', 215., 107., 330., nan, nan, nan), ('Jul-22', 219., 108., 321., nan, nan, nan), ('Aug-22', 220., 105., 332., nan, nan, nan), ('Sep-22', 222., 111., 335., nan, nan, nan), ('Oct-22', 223., 115., 338., nan, nan, nan), ('Nov-22', 225., 118., 339., nan, nan, nan), ('Dec-22', 230., 122., 341., nan, nan, nan), ('Jan-23', 235., 124., 342., 17.5, 24. , 5.2), ('Feb-23', 236., 126., 348., 12.4, 24.8, 6.1), ('Mar-23', 267., 128., 351., 25.9, 25.5, 6.7), ('Apr-23', 288., 130., 326., 36.5, 25. , -1.2), ('May-23', 299., 132., 359., 43.8, 25.7, 9.1), ('Jun-23', 300., 133., 363., 39.5, 24.3, 10. ), ('Jul-23', nan, nan, nan, 12. , 11. , 5. ), ('Aug-23', nan, nan, nan, 11. , 12. , 4. ), ('Sep-23', nan, nan, nan, 10. , 11. , 3. ), ('Oct-23', nan, nan, nan, 9. , 13. , 2. ), ('Nov-23', nan, nan, nan, 8. , 14. , 1. ), ('Dec-23', nan, nan, nan, 7. , 7. , 4. ), ('Jan-24', nan, nan, nan, 6. , 8. , 3. ), ('Feb-24', nan, nan, nan, 6. , 7. , 1. ), ('Mar-24', nan, nan, nan, 5. , 8. , 0. ), ('Apr-24', nan, nan, nan, 4. , 9. , -5. ), ('May-24', nan, nan, nan, 3. , 10. , -5. ), ('Jun-24', nan, nan, nan, 4. , 5. , -4. ), ('Jul-24', nan, nan, nan, 5. , 4. , -2. ), ('Aug-24', nan, nan, nan, 6. , 2. , -1. ), ('Sep-24', nan, nan, nan, 7. , 2. , -4. )], dtype=[('date', 'O'), ('series1', '<f8'), ('series2', '<f8'), ('series3', '<f8'), ('series1_yoy', '<f8'), ('series2_yoy', '<f8'), ('series3_yoy', '<f8')])) df = df.set_index('date')
其中series1、series2、series3为水平值,后缀为yoy的对应列是12个月同比增长率。用户希望从2023年7月开始,通过取12个月前的数值并应用给定增长率来计算填充这三列的NaN值,使用了如下代码:
columns_to_process = ['series1','series2','series3'] yoy_columns = ['series1_yoy', 'series2_yoy', 'series3_yoy'] df = df.ffill() for col, yoy_col in zip(columns_to_process, yoy_columns): df[col] = df[col].fillna(df[col].shift(12) * (1+df[yoy_col] / 100)) df
但运行后发现2023年6月的数据被意外外推(即后续月份被填充为6月的值),不符合预期。
代码问题根源
- 全局
ffill()的滥用:这一步会把所有NaN值用前一行的有效值填充,直接把2023年7月及之后的空缺全部用2023年6月的值填满了,导致后续基于同比增长率的填充逻辑完全没有发挥作用——因为此时series1/series2/series3已经没有NaN需要处理了。 - 未限定填充范围:需求是仅对2023年7月及之后的NaN进行计算填充,但原代码的全局操作打乱了预期的填充逻辑。
修正后的代码
方式一:直接去掉全局ffill()
由于原数据中仅2023年7月及之后的series1/series2/series3存在NaN,直接执行基于同比的填充逻辑即可,fillna只会处理NaN值,不会修改已有真实数据:
columns_to_process = ['series1','series2','series3'] yoy_columns = ['series1_yoy', 'series2_yoy', 'series3_yoy'] # 去掉全局ffill,直接按同比逻辑填充 for col, yoy_col in zip(columns_to_process, yoy_columns): df[col] = df[col].fillna(df[col].shift(12) * (1 + df[yoy_col] / 100)) df
方式二:明确限定填充范围(更严谨)
如果需要确保只对特定日期范围的NaN进行填充,可以添加掩码过滤:
columns_to_process = ['series1','series2','series3'] yoy_columns = ['series1_yoy', 'series2_yoy', 'series3_yoy'] # 定位2023年7月及之后的行 start_date = 'Jul-23' mask = df.index >= start_date for col, yoy_col in zip(columns_to_process, yoy_columns): # 仅对指定范围内的NaN执行同比填充 df.loc[mask, col] = df.loc[mask, col].fillna( df[col].shift(12)[mask] * (1 + df[yoy_col][mask] / 100) ) df
关键说明
- 原代码的核心错误是提前用
ffill()覆盖了需要计算填充的NaN,完全违背了用同比增长率推导数值的需求。 - 修正后仅针对原本的NaN值,用12个月前的真实值乘以(1+同比增长率)进行填充,不会影响已有数据,也符合预期的时间范围要求。
内容的提问来源于stack exchange,提问作者jack homareau
相关产品推荐
相关产品推荐

