数据标准化与预处理:变量命名规范、单位统一、缺失值填充策略、异常值标记规则
好,咱们直接进入第4章。集群间结果一致性的坑,八成出在预处理这个环节。我个人习惯把预处理比作「地基」——地基歪了,后面楼盖得再漂亮也得塌。你想想看,两个集群如果连变量名都对不上,单位也各搞一套,那对比还有什么意义?今天咱们就把这四块硬骨头啃下来。
一、变量命名规范:别让名字成为事故现场
我在项目中遇到过最离谱的事:同一个变量,集群A叫patient_id,集群B叫PatientID,集群C叫PAT_ID。最后合并的时候,脚本跑出来全是缺失——你以为字段不匹配,其实只是大小写和分隔符的锅。
核心原则:约定大于配置。不管用蛇形、驼峰还是全大写,整个项目必须统一。我推荐一套通用规则:
- 统一使用小写字母 + 下划线(snake_case)
- 避免特殊字符(空格、$、@、#)
- 变量名不超过32字符,但也不小于2字符
- 禁止使用纯数字开头
- 每个字段加一层源标签前缀(如
qa_、prod_)
| 错误示例 | 正确示例 | 说明 |
|---|---|---|
| Name / name2 | name | 大小写统一,去掉无意义后缀 |
| 第1次测量值 | measure_1 | 禁止中文字段名,数字放末尾 |
| Date-Time | date_time | 连字符改为下划线 |
| result (不同集群含义不同) | clustera_result / clusterb_result | 加集群前缀避免歧义 |
bg_year(背景年?不,是“报告”的拼音开头)。这种名字让三拨人猜了三天。后来我们强制所有新字段必须走命名注册表,违者要被拉去小黑屋review代码。
二、单位统一:小数点挪一位,结果差十倍
单位不一致是集群间差异的隐形杀手。说白了,集群A用毫米,集群B用厘米,你直接拿一列除另一列,结果能不崩吗?我见过一个案例:肿瘤直径A集群存的是mm,B集群存的是cm,最后模型训练出来的尺寸分布直接对不上——那叫一个酸爽。
统一策略分三步:
- 建立单位字段——每个数值变量旁边必须有一个单位字段(如
weight_unit = "kg") - 转成基准单位——可配置的转换表,运行时统一转成SI单位
- 校验阈值——转换后如果数值超出合理范围,直接标记异常
看一个简单的单位转换脚本(Python 示例):
unit_conversion = {
'mm': 0.001,
'cm': 0.01,
'm': 1.0,
'km': 1000,
'inch': 0.0254,
'g': 0.001,
'kg': 1.0,
'mg': 0.000001
}
def normalize_quantity(value, unit, target_unit='m'):
if unit not in unit_conversion:
raise ValueError(f"Unknown unit: {unit}")
base_value = value * unit_conversion[unit]
return base_value / unit_conversion[target_unit]
三、缺失值填充策略:别乱填,先问原因
缺失值处理,我见过最野的做法是直接填0——嗯,填完模型预测率直接掉10个点。你想想看:身高缺失填0,体表面积填0,这科学吗?
正确的思路是:先分类型,再定策略。我个人习惯把缺失分为三类:
- 随机缺失(MCAR)——数据完全随机少了几条,可以考虑均值/中位数填充,或者直接删除(比例<5%时)
- 受其他变量影响的缺失(MAR)——比如年龄大的患者更容易漏填某项,可以用回归或MICE插补
- 非随机缺失(MNAR)——缺失本身就带有信息(比如设备故障导致的连续缺失),这时候绝对不能补,必须保留缺失标记
还拿不准?用这个判断流程:
missing_ratio = df['weight'].isna().mean()
if missing_ratio < 0.05:
# 直接删除带缺失的行
df.dropna(subset=['weight'], inplace=True)
elif missing_ratio < 0.3:
# 用KNN插补(取最近邻3个样本的均值)
from sklearn.impute import KNNImputer
imputer = KNNImputer(n_neighbors=3)
df['weight'] = imputer.fit_transform(df[['weight', ...]])
else:
# 保留缺失列,并加一个标记列
df['weight_missing'] = df['weight'].isna().astype(int)
# 同时用其他模型预测填充(比如线性回归)
四、异常值标记规则:宁可标错,不可漏标
异常值处理,我坚持一个原则:先标记,后决策。绝不直接删除。你想想看,一个离群点可能是数据采集错误,也可能是真正的罕见病例——丢了它,分析结论可能就变了。
具体的标记规则要提前约定:
- IQR规则:超出 [Q1 - 1.5×IQR, Q3 + 1.5×IQR] 标记为可疑异常
- Z-score规则:|z| > 3 标记为极端异常
- 业务规则:比如收缩压 < 40 或 > 300 mmHg 直接标“物理不可能”
每个异常值生成一个标记字段,保留原始数据。看一个示例逻辑:
def flag_outliers(series, method='iqr'):
flags = pd.Series(0, index=series.index, dtype=str)
if method == 'iqr':
q1 = series.quantile(0.25)
q3 = series.quantile(0.75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
flags[series < lower] = 'low_outlier'
flags[series > upper] = 'high_outlier'
elif method == 'zscore':
z = (series - series.mean()) / series.std()
flags[abs(z) > 3] = 'extreme_outlier'
return flags
{
"outlier_method": "iqr",
"iqr_multiplier": 1.5,
"zscore_threshold": 3.0,
"physical_bounds": {
"systolic_bp": [40, 300],
"heart_rate": [20, 220]
}
}
配置文件版本必须打进集群环境,每次加载都校验哈希。
最后聊两句配置管理
这四个步骤,每个都需要一个配置文件或代码库来实现集群间同步。我个人推荐用YAML写一个主控文件,Git里锁死版本。任何修改都要走PR流程,不然两个集群的预处理参数错开一次,后面就得花一周追查差异来源。嗯,说白了,预处理就是一场精细的“对齐”游戏——认真玩,后面就轻松。