绘制火山图 R、python

绘制火山图 R、python

R

对每个微生物(菌种)进行线性混合效应模型(LMM)分析,得到每个微生物的效应量(log2 fold change)和p值。

对p值进行Benjamini-Hochberg多重检验校正,得到校正后的p值(即FDR)。

根据给定的阈值(log2FC的绝对值≥0.58,且-log10(FDR)≥1.301)确定显著差异的微生物。

绘制火山图,用虚线标明阈值线。

由于问题中没有提供具体的数据结构,我们假设数据格式如下:

一个数据框,行是微生物(菌种),列是样本。另外,还有一个分组变量(例如,两组:Group1和Group2)以及可能的随机效应变量(例如,个体ID,时间点等)。

但是,由于问题中只提到了两组微生物的表达数据,并且使用了线性混合效应模型,我们假设数据是纵向数据或者有配对设计,因此需要考虑随机效应。

然而,问题中并没有提供具体的随机效应变量。为了简化,我们假设每个样本有重复测量,并且有一个随机效应(例如,患者ID)。因此,我们需要在模型中加入随机效应。

步骤:

加载必要的R包:lme4(用于线性混合效应模型),lmerTest(用于得到p值),ggplot2(用于绘图),dplyr(用于数据处理)。

准备数据:将微生物表达数据整理成长格式,每一行是一个样本的一个微生物的表达值。

对每个微生物拟合线性混合效应模型,提取log2FC和p值。

对p值进行多重检验校正。

根据阈值标注显著差异的微生物。

绘制火山图。

注意:由于微生物数量可能很多,我们需要高效地拟合模型。可以使用循环或者函数式编程。

假设数据框结构如下:

样本信息:SampleID, Group, SubjectID (随机效应)

微生物表达数据:每个微生物一列,例如 Microbe1, Microbe2, ...

我们将数据转换为长格式,然后按微生物分组拟合模型。

由于问题中没有提供数据,我将生成一个模拟数据来演示。

但是,请注意:在实际分析中,你的数据可能有所不同,需要根据实际情况调整。

我们开始写代码:

步骤1:生成模拟数据(假设有100个微生物,20个样本,10个受试者,每个受试者有两个时间点或两个条件)

步骤2:数据整理

步骤3:模型拟合

步骤4:校正p值

步骤5:火山图

由于线性混合效应模型拟合可能比较慢,我们可以使用lme4包中的lmer函数,并使用lmerTest获取p值。

注意:在模拟数据中,我们假设有一些微生物是差异表达的。

r 复制代码
# 加载必要的包:
# install.packages(c("ggplot2", "dplyr", "lme4", "lmerTest", "reshape2"))
library(ggplot2)
library(dplyr)
library(lme4)
library(lmerTest)
library(reshape2)

# 设置随机种子以保证结果可重复
set.seed(123)

# 1. 创建模拟数据(如果您已有实际数据,请跳过此步骤)
# 假设有100个微生物物种,20个样本(10个对照组,10个实验组)
create_simulated_data <- function(n_species = 100, n_samples_per_group = 10) {
  # 创建微生物名称
  species_names <- paste0("Species_", sprintf("%03d", 1:n_species))
  
  # 创建样本信息
  samples <- c(paste0("Control_", 1:n_samples_per_group), 
               paste0("Treatment_", 1:n_samples_per_group))
  groups <- factor(c(rep("Control", n_samples_per_group), 
                     rep("Treatment", n_samples_per_group)))
  subject <- factor(rep(1:(n_samples_per_group/2), each = 2, times = 2))  # 模拟配对设计
  
  # 创建表达矩阵(添加组间差异)
  expr_matrix <- matrix(NA, nrow = n_species, ncol = length(samples))
  rownames(expr_matrix) <- species_names
  colnames(expr_matrix) <- samples
  
  for (i in 1:n_species) {
    # 基础表达水平
    base_expr <- rnorm(1, mean = 10, sd = 2)
    
    # 模拟差异表达
    if (i <= 10) {  # 前10个物种显著上调
      control_expr <- rnorm(n_samples_per_group, mean = base_expr, sd = 1)
      treatment_expr <- rnorm(n_samples_per_group, mean = base_expr + 2, sd = 1)
    } else if (i <= 20) {  # 11-20个物种显著下调
      control_expr <- rnorm(n_samples_per_group, mean = base_expr, sd = 1)
      treatment_expr <- rnorm(n_samples_per_group, mean = base_expr - 2, sd = 1)
    } else {  # 其他物种无显著差异
      control_expr <- rnorm(n_samples_per_group, mean = base_expr, sd = 1)
      treatment_expr <- rnorm(n_samples_per_group, mean = base_expr, sd = 1)
    }
    
    expr_matrix[i, 1:n_samples_per_group] <- control_expr
    expr_matrix[i, (n_samples_per_group + 1):(2 * n_samples_per_group)] <- treatment_expr
  }
  
  # 创建元数据
  metadata <- data.frame(
    Sample = samples,
    Group = groups,
    Subject = subject
  )
  
  return(list(expr_matrix = expr_matrix, metadata = metadata))
}

# 2. 差异表达分析函数(使用线性混合效应模型)
perform_differential_analysis <- function(expr_matrix, metadata) {
  results <- data.frame()
  
  # 对每个微生物物种进行分析
  for (species in rownames(expr_matrix)) {
    # 准备数据
    df <- data.frame(
      Expression = as.numeric(expr_matrix[species, ]),
      Group = metadata$Group,
      Subject = metadata$Subject,
      Sample = metadata$Sample
    )
    
    # 拟合线性混合效应模型
    # 这里假设Subject是随机效应,Group是固定效应
    tryCatch({
      # 使用lmerTest包中的lmer,可以计算p值
      model <- lmer(Expression ~ Group + (1 | Subject), data = df)
      
      # 获取模型摘要
      model_summary <- summary(model)
      
      # 提取Treatment组的效应
      coef_row <- which(rownames(model_summary$coefficients) == "GroupTreatment")
      
      if (length(coef_row) > 0) {
        # 计算log2 fold change
        # 注意:对于线性模型,系数本身就是Treatment vs Control的差异
        log2FC <- model_summary$coefficients[coef_row, "Estimate"]
        
        # 获取p值
        p_value <- model_summary$coefficients[coef_row, "Pr(>|t|)"]
        
        # 保存结果
        results <- rbind(results, data.frame(
          Species = species,
          log2FC = log2FC,
          p_value = p_value
        ))
      }
    }, error = function(e) {
      # 如果模型拟合失败,记录NA值
      results <<- rbind(results, data.frame(
        Species = species,
        log2FC = NA,
        p_value = NA
      ))
    })
  }
  
  # 多重检验校正(Benjamini-Hochberg方法)
  results$adj_p_value <- p.adjust(results$p_value, method = "BH")
  
  # 计算-log10(p值)
  results$neg_log10_p <- -log10(results$adj_p_value)
  
  return(results)
}

# 3. 筛选显著差异表达的物种
identify_significant_species <- function(results, 
                                         log2FC_threshold = 0.58, 
                                         neg_log10_p_threshold = 1.301) {
  results$Significance <- "Not Significant"
  
  # 根据阈值标注显著性
  results$Significance[results$log2FC >= log2FC_threshold & 
                         results$neg_log10_p >= neg_log10_p_threshold] <- "Up-regulated"
  
  results$Significance[results$log2FC <= -log2FC_threshold & 
                         results$neg_log10_p >= neg_log10_p_threshold] <- "Down-regulated"
  
  # 统计显著物种数量
  n_up <- sum(results$Significance == "Up-regulated")
  n_down <- sum(results$Significance == "Down-regulated")
  
  cat(sprintf("显著上调物种数: %d\n", n_up))
  cat(sprintf("显著下调物种数: %d\n", n_down))
  cat(sprintf("总显著物种数: %d\n", n_up + n_down))
  
  return(results)
}

# 4. 绘制火山图
create_volcano_plot <- function(results, 
                                log2FC_threshold = 0.58, 
                                neg_log10_p_threshold = 1.301,
                                title = "Differential Abundance of Microbial Species") {
  
  # 设置颜色
  colors <- c("Up-regulated" = "#E64B35", 
              "Down-regulated" = "#3182bd", 
              "Not Significant" = "#bdbdbd")
  
  # 创建火山图
  p <- ggplot(results, aes(x = log2FC, y = neg_log10_p, color = Significance)) +
    geom_point(alpha = 0.6, size = 2) +
    scale_color_manual(values = colors) +
    
    # 添加阈值线
    geom_vline(xintercept = c(-log2FC_threshold, log2FC_threshold), 
               linetype = "dashed", color = "black", alpha = 0.5) +
    geom_hline(yintercept = neg_log10_p_threshold, 
               linetype = "dashed", color = "black", alpha = 0.5) +
    
    # 设置坐标轴
    labs(x = expression(Log[2]~Fold~Change~"(Treatment/Control)"),
         y = expression(-Log[10]~"(Adjusted P-value)"),
         title = title,
         color = "Significance") +
    
    # 添加主题
    theme_minimal(base_size = 12) +
    theme(
      plot.title = element_text(hjust = 0.5, face = "bold", size = 14),
      axis.title = element_text(face = "bold"),
      legend.position = "right",
      panel.grid.major = element_line(color = "grey90", linewidth = 0.2),
      panel.grid.minor = element_blank(),
      panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5)
    ) +
    
    # 调整坐标轴范围
    xlim(min(results$log2FC, na.rm = TRUE) - 0.5, 
         max(results$log2FC, na.rm = TRUE) + 0.5) +
    ylim(0, max(results$neg_log10_p, na.rm = TRUE) + 0.5)
  
  return(p)
}

# 5. 获取top差异表达物种
get_top_species <- function(results, n_top = 10) {
  # 按显著性排序
  results_sorted <- results[order(results$adj_p_value), ]
  
  # 获取上调物种
  up_species <- results_sorted[results_sorted$log2FC > 0, ]
  top_up <- head(up_species, n_top)
  
  # 获取下调物种
  down_species <- results_sorted[results_sorted$log2FC < 0, ]
  top_down <- head(down_species, n_top)
  
  return(list(top_up = top_up, top_down = top_down))
}

# 6. 主分析流程
main_analysis <- function() {
  cat("=== 微生物差异表达分析 ===\n")
  
  # 步骤1: 创建或加载数据
  cat("1. 准备数据...\n")
  data <- create_simulated_data(n_species = 100, n_samples_per_group = 10)
  
  # 如果您有实际数据,请替换为:
  # expr_matrix <- your_expression_matrix  # 行是物种,列是样本
  # metadata <- your_metadata_dataframe    # 包含Group, Subject等列
  
  # 步骤2: 差异表达分析
  cat("2. 进行差异表达分析(线性混合效应模型)...\n")
  results <- perform_differential_analysis(data$expr_matrix, data$metadata)
  
  # 步骤3: 筛选显著物种
  cat("3. 筛选显著差异表达的物种...\n")
  results <- identify_significant_species(results)
  
  # 步骤4: 创建火山图
  cat("4. 绘制火山图...\n")
  volcano_plot <- create_volcano_plot(results)
  print(volcano_plot)
  
  # 步骤5: 获取top物种
  cat("5. 获取top差异表达物种...\n")
  top_species <- get_top_species(results, n_top = 10)
  
  cat("\n=== Top 10 上调物种 ===\n")
  print(top_species$top_up[, c("Species", "log2FC", "adj_p_value")])
  
  cat("\n=== Top 10 下调物种 ===\n")
  print(top_species$top_down[, c("Species", "log2FC", "adj_p_value")])
  
  # 步骤6: 保存结果
  cat("\n6. 保存结果...\n")
  
  # 保存完整结果
  write.csv(results, "microbial_differential_analysis_results.csv", row.names = FALSE)
  
  # 保存火山图
  ggsave("volcano_plot.png", volcano_plot, width = 10, height = 8, dpi = 300)
  
  cat("\n分析完成!结果已保存到当前目录。\n")
  
  return(list(results = results, plot = volcano_plot))
}

# 7. 运行分析
analysis_results <- main_analysis()

# 8. 可选:高级可视化(添加物种标签)
create_labeled_volcano <- function(results, n_label = 10) {
  # 选择最显著的物种进行标注
  results_significant <- results[results$Significance != "Not Significant", ]
  results_significant <- results_significant[order(results_significant$adj_p_value), ]
  
  # 获取top上调物种
  top_up <- head(results_significant[results_significant$log2FC > 0, ], n_label/2)
  
  # 获取top下调物种
  top_down <- head(results_significant[results_significant$log2FC < 0, ], n_label/2)
  
  top_labels <- rbind(top_up, top_down)
  
  # 创建带标签的火山图
  p_labeled <- create_volcano_plot(results) +
    geom_text(data = top_labels, 
              aes(label = Species), 
              size = 3, 
              color = "black",
              vjust = -0.5, 
              hjust = 0.5,
              check_overlap = TRUE) +
    labs(subtitle = paste("Labeled:", n_label, "most significant species"))
  
  return(p_labeled)
}

# 创建带标签的火山图
labeled_plot <- create_labeled_volcano(analysis_results$results, n_label = 10)
print(labeled_plot)
ggsave("volcano_plot_labeled.png", labeled_plot, width = 12, height = 9, dpi = 300)

python

我们使用Python中的类似ggplot风格的绘图库,比如plotnine,以及用于统计分析的statsmodels或pingouin等库。

但是请注意,Python中没有直接对应于线性混合效应模型(LMM)并能够方便地进行多重比较校正的完整函数链,因此我们需要结合多个步骤。

步骤:

加载必要的库:pandas, numpy, statsmodels, multipy(用于多重检验校正),以及plotnine用于绘图。

准备数据:假设我们有两个组(例如对照组和实验组),并且有多个样本,每个样本有多个微生物物种的表达量。

对于每个物种,使用线性混合效应模型(LMM)分析,其中组别(Group)作为固定效应,个体(Subject)作为随机效应。

提取每个物种的log2FoldChange和p值,然后进行Benjamini-Hochberg校正。

根据阈值(log2FC绝对值≥0.58,且-log10(校正后p值)≥1.301)标记显著物种。

绘制火山图。

注意:由于Python中LMM的实现(如statsmodels的MixedLM)不直接提供p值,我们需要使用其他方法(如似然比检验)来计算p值,或者使用pingouin包的mixed_anova,但这里的数据结构可能不符合ANOVA的要求。

另一种方法是使用线性模型(LM)而不是混合效应模型,如果数据没有明显的重复测量或配对设计,但根据您的描述,似乎使用了线性混合效应模型,并且有随机效应(Subject)。

由于复杂性,我们将使用一个简化方法:对于每个物种,我们使用线性混合效应模型,但为了简单起见,我们假设数据是独立的(即不使用混合效应模型)并使用线性模型,然后进行多重检验校正。

如果您确实需要混合效应模型,我们可以使用statsmodels的MixedLM,但计算p值可能会比较麻烦。

根据您的要求,我们假设数据已经准备好,并且您希望进行混合效应模型分析。但是,由于时间关系,我们这里使用一个简化的线性模型(固定效应模型)来演示流程,并假设数据是独立的。

如果您需要严格的混合效应模型,请参考statsmodels的MixedLM,并使用似然比检验计算p值。

由于代码的复杂性,我们将按照以下步骤进行:

由于我们可能没有Subject的随机效应,我们这里假设每个样本是独立的,使用普通的线性模型(lm)进行差异分析,然后进行多重检验校正。

但是,根据您的要求,我们假设数据是配对的,所以我们需要使用混合效应模型。然而,在Python中实现混合效应模型并提取p值是一个复杂的过程。

考虑到时间和代码的简洁性,我们这里将使用一个替代方法:使用pingouin库的mixed_anova(如果数据是重复测量的)或者使用statsmodels的MixedLM。

我们选择使用statsmodels的MixedLM,并计算每个物种的p值。

步骤:

对于每个物种,构建一个MixedLM模型,固定效应为Group,随机效应为Subject。

由于物种数量可能很多,我们将循环每个物种,拟合模型,并记录结果。

注意:MixedLM不直接提供p值,我们需要使用参数估计的标准误差来计算t统计量和p值。但是,这要求模型收敛并且参数估计可靠。

我们将尝试使用以下步骤:

导入必要的库。

读取数据(这里我们生成模拟数据)。

定义函数对单个物种进行MixedLM分析。

循环所有物种,收集log2FC和p值。

进行多重检验校正(BH方法)。

根据阈值标记显著物种。

绘制火山图。

由于这个过程的复杂性,我们可能需要处理模型不收敛的情况。

我们首先尝试使用statsmodels的MixedLM。但是,请注意,MixedLM的默认方法可能不提供p值,但我们可以通过使用summary()来获取t统计量和p值(如果模型使用最大似然估计或限制性最大似然估计,并且假设参数渐近正态分布)。

实际上,statsmodels的MixedLM的summary方法提供了参数的t检验和p值,但仅针对固定效应。我们可以使用它来获取Group的p值。

因此,我们可以这样设计:

对于每个物种,我们有一个公式:Expression ~ Group,随机效应是Subject。

代码步骤:

注意:我们假设数据是长格式,即每一行是一个样本的一个物种的表达量,列包括:Sample, Subject, Group, Species, Expression。

但是,通常我们的数据是宽格式,即行是物种,列是样本。所以我们需要先转换成长格式。

由于数据量可能很大,转换长格式可能会消耗内存,所以我们逐个物种分析,而不转换整个数据为长格式。

我们假设原始数据:

expr_matrix: 物种(行)x 样本(列)

metadata: 每个样本的信息,包括Group和Subject

我们逐个物种提取表达量,并与metadata合并,然后拟合模型。

但是,这样循环1000个物种,每个物种拟合一个混合效应模型,可能会很慢。

我们将尝试使用向量化操作,但这里无法避免循环。

开始编写代码:

注意:由于模拟数据,我们假设metadata中的Subject列表示个体,且每个个体有多个样本(例如,同一个体处理前后)。

我们使用以下模型:Expression ~ Group + (1|Subject)

在statsmodels的MixedLM中,随机效应通过groups参数指定。

具体步骤:

导入库

创建模拟数据(长格式)

定义分析函数

循环物种

多重检验校正

绘制火山图

由于时间关系,我们使用一个简化的模拟数据生成过程,生成长格式数据。

如果您的数据是宽格式,请先转换为长格式。

我们假设您已经有表达矩阵和样本信息,我们将它们合并成长格式。

由于代码较长,我们逐步实现。

注意:我们使用plotnine绘制火山图,它的风格类似于ggplot2。

但是,如果数据量很大,MixedLM拟合可能较慢,我们可能需要考虑使用更高效的方法。

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

# 设置ggplot风格
plt.style.use('ggplot')

# 设置随机种子
np.random.seed(42)

# ==================== 1. 创建模拟数据 ====================
def create_simulated_data(n_species=100, n_subjects=5, n_replicates=2):
    """
    创建模拟的微生物表达数据
    n_subjects: 个体数量(用于随机效应)
    n_replicates: 每个个体的重复数
    """
    # 创建微生物名称
    species_names = [f'Species_{i:03d}' for i in range(1, n_species + 1)]
    
    # 创建样本信息
    n_samples = n_subjects * n_replicates * 2  # 对照组和实验组
    subjects = []
    groups = []
    sample_names = []
    
    for subj in range(1, n_subjects + 1):
        for rep in range(1, n_replicates + 1):
            # 对照组样本
            subjects.append(f'Subject_{subj}')
            groups.append('Control')
            sample_names.append(f'Control_S{subj}_R{rep}')
            
            # 实验组样本
            subjects.append(f'Subject_{subj}')
            groups.append('Treatment')
            sample_names.append(f'Treatment_S{subj}_R{rep}')
    
    # 创建表达矩阵
    expr_matrix = np.zeros((n_species, n_samples))
    
    # 设置一些物种的差异表达
    for i in range(n_species):
        # 基础表达水平
        base_expr = np.random.lognormal(mean=5, sigma=0.5)
        
        # 添加个体间的随机效应
        subject_effects = {}
        for subj in range(1, n_subjects + 1):
            subject_effects[f'Subject_{subj}'] = np.random.normal(0, 0.3)
        
        # 设置差异表达模式
        if i < 10:  # 前10个物种显著上调
            fc = np.random.uniform(1.6, 3.0)  # 1.6-3倍变化
        elif i < 20:  # 11-20个物种显著下调
            fc = np.random.uniform(0.33, 0.625)  # 0.33-0.625倍变化
        else:  # 其他物种无显著差异
            fc = np.random.uniform(0.8, 1.25)  # 轻微变化
            
        # 为每个样本生成表达值
        for j, (subject, group) in enumerate(zip(subjects, groups)):
            # 基础表达 + 个体效应 + 组效应 + 技术噪声
            if group == 'Control':
                group_effect = 0
            else:  # Treatment
                group_effect = np.log2(fc)  # log2 fold change
            
            expr_value = base_expr + subject_effects[subject] + group_effect + np.random.normal(0, 0.2)
            expr_matrix[i, j] = expr_value
    
    # 创建数据框
    expr_df = pd.DataFrame(expr_matrix, index=species_names, columns=sample_names)
    
    # 创建元数据
    metadata = pd.DataFrame({
        'Sample': sample_names,
        'Group': groups,
        'Subject': subjects
    })
    
    return expr_df, metadata

# ==================== 2. 线性混合效应模型分析 ====================
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats.multitest import multipletests

def fit_lmm_for_species(expr_values, metadata):
    """为单个物种拟合线性混合效应模型"""
    # 准备数据
    data = metadata.copy()
    data['Expression'] = expr_values
    
    # 拟合线性混合效应模型
    try:
        # 使用Subject作为随机效应,Group作为固定效应
        model = smf.mixedlm("Expression ~ Group", data, groups=data["Subject"])
        result = model.fit()
        
        # 提取结果
        log2fc = result.params.get('Group[T.Treatment]', 0)
        p_value = result.pvalues.get('Group[T.Treatment]', 1)
        
        return {
            'log2fc': log2fc,
            'p_value': p_value,
            'converged': result.converged
        }
    except:
        # 如果模型拟合失败,返回空值
        return {
            'log2fc': np.nan,
            'p_value': np.nan,
            'converged': False
        }

def perform_differential_analysis(expr_df, metadata):
    """对所有物种进行差异表达分析"""
    results = []
    
    print("正在进行差异表达分析...")
    for i, species in enumerate(expr_df.index):
        if (i + 1) % 10 == 0:
            print(f"  正在分析第 {i+1}/{len(expr_df)} 个物种...")
        
        expr_values = expr_df.loc[species].values
        result = fit_lmm_for_species(expr_values, metadata)
        
        results.append({
            'Species': species,
            'log2fc': result['log2fc'],
            'p_value': result['p_value'],
            'converged': result['converged']
        })
    
    # 转换为DataFrame
    results_df = pd.DataFrame(results)
    
    # 过滤掉模型未收敛的物种
    results_df = results_df[results_df['converged']].copy()
    
    # 多重检验校正 (Benjamini-Hochberg)
    results_df['adj_p_value'] = multipletests(results_df['p_value'], method='fdr_bh')[1]
    
    # 计算-log10(p值)
    results_df['neg_log10_p'] = -np.log10(results_df['adj_p_value'])
    
    return results_df

# ==================== 3. 识别显著差异物种 ====================
def identify_significant_species(results_df, log2fc_threshold=0.58, neg_log10_p_threshold=1.301):
    """根据阈值识别显著差异表达的物种"""
    # 创建显著性列
    conditions = [
        (results_df['log2fc'] >= log2fc_threshold) & (results_df['neg_log10_p'] >= neg_log10_p_threshold),
        (results_df['log2fc'] <= -log2fc_threshold) & (results_df['neg_log10_p'] >= neg_log10_p_threshold)
    ]
    choices = ['Up-regulated', 'Down-regulated']
    
    results_df['Significance'] = np.select(conditions, choices, default='Not Significant')
    
    # 统计
    n_up = (results_df['Significance'] == 'Up-regulated').sum()
    n_down = (results_df['Significance'] == 'Down-regulated').sum()
    n_total = n_up + n_down
    
    print("\n" + "="*50)
    print("差异表达分析结果统计:")
    print(f"  显著上调物种数: {n_up}")
    print(f"  显著下调物种数: {n_down}")
    print(f"  总显著物种数: {n_total} (占总物种的 {n_total/len(results_df)*100:.1f}%)")
    print("="*50)
    
    return results_df

# ==================== 4. 绘制ggplot风格火山图 ====================
def create_ggplot_volcano(results_df, log2fc_threshold=0.58, neg_log10_p_threshold=1.301):
    """创建ggplot风格的火山图"""
    # 创建图形
    fig, ax = plt.subplots(figsize=(10, 8))
    
    # 定义颜色映射
    colors = {
        'Up-regulated': '#E64B35',    # 红色
        'Down-regulated': '#3182bd',  # 蓝色
        'Not Significant': '#999999'  # 灰色
    }
    
    # 为每个点分配颜色
    point_colors = [colors[sig] for sig in results_df['Significance']]
    
    # 绘制散点
    scatter = ax.scatter(
        results_df['log2fc'], 
        results_df['neg_log10_p'],
        c=point_colors,
        alpha=0.6,
        edgecolors='white',
        linewidth=0.5,
        s=60  # 点的大小
    )
    
    # 添加阈值线
    ax.axvline(x=log2fc_threshold, color='black', linestyle='--', alpha=0.7, linewidth=1)
    ax.axvline(x=-log2fc_threshold, color='black', linestyle='--', alpha=0.7, linewidth=1)
    ax.axhline(y=neg_log10_p_threshold, color='black', linestyle='--', alpha=0.7, linewidth=1)
    
    # 添加显著区域标签
    ax.text(log2fc_threshold + 0.1, ax.get_ylim()[1] * 0.95, 
            'Up-regulated', fontsize=10, color='#E64B35', fontweight='bold')
    ax.text(-log2fc_threshold - 0.8, ax.get_ylim()[1] * 0.95, 
            'Down-regulated', fontsize=10, color='#3182bd', fontweight='bold')
    
    # 设置坐标轴标签
    ax.set_xlabel(r'$\log_2$(Fold Change)', fontsize=12, fontweight='bold')
    ax.set_ylabel(r'$-\log_{10}$(Adjusted P-value)', fontsize=12, fontweight='bold')
    
    # 设置标题
    ax.set_title('Differential Microbial Abundance Analysis\n(Linear Mixed Effect Model)', 
                 fontsize=14, fontweight='bold', pad=20)
    
    # 设置网格
    ax.grid(True, alpha=0.3, linestyle='-', linewidth=0.5)
    
    # 设置坐标轴范围
    x_min = results_df['log2fc'].min() - 0.5
    x_max = results_df['log2fc'].max() + 0.5
    ax.set_xlim(x_min, x_max)
    
    # 创建图例
    from matplotlib.lines import Line2D
    legend_elements = [
        Line2D([0], [0], marker='o', color='w', markerfacecolor=colors['Up-regulated'], 
               markersize=10, label=f'Up-regulated (n={sum(results_df["Significance"]=="Up-regulated")})'),
        Line2D([0], [0], marker='o', color='w', markerfacecolor=colors['Down-regulated'], 
               markersize=10, label=f'Down-regulated (n={sum(results_df["Significance"]=="Down-regulated")})'),
        Line2D([0], [0], marker='o', color='w', markerfacecolor=colors['Not Significant'], 
               markersize=10, label=f'Not Significant (n={sum(results_df["Significance"]=="Not Significant")})'),
    ]
    ax.legend(handles=legend_elements, loc='upper right', framealpha=0.9)
    
    # 添加文本说明
    stats_text = f"Thresholds:\n|log₂FC| ≥ {log2fc_threshold}\n-log₁₀(P) ≥ {neg_log10_p_threshold}"
    ax.text(0.02, 0.98, stats_text, transform=ax.transAxes, 
            fontsize=9, verticalalignment='top',
            bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
    
    plt.tight_layout()
    return fig, ax

# ==================== 5. 获取Top差异物种 ====================
def get_top_differential_species(results_df, top_n=10):
    """获取Top差异表达的物种"""
    # 上调物种
    up_species = results_df[results_df['Significance'] == 'Up-regulated'].copy()
    up_species = up_species.sort_values('adj_p_value')
    top_up = up_species.head(top_n)[['Species', 'log2fc', 'adj_p_value']]
    
    # 下调物种
    down_species = results_df[results_df['Significance'] == 'Down-regulated'].copy()
    down_species = down_species.sort_values('adj_p_value')
    top_down = down_species.head(top_n)[['Species', 'log2fc', 'adj_p_value']]
    
    return top_up, top_down

# ==================== 6. 保存结果 ====================
def save_results(results_df, volcano_fig, prefix='microbial_analysis'):
    """保存分析结果"""
    # 保存CSV文件
    csv_filename = f'{prefix}_results.csv'
    results_df.to_csv(csv_filename, index=False)
    print(f"分析结果已保存到: {csv_filename}")
    
    # 保存火山图
    png_filename = f'{prefix}_volcano_plot.png'
    volcano_fig.savefig(png_filename, dpi=300, bbox_inches='tight')
    print(f"火山图已保存到: {png_filename}")
    
    # 保存Top物种
    top_up, top_down = get_top_differential_species(results_df)
    top_up_filename = f'{prefix}_top_upregulated.csv'
    top_down_filename = f'{prefix}_top_downregulated.csv'
    top_up.to_csv(top_up_filename, index=False)
    top_down.to_csv(top_down_filename, index=False)
    print(f"Top上调物种已保存到: {top_up_filename}")
    print(f"Top下调物种已保存到: {top_down_filename}")

# ==================== 7. 主分析函数 ====================
def main_analysis():
    """主分析流程"""
    print("开始微生物差异表达分析...")
    print("="*60)
    
    # 1. 创建模拟数据
    print("步骤1: 创建模拟数据...")
    expr_df, metadata = create_simulated_data(n_species=100, n_subjects=5, n_replicates=2)
    print(f"   数据形状: {expr_df.shape[0]}个物种 × {expr_df.shape[1]}个样本")
    print(f"   组别分布: Control={sum(metadata['Group']=='Control')}, Treatment={sum(metadata['Group']=='Treatment')}")
    
    # 2. 差异表达分析
    print("\n步骤2: 进行线性混合效应模型分析...")
    results_df = perform_differential_analysis(expr_df, metadata)
    print(f"   成功分析 {len(results_df)} 个物种")
    
    # 3. 识别显著物种
    print("\n步骤3: 识别显著差异表达物种...")
    results_df = identify_significant_species(results_df)
    
    # 4. 绘制火山图
    print("\n步骤4: 绘制火山图...")
    fig, ax = create_ggplot_volcano(results_df)
    plt.show()
    
    # 5. 显示Top物种
    print("\n步骤5: 显示Top差异表达物种...")
    top_up, top_down = get_top_differential_species(results_df, top_n=5)
    
    print("\nTop 5 上调物种:")
    print(top_up.to_string(index=False))
    
    print("\nTop 5 下调物种:")
    print(top_down.to_string(index=False))
    
    # 6. 保存结果
    print("\n步骤6: 保存分析结果...")
    save_results(results_df, fig)
    
    print("\n" + "="*60)
    print("分析完成!")
    
    return results_df, fig

# ==================== 8. 高级可视化选项 ====================
def create_annotated_volcano(results_df, n_annotate=10):
    """创建带标签的火山图"""
    fig, ax = create_ggplot_volcano(results_df)
    
    # 获取最显著的物种
    significant_df = results_df[results_df['Significance'] != 'Not Significant'].copy()
    significant_df = significant_df.sort_values('adj_p_value')
    
    # 标注Top上调物种
    top_up = significant_df[significant_df['log2fc'] > 0].head(n_annotate // 2)
    for _, row in top_up.iterrows():
        ax.annotate(row['Species'], 
                   xy=(row['log2fc'], row['neg_log10_p']),
                   xytext=(5, 5), textcoords='offset points',
                   fontsize=8, color='#E64B35',
                   bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.7, edgecolor='none'))
    
    # 标注Top下调物种
    top_down = significant_df[significant_df['log2fc'] < 0].head(n_annotate // 2)
    for _, row in top_down.iterrows():
        ax.annotate(row['Species'], 
                   xy=(row['log2fc'], row['neg_log10_p']),
                   xytext=(5, 5), textcoords='offset points',
                   fontsize=8, color='#3182bd',
                   bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.7, edgecolor='none'))
    
    ax.set_title('Annotated Volcano Plot\n(Top Significant Species Labeled)', 
                 fontsize=14, fontweight='bold', pad=20)
    
    plt.tight_layout()
    return fig, ax

# ==================== 9. 运行分析 ====================
if __name__ == "__main__":
    # 运行主分析
    results_df, volcano_fig = main_analysis()
    
    # 可选:创建带标签的火山图
    print("\n创建带标签的火山图...")
    annotated_fig, _ = create_annotated_volcano(results_df, n_annotate=10)
    plt.show()
    
    # 保存带标签的火山图
    annotated_fig.savefig('microbial_analysis_annotated_volcano.png', dpi=300, bbox_inches='tight')
    print("带标签的火山图已保存到: microbial_analysis_annotated_volcano.png")
复制代码
代码说明和自定义方法:
1. 准备您的真实数据
如果您有自己的数据,需要准备以下两个DataFrame:
python 复制代码
# 示例:如何准备您的数据
# expr_df: 行是微生物物种,列是样本
expr_df = pd.read_csv('your_expression_data.csv', index_col=0)

# metadata: 样本信息,必须包含'Group'和'Subject'列
metadata = pd.DataFrame({
    'Sample': expr_df.columns.tolist(),
    'Group': ['Control', 'Control', 'Treatment', 'Treatment', ...],  # 您的组别信息
    'Subject': ['S1', 'S1', 'S2', 'S2', ...]  # 个体信息,用于随机效应
})
复制代码
2. 使用您的数据进行分析
python 复制代码
# 直接使用您的数据进行替代
# 步骤1: 使用您的数据
# expr_df, metadata = 加载您的数据

# 步骤2: 差异表达分析
results_df = perform_differential_analysis(expr_df, metadata)

# 步骤3: 识别显著物种
results_df = identify_significant_species(results_df)

# 步骤4: 绘制火山图
fig, ax = create_ggplot_volcano(results_df)
plt.show()

# 步骤5: 保存结果
save_results(results_df, fig, prefix='your_analysis')
复制代码
3. 参数调整
您可以根据需要调整阈值:
python 复制代码
# 调整显著性阈值
log2fc_threshold = 0.58    # 对应1.5倍变化
neg_log10_p_threshold = 1.301  # 对应p=0.05

# 使用自定义阈值
results_df = identify_significant_species(
    results_df, 
    log2fc_threshold=0.58, 
    neg_log10_p_threshold=1.301
)

fig, ax = create_ggplot_volcano(
    results_df,
    log2fc_threshold=0.58,
    neg_log10_p_threshold=1.301
)
复制代码
4. 安装依赖包
bash
pip install numpy pandas matplotlib seaborn scipy statsmodels

5. 输出说明
代码会生成以下文件:

microbial_analysis_results.csv - 完整分析结果
microbial_analysis_volcano_plot.png - 火山图
microbial_analysis_top_upregulated.csv - Top上调物种
microbial_analysis_top_downregulated.csv - Top下调物种
microbial_analysis_annotated_volcano.png - 带标签的火山图

6. 统计分析细节
线性混合效应模型:使用statsmodels的mixedlm函数,以Subject作为随机效应

多重检验校正:使用Benjamini-Hochberg (FDR)方法

显著性标准:

|log2FC| ≥ 0.58 (对应1.5倍变化)

-log10(p_adj) ≥ 1.301 (对应p_adj ≤ 0.05)

7. 可视化特点
火山图具有以下ggplot风格特征:

灰色网格背景

清晰的颜色编码(红=上调,蓝=下调,灰=不显著)

虚线表示显著性阈值

图例和统计信息框

合适的点大小和透明度
相关推荐
白日做梦Q1 小时前
ICMP互联网控制报文协议的详细介绍(基本概念、用处、故障排查)
开发语言·网络·php
听风吟丶1 小时前
Java 响应式编程实战:Spring WebFlux+Reactor 构建高并发电商系统
java·开发语言·spring
love530love1 小时前
【实践指南】Windows 下 Stable Diffusion WebUI 与 ComfyUI 模型库“完美共存”指南
人工智能·windows·python·stable diffusion·大模型·aigc·comfyui
Aerelin1 小时前
爬虫图片采集(自动化)
开发语言·前端·javascript·爬虫·python·html
曲幽1 小时前
Flask路由参数处理:GET与POST的实战指南
python·web·route·form·get·post
枫叶丹41 小时前
浙人医信创实践:电科金仓异构多活架构破解集团化医院转型难题
开发语言·数据库·架构
小李小李快乐不已1 小时前
图论理论基础(2)
java·开发语言·c++·算法·图论
yddddddy1 小时前
Django在项目中的作用
数据库·python·django