资讯详情

Pandas线性插值实战:空气质量数据异常值与缺失值处理

📅 2026/10/14 14:37:49 | 华诺云谱 👁 阅读
Pandas线性插值实战:空气质量数据异常值与缺失值处理
这段时间帮某个环境监测项目处理城市空气质量数据遇到最多的不是模型效果差而是数据本身的问题。一套PM2.5、PM10、NO2、SO2浓度数据拿过来缺失值、异常值、重复记录混在一起直接建模结果根本没法看。把这些数据清洗干净用到的核心工具就是Pandas的插值函数尤其是interpolate()里的线性插值方式。这篇就围绕这个过程把异常值处理和线性插值的完整思路、坑点和实操代码整理出来希望对正在跟空气质量数据“搏斗”的朋友能有点帮助。1. 问题全貌城市空气质量数据到底“脏”在哪里1.1 三类常见数据问题城市空气质量数据来源很多既有国控监测站点自动上传的逐小时数据也有第三方传感器部署在路边、小区、工业园区的补充数据。不管来源是哪一种落到手里的DataFrame通常都会带着下面三类毛病。第一是缺失值。监测仪器需要定期校准网络传输偶尔丢包断电维护更是家常便饭结果就是时间序列上出现一个个空洞。有些是单个小时的缺失有些则是连续几天完全没有记录。第二是异常值传感器故障可能把PM2.5从80直接报成800或者出现负值这种明显不符合物理常识的记录。第三是重复值和格式问题比如相同时间戳重复出现两条记录或者时间列读进来是字符串而不是datetime类型。这三类问题如果不处理后面做趋势分析、模型预测、甚至简单的每日均值统计都会被严重带偏。尤其是异常值它对均值和方差的影响非常大几万个正常数据里混进几个极端值均值可能就被拉高了一大截。1.2 动手之前先明确目标处理数据之前我会先问自己三个问题这批数据要做描述性统计还是建模预测处理后的数据需要保留原始时间粒度还是聚合到小时/日均值后续对数据质量有没有明确的业务要求这三个问题的答案决定了异常值和缺失值的处理策略。如果只是做月均浓度统计单个小时的缺失值完全可以直接忽略或简单填充但如果要做小时级预测模型缺失值就必须谨慎插值异常值也不能随便删除否则模型会学到错误的模式。我习惯把原始数据先用info()和head()快速扫一遍确认列名、数据类型、时间范围、记录条数再决定后续处理管线。这一步虽然简单但能避免后面反复返工。2. 识别异常值先把问题“看出来”2.1 用describe()和箱线图做快速体检拿到数据后我一般先跑一段快速的探索性代码对每一列污染物浓度做基础统计。以PM2.5为例import pandas as pd import numpy as np import matplotlib.pyplot as plt df pd.read_csv(air_quality.csv, parse_dates[time]) df.set_index(time, inplaceTrue) print(df.describe())describe()会把每一列的计数、均值、标准差、最小值、四分位数、最大值都列出来。单看这张表就能发现很多问题如果count明显少于总行数说明有缺失如果min出现负数浓度字段肯定有异常如果max远远大于75%分位数的好几倍极有可能是少数极端值在作怪。只看统计数字还不够直观箱线图是更好的辅助工具plt.figure(figsize(12, 6)) df[PM2.5].plot(kindbox) plt.title(PM2.5 Boxplot) plt.show()箱线图能清楚展示数据的分布范围超出上下须的点就会被单独画出来这些点往往是异常值的高概率候选。但我必须提醒一句箱线图不能代替业务判断离群点不一定都是错误值有时候重污染天气下PM2.5浓度冲到300以上是真实存在的不能一股脑当作异常删掉。2.2 两种常用判定方法常用的异常值判定方法有两种IQR四分位距法和Z-score标准分数法。IQR方法的计算逻辑是先算出第一四分位数Q1和第三四分位数Q3IQR Q3 - Q1然后把小于Q1 - 1.5 * IQR或大于Q3 1.5 * IQR的值视为异常。这个方法的优点是无需假设数据服从正态分布对偏态分布比较稳健所以在空气质量数据这种右偏分布明显的场景里很常用。Z-score方法则是计算每个值与均值之间的标准距离公式为z (x - mean) / std一般认为绝对值大于3的值是异常值。这个方法适合近似正态分布的数据但如果数据本身有多个极端值均值和标准差都会被拉偏Z-score判定效果会打折扣。实际处理时我通常两种方法都试一遍把两个方法的异常候选集合并集作为重点关注对象然后逐个检查而不是直接自动删除。2.3 结合业务规则的二次判断统计方法圈定的是“统计意义上的异常”但空气质量领域还有自己的业务规则。比如PM2.5浓度的物理范围基本在0到1000微克每立方米之间超过这个范围要么是沙尘暴级别的极端事件要么就是传感器故障。负值一定是异常浓度突然从100跳变到0再跳回100大概率是仪器切换或传输故障。我自己的做法是先按IQR和Z-score筛一遍再结合业务上下限把明显不合理的记录挑出来最后把这些候选异常值用散点图可视化看它们在时间序列上的位置和形态。如果某个异常值出现在数据跳变的边缘且周围数据完全正常那基本可以判定为传感器毛刺。3. 异常值处理不是只有删除一条路3.1 直接删除的适用场景最省事的办法是把异常值所在的整行删除。当异常值只占全部数据的极小比例比如小于1%且删掉之后不会破坏时间序列的连续性时直接删除是合理的选择。# 按IQR方法标记异常值 Q1 df[PM2.5].quantile(0.25) Q3 df[PM2.5].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR outlier_mask (df[PM2.5] lower_bound) | (df[PM2.5] upper_bound) print(f异常值数量: {outlier_mask.sum()}) df_clean df[~outlier_mask]删除之后一定要留意索引和时间连续性。如果后面还要用interpolate()插值删除异常值会在时间轴上留下空洞这些空洞反而成为新的缺失值。所以删除异常值需要和缺失值填充一起考虑最好先分析异常、再确定是否删除、最后统一处理缺失。3.2 用估计值替换异常值有些场景不适合直接删除比如异常值数量较多或者删除后会破坏后续模型训练的数据连续性。这时候可以用估计值替换。替换的思路有两种一是用该列的中位数或均值填充二是用时序上相邻的正常值通过插值得到。用中位数替换简单粗暴对数据分布影响较小但会造成该时间点的数值与前后趋势脱节。用插值替换则可以把异常点“拉回”到正常趋势线上效果更自然。我个人的经验是先把异常值统一改为NaN然后用后面的线性插值一并处理。这样既能识别异常又能用趋势信息填充比单独替换成一整个固定值要灵活得多。3.3 处理原则与注意事项处理异常值最重要的一条原则不要盲目自动删除。我在实际项目中遇到过一种情况某城市春节期间的PM2.5浓度因烟花爆竹燃放出现了持续多天的高值从统计角度看这些值是离群点但实际上它们反映的是真实污染事件。如果把这类值删掉后续做空气质量评价反而失真。所以我通常把异常值处理分成两步走先自动识别并标记再结合业务背景人工判断哪些要删、哪些要改、哪些保留。自动化的“一键清洗”工具看起来很爽但在空气质量数据这种对真实性要求很高的场景里人工复核环节不可省略。4. 缺失值处理与interpolate()线性插值4.1 为什么插值优于固定值填充缺失值的处理方式大致分三类删除缺失行、固定值填充、插值填充。删除缺失行会损失大量宝贵数据特别是连续监测数据中缺失率超过10%时直接删除会让后续分析样本量大打折扣。固定值填充比如用均值、中位数虽然简单但没有考虑时间序列的趋势特征容易在缺失点产生突兀的“平台”效果。线性插值则是在已知数据点之间画一条直线用直线上的值估计缺失点的数值。它能保持时间序列的连续性和趋势对短时间段的缺失效果尤其好。比如某个小时PM2.5是50下个小时是70中间一个小时缺失线性插值就会平滑地填上60这比用均值填一个固定值要合理得多。4.2 interpolate()的参数详解Pandas的Series.interpolate()和DataFrame.interpolate()方法提供了多种插值方式最常用的是methodlinear即线性插值。它的使用很简单# 先将异常值也转为NaN统一交给插值处理 df[PM2.5] df[PM2.5].mask(df[PM2.5] 0) # 线性插值填补缺失 df[PM2.5] df[PM2.5].interpolate(methodlinear, limit_directionboth)这里有两个参数值得重点说明。limit_direction控制插值方向默认是forward表示只填补序列中间和后部的缺失值如果开头就有缺失是不管的改成both可以在序列开头和结尾都会尝试用最近的有效值进行填充。另一个参数是limit它控制连续缺失点的最大填补数量。比如连续缺失超过5个小时线性插值可能不够准确可以设置limit4限制单段最大连续插值长度超过部分保持NaN交给其他方式处理。这个参数在实际数据中非常实用因为空气质量数据的长时间连续缺失很常见盲目用线性插值补齐连续几十个小时的缺失插出来的是直线完全丢失了真实的波动信息。时间序列插值还要注意索引类型。如果索引是整型线性插值直接按整数位置计算如果索引是DatetimeIndexPandas会按时间间隔做插值计算。默认情况下interpolate()利用的是索引值作为插值的x坐标所以提前把时间列转成DatetimeIndex非常重要。4.3 线性插值的适用边界线性插值适合缺失段较短、前后数据变化平稳的场景。如果某一天内PM2.5浓度在早高峰和晚高峰之间自然起伏缺失一两个小时完全可以用线性插值补。但如果缺失发生在冷空气过境、污染物浓度剧烈波动的时段线性插值补出来的值会低估波峰或高估波谷。对于更长、波动更大的缺失段可以考虑methodtime按时间间隔加权、methodpolynomial多项式插值或methodspline样条插值但高阶插值容易产生过冲现象。在多数空气质量分析场景中线性插值已经足够不必盲目追求高级方法。5. 完整实操流程从原始数据到干净数据5.1 清洗流程脚本下面是一套完整的流程把前面讲到的异常值识别、转换NaN、线性插值串在一起并加了详细注释可以直接复制使用。import pandas as pd import numpy as np import matplotlib.pyplot as plt # 读入数据把时间列解析为datetime格式 df pd.read_csv(air_quality.csv, parse_dates[time]) df.set_index(time, inplaceTrue) # 1. 初步检查 print(df.info()) print(df.head()) # 2. 按业务规则把物理上不可能的值转为NaN for col in [PM2.5, PM10, NO2, SO2]: df.loc[df[col] 0, col] np.nan # 3. 用IQR方法标记统计意义上的异常值先转为NaN for col in [PM2.5, PM10, NO2, SO2]: Q1 df[col].quantile(0.25) Q3 df[col].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR df.loc[(df[col] lower) | (df[col] upper), col] np.nan # 4. 对每一列做线性插值允许向前和向后 for col in [PM2.5, PM10, NO2, SO2]: df[col] df[col].interpolate(methodlinear, limit_directionboth, limit8) # 5. 检查剩余缺失情况 print(df.isnull().sum())这段代码把异常值识别和缺失值处理合并成了一个流程。注意我用了limit8也就是单段最长允许8个连续缺失点做线性插值超过8个的连续缺失段保持NaN。这个数值可以根据数据实际情况调整如果每小时一个数据点8个小时的连续缺失用线性插值补出来还说得过去如果缺失超过两天建议考虑用前后日均趋势或其他方法。5.2 清洗效果评估清洗完不能直接拿去用要先评估效果。我会做两类检查一是看isnull().sum()是否还有大量残留缺失二是把清洗前后的数据分布打印对比确认没有引入新的极端值。print(df.describe())如果describe()里出现最小值仍然为负、最大值比清洗前更离谱等情况说明清洗流程中有环节出了问题要回头检查是异常值判断条件写错了还是插值后的数据仍包含异常值。另外一个快速有效的可视化检查是画清洗前后的时间序列对比图fig, ax plt.subplots(2, 1, figsize(12, 8)) ax[0].plot(df.index, df[PM2.5], colorgray, alpha0.5) ax[1].plot(df.index, df[PM2.5_original], colorblue, alpha0.5) plt.show()如果清洗后曲线明显出现大段直线平台那很可能是连续缺失被线性插值强行拉平了说明limit参数设得太宽松或缺失段本身太长需要调整策略。清洗完成后我习惯把清洗前后的数据另存为两份文件比如raw_air_quality.csv和clean_air_quality.csv。原始数据保留一份是必须的因为任何清洗操作都可能导致数据失真保留原始数据可以随时回溯和恢复这在后续写报告或审计时非常重要。5.3 后续扩展方向清洗只完成了第一步后续可以根据项目需求继续扩展。如果要做空气质量预测可以基于清洗后的数据构造滞后特征、滚动均值特征按时间窗口滑动生成建模数据集。如果要做站点对比可以把多个站点数据按时间对齐合并处理量会成倍增加。我自己在这类项目里最常用到的扩展是把逐小时数据聚合到日均值计算AQI指数分指数然后对各站点做相关性热力图。这些下游任务都建立在数据质量可靠的基础上前面的清洗步骤做不好后面再漂亮的图表都只是“垃圾进垃圾出”。6. 常见问题与避坑指南6.1 interpolate()不生效先从这三个地方排查很多人会遇到调用interpolate()后数据仍然是NaN的情况。我排查这个问题会按顺序看三处。先看数据类型如果列里面混入字符串或者object类型interpolate()不会报错但也不会产生任何插值效果因为数值运算在这种列上根本不会生效。用pd.to_numeric()强制转换一下通常能解决。第二看缺失值形态如果连续的缺失点太多超过limit限制插值就会被跳过。第三看索引是否有序Pandas在有些插值方法上会因索引未排序产生警告或非预期结果提前sort_index()总能避免很多麻烦。6.2 时间索引的坑这是最容易被忽略的细节。interpolate()默认按索引的数值关系计算插值权重。如果索引是DatetimeIndex那么插值会按时间间隔比例计算如果索引是普通整数索引则按位置等距计算。举个例子某个数据集下午14点到16点之间缺失数据但索引本身是不等间距的时间戳那么整数索引下的线性插值结果会和真实时间权重下的结果有差距。正确做法是在读取数据时就parse_dates[time]并set_index(time)确保后续所有插值都基于时间轴。另外要注意索引里如果有重复时间戳interpolate()的结果会变得奇怪应先用df.index.duplicated()检查重复索引并及早处理。6.3 异常值删除后的连锁反应前面提到的“先转NaN再插值”方案虽然灵活但也有一个隐患异常值点被转换为NaN后如果插值间距过长会被线性插值拉出一个“回归均值”的低谷。这会让原本的极端观测值被“平滑”掉在重污染时段尤其明显。如果你认为某些异常值其实是真实极端天气的反映就不要把它们当异常处理。务必结合业务背景人工筛选不要让算法替你决定什么该删、什么该留。我吃过一次亏某次处理一个沿海城市的NO2数据把台风登陆前后的极高浓度值一并用IQR标记为异常删掉了结果后来源分析时发现那几天浓度升高是真实气象过程造成的删掉后整个事件信号就消失了后来只能从原始备份里找回重做。6.4 inplaceTrue 的争议Pandas的interpolate()支持inplaceTrue参数可以原地修改DataFrame。我个人的习惯是一律不用因为清洗流程很长中间每个步骤都可能出问题用inplaceTrue修改后如果发现前面的步骤有误就得从头重新读取原始文件非常不方便。正确做法是每次处理都生成新的DataFrame或者显式赋值给新的变量虽然多占一些内存但可追溯性大大提高。6.5 长连续缺失的正确打开方式前面提到连续缺失段超过limit时插值不会生效。这时候如果删掉这些行可能会丢失一整段有效数据。我建议的做法是评估这段缺失在整体数据中的占比如果缺失段前后有明确的周期规律可以用同期历史数据的均值做填充如果缺失段实在太长比如超过1个月直接标记该时间段为无效在后续分析中剔除不做强行插值。空气质量数据强调真实性和可解释性插值补出来的值只是估算值如果估算段过长反而会误导下游分析。我有一次处理某站点的逐小时数据出现连续一周的仪器维护导致缺失直接线性插值补出一条近乎直线的浓度曲线后来做趋势分析时这一段被识别为“异常平稳”不仅没有解决质量问题反而引入了新的误导信息。6.6 多列同时插值的注意点DataFrame.interpolate()可以一次性对多列插值非常方便。但要注意不同列的缺失模式可能完全不同统一设置limit8对某些列合适对另一些列可能太宽松。最好先看各列缺失数量和连续缺失段的分布再决定全局参数还是分列参数。我在处理PM2.5和PM10时通常会设置不同的插值参数因为PM10的波动往往更大连续缺失容忍度要比PM2.5更低一些。这种细节上的差异在后续模型评估时会有明显体现。写在最后处理城市空气质量数据最花时间的往往不是写代码而是理解数据的“脾气”。我刚开始接触这类项目时也犯过“拿到数据就建模”的错结果模型的结果看起来不错但经过仔细核查后发现所谓的规律根本是异常值制造出来的幻觉。后来学乖了把异常值处理和缺失值插值这一步当成正式项目流程的前置模块花大力气做扎实后续每一步都顺畅很多。interpolate()线性插值不是银弹它适合处理短段的、前后数据走势平稳的缺失面对连续长缺失、剧烈波动时段还是要结合业务规则和人工判断。最好的做法是用统计方法识别问题用业务逻辑判断问题用插值方法修复少量问题用人工复核兜底大问题。这套流程看起来朴素但在真实项目中往往比任何花哨算法都管用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑