3-1 缺失值的化验单


文档摘要

3-1 缺失值的化验单:探测与处置 本节摘要:isna族负责探测缺失,dropna负责丢弃,fillna与interpolate负责补值。本节的核心不是函数参数,而是先判语义再选策略:真缺失、未采集、不适用三种缺失对应三种完全不同的医嘱。 承接第02章结尾的伏笔:布尔条件静默丢弃缺失行。内科第一步是把这些躲过筛选的缺失行正式揪出来,开一张化验单。 先出化验单 三张化验单各有用途:逐格探测用于定位,列占比用于决策(占比超三成的列补值意义已存疑),info用于总览。 NaN的血型知识 💡 关键直觉:判断缺失只有isna一个入口。所有拿NaN去比较的写法都是静默的空结果,不报错、不出数,是最阴险的一类bug。

3-1 缺失值的化验单:探测与处置

本节摘要:isna族负责探测缺失,dropna负责丢弃,fillna与interpolate负责补值。本节的核心不是函数参数,而是先判语义再选策略:真缺失、未采集、不适用三种缺失对应三种完全不同的医嘱。

承接第02章结尾的伏笔:布尔条件静默丢弃缺失行。内科第一步是把这些躲过筛选的缺失行正式揪出来,开一张化验单。

先出化验单

import pandas as pd import numpy as np df = pd.DataFrame({ '门店': ['A', 'B', 'A', 'C', 'A'], '金额': [1280, np.nan, 1732, np.nan, 880], '会员': ['M23', None, 'M08', 'M23', None], }) # 口径一:逐格探测 print(df.isna()) # 门店 金额 会员 # 0 False False False # 1 False True True # 2 False False False # 3 False True False # 4 False False True # 口径二:每列缺失数与占比 print(df.isna().sum()) # 门店 0 # 金额 2 # 会员 2 print((df.isna().mean() * 100).round(1)) # 门店 0.0 # 金额 40.0 # 会员 40.0 # 口径三:info一把梭,规模与稀疏度同看 df.info() # <class 'pandas.core.frame.DataFrame'> # RangeIndex: 5 entries, 0 to 4 # Data columns (total 3 columns): # # Column Non-Null Count Dtype # 0 门店 5 non-null object # 1 金额 3 non-null float64 # 2 会员 3 non-null object

三张化验单各有用途:逐格探测用于定位,列占比用于决策(占比超三成的列补值意义已存疑),info用于总览。

NaN的血型知识

print(np.nan == np.nan) # False!NaN不等于任何值包括自己 print(pd.isna(None)) # True:None在Pandas里被认作缺失 print(pd.Series([1, None]).dtype) # float64,整数列沾缺失即升格浮点 # 由此推论:== np.nan 永远筛不出缺失,必须用 isna # df[df['金额'] == np.nan] 永远是空表 # df[df['金额'].isna()] 才是对的

💡 关键直觉:判断缺失只有isna一个入口。所有拿NaN去比较的写法都是静默的空结果,不报错、不出数,是最阴险的一类bug。

处置:丢弃的三个旋钮

# 旋钮一 axis:按行丢还是按列丢 print(df.dropna(axis=0).shape) # 缺失行全丢 (1, 3) print(df.dropna(axis=1).shape) # 含缺失的列全丢 (5, 1) # 旋钮二 how:any有一颗就丢,all整行烂透才丢 print(df.dropna(how='all').shape) # (5, 3) 没有全空行 print(df.dropna(subset=['金额']).shape) # (3, 3) 只盯金额列 # 旋钮三 thresh:至少保留几个有效值 print(df.dropna(thresh=2).shape) # (3, 3) 至少2列有效的行留下

subset是最常用也最容易被忽略的旋钮:会员缺失很正常(散客),金额缺失才致命。判重的标准应该盯住关键列,而不是整行。

补值的三种策略

# 策略一:固定值与统计量 filled = df['金额'].fillna(df['金额'].median()) print(filled) # 0 1280.0 # 1 1080.0 # 2 1732.0 # 3 1080.0 # 4 880.0 # 策略二:前后值填充,适合有序数据(时间序列详见第07章) ts = pd.Series([100, None, None, 130], index=['1月', '2月', '3月', '4月']) print(ts.ffill()) # 1月 100.0 # 2月 100.0 # 3月 100.0 # 4月 130.0 # 策略三:插值,按位置线性估计 print(ts.interpolate()) # 1月 100.0 # 2月 110.0 # 3月 120.0 # 4月 130.0

选哪种?我的经验法则:类别列用众数或"未知"占位,数值列看有没有顺序——有顺序用插值或ffill,没顺序用中位数;均值要慎用,离群值会把均值带偏(离群值的判定是4-2的事)。

缺失处置决策路径

三种缺失语义对应的医嘱

三种缺失语义对应的医嘱

本节要点回顾

  • 三张化验单:逐格isna定位、列占比决策、info总览
  • NaN不等于自身,缺失判断只有isna一个正规入口
  • dropna三旋钮:axis、how与subset,subset盯关键列最实用
  • 补值三策略:统计量、前后填充、插值,按列的顺序性选择
  • 语义先行:真缺失保留、未采集补、不适用占位,混同必出荒谬值

追问:插值和ffill到底选哪个

补值策略最容易摇摆的就是这两个。判断依据是数据的"顺序里有没有信息":每日销量这种均匀时间序列,中间缺一天,插值按两侧水平线性估计是合理的;累计指标(库存余额、会员总数)只会继承不会凭空算,ffill才是对的;而散客的交易记录之间没有顺序含义,任何补值都是编造,保留NaN让聚合自动跳过才是诚实的做法。再补一个实战细节:插值默认只对"中间的洞"生效,序列开头的缺失插不出来(前面没有参照),那类洞要么回源补采,要么明确标记后保留。

ts = pd.Series([None, 110, None, 130, None], index=range(5)) print(ts.interpolate(limit_area='inside')) # 0 NaN 开头的洞插不了 # 1 110.0 # 2 120.0 # 3 130.0 # 4 NaN

一张完整的缺失处置单

把本节器械串成流程:探测、定语义、选策略、验结果。

report = pd.DataFrame({ '门店': ['A', 'B', 'A', 'C'], '库存': [120, None, 135, None], # 存量指标 '销量': [30, 25, None, 40], # 流量指标 }) report['库存'] = report['库存'].ffill() # 存量:前值继承 report['销量'] = report['销量'].fillna(0) # 流量:当天无单记零 print(report.isna().sum().sum()) # 0,处置闭环

同一家店的两列用了两种策略——策略跟着指标语义走,不跟着列走。这张处置单的思路可以原样搬进任何真实业务表。

下一节处理内科的第二类基础病:重复记录。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U