4. 数据清洗与预处理:缺失值处理、异常值识别、数据标准化与归一化、数据合并(Merge/Join)

各位工程师,欢迎来到第四章。数据清洗,说白了就是给数据“洗澡”。

我见过太多人,拿到数据就急着建模跑分析。结果呢?模型跑出来一塌糊涂。为什么?因为数据本身就有问题。你想想看,垃圾进,垃圾出,这个道理在半导体良率分析里特别明显。

这一章,我把自己踩过的坑、积累的经验,全部分享给你。咱们从四个核心环节入手:缺失值、异常值、标准化、数据合并。

4.1 缺失值处理:别让“空”坑了你

在Fab里,数据缺失太常见了。设备宕机、传感器故障、人为漏录……原因五花八门。我个人习惯,拿到数据第一件事就是看缺失率。

核心原则:缺失率低于5%,直接删除行;缺失率在5%-20%,考虑填充;超过20%,这个特征基本废了,建议直接扔掉。

填充方法,我常用这几种:

  • 均值/中位数填充:适合正态分布的数据。比如CD(关键尺寸)测量值,用中位数填充比较稳。
  • 前向/后向填充:时间序列数据,比如机台PM后的参数漂移,用前向填充很合理。
  • 模型预测填充:用KNN或回归模型预测缺失值。我在处理WAT(晶圆允收测试)数据时用过,效果不错,但计算量大。

我的经验:千万别用0去填充缺失值!有一次,一个同事把缺失的良率数据填了0,结果模型直接崩了。0在良率数据里是极端异常值,会严重扭曲分布。

# Python示例:缺失值处理
import pandas as pd
import numpy as np

# 读取数据
df = pd.read_csv('yield_data.csv')

# 查看缺失率
missing_rate = df.isnull().mean()
print(missing_rate)

# 中位数填充
df['CD_measurement'].fillna(df['CD_measurement'].median(), inplace=True)

# 前向填充(时间序列)
df['temperature'].fillna(method='ffill', inplace=True)

4.2 异常值识别:揪出“害群之马”

异常值,就是数据里的“老鼠屎”。在半导体良率分析中,异常值往往对应着设备异常、工艺偏移或者测量错误。

我曾经遇到过一个案例:一批晶圆的良率突然暴跌,查了三天没找到原因。最后发现,是一个探针卡接触不良,导致测试数据全是异常值。你看,异常值识别有多重要。

我常用的识别方法:

  1. 3σ原则:适用于正态分布的数据。超出均值±3个标准差的值,视为异常。
  2. 箱线图法(IQR):不依赖分布假设。Q1-1.5*IQR 到 Q3+1.5*IQR 之外的值,就是异常。
  3. 领域知识法:这个最靠谱。比如,光刻胶厚度不可能为负,也不可能超过100μm。超出物理极限的,直接标记。

注意:异常值不一定要删除!有时候,异常值本身就是重要信号。比如,某个机台的温度突然飙升,这可能是故障前兆。我建议先标记,再根据业务判断是否保留。

# Python示例:箱线图法识别异常值
Q1 = df['yield_rate'].quantile(0.25)
Q3 = df['yield_rate'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers = df[(df['yield_rate'] < lower_bound) | (df['yield_rate'] > upper_bound)]
print(f"发现 {len(outliers)} 个异常值")

4.3 数据标准化与归一化:让数据“站在同一起跑线”

为什么需要标准化?你想想看,温度是几十度,压力是几百帕,良率是百分之几十。量纲不同,直接放一起分析,结果肯定偏。

我见过有人直接用原始数据跑PCA(主成分分析),结果第一主成分完全被压力这个特征主导了。嗯,这就是没标准化的后果。

两种常用方法:

方法 公式 适用场景
Z-score标准化 (x - μ) / σ 数据近似正态分布,适合回归、SVM等模型
Min-Max归一化 (x - min) / (max - min) 数据有明确边界,适合神经网络、KNN

我的建议:在半导体良率分析中,我更喜欢用Z-score。因为良率数据经常有离群点,Min-Max会被极端值压缩,而Z-score相对稳健。当然,如果你用深度学习,还是归一化更合适。

# Python示例:标准化与归一化
from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Z-score标准化
scaler_std = StandardScaler()
df_scaled = scaler_std.fit_transform(df[['temperature', 'pressure', 'yield_rate']])

# Min-Max归一化
scaler_mm = MinMaxScaler()
df_normalized = scaler_mm.fit_transform(df[['temperature', 'pressure', 'yield_rate']])

4.4 数据合并(Merge/Join):把碎片拼成拼图

半导体数据,从来不是一张表能搞定的。WAT数据、CP测试数据、FT测试数据、设备日志……它们分散在不同的系统里。数据合并,就是把碎片拼成完整的拼图。

我记得有一次做良率根因分析,需要把WAT数据和CP数据合并。两个表的晶圆ID格式不一样,一个带字母前缀,一个纯数字。折腾了半天,最后发现是编码规则变了。嗯,这种坑,我踩过不止一次。

合并的三种方式:

  • Inner Join:只保留两个表都有的记录。适合严格匹配的场景。
  • Left/Right Join:保留主表所有记录。适合以某个表为主的情况。
  • Outer Join:保留所有记录。适合数据完整性要求高的场景。

避坑指南:合并前,一定要检查键的唯一性和数据类型。我曾经因为一个空格没去掉,导致合并后数据量翻了三倍。还有,注意重复列名,合并后记得加后缀区分。

# Python示例:数据合并
# 表1:WAT数据
df_wat = pd.DataFrame({
    'wafer_id': ['W001', 'W002', 'W003'],
    'Vt': [0.45, 0.48, 0.42]
})

# 表2:CP测试数据
df_cp = pd.DataFrame({
    'wafer_id': ['W001', 'W002', 'W004'],
    'yield': [92.5, 95.1, 88.3]
})

# Inner Join
df_inner = pd.merge(df_wat, df_cp, on='wafer_id', how='inner')
print(df_inner)

# Left Join
df_left = pd.merge(df_wat, df_cp, on='wafer_id', how='left')
print(df_left)

好了,数据清洗与预处理就讲到这里。这四个环节,每一个都值得你花时间打磨。记住,好的数据清洗,是良率分析成功的一半。下一章,咱们聊聊特征工程,看看怎么从数据里挖出更多有价值的信息。