跳转至

微生物组时间序列分析


一句话概述

纵向微生物组数据分析通过追踪微生物群落随时间的动态变化,揭示群落演替规律、干预效应和个体化微生物组轨迹,本教程覆盖从实验设计到统计建模的完整方法体系。


目录


1. 核心知识点

序号步骤工具目的输入输出关键参数
1数据预处理QIIME2/DADA2去噪获得ASV/OTU表原始测序数据特征表+代表序列--p-trunc-len
2纵向数据整理R/Python构建时间序列数据框架特征表+样本元数据长格式数据框时间点、受试者ID
3时间序列可视化ggplot2/q2-longitudinal探索群落动态模式长格式数据时序图/热图facet_wrap/geom_smooth
4α多样性纵向分析q2-longitudinal/lme4检测多样性随时间变化α多样性指标+时间线性混合模型结果随机截距/斜率
5β多样性纵向分析vegan/PERMANOVA检测群落组成随时间的变化距离矩阵+元数据统计检验结果strata参数
6差异丰度时序分析MaAsLin2/LinDA识别随时间变化的分类群特征表+元数据差异分类群列表random_effects
7波动性分析q2-longitudinal量化群落稳定性和恢复力纵向特征表波动性指标volatility()
8状态转移分析Markov模型/DMM识别群落状态和转移概率分组后的群落数据状态转移矩阵状态数k选择
9干预效应评估中断时间序列/DID评估干预对微生物组的影响干预前后数据干预效应估计干预时间点
10预测与因果推断VAR/Granger检验预测未来组成/推断因果关系多时间点数据预测模型/因果网络滞后阶数

2. 各步骤详解

步骤1:数据预处理

白话解释:在进行时间序列分析之前,需要先把原始测序数据处理成标准的特征表(ASV/OTU表)。纵向研究中,所有时间点的样本应该在同一批次中统一处理,避免引入批次效应。

技术细节

纵向微生物组研究的数据预处理有几个特殊注意点:

  1. 统一处理:所有时间点的样本应使用相同的去噪参数(如DADA2的截断长度),确保不同时间点的数据可比。

  2. 批次校正:如果不同时间点的样本在不同批次测序(常见于长期随访研究),需要识别并校正批次效应。可以加入批次间的技术重复样本来评估批次效应大小。

  3. 样本追踪:纵向研究中样本数量大,确保样本-受试者-时间点的对应关系正确是基础。建议在元数据中使用唯一的受试者ID和标准化的时间编码。

  4. 缺失值处理:纵向研究不可避免会有部分时间点样本缺失(受试者退出、样本处理失败),需要在后续统计分析中妥善处理。

# QIIME2 纵向数据预处理
# 导入所有时间点的数据
qiime tools import \
  --type 'SampleData[PairedEndSequencesWithQuality]' \
  --input-path manifest_all_timepoints.tsv \
  --output-path all_reads.qza \
  --input-format PairedEndFastqManifestPhred33V2

# DADA2去噪(统一参数)
qiime dada2 denoise-paired \
  --i-demultiplexed-seqs all_reads.qza \
  --p-trunc-len-f 250 \
  --p-trunc-len-r 200 \
  --p-n-threads 16 \
  --o-table table.qza \
  --o-representative-sequences rep-seqs.qza \
  --o-denoising-stats denoise-stats.qza

步骤2:纵向数据整理

白话解释:把特征表和样本元数据组织成适合时间序列分析的"长格式"数据框,每一行代表一个样本(= 一个受试者在一个时间点的观测),包含受试者ID、时间点、分组等信息。

技术细节

纵向数据的结构: - 宽格式(wide format):每个受试者一行,不同时间点为不同列 - 长格式(long format):每个观测一行,更适合大多数统计建模

关键元数据字段: - subject_id:受试者唯一标识,用于关联同一个体的多次观测 - timepoint:时间信息,可以是序数(T1, T2, T3)或连续变量(天数/周数) - group:实验分组(如治疗组/对照组) - age/bmi等协变量:可能随时间变化的协变量

数据完整性检查: - 每个受试者有多少个时间点? - 时间间隔是否均匀? - 缺失模式是随机的还是系统性的?

library(phyloseq)
library(tidyverse)

# 读取phyloseq对象
ps <- readRDS("phyloseq_object.rds")

# 检查纵向结构
meta <- sample_data(ps) %>% as.data.frame()
meta$subject_id <- as.factor(meta$subject_id)
meta$timepoint <- as.numeric(meta$day)

# 数据完整性汇总
completeness <- meta %>%
  group_by(subject_id) %>%
  summarise(
    n_timepoints = n(),
    first_time = min(timepoint),
    last_time = max(timepoint),
    group = first(treatment_group)
  )

cat("受试者数:", n_distinct(meta$subject_id), "\n")
cat("每人平均时间点数:", mean(completeness$n_timepoints), "\n")
cat("完整随访率:", mean(completeness$n_timepoints == max(completeness$n_timepoints)), "\n")

# 转换为长格式丰度表
otu_long <- psmelt(ps) %>%
  select(Sample, OTU, Abundance, subject_id, timepoint, treatment_group) %>%
  group_by(Sample) %>%
  mutate(rel_abundance = Abundance / sum(Abundance))

步骤3:时间序列可视化

白话解释:在开始复杂的统计分析之前,先用图形直观地看看微生物组是如何随时间变化的。这包括追踪关键分类群的丰度变化、群落多样性的波动,以及个体间差异的大小。

技术细节

常用的纵向可视化类型:

  1. 轨迹图(Spaghetti plot):每条线代表一个受试者,展示某指标随时间的变化轨迹
  2. 组均值趋势图:带置信区间的分组平均趋势
  3. 堆叠面积图(时间轴):展示群落组成随时间的变化
  4. 热图(按时间排列):纵轴为分类群,横轴为时间
  5. PCoA动画:在低维空间中展示群落状态的移动轨迹

可视化设计原则: - 使用颜色区分不同组/受试者 - 用平滑曲线(loess/GAM)展示总体趋势 - 在组内个体变异和组间差异之间找到平衡

library(ggplot2)
library(gganimate)

# 1. α多样性轨迹图
alpha_div <- estimate_richness(ps, measures = c("Shannon", "Observed"))
alpha_div$subject_id <- meta$subject_id
alpha_div$timepoint <- meta$timepoint
alpha_div$group <- meta$treatment_group

ggplot(alpha_div, aes(x=timepoint, y=Shannon, group=subject_id, color=group)) +
  geom_line(alpha=0.3) +
  geom_smooth(aes(group=group), method="loess", se=TRUE, linewidth=1.5) +
  labs(x="时间(天)", y="Shannon多样性", title="微生物组α多样性时间轨迹") +
  scale_color_brewer(palette="Set1") +
  theme_minimal(base_size=14)

# 2. 关键分类群丰度变化
top_genera <- otu_long %>%
  group_by(OTU) %>%
  summarise(mean_abund = mean(rel_abundance)) %>%
  top_n(10, mean_abund) %>%
  pull(OTU)

otu_long %>%
  filter(OTU %in% top_genera) %>%
  ggplot(aes(x=timepoint, y=rel_abundance, color=treatment_group)) +
  geom_smooth(method="loess", se=TRUE) +
  facet_wrap(~OTU, scales="free_y", ncol=5) +
  labs(x="时间(天)", y="相对丰度") +
  theme_minimal()

# 3. 群落组成堆叠面积图(按时间)
otu_long %>%
  filter(OTU %in% top_genera) %>%
  group_by(timepoint, treatment_group, OTU) %>%
  summarise(mean_abund = mean(rel_abundance), .groups="drop") %>%
  ggplot(aes(x=timepoint, y=mean_abund, fill=OTU)) +
  geom_area() +
  facet_wrap(~treatment_group) +
  labs(x="时间(天)", y="平均相对丰度") +
  theme_minimal()

步骤4:α多样性纵向分析

白话解释:α多样性反映单个样本内的微生物多样性。纵向分析的核心问题是:多样性随时间如何变化?不同处理组的变化趋势是否不同?这需要用到考虑重复测量的统计方法——线性混合效应模型。

技术细节

线性混合效应模型(LME)是纵向数据分析的核心工具,因为它能: - 处理同一受试者的重复测量(自相关结构) - 允许不等间距时间点和缺失数据 - 区分固定效应(组间效应)和随机效应(个体间变异)

模型结构:Shannon ~ Time * Group + (1 + Time | Subject) - 固定效应:时间主效应、组别主效应、时间×组别交互(关键!) - 随机效应:随机截距(个体基线差异)+ 随机斜率(个体变化速率差异)

交互项的解读:如果时间×组别交互显著,说明不同组的多样性变化趋势不同(如抗生素组多样性下降速度比对照组快)。

QIIME2的q2-longitudinal插件提供了专门的纵向分析接口,但对于复杂设计,R的lme4/nlme包更灵活。

library(lme4)
library(lmerTest)  # 提供p值
library(emmeans)

# 拟合线性混合模型
model_shannon <- lmer(
  Shannon ~ timepoint * treatment_group + (1 + timepoint | subject_id),
  data = alpha_div
)

# 模型摘要
summary(model_shannon)

# ANOVA表(Type III检验)
anova(model_shannon, type = 3)

# 如果交互作用显著,进行简单斜率分析
emtrends(model_shannon, pairwise ~ treatment_group, var = "timepoint")

# 可视化模型拟合
library(effects)
plot(allEffects(model_shannon))
# QIIME2 longitudinal 分析
qiime longitudinal linear-mixed-effects \
  --m-metadata-file metadata.tsv \
  --m-metadata-file alpha_diversity.qza \
  --p-metric shannon_entropy \
  --p-state-column day \
  --p-individual-id-column subject_id \
  --p-group-columns treatment_group \
  --o-visualization lme_shannon.qzv

步骤5:β多样性纵向分析

白话解释:β多样性衡量样本之间的群落组成差异。纵向分析中,我们想知道:不同时间点之间群落组成是否显著变化?不同组的变化方向和幅度是否不同?

技术细节

纵向β多样性分析比横截面分析复杂,因为需要考虑重复测量的非独立性:

  1. PERMANOVA with strata:使用strata参数指定受试者ID,使置换仅在受试者内进行,尊重重复测量结构。但这种方法的统计功效有限。

  2. PCoA + 轨迹分析:在PCoA空间中追踪个体和组的中心点移动轨迹。

  3. 变化量分析(Bray-Curtis距离的变化):计算每个受试者相邻时间点之间的β多样性变化量,然后比较组间差异。

  4. PERMANOVA(时间切片):在每个时间点单独做PERMANOVA,观察组间差异何时出现/消失。

  5. 距离基矩阵分析(adonis2):将时间和组别同时纳入模型。

library(vegan)
library(phyloseq)

# 计算Bray-Curtis距离
bc_dist <- distance(ps, method="bray")

# 1. PERMANOVA with strata(考虑重复测量)
adonis_result <- adonis2(
  bc_dist ~ timepoint * treatment_group,
  data = meta,
  strata = meta$subject_id,
  permutations = 999
)
print(adonis_result)

# 2. 计算个体内时间变化量(turnover)
calc_turnover <- function(ps, meta) {
  subjects <- unique(meta$subject_id)
  turnover_list <- list()

  for (subj in subjects) {
    subj_samples <- meta %>% filter(subject_id == subj) %>% arrange(timepoint)
    if (nrow(subj_samples) < 2) next

    for (i in 2:nrow(subj_samples)) {
      s1 <- subj_samples$sample_id[i-1]
      s2 <- subj_samples$sample_id[i]
      bc <- as.matrix(bc_dist)[s1, s2]
      turnover_list[[length(turnover_list)+1]] <- data.frame(
        subject_id = subj,
        from_time = subj_samples$timepoint[i-1],
        to_time = subj_samples$timepoint[i],
        turnover = bc,
        group = subj_samples$treatment_group[1]
      )
    }
  }
  bind_rows(turnover_list)
}

turnover_df <- calc_turnover(ps, meta)

# 比较组间turnover差异
wilcox.test(turnover ~ group, data = turnover_df)

# 可视化
ggplot(turnover_df, aes(x=to_time, y=turnover, color=group)) +
  geom_smooth(method="loess", se=TRUE) +
  labs(x="时间(天)", y="Bray-Curtis变化量", title="群落组成变化速率") +
  theme_minimal()

步骤6:差异丰度时序分析

白话解释:找出哪些具体的微生物(属、种或ASV水平)在不同组之间随时间变化存在显著差异。比如,抗生素治疗后哪些细菌显著减少了?恢复期哪些细菌最先回升?

技术细节

MaAsLin2(Microbiome Multivariable Associations with Linear Models 2)是纵向微生物组差异丰度分析的首选工具,因为它: - 原生支持随机效应(random_effects参数) - 支持多种数据转换和正态化方法 - 自动处理稀疏零值 - 支持多协变量和交互效应

模型选择考虑: - 固定效应:时间、组别、时间×组别交互、协变量 - 随机效应:受试者ID(至少需要随机截距) - 数据转换:LOG、AST、NONE - 正态化:TSS(总和缩放)、CSS、NONE

替代工具: - LinDA:基于线性模型的纵向差异丰度分析 - ZIBR:零膨胀Beta回归(处理高零值的丰度数据) - NBZIMM:负二项零膨胀混合模型

library(Maaslin2)

# 准备输入数据
features <- as.data.frame(otu_table(ps))  # 特征表(行=样本, 列=特征)
metadata <- as.data.frame(sample_data(ps))

# MaAsLin2纵向分析
fit_data <- Maaslin2(
  input_data = features,
  input_metadata = metadata,
  output = "maaslin2_longitudinal",
  fixed_effects = c("timepoint", "treatment_group", "timepoint:treatment_group"),
  random_effects = c("subject_id"),
  normalization = "TSS",
  transform = "LOG",
  min_abundance = 0.001,
  min_prevalence = 0.1,
  max_significance = 0.05,
  cores = 8
)

# 查看显著的时间×组别交互效应
sig_interaction <- fit_data$results %>%
  filter(metadata == "timepoint:treatment_group" & qval < 0.05) %>%
  arrange(qval)

print(sig_interaction)

步骤7:波动性分析

白话解释:微生物组的"稳定性"是纵向研究的重要指标。有些人的微生物组很稳定(天天差不多),有些人波动很大。波动性分析量化这种稳定性,并研究哪些因素影响群落稳定性。

技术细节

波动性(Volatility)的量化方法:

  1. 个体内变异系数(CV):对每个受试者计算各时间点α多样性或特定分类群丰度的变异系数
  2. 时间自相关(Temporal autocorrelation):相邻时间点的相关性,高自相关=稳定
  3. β多样性方差:同一受试者不同时间点之间的平均Bray-Curtis距离
  4. Novelty/Stability指标:QIIME2定义的指标

恢复力(Resilience)分析: - 干预后群落恢复到基线状态的速度 - 计算方法:干预后各时间点与基线的Bray-Curtis距离的衰减速率

# QIIME2 波动性分析
qiime longitudinal volatility \
  --m-metadata-file metadata.tsv \
  --m-metadata-file shannon.qza \
  --p-state-column day \
  --p-individual-id-column subject_id \
  --p-default-group-column treatment_group \
  --p-default-metric shannon_entropy \
  --o-visualization volatility.qzv
# R中手动计算波动性指标
volatility_metrics <- meta %>%
  left_join(alpha_div, by=c("sample_id")) %>%
  group_by(subject_id) %>%
  arrange(timepoint) %>%
  summarise(
    group = first(treatment_group),
    mean_shannon = mean(Shannon),
    sd_shannon = sd(Shannon),
    cv_shannon = sd(Shannon) / mean(Shannon),  # 变异系数
    range_shannon = max(Shannon) - min(Shannon),
    # 相邻时间点变化量的均值
    mean_delta = mean(abs(diff(Shannon))),
    n_timepoints = n()
  )

# 比较组间波动性
wilcox.test(cv_shannon ~ group, data = volatility_metrics)

# 可视化
ggplot(volatility_metrics, aes(x=group, y=cv_shannon, fill=group)) +
  geom_boxplot() +
  geom_jitter(width=0.2, alpha=0.5) +
  labs(x="组别", y="Shannon变异系数", title="微生物组稳定性比较") +
  theme_minimal()

步骤8:状态转移分析

白话解释:微生物群落可能存在几种"典型状态"(如健康状态、失调状态),不同的人在不同时间可能处于不同状态。状态转移分析就是找出这些状态,并计算从一个状态转移到另一个状态的概率。

技术细节

Dirichlet Multinomial Mixtures (DMM) 是识别群落类型(community state types, CST)的常用方法: 1. 将所有样本聚类为k种状态 2. 对每个受试者的每个时间点标记其所属状态 3. 构建状态转移矩阵(Markov transition matrix)

马尔可夫模型假设:当前状态转移到下一状态的概率仅取决于当前状态,与历史无关。这在微生物组中是一个简化假设,但通常是合理的起点。

隐马尔可夫模型(HMM)是更复杂的扩展,允许存在不可直接观测的"隐状态"。

library(DirichletMultinomial)
library(markovchain)

# 1. DMM聚类确定群落状态
count_matrix <- as.matrix(otu_table(ps))  # 样本×OTU
# 尝试不同的k值
fit_list <- lapply(1:6, function(k) dmn(count_matrix, k))

# 选择最优k(最小Laplace近似)
laplace_vals <- sapply(fit_list, laplace)
best_k <- which.min(laplace_vals)
cat("最优状态数:", best_k, "\n")

# 分配状态标签
best_fit <- fit_list[[best_k]]
state_assignments <- apply(mixture(best_fit), 1, which.max)
meta$community_state <- paste0("CST", state_assignments)

# 2. 构建转移矩阵
transitions <- meta %>%
  arrange(subject_id, timepoint) %>%
  group_by(subject_id) %>%
  mutate(next_state = lead(community_state)) %>%
  filter(!is.na(next_state))

# 估计转移概率
trans_matrix <- table(transitions$community_state, transitions$next_state)
trans_prob <- prop.table(trans_matrix, margin=1)
print(round(trans_prob, 3))

# 3. 拟合马尔可夫链
mc <- new("markovchain",
  states = colnames(trans_prob),
  transitionMatrix = as.matrix(trans_prob)
)

# 稳态分布
steady <- steadyStates(mc)
cat("稳态分布:\n")
print(round(steady, 3))

步骤9:干预效应评估

白话解释:如果研究涉及某种干预(如抗生素治疗、饮食改变、益生菌补充),我们需要正式地评估这个干预对微生物组产生了多大的影响,以及这个影响持续了多久。

技术细节

评估干预效应的方法:

  1. 前后比较(Paired test):最简单但信息量有限
  2. 中断时间序列分析(ITS):检测干预时间点前后趋势的"断点"
  3. 差异中的差异(DID):比较治疗组和对照组在干预前后的变化差异
  4. 广义估计方程(GEE):适合群体水平推断

DID模型:Y = β₀ + β₁*Time + β₂*Treatment + β₃*Time×Treatment + ε - β₃是核心关注的参数:干预效应(干预组相对于对照组的额外变化)

恢复时间估计: - 定义"恢复"标准(如Bray-Curtis距离回到基线±1SD范围内) - 使用生存分析方法估计恢复时间的中位数和分布

# 差异中的差异(DID)分析
library(lme4)

# 标记干预前后
meta$post_intervention <- ifelse(meta$timepoint > intervention_day, 1, 0)

# DID模型
did_model <- lmer(
  Shannon ~ post_intervention * treatment_group + (1 | subject_id),
  data = alpha_div_meta
)
summary(did_model)

# 恢复时间分析
library(survival)

# 定义恢复事件
recovery_data <- meta %>%
  filter(treatment_group == "antibiotic", timepoint > intervention_day) %>%
  group_by(subject_id) %>%
  mutate(
    baseline_dist = bc_to_baseline,  # 与基线的Bray-Curtis距离
    recovered = baseline_dist < recovery_threshold,
    time_since_intervention = timepoint - intervention_day
  ) %>%
  summarise(
    recovery_time = min(time_since_intervention[recovered], na.rm=TRUE),
    censored = all(!recovered)
  )

# Kaplan-Meier恢复曲线
surv_obj <- Surv(recovery_data$recovery_time, !recovery_data$censored)
km_fit <- survfit(surv_obj ~ 1)
plot(km_fit, xlab="干预后天数", ylab="未恢复比例", main="微生物组恢复曲线")

步骤10:预测与因果推断

白话解释:如果有足够多的时间点,我们可以尝试预测微生物组未来的状态,或者推断不同微生物之间是否存在因果关系(如A的增加是否导致了B随后的减少)。

技术细节

向量自回归(VAR)模型和Granger因果检验是时间序列分析中推断方向性关系的经典方法:

Granger因果:如果加入X的历史信息能显著改善对Y的预测(相比仅用Y自身历史),则X"Granger-causes"Y。注意:这不是真正的因果关系,只是预测性关系。

稀疏VAR(SVAR):对于高维微生物组数据,需要使用正则化方法(如LASSO-VAR)来估计稀疏的微生物间相互作用网络。

动态贝叶斯网络(DBN):另一种推断时间序列因果结构的方法,对短时间序列更适用。

注意事项: - Granger因果需要平稳时间序列(可能需要差分) - 时间序列长度要足够(通常每个受试者≥10个时间点) - 组合学丰度数据的特殊性(和约束)需要考虑

library(vars)
library(SparseTSCGM)

# 选择几个关键属进行VAR分析
key_genera <- c("Bacteroides", "Faecalibacterium", "Prevotella", "Ruminococcus")

# 对单个受试者拟合VAR模型(需要足够多的时间点)
subj_data <- otu_long %>%
  filter(subject_id == "S001", OTU %in% key_genera) %>%
  pivot_wider(names_from=OTU, values_from=rel_abundance) %>%
  arrange(timepoint) %>%
  select(all_of(key_genera))

# CLR变换(处理组合数据)
subj_clr <- compositions::clr(subj_data + 1e-6)

# 选择最优滞后阶数
var_select <- VARselect(subj_clr, lag.max=5, type="const")
optimal_lag <- var_select$selection["AIC(n)"]

# 拟合VAR模型
var_model <- VAR(subj_clr, p=optimal_lag, type="const")

# Granger因果检验
causality(var_model, cause="Bacteroides")

# 脉冲响应函数(一个属的扰动如何影响其他属)
irf_result <- irf(var_model, impulse="Bacteroides", response="Faecalibacterium", n.ahead=10)
plot(irf_result)

3. 实战命令/代码(完整流程)

#!/usr/bin/env Rscript
# =====================================================
# 微生物组时间序列分析完整流程
# 场景:抗生素干预研究,追踪肠道菌群变化与恢复
# =====================================================

# ---- 加载包 ----
library(phyloseq)
library(tidyverse)
library(lme4)
library(lmerTest)
library(vegan)
library(Maaslin2)
library(ggplot2)

# ---- 1. 数据加载 ----
ps <- readRDS("data/phyloseq_longitudinal.rds")
meta <- as.data.frame(sample_data(ps))

# 确保关键变量类型正确
meta$subject_id <- as.factor(meta$subject_id)
meta$day <- as.numeric(meta$day)
meta$group <- factor(meta$group, levels=c("control","antibiotic"))
meta$phase <- factor(
  ifelse(meta$day < 0, "baseline",
    ifelse(meta$day <= 7, "treatment", "recovery")),
  levels=c("baseline","treatment","recovery")
)
sample_data(ps) <- sample_data(meta)

cat("样本数:", nsamples(ps), "\n")
cat("受试者数:", n_distinct(meta$subject_id), "\n")
cat("时间点范围:", range(meta$day), "\n")

# ---- 2. α多样性时序分析 ----
alpha <- estimate_richness(ps, measures=c("Shannon","Observed","Simpson"))
alpha$sample_id <- rownames(alpha)
alpha_meta <- cbind(alpha, meta)

# 线性混合效应模型
lme_shannon <- lmer(
  Shannon ~ day * group + phase * group + (1 + day | subject_id),
  data = alpha_meta
)
cat("\n=== Shannon多样性混合模型 ===\n")
print(anova(lme_shannon, type=3))

# 可视化
p_alpha <- ggplot(alpha_meta, aes(x=day, y=Shannon, color=group)) +
  geom_line(aes(group=subject_id), alpha=0.2) +
  geom_smooth(method="loess", linewidth=1.5, se=TRUE) +
  geom_vline(xintercept=c(0, 7), linetype="dashed", color="grey50") +
  annotate("rect", xmin=0, xmax=7, ymin=-Inf, ymax=Inf, alpha=0.1, fill="red") +
  labs(x="天数(Day 0 = 干预开始)", y="Shannon多样性",
       title="肠道微生物组α多样性纵向变化") +
  scale_color_brewer(palette="Set1", name="组别") +
  theme_minimal(base_size=13)
ggsave("figures/alpha_longitudinal.pdf", p_alpha, width=10, height=6)

# ---- 3. β多样性时序分析 ----
bc_dist <- distance(ps, method="bray")

# 按时间切片的PERMANOVA
timepoints <- sort(unique(meta$day))
permanova_results <- lapply(timepoints, function(t) {
  samps <- meta$sample_id[meta$day == t]
  if (length(samps) < 10) return(NULL)
  sub_dist <- usedist::dist_subset(bc_dist, samps)
  sub_meta <- meta[meta$day == t, ]
  result <- adonis2(sub_dist ~ group, data=sub_meta, permutations=999)
  data.frame(day=t, R2=result$R2[1], pvalue=result$`Pr(>F)`[1])
}) %>% bind_rows()

# ---- 4. 差异丰度分析(MaAsLin2) ----
features_df <- as.data.frame(t(otu_table(ps)))
metadata_df <- as.data.frame(sample_data(ps))

maaslin_result <- Maaslin2(
  input_data = features_df,
  input_metadata = metadata_df,
  output = "results/maaslin2_longitudinal",
  fixed_effects = c("day", "group", "day:group", "phase"),
  random_effects = c("subject_id"),
  normalization = "TSS",
  transform = "LOG",
  min_abundance = 0.001,
  min_prevalence = 0.1,
  cores = 8
)

# ---- 5. 波动性计算 ----
volatility <- alpha_meta %>%
  group_by(subject_id, group) %>%
  arrange(day) %>%
  summarise(
    cv_shannon = sd(Shannon) / mean(Shannon),
    mean_delta = mean(abs(diff(Shannon))),
    .groups = "drop"
  )

vol_test <- wilcox.test(cv_shannon ~ group, data=volatility)
cat(sprintf("\n波动性比较 (Wilcoxon): p = %.4f\n", vol_test$p.value))

# ---- 6. 恢复时间分析 ----
# 计算每个样本与该受试者基线的距离
baseline_samples <- meta %>% filter(phase == "baseline") %>%
  group_by(subject_id) %>% slice_tail(n=1) %>% pull(sample_id)

recovery_dist <- meta %>%
  filter(group == "antibiotic", day > 7) %>%
  rowwise() %>%
  mutate(
    baseline_sample = baseline_samples[match(subject_id, 
      meta$subject_id[meta$sample_id %in% baseline_samples])],
    dist_to_baseline = as.matrix(bc_dist)[sample_id, baseline_sample]
  )

p_recovery <- ggplot(recovery_dist, aes(x=day, y=dist_to_baseline)) +
  geom_line(aes(group=subject_id), alpha=0.3) +
  geom_smooth(method="loess", color="red") +
  geom_hline(yintercept=0.3, linetype="dashed", color="blue") +
  labs(x="天数", y="与基线的Bray-Curtis距离", title="抗生素组微生物组恢复轨迹") +
  theme_minimal()
ggsave("figures/recovery_trajectory.pdf", p_recovery, width=8, height=5)

cat("\n=== 分析完成 ===\n")

4. 面试常问点

Q1:纵向微生物组研究相比横截面研究有什么优势?

纵向研究能:(1)追踪个体内微生物组的动态变化,区分个体间差异和个体内时间变化;(2)建立时间顺序,为因果推断提供基础;(3)评估干预效应的时效性和持续性;(4)量化群落稳定性和恢复力。横截面研究只能捕捉一个时间快照,无法区分"状态"和"过程"。

Q2:为什么纵向数据不能用普通的t检验或ANOVA?

因为同一受试者的重复测量之间存在相关性(自相关),违反了独立性假设。如果忽略这种相关性:(1)标准误估计不正确;(2)p值不可靠(通常偏小,导致假阳性);(3)无法充分利用个体内变化的信息。需要使用线性混合模型、GEE等考虑相关结构的方法。

Q3:线性混合效应模型中的随机效应有什么生物学含义?

随机截距反映每个受试者的微生物组"基线水平"不同(有人天然多样性高,有人低);随机斜率反映不同受试者对时间或干预的"响应速率"不同(有人恢复快,有人慢)。这种个体间异质性在微生物组研究中非常普遍,必须在建模中考虑。

Q4:微生物组时间序列数据有什么特殊的统计挑战?

(1)组合性(compositionality):相对丰度加和为1,组分间非独立;(2)零膨胀:大量零值,且零可能是"真零"或"采样零";(3)高维度:物种数远多于时间点数;(4)不等间距时间点;(5)缺失数据;(6)非正态性:丰度数据通常右偏。这些挑战需要专门的统计方法。

Q5:如何评估微生物组的"恢复力"(resilience)?

常用方法:(1)计算扰动后各时间点与基线的β多样性距离,观察其衰减速率;(2)定义恢复阈值(如距离回到基线±1SD),用生存分析估计恢复时间;(3)比较恢复后的"新稳态"是否与原始基线相同(弹性恢复vs塑性变形);(4)α多样性的恢复速率(用指数衰减模型拟合)。

Q6:Granger因果在微生物组中的应用有什么局限性?

(1)需要平稳时间序列,但微生物丰度数据通常非平稳;(2)需要较长的时间序列(每个受试者≥10-15个时间点),很多研究达不到;(3)Granger因果不是真正的因果关系,只是预测性关系;(4)高维数据中多重检验问题严重;(5)组合数据的约束使标准VAR模型的假设被违反。


5. 易错/易混淆点

  1. 忽略重复测量的非独立性:对纵向数据直接使用t检验、Wilcoxon检验或标准PERMANOVA会严重低估p值,产生大量假阳性。必须使用考虑相关结构的方法(LME、GEE、分层置换)。

  2. 时间变量的类型选择:时间可以作为连续变量(天数)或分类变量(phase/阶段)纳入模型,两者回答不同的问题。连续时间检测线性趋势,分类时间比较阶段间差异。选择不当会错过重要效应。

  3. 混淆个体间差异和个体内变化:纵向数据中的总变异包含"受试者间变异"和"受试者内变异"两个层次。如果不正确建模随机效应,可能将稳定的个体间差异误报为时间效应。

  4. 缺失数据处理不当:纵向研究中常见样本缺失。如果缺失与结局相关(如重症患者退出),简单的完整案例分析会引入偏倚。应使用能处理不平衡数据的方法(如LME天然支持不等长时间序列)。

  5. 批次效应与时间效应混淆:如果不同时间点的样本在不同批次测序,批次效应可能被误判为时间效应。应在同一批次中处理所有时间点的样本,或加入批次间重复样本进行评估。

  6. 稀疏数据的零值处理:低丰度物种在某些时间点完全检测不到(零值),这可能是真实缺失或采样不足。直接log变换会产生负无穷。需要使用适当的zero-handling策略(伪计数、零膨胀模型等)。

  7. 过度解读短时间序列的因果关系:仅有3-5个时间点的数据不适合做VAR/Granger因果分析。这些方法需要足够长的时间序列来可靠地估计滞后效应。


6. 补充知识

实验设计考量

纵向微生物组研究的实验设计需要特别考虑: - 采样频率:取决于预期的变化速率(肠道菌群日变化vs季节变化) - 随访时长:覆盖感兴趣的生物学过程(如抗生素恢复通常需要1-3个月) - 样本量计算:纵向研究的效能分析需要考虑组内相关系数(ICC) - 技术重复:每个批次应包含质控样本(mock community、重提取重复) - 样本保存:长期研究中样本保存条件的一致性至关重要

推荐工具与资源

  • QIIME2 q2-longitudinal: https://docs.qiime2.org/latest/plugins/available/longitudinal/
  • MaAsLin2: https://huttenhower.sph.harvard.edu/maaslin2/
  • SplinectomeR: 基于样条的纵向微生物组分析
  • TimeSeriesExperiment: Bioconductor包
  • 综述: Gerber (2014) "The dynamic microbiome" FEBS Letters
  • 综述: Faust et al. (2015) "Metagenomics meets time series analysis"

新兴方向

  • 多组学纵向整合:同时追踪微生物组、代谢组、免疫指标的协同变化
  • 个性化基线(personalized baseline):承认每个人有不同的"正常"微生物组
  • 实时微生物组监测:结合可穿戴设备和快速测序的连续监测
  • 因果推断新方法:整合干预实验和观察性纵向数据的因果推断框架