如何在Pandas中按列对含NaN的数据缩尾并保留数据结构
实现按列缩尾并保留原数据结构的解决方案
你遇到的问题其实可以通过两种简洁的方式解决,核心思路是对每列单独计算非NaN值的分位数,再对该列的非NaN值进行缩尾,同时保留原有的NaN和行结构。下面是具体的实现方法:
方法一:使用Pandas原生函数(推荐,无需额外依赖)
Pandas的clip()方法可以直接实现缩尾逻辑,配合quantile()计算分位数时跳过NaN,完美满足你的需求:
# 计算每列的1%和99%分位数(自动跳过NaN) quantiles = df.quantile([0.01, 0.99], skipna=True) # 对每列执行缩尾操作:将低于1%分位数的值替换为1%分位数,高于99%分位数的替换为99%分位数 # axis=1表示按列匹配分位数,原数据中的NaN会被保留 wins_pandas = df.clip(lower=quantiles.loc[0.01], upper=quantiles.loc[0.99], axis=1)
验证缩尾效果:
wins_pandas.quantile([0, 0.01, 0.5, 0.99, 1], skipna=True)
此时你会看到:
- 每列的最小值(0分位数)等于1%分位数
- 每列的最大值(1分位数)等于99%分位数
- 原数据中的NaN位置完全保留,行数和原DataFrame一致
方法二:正确使用Scipy的winsorize
你之前使用的scipy.stats.mstats.winsorize其实本身就支持忽略NaN并保留原数组长度,可能是你对结果的验证存在误解。调整函数实现如下:
from scipy.stats.mstats import winsorize def winsorize_single_col(s): # mstats.winsorize会自动忽略NaN,返回的数组中原有NaN位置保持不变 return winsorize(s, limits=[0.01, 0.01]) # 按列应用函数,保留原数据结构 wins_scipy = df.apply(winsorize_single_col, axis=0)
验证后你会发现,缩尾后的非NaN值已经被正确处理,NaN位置完全保留,不会丢失任何行。
为什么你之前的方法看起来“结果错误”?
你提到“其余列结果错误(99分位数与最大值应一致)”,这其实是缩尾后的正常现象:缩尾操作就是把超过99%分位数的所有值替换为99%分位数,因此缩尾后的最大值必然等于99%分位数。而你用df.quantile()查看结果时,函数会自动跳过NaN计算分位数,所以这个结果是完全正确的。
而删除所有含NaN行的方法会丢失大量数据,这两种方法都完美避免了这个问题,同时保证了缩尾效果的准确性。
内容的提问来源于stack exchange,提问作者Jesse Blocher
相关产品推荐
相关产品推荐

