2. 材料数据清洗与缺失值处理

材料数据清洗与缺失值处理 学习卡片

任务对象与数据问题

本节使用一份包含 12 条记录的镍基高温合金性能数据集,字段包括:alloy_id、Cr_wt%、Co_wt%、Mo_wt%、Ti_wt%、Al_wt%、solution_T_C、aging_T_C、aging_h、YS_MPa、UTS_MPa、elong_%。数据中存在三类典型问题:

  • 缺失值:Co_wt%、Mo_wt%、YS_MPa、elong_% 等列存在空值。
  • 异常值:YS_MPa 出现 9999 的占位值,aging_h 出现负值,elong_% 出现 250 的明显越界值。
  • 单位不统一:solution_T_C 与 aging_T_C 部分记录以 K 为单位填写(如 1273、1173),需统一换算为 ℃。

清洗与插补流程

整体处理链为:读取原始表 → 单位统一 → 异常值识别与修正 → 缺失值插补 → 输出干净表。单位换算采用 T(℃) = T(K) - 273.15,对温度列中大于 1000 的值判定为 K 并换算。异常值处理策略:YS_MPa 中 9999 视为缺失后插补;aging_h 负值取绝对值;elong_% 大于 100 视为缺失后插补。缺失值插补采用按合金体系分组的中位数插补,对成分列用全体中位数,对性能列用同 Cr_wt% 分档的中位数,避免不同体系间差异被抹平。

合金性能数据集清洗与插补处理链 原始数据表 12 条合金记录 成分 / 工艺 / 性能 缺失 · 异常 · 单位混用 alloy_id 唯一 单位统一 T > 1000 判定为 K T(℃) = T(K) − 273.15 solution_T_C aging_T_C 异常值识别与修正 YS_MPa = 9999 → 缺失 aging_h < 0 → 取绝对值 elong_% > 100 → 缺失 保留原始记录索引 缺失值插补 成分列:全体中位数 性能列:Cr 分档中位数 记录插补来源标记 imputed_flag 字段级处理映射与输出结果 字段 问题类型 处理动作 输出状态 solution_T_C 单位混用 (K/℃) K → ℃ 换算 统一为 ℃ aging_T_C 单位混用 (K/℃) K → ℃ 换算 统一为 ℃ YS_MPa 9999 占位 + 缺失 9999 → NaN → 分档中位数 插补 + 标记 elong_% 250 越界 + 缺失 >100 → NaN → 分档中位数 插补 + 标记 aging_h 负值 取绝对值 修正为正值 Co_wt% / Mo_wt% 缺失 全体中位数插补 插补 + 标记 输出:clean_alloy.csv — 无缺失、无越界、单位统一、含 imputed_flag 标记

Python 实现

以下代码可直接运行,输入为 raw_alloy.csv,输出为 clean_alloy.csv。代码中同时打印每列插补数量,便于核对处理结果。

import pandas as pd
import numpy as np

# 1. 读取原始数据
df = pd.read_csv("raw_alloy.csv")
print("原始形状:", df.shape)
print("原始缺失:\n", df.isna().sum())

# 2. 单位统一:温度列中 >1000 的视为 K,换算为 ℃
for col in ["solution_T_C", "aging_T_C"]:
    mask_k = df[col] > 1000
    df.loc[mask_k, col] = df.loc[mask_k, col] - 273.15
    print(f"{col} 换算 K→℃ 条数: {mask_k.sum()}")

# 3. 异常值处理
# YS_MPa 中 9999 为占位异常值,置为 NaN
df.loc[df["YS_MPa"] == 9999, "YS_MPa"] = np.nan
# aging_h 负值取绝对值
df["aging_h"] = df["aging_h"].abs()
# elong_% 大于 100 视为越界,置为 NaN
df.loc[df["elong_%"] > 100, "elong_%"] = np.nan

# 4. 缺失值插补
# 4.1 成分列用全体中位数
comp_cols = ["Cr_wt%", "Co_wt%", "Mo_wt%", "Ti_wt%", "Al_wt%"]
for col in comp_cols:
    if df[col].isna().any():
        median_val = df[col].median()
        df[col] = df[col].fillna(median_val)
        print(f"{col} 中位数插补: {median_val:.3f}")

# 4.2 性能列按 Cr 含量分档中位数插补
df["Cr_bin"] = pd.cut(df["Cr_wt%"], bins=[0, 15, 20, 25, 100],
                      labels=["低Cr", "中Cr", "高Cr", "超高Cr"])
perf_cols = ["YS_MPa", "UTS_MPa", "elong_%"]
for col in perf_cols:
    if df[col].isna().any():
        df[col] = df.groupby("Cr_bin", observed=True)[col].transform(
            lambda s: s.fillna(s.median())
        )
        # 若某档全空,回退到全体中位数
        df[col] = df[col].fillna(df[col].median())

# 5. 记录插补标记
df["imputed_flag"] = df[["YS_MPa", "UTS_MPa", "elong_%",
                          "Co_wt%", "Mo_wt%"]].isna().any(axis=1).astype(int)

# 6. 输出干净表
df = df.drop(columns=["Cr_bin"])
df.to_csv("clean_alloy.csv", index=False)
print("清洗后形状:", df.shape)
print("清洗后缺失:\n", df.isna().sum())
print("输出文件: clean_alloy.csv")

结果判读与验证

运行后应满足以下条件,方可认为清洗完成:

  • df.isna().sum() 所有列均为 0,无残留缺失。
  • solution_T_C 与 aging_T_C 全部落在合理 ℃ 区间(如 800–1300 ℃)。
  • YS_MPa 无 9999,aging_h 无负值,elong_% 全部 ≤ 100。
  • imputed_flag 为 0/1,可追溯哪些行经过插补。
若某 Cr 分档内性能列全部缺失,分组中位数会返回 NaN,代码已回退到全体中位数。实际数据中若某档样本过少(如少于 3 条),建议合并相邻档或改用全体中位数,避免插补值不稳定。
不要用 0 填充成分或性能缺失值。0 在成分中代表“不含该元素”,在性能中代表“无强度”,都会引入严重偏差。中位数插补是保守且可解释的基线策略,后续建模时可通过 imputed_flag 做敏感性分析。