本文介绍在高维稀疏医学数据(如含5300列代谢物、200例样本且缺失值密集)中正确实施pca的方法,重点解析跳过完整样本删

在医学代谢组学研究中,PCA常被用于降维与疾病分型探索——例如区分疾病1(D1)、疾病2(D2)及健康对照。但现实数据(如您描述的Excel中200行×5300列代谢物时序AUC值)往往存在大量缺失:不同患者检出的代谢物种类与时间点差异显著,导致矩阵高度稀疏。此时若机械套用sklearn.PCA,会因默认拒绝NaN而报错;若盲目填充(如均值/中位数插补)或直接删列,则可能引入偏差、丢失关键生物信号,甚至破坏代谢物间的生理相关性。
核心原则:PCA的本质是协方差矩阵的特征分解,而非逐样本线性变换
标准PCA仅依赖变量两两之间的协方差 $ \text{Cov}(X_i, Xj) = \frac{1}{n{ij}} \sum{k: x{ki},x{kj}\text{ observed}} x{ki}x{kj} $,其中 $ n{ij} $ 是同时观测到变量 $ i $ 和 $ j $ 的样本数。这意味着:
✅ 不要求任一患者具备全部5300项代谢物数据;
✅ 只需对每一对代谢物 $ (i,j) $,存在足够多(如 ≥30)共同有效观测的患者即可可靠估计协方差;
❌ 不能简单删除含缺失的整行(将损失全部200例样本),也不应删除高缺失率的代谢物列(可能剔除关键生物标志物)。
推荐实践路径:基于成对有效观测的手动协方差矩阵构建
以下代码展示如何在含海量缺失值(如40%元素为NaN)时稳健计算PCA主成分(无需任何插补或行删除):
import numpy as np
from sklearn.decomposition import PCA
# 假设 data 是您的原始数组 (200, 5300),含 np.nan 表示缺失
# Step 1: 构建成对有效计数矩阵 n_ij
mask = ~np.isnan(data) # True表示有效值
# 利用广播生成三维布尔矩阵:mask[k,i] & mask[k,j] 对所有k,i,j
valid_pairs = mask[:, :, None] & mask[:, None, :] # shape (n_samples, n_features, n_features)
n_ij = valid_pairs.sum(axis=0) # shape (n_features, n_features), 每个元素为对应变量对的有效样本数
# Step 2: 计算加权协方差矩阵(忽略NaN,仅用有效乘积求和)
data_filled = np.where(mask, data, 0) # 将NaN替换为0,便于向量化乘法
sum_products = data_filled.T @ data_filled # sum over samples: Σ_k x_ki * x_kj
cov_matrix = sum_products / n_ij # 逐元素除以对应n_ij
# Step 3: 特征分解获取主成分
eigenvals, eigenvecs = np.linalg.eigh(cov_matrix) # eigh更稳定,返回升序特征值
# 逆序排列以获得最大方差主成分在前
eigenvals = eigenvals[::-1]
eigenvecs = eigenvecs[:, ::-1]
# Step 4: 投影原始数据(对每行样本,仅使用其有效特征计算得分)
def project_to_pcs(X, components, n_features):
"""安全投影:对每样本,仅用其非NaN特征加权求和"""
scores = np.zeros((X.shape[0], components.shape[1]))
for i in range(X.shape[0]):
mask_i = ~np.isnan(X[i])
if mask_i.sum() > 0:
scores[i] = (X[i][mask_i] @ components[mask_i]).T
return scores
# 示例:投影到前2个主成分
X_pca = project_to_pcs(data, eigenvecs[:, :2], data.shape[1])关键注意事项:
综上,面对高缺失率医学数据,放弃“完美数据”执念,回归PCA的数学本源——协方差驱动的线性子空间发现,辅以向量化成对统计,即可在信息不完整约束下释放PCA的强大表征能力。