3. 数据探查与剖析实战
拿到一批来自不同实验设备、不同采集批次的数据后,最忌讳的做法是直接开始清洗。清洗规则必须由数据本身的问题决定,而问题需要先被"看见"。本节围绕四类探查手段展开:描述性统计看数值分布与量纲,频率分布看类别构成与异常取值,缺失值矩阵看缺失的位置与模式,唯一值分析看主键、重复与常量列。四者组合,能在十几分钟内形成对数据集结构与问题的整体判断。
一、先建立数据画像:字段清单与类型判定
探查的第一步不是算统计量,而是把字段按语义分组。假设我们拿到三份实验数据:设备A导出的run_a.csv(含sample_id, batch, temp_c, pressure_kpa, ph, operator, measure_time),设备B导出的run_b.csv(含sample_id, batch, temp, press, ph_value, measure_time),以及一份人工记录的meta.xlsx(含sample_id, group, reagent_lot)。此时应先把字段分为四类:标识列(sample_id)、分组列(batch、group、operator)、测量列(temp、pressure、ph)、时间列(measure_time)。类型判定要警惕"看起来是数字的字符串"——例如temp列中混入"25.3*"或"ND",pandas 读入后整列会变成 object 类型,后续统计全部失效。因此读入时应显式指定 dtype 或先用 pd.to_numeric(errors='coerce') 试探转换,统计转换后产生的 NaN 数量,这个数量本身就是一条重要问题线索。
二、描述性统计:量纲、偏态与可疑极值
对测量列调用 describe() 并补充偏度与缺失率,是性价比最高的动作。以温度为例,若设备A的temp_c给出 count=1180、mean=24.8、std=1.9、min=22.1、50%=24.7、max=26.3,而设备B的temp给出 count=1195、mean=297.6、std=2.1、min=295.2、max=299.8,则一眼可判断:B的温度单位是开尔文,需减去273.15后再合并。压力列若出现 min=0 而 25% 分位数是 101.2,说明存在零值占位,很可能是缺失被填成了0,这类"伪有效值"必须靠最小值与低分位数对比才能发现。pH 列若 max=14.2,则超出理论范围,属于录入或传感器饱和问题。偏度绝对值大于1时,提示分布长尾,后续标准化应考虑对数变换或稳健缩放,而不是直接做 z-score。
描述性统计还应横向对比:同一物理量在不同来源下的均值差、标准差比。若设备A的 pH 标准差为0.05而设备B为0.42,在排除真实实验差异后,应怀疑B的传感器漂移或读数精度不足,这类判断直接影响后续是否对B做平滑或降权。
三、频率分布:类别构成与"隐形异常值"
对分组列做 value_counts(dropna=False),重点看三件事:类别是否拼写不一致(如 Batch01 与 batch01、张三 与 张三 带尾空格)、是否存在低频类别(出现1~2次的批次往往是录入错误或测试残留)、缺失是否被编码成特殊字符串("-"、"NA"、"未知")。对测量列则用分箱后的直方图或 value_counts 观察离散化取值:若某压力列 90% 的记录集中在 101.3,其余散落,说明该列可能大部分是默认值填充。频率分布还能暴露"哨兵值",例如温度列中 -999 出现 37 次,这类值在均值上会被稀释,但在频次表里非常醒目。
四、缺失值矩阵:位置比数量更重要
缺失率本身只是标量,真正有价值的是缺失的空间模式。做法是先 df.isna() 得到布尔矩阵,再按列求均值得到缺失率,按行求和得到每条记录的缺失字段数,最后按批次分组统计缺失率。三种典型模式对应三种处理策略:随机散点缺失(各批次缺失率接近,字段间无共现)可考虑插补;整列缺失(某字段缺失率接近100%)应直接剔除该字段并回溯采集配置;整段缺失(某批次某时间段全部为空)往往意味着设备停机或文件截断,属于结构性缺失,插补会制造虚假数据,正确做法是标记该段为无效并回采。若发现 ph 与 pressure 的缺失高度共现(同一批记录同时为空),说明二者来自同一采集通道,应作为一个整体判断而非分别插补。
五、唯一值分析:主键、重复与常量列
对每个字段计算 nunique(dropna=False),并检查 sample_id 是否满足唯一性。若 len(df) - df['sample_id'].nunique() = 12,说明存在12条重复主键,需进一步用 duplicated(subset='sample_id', keep=False) 定位,判断是完全重复行还是同一样本多次测量(后者应保留但需加测量序号)。nunique=1 的列是常量列,例如 operator 全为同一人,对建模无信息量,可在合并前剔除以免干扰。此外,nunique 异常高的类别列(如 batch 有200个取值而样本仅1200条)提示编码粒度过细或存在自由文本污染,需要规范化。
六、把四类视图串成一次探查流程
实际操作可按如下顺序执行,每步产出一条结论再进入下一步:
import pandas as pd
a = pd.read_csv("run_a.csv", dtype=str)
b = pd.read_csv("run_b.csv", dtype=str)
m = pd.read_excel("meta.xlsx", dtype=str)
# 1 类型试探:统计无法转数值的个数
for col in ["temp_c", "pressure_kpa", "ph"]:
bad = pd.to_numeric(a[col], errors="coerce").isna().sum()
print(col, "非数值/缺失:", bad)
# 2 描述性统计(转换后)
num = a[["temp_c", "pressure_kpa", "ph"]].apply(pd.to_numeric, errors="coerce")
print(num.describe().T)
print("偏度:", num.skew())
# 3 频率分布
print(a["batch"].value_counts(dropna=False).head(10))
print(a["pressure_kpa"].value_counts().head(5))
# 4 缺失矩阵:按批次看缺失率
miss = a.isna().groupby(a["batch"]).mean()
print(miss)
# 5 唯一值
print("sample_id 重复数:", a["sample_id"].duplicated().sum())
print(a.nunique(dropna=False).sort_values())
示例中若输出显示 temp 非数值37条、pressure_kpa 最小值0且频次表首位为101.3、batch 出现 Batch01 与 batch01 两个键、sample_id 重复12条、operator 唯一值为1,则探查结论可直接写成一张问题—证据—处置的对照表,作为下一节清洗规则的输入。需要强调的是,上述数值均为教学示例,真实项目应以实际输出为准;探查阶段不修改数据,只记录证据,避免"边看边改"导致问题被掩盖而无法追溯。