3. 硅光晶圆数据清洗实战

硅光晶圆测试数据的原始形态往往比想象中更“脏”。同一片晶圆可能经历多次光学耦合测试、电学IV扫描和光谱扫描,不同测试机台导出的CSV字段名不一致,有的用InsertionLoss_dB,有的写成IL(dB);时间戳有的带时区,有的只精确到秒;功率单位混用dBm与mW;某些测试点因耦合失败直接留空。如果直接把这些数据送入良率模型,缺失值会被当作0参与统计,异常值会拉偏均值,重复记录会让同一器件被重复计数,单位不统一则导致阈值判断完全失效。因此,数据清洗不是“预处理的可选项”,而是硅光良率分析能否成立的前提。

本节以一个典型的硅光晶圆测试数据集为例,完整走一遍清洗链路。假设我们有一份来自某8英寸硅光晶圆的数据,包含晶圆ID、芯片坐标(die_x, die_y)、测试时间、插入损耗、消光比、工作波长、输出光功率、偏置电压等字段,共约12万行。下面按缺失值、异常值、重复记录、单位统一、时间戳对齐五个环节展开,最后给出可复用的Python清洗脚本。

缺失值处理:先判断缺失机制,再决定填充策略

硅光数据中的缺失通常分三类。第一类是结构性缺失,例如某些芯片没有做光谱扫描,wavelength_nm整列为空,这类缺失不应填充,而应保留为“未测试”标记。第二类是随机缺失,例如光功率计偶发通信超时导致output_power_dBm为空,这类可按同晶圆同测试条件的均值填充。第三类是耦合失败导致的缺失,例如insertion_loss_dB为空且coupling_flag为0,这类应直接标记为失效,不能填充。

实际操作中,先用df.isnull().sum()统计各列缺失比例。若某列缺失超过60%,优先考虑整列剔除或转为布尔标记;若缺失在5%以内,可对连续型字段用中位数填充,对类别型字段用众数填充。对于硅光插入损耗这类对耦合敏感的参数,更稳妥的做法是按wafer_id和test_station分组后填充组内中位数,避免用全局均值掩盖机台差异。

import pandas as pd
import numpy as np

df = pd.read_csv("si_photonics_wafer_raw.csv")

# 缺失比例统计
missing_ratio = df.isnull().mean().sort_values(ascending=False)
print(missing_ratio[missing_ratio > 0])

# 结构性缺失:整列缺失超过60%则剔除
cols_to_drop = missing_ratio[missing_ratio > 0.6].index.tolist()
df = df.drop(columns=cols_to_drop)

# 随机缺失:按晶圆+测试站分组填充中位数
for col in ["insertion_loss_dB", "extinction_ratio_dB", "output_power_dBm"]:
    if col in df.columns:
        df[col] = df.groupby(["wafer_id", "test_station"])[col]\
                    .transform(lambda s: s.fillna(s.median()))

# 耦合失败标记:不填充,保留失效语义
df["coupling_fail"] = df["coupling_flag"].eq(0)

异常值识别:用物理边界与统计边界双重约束

硅光参数的异常值不能只靠3σ法则。插入损耗在C波段通常为1~8 dB,若出现-20 dB或50 dB,前者可能是参考校准错误,后者可能是耦合完全失败。消光比一般在10~30 dB,若为0则说明调制器未工作。因此异常值识别应同时使用物理边界和统计边界:物理边界由器件原理和测试条件决定,统计边界用IQR或MAD在组内计算。

推荐流程是:先按wafer_id和test_station分组,对每个参数计算Q1、Q3和IQR,将超出[Q1-1.5*IQR, Q3+1.5*IQR]的点标记为统计异常;再叠加物理边界,例如insertion_loss_dB必须在0~15 dB之间,extinction_ratio_dB必须在5~40 dB之间。两者取并集作为最终异常标记。异常值不直接删除,而是新增is_outlier列,便于后续按比例评估良率损失。

def mark_outliers(group, col, low, high):
    q1, q3 = group[col].quantile([0.25, 0.75])
    iqr = q3 - q1
    stat_mask = (group[col] < q1 - 1.5*iqr) | (group[col] > q3 + 1.5*iqr)
    phys_mask = (group[col] < low) | (group[col] > high)
    group[f"{col}_outlier"] = stat_mask | phys_mask
    return group

df = df.groupby(["wafer_id", "test_station"], group_keys=False)\
       .apply(lambda g: mark_outliers(g, "insertion_loss_dB", 0, 15))
df = df.groupby(["wafer_id", "test_station"], group_keys=False)\
       .apply(lambda g: mark_outliers(g, "extinction_ratio_dB", 5, 40))

重复记录去重:区分完全重复与业务重复

硅光测试数据中的重复有两种。完全重复是同一行数据被多次导出,直接drop_duplicates()即可。业务重复更隐蔽:同一芯片在同一测试站被重复测试多次,时间戳不同但器件坐标相同。此时不能简单去重,而应保留最后一次有效测试,或按测试时间取最新记录。判断依据是wafer_id + die_x + die_y + test_station组合键。

# 完全重复
df = df.drop_duplicates()

# 业务重复:同一芯片同一测试站保留最新一次
df = df.sort_values("test_time")\
       .drop_duplicates(subset=["wafer_id", "die_x", "die_y", "test_station"],
                        keep="last")

单位统一与时间戳对齐

单位统一的核心是建立“基准单位”并做显式换算。建议光功率统一为dBm,插入损耗统一为dB,波长统一为nm,电压统一为V。若原始数据中output_power混用mW和dBm,可按P_dBm = 10*log10(P_mW)换算。时间戳对齐则要求所有测试记录统一到同一时区,并转换为datetime64[ns]类型。若不同机台时间戳精度不同,可统一截断到秒,再按wafer_id排序生成测试顺序号。

# 单位统一:mW -> dBm
mask_mw = df["power_unit"] == "mW"
df.loc[mask_mw, "output_power_dBm"] = 10 * np.log10(df.loc[mask_mw, "output_power"])
df.loc[~mask_mw, "output_power_dBm"] = df.loc[~mask_mw, "output_power"]

# 时间戳对齐
df["test_time"] = pd.to_datetime(df["test_time"], utc=True)\
                    .dt.tz_convert("Asia/Shanghai")\
                    .dt.floor("s")
df = df.sort_values(["wafer_id", "test_time"])
df["test_seq"] = df.groupby("wafer_id").cumcount() + 1
硅光晶圆数据清洗链路与关键判断 原始CSV 12万行 · 多机台 字段名不统一 缺失值处理 结构性缺失→剔除 随机缺失→组内中位数 耦合失败→标记不填充 异常值识别 IQR统计边界 + 物理边界 标记不删除 重复记录去重 完全重复→drop 业务重复→保留最新 键:晶圆+坐标+站 单位/时间 mW→dBm 统一时区 截断到秒 清洗后数据集 字段统一 · 单位一致 · 时间有序 · 异常标记 清洗关键参数与判断依据(示例) 字段 物理边界 缺失策略 异常处理 insertion_loss_dB 0 ~ 15 dB 组内中位数 IQR + 物理边界 extinction_ratio_dB 5 ~ 40 dB 组内中位数 IQR + 物理边界 output_power_dBm -10 ~ 10 dBm 组内中位数 单位换算后标记 test_time UTC+8 不可填充 截断到秒排序

清洗脚本的组织与验证

把上述步骤封装成一个可复用的清洗函数,输入原始CSV路径,输出清洗后DataFrame和清洗报告。清洗报告应包含:各列缺失填充数量、异常值标记数量、重复记录删除数量、单位换算记录数、时间戳对齐后的时间范围。验证时重点检查三点:清洗后insertion_loss_dB是否全部落在物理边界内;同一wafer_id + die_x + die_y + test_station是否唯一;test_time是否单调不减。若这三点通过,数据即可进入良率统计与建模环节。

清洗脚本建议保留原始数据副本,所有清洗操作在副本上进行,并用is_outlier、coupling_fail等标记列记录处理痕迹,便于后续追溯和良率损失归因。

需要强调的是,硅光数据清洗没有“一刀切”的参数。不同晶圆厂、不同测试机台、不同产品代的物理边界和缺失机制都可能不同。本节给出的边界值和填充策略属于教学示例,实际使用时必须结合具体工艺和测试规范调整。清洗的目标不是让数据“看起来干净”,而是让后续的良率分析建立在可信、可追溯、可复现的数据基础上。