3. 数据格式要求:理解MOFA输入数据的结构

开始调MOFA模型之前,有一件事你必须搞明白——数据长什么样才能往里喂。

我见过不少同学,代码跑得飞起,结果一读数据直接报错。说白了,MOFA对输入格式有自己的一套规矩。

这一章,我们就把它彻底捋清楚。

3.1 宽表格:横着摆,竖着读

MOFA的核心输入,是一种叫“宽表格”的东西。你想想看,其实就是行是样本、列是特征。

举个例子:

            Gene1  Gene2  Gene3  ...  Metabolite1  Metabolite2  ...
Sample_1      5.2    3.1    0.8          0.45         1.23
Sample_2      4.8    2.9    1.1          0.52         1.18
Sample_3      6.1    3.5    0.9          0.39         1.31
Sample_4      5.5    3.0    1.0          0.48         1.27

看到没?每个sample一行,所有特征横着排开。如果你的数据是长表格(每个sample多行),那必须先做pivot。

我的小习惯:拿到数据后,先用 df.shape 看一眼。如果行数远小于列数,大概率就是宽表格。否则,先转置或者pivot一下。

3.2 样本×特征:必须搞清楚的方向

MOFA的文档里反复强调“samples × features”的结构。其实说白了就是:行是样本,列是特征。

我曾经在一个代谢组学项目里吃过这个亏。数据导出来是特征×样本的格式,我没注意直接往里灌,模型跑了三天,结果全是垃圾。后来一查才发现,方向搞反了。

所以,这里有三种方法帮你确认方向:

  • 看索引:样本名通常在行索引里
  • 看列名:特征名通常在列名里
  • 看数值范围:同一特征的数值量级通常接近

如果你拿不准,手动检查几行就明白了。

3.3 缺失值处理:不是填个0就完事了

MOFA模型支持缺失值,但不代表你可以随便处理。我记得刚入行时,导师丢给我一句“缺失值用0填充就行”,结果模型一发散,收敛不了。

缺失值分三种情况:

缺失类型 含义 处理方法(我建议)
完全随机缺失(MCAR) 缺失和任何变量无关 可以直接用均值/中位数填充
随机缺失(MAR) 缺失和其他变量有关 用模型预测填充,比如KNN或MICE
非随机缺失(MNAR) 缺失和自身值有关 这比较棘手,建议保留缺失标记,交给MOFA内部处理
这里要特别小心:如果某个特征缺失率超过50%,我一般直接扔掉。保留这种特征,MOFA会花大量时间去估算,结果还不好。

实操中,我常用这个简单的处理方法:

import pandas as pd
import numpy as np

# 假设 df 是你的数据,缺失用 NaN 表示
# 先看看缺失比例
missing_ratio = df.isnull().mean()
print(missing_ratio[missing_ratio > 0])

# 删除缺失超过50%的特征
df_clean = df.loc[:, missing_ratio < 0.5]

# 剩余缺失用特征均值填充
df_filled = df_clean.fillna(df_clean.mean())

3.4 标准化:不同模态必须统一量纲

MOFA的一个核心假设是:不同特征之间可以比较。但基因表达量可能是0.1~10,代谢物浓度可能是100~1000,直接放一起,结果会被量级大的特征主导。

说实话,这个问题我踩过很多次坑。有一次做多组学整合,转录组和蛋白组量级差了两个数量级,出来的因子全部被转录组占了,蛋白组的信息一点没体现。

标准化这里,有两种主流方式:

方法 公式 适用场景(我的经验)
Z-score (x - mean) / std 大多数情况都用这个,模型稳定
Min-Max (x - min) / (max - min) 数据波动很大,或者有明确边界时用

具体代码:

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Z-score 标准化
scaler_z = StandardScaler()
df_zscore = pd.DataFrame(scaler_z.fit_transform(df_filled),
                         index=df_filled.index,
                         columns=df_filled.columns)

# Min-Max 标准化
scaler_mm = MinMaxScaler()
df_minmax = pd.DataFrame(scaler_mm.fit_transform(df_filled),
                         index=df_filled.index,
                         columns=df_filled.columns)

重要提醒:标准化一定要在填充缺失值之后做。否则均值和标准差会被缺失值带偏。

3.5 完整的数据预处理流程

好了,我们把前面这几步串起来。这是我个人习惯的一套流程:

  1. 读数据:确认宽表格结构,样本×特征方向
  2. 检查缺失:查看每列缺失比例,删除高缺失特征
  3. 填充缺失:用均值或中位数填充剩余缺失值
  4. 标准化:对每列做Z-score标准化
  5. 最终检查:确认无缺失、均值为0、方差为1

用代码实现就是:

def preprocess_for_mofa(df, missing_threshold=0.5):
    # 1. 删除高缺失特征
    df = df.loc[:, df.isnull().mean() < missing_threshold]
    # 2. 均值填充
    df = df.fillna(df.mean())
    # 3. Z-score标准化
    scaler = StandardScaler()
    df_scaled = pd.DataFrame(scaler.fit_transform(df),
                             index=df.index,
                             columns=df.columns)
    return df_scaled

嗯,这样处理完的数据,MOFA模型跑起来很顺。

3.6 数据基础设施总览

我画了一张图,帮你把整个数据准备流程串起来。你看一眼就明白了:

数据基础设施架构 原始数据 宽表格结构 样本×特征确认 缺失值处理 标准化(Z-score/Min-Max) 最终MOFA输入数据 每个样本占一行 每个特征占一列 缺失值不能放任不管 量纲必须统一

总结一下这章的核心:数据格式是MOFA的门槛,宽表格、样本×特征、缺失值处理和标准化,四步缺一不可。