数据清洗与预处理

生理学实验通常产生多模态、纵向、个体化的数据:既有连续时间序列,也有事件标记、离散采样和实验室指标。原始记录往往包含缺失、噪声、伪迹、时间不同步、单位不一致和批间差异。数据清洗与预处理的目标不是“让数据好看”,而是建立一条从原始记录到可分析数据集的证据链,使结果可重复、可比较、可解释。

在生理学研究中,清洗与预处理应服务于科学问题。例如,研究自主神经调节时,需要保留与事件相关的时间精度;研究代谢或体温节律时,则更关注慢变趋势和昼夜相位。不同子主题的专属处理方法会各有侧重,但总览层面的原则一致:先理解数据生成过程,再选择处理策略。

通用处理流程

一个稳健的预处理流程通常包括以下步骤:

  1. 数据审计与元数据整理
    明确受试者编号、分组、采样率、设备、单位、事件定义和缺失标记。元数据不完整时,后续清洗很容易产生错误解释。

  2. 质量评估与可视化
    对每个受试者、每个通道绘制时间序列图、分布图和缺失热图。可视化能发现自动算法难以识别的漂移、饱和或阶段性伪迹。

  3. 缺失值处理
    区分随机缺失与系统性缺失。短时缺失可插值,长时缺失或关键事件缺失应标记为无效,而不是强行填补。

  4. 异常值与伪迹识别
    可使用固定阈值、分位数、稳健 z 分数或模型残差。生理信号中的异常可能是真实反应,也可能是运动、电极松动或环境干扰,需结合事件记录判断。

  5. 时间对齐与重采样
    多设备采集时,先统一时间基准,再按分析需求重采样。重采样会改变信号频谱和峰值,应记录方法与参数。

  6. 变换与标准化
    根据问题选择基线校正、滤波、对数变换、z 分数或百分位归一化。标准化参数应在训练集上拟合,再应用于验证集和测试集,避免数据泄漏。

  7. 文档与版本控制
    保存原始数据、清洗脚本、参数和中间版本。任何不可逆步骤都应保留可追溯记录。

横向对比:不同生理数据模态的共性

神经与内分泌调节、循环呼吸与排泄、消化代谢与体温、免疫防御与机体稳态等方向的数据形态不同,但清洗逻辑有共性:

  • 个体基线差异大:常需受试者内标准化或基线校正。
  • 时间依赖强:不能随意打乱顺序,插值和滤波需考虑时间结构。
  • 多源同步要求高:事件、刺激、采样和实验记录必须对齐。
  • 伪迹来源多样:运动、呼吸、电极、采血时间、批次效应等。
  • 真实变异与噪声难分:需结合生理机制和实验设计判断。

因此,预处理不是一套固定公式,而是“通用原则 + 模态适配”的组合。

实用示例:一个通用清洗流水线

下面以 Python 的 pandas 为例,展示一个简化但通用的生理数据清洗流程。假设数据包含受试者编号、时间、信号值和事件标记。

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "subject_id": ["S1", "S1", "S1", "S1", "S2", "S2", "S2", "S2"],
    "time_s": [0, 1, 2, 3, 0, 1, 2, 3],
    "signal": [10.2, np.nan, 10.8, 99.0, 8.1, 8.3, np.nan, 8.6],
    "event": [0, 0, 1, 0, 0, 0, 1, 0]
})

# 1. 统一时间与数值类型
df["time_s"] = pd.to_numeric(df["time_s"], errors="coerce")
df["signal"] = pd.to_numeric(df["signal"], errors="coerce")

# 2. 按受试者插值短时缺失
df["signal"] = df.groupby("subject_id")["signal"].transform(
    lambda s: s.interpolate(limit_direction="both")
)

# 3. 基于稳健 z 分数识别异常值
def robust_z(s):
    med = s.median()
    mad = (s - med).abs().median()
    if mad == 0:
        return pd.Series(0, index=s.index)
    return 0.6745 * (s - med) / mad

df["rz"] = df.groupby("subject_id")["signal"].transform(robust_z)
df.loc[df["rz"].abs() > 3.5, "signal"] = np.nan

# 4. 再次插值并生成标准化信号
df["signal"] = df.groupby("subject_id")["signal"].transform(
    lambda s: s.interpolate(limit_direction="both")
)
df["signal_z"] = df.groupby("subject_id")["signal"].transform(
    lambda s: (s - s.mean()) / s.std(ddof=0)
)

print(df)

该示例体现了几个关键点:按受试者处理、使用稳健统计、保留事件列、区分原始信号与标准化信号。实际研究中,还应保存每一步的参数和中间结果。

常见陷阱与检查清单

  • 不要用全局均值填补缺失值,忽略个体基线。
  • 不要在划分训练集和测试集之前拟合标准化参数。
  • 不要过度滤波,以免削弱真实生理波动。
  • 不要忽略事件标记与信号的时间对齐。
  • 不要删除异常值而不记录原因。
  • 不要只保留清洗后数据,原始数据必须可追溯。

预处理完成后,建议核对以下清单:

  • 每个受试者、每个通道是否都有质量报告?
  • 缺失、异常、插值和排除规则是否明确?
  • 时间基准、采样率和单位是否统一?
  • 标准化参数是否只来自训练集?
  • 处理脚本和版本是否可复现?

数据清洗与预处理是生理学实验方法中的基础环节。它连接原始记录与统计分析,也直接影响结论的可靠性。把通用原则、模态特点和可追溯性结合起来,才能为后续深入分析提供可信的数据基础。