METAflux | 实操教程4-源码理解

文化   2024-10-18 11:02   黑龙江  

今天我们继续学习METAflux这个工具,在前面的推文中我们一起详细的学习了METAflux的文献和代码实操部分,感兴趣的小伙伴可以先阅读这部分内容:

METAFlux:特异解析肿瘤微环境的代谢重编程

METAflux | 实操教程1-简介和快速上手

METAflux | 实操教程2-Bulk RNA-seq数据分析步骤拆解

METAflux | 实操教程3-single cell RNA-seq分析步骤拆解

计算反应活性

scores<-calculate_reaction_score(bulk_test_example)

相关源码及其解读

#' Calculate metabolic reaction scores (MRAS) for 13082 reactions
#'
#' @param data gene expression data.1.The gene expression matrix should be gene by sample matrix where row names are human gene names (gene symbols),
#' and column names should be sample names. Please note that METAFlux does not support other gene IDs.
#' 2.The input gene expression matrix should be normalized (e.g., log-transformed, etc.) before using METAFlux.
#' METAflux will not perform any normalization on expression data.
#' 3.Gene expression data cannot have negative values.
#'
#' @return
#' @export
#'
#' @examples  calculate_reaction_score(bulk_test_example)
calculate_reaction_score <- function(data) {
  if (sum(data < 0) > 0)
    stop("Expression data needs to be all positive")
  # make sure features are present
  features <- rownames(data)
  if (sum(features %in% rownames(gene_num)) == 0)
    stop("Requested gene names cannot be found. Rownames of input data should be human gene names.Please check the rownames of input data.")  #change
  message(paste0(round(sum(features %in% rownames(gene_num))/3625 * 100,
                       3), "% metabolic related genes were found......"))
  #计算匹配的基因名占总基因名的百分比
  gene_num <- METAFlux:::gene_num#获取 gene_num 数据
  Hgem <- METAFlux:::Hgem#代谢反应的信息
  iso <- METAFlux:::iso#同位素标记代谢物的信息
  multi_comp <- METAFlux:::multi_comp#多组分复合体的信息
  simple_comp <- METAFlux:::simple_comp#简单复合体的信息
  message("Computing metabolic reaction activity scores......")
  core <- do.call(rbind, lapply(1:length(iso), calculate_iso_score, data = data,
                                list = iso, gene_num = gene_num))
  core2 <- do.call(rbind, lapply(1:length(simple_comp), calculate_simple_comeplex_score,
                                 data, list = simple_comp, gene_num = gene_num))
  core3 <- do.call(rbind, lapply(1:length(multi_comp), calculate_multi_comp,
                                 data = data, gene_num = gene_num))
  message("Preparing for score matrix......")
  rownames(core) <- names(iso)
  rownames(core2) <- names(simple_comp)
  rownames(core3) <- names(multi_comp)
  big_score_matrix <- rbind(core, core2, core3)
  big_score_matrix <- apply(big_score_matrix, 2, stdize, na.rm = T)
  # handle NAN
  big_score_matrix[is.na(big_score_matrix)] <- 0
  empty_helper <- as.data.frame(Hgem$Reaction)
  colnames(empty_helper) <- "reaction"
  Final_df <- merge(empty_helper, big_score_matrix, all.x = T, by.x = 1,
                    by.y = 0)
  Final_df[is.na(Final_df)] <- 1
  rownames(Final_df) <- Final_df$reaction
  Final_df$reaction <- NULL
  Final_df <- Final_df[Hgem$Reaction, , drop = F]
  #以上是对计算结果处理,首先合并成一个大的得分矩阵;、
  #将得分矩阵中的NaN值替换为0;将得分矩阵和反应信息合并;
  #将合并后矩阵中的NaN值替换为1;删除不再需要的反应
  if (all.equal(rownames(Final_df), Hgem$Reaction)) {
    message("Metabolic reaction activity scores successfully calculated \n")
  } else {
    message("Calculation not reliable Check input data format \n")
  }
  Final_df[which(Hgem$LB == 0 & Hgem$UB == 0), ] <- 0  #this is biomass reaction, do not use
  return(Final_df)
}
#确保最终得分矩阵的行名与 Hgem 数据集中的反应名称匹配
#检查最终得分矩阵的行名是否与 Hgem 数据集中的反应名称完全匹配,并给出相应的信息
#将生物量反应(下界和上界都为0的反应)的得分设置为0,得到最终的代谢反应活性得分矩阵

计算代谢反应通量

#flux<-compute_flux(mras=scores,medium = cell_medium)#if data are cell line samples
flux<-compute_flux(mras=scores,medium = human_blood)#if data are human derived samples

相关源码及其解读

#' Final optimization step for flux calculation
#'
#' @param mras metabolic reaction activity scores
#' @param medium input medium file which indicates the nutrients available in the medium.
#' We provide 2 general mediums if you have no prior knowledge about your medium: cell line medium and human blood medium if prior knowledge of medium is not available.
#' Please see tutorial for more details.
#'
#' @return Calculated fluxes
#' @export
#' @import osqp
#'
#' @examples
compute_flux <- function(mras, medium) {
  message("Setting up for optimization.....")
  Hgem <- METAFlux:::Hgem
  P <- diag(1, ncol(Hgem$S), ncol(Hgem$S))
  #创建一个与代谢网络模型的 S 矩阵列数相同大小的单位对角矩阵 P
  q <- rep(0, ncol(Hgem$S))#创建一个全零向量 q,其长度与 S 矩阵的列数相同
  q[which(Hgem$Obj == 1)] <- -10000
  #在目标函数中,对于目标反应设置一个很大的负值,以确保在优化过程中这些反应的通量最小化
  A <- as.matrix(rbind(Hgem$S, P))#将 S 矩阵和单位矩阵 P 合并,形成线性规划问题的 A 矩阵
  resu <- list()#初始化两个列表,用于存储每次优化的结果和通量向量
  flux_vector <- list()
  message("Computing bulk RNA-seq flux.....")
  Seq <- seq(1, ncol(mras))
  pb <- txtProgressBar(0, length(Seq), style = 3)
  #循环遍历 mras 矩阵的每一列,对每个代谢反应活性评分进行优化计算
  for (i in Seq) {
    setTxtProgressBar(pb, i)
    origlb <- Hgem$LB#获取原始的下界
    origlb[Hgem$rev == 1] <- -mras[, i][Hgem$rev == 1]#对于可逆反应,设置下界为 mras 的负值
    origlb[Hgem$rev == 0] <- 0#对于不可逆反应,设置下界为 0
    origlb <- origlb[, 1]#确保下界是一维向量
    origub <- mras[, i]#设置上界(upper bound)为当前迭代的 mras 值
    origlb[Hgem$Reaction %in% Hgem$Reaction[which(Hgem$pathway == "Exchange/demand reactions")]] <- 0
    #对于交换/需求反应,设置下界为 0
    origlb[Hgem$Reaction %in% medium$reaction_name] <- -1
    l <- c(rep(0, nrow(Hgem$S)), origlb)#创建线性规划问题的下界向量 l
    u <- c(rep(0, nrow(Hgem$S)), origub)#创建线性规划问题的上界向量 u
    settings <- osqpSettings(max_iter = 1000000L, eps_abs = 1e-04,
                             eps_rel = 1e-04, adaptive_rho_interval = 50, verbose = FALSE)
    #设置 OSQP 优化器的参数
    model <- osqp(P, q, A, l, u, settings)#创建并解决 OSQP 优化模型
    # Solve problem
    res <- model$Solve()#解决优化问题并获取结果
    resu[[i]] <- res
    flux_vector[[i]] <- res$x#将优化结果和通量向量存储到列表中
  }
  close(pb)
  flux_vector <- do.call(cbind, flux_vector)#将通量向量的列表合并成一个矩阵
  colnames(flux_vector) <- colnames(mras)#设置通量向量矩阵的列名为 mras 矩阵的列名
  rownames(flux_vector) <- Hgem$Reaction#设置通量向量矩阵的行名为反应名称
  return(flux_vector)
}

建模理论知识

在代谢通量计算中,S矩阵(也称为斯托基矩阵或反应系数矩阵)和A矩阵(约束矩阵)是两个关键的数学对象,它们在构建和解决代谢网络模型中起着至关重要的作用。S矩阵提供了代谢网络的结构信息,而A矩阵则包含了确保解决方案符合生物学和实验约束的条件。通过结合这两个矩阵,可以构建和求解代谢网络模型,从而预测和理解细胞在不同条件下的代谢行为。

S矩阵(斯托基矩阵)

S矩阵是一个二维矩阵,它描述了代谢网络中所有代谢物(如酶、辅酶等)与所有代谢反应之间的关系。每一行代表一个代谢物,每一列代表一个特定的代谢反应。矩阵中的每个元素表示该代谢物在相应反应中的系数,即该代谢物在反应中被消耗或产生的数量。如果一个代谢物在某个反应中被消耗,那么相应的元素是负数;如果它是产物,则元素是正数;如果该代谢物不参与某个反应,则元素是零。S矩阵的主要作用是:

  • 表示代谢网络的结构。
  • 用于计算代谢物的平衡,即输入和输出的代谢物的净变化。
  • 作为线性规划问题中的约束矩阵,用于求解代谢通量。

A矩阵(约束矩阵)

A矩阵是一个更大的矩阵,它不仅包括了S矩阵的信息,还包含了额外的约束条件,如代谢反应的方向性(可逆或不可逆)、代谢物的边界条件(如细胞边界或细胞器边界)等。A矩阵通常用于线性规划问题中,以确保求解过程中遵守这些约束条件。A矩阵的主要作用是:

  • 将S矩阵的约束与其他类型的约束(如上界和下界)结合起来。
  • 用于定义线性规划问题的可行解空间。
  • 确保计算出的代谢通量符合生物学意义和实验条件。

线性规划问题

在代谢通量计算中,通常将问题表述为一个线性规划问题,目标是最大化或最小化某个特定的代谢目标(如生长速率、能量产生等)。S矩阵和A矩阵在这个过程中扮演着核心角色:

  • 目标函数:通常由S矩阵中的特定列(代表目标代谢物)构成,通过调整这些代谢物的通量来优化目标函数。
  • 约束条件:由A矩阵定义,确保所有代谢反应的通量都符合代谢网络的结构和细胞的生理条件。

通过解决这个线性规划问题,可以得到每个代谢反应在给定条件下的最优通量,即代谢网络在特定条件下的工作状态。

  • 注意:由于最小化模型中总通量的总和,因此我们将得到一个简洁的通量数据输出,这意味着许多反应将接近 0 通量。(例如,有些反应应该只向前进行,但预测的通量有一个非常小的负号,可以认为接近 0 通量)

计算表达量矩阵

mean_exp=calculate_avg_exp(myseurat = sc_test_example,myident = 'Cell_type',n_bootstrap=3,seed=1)

相关源码及其解读

generate_boots

#' Generate bootstrap index data
#'
#' @param celltype colnames of single cell data.Colnames should be labeled as cell type or cluster.
#' @param n  number of bootstrap
#' @import dplyr
#'
#' @return
#'
#'
#' @examples
generate_boots <- function(celltype, n) {
  dt <- data.frame(cluster = celltype, id = 1:length(celltype))
  index <- do.call(cbind, sapply(1:n, function(x) {
    splits <- dt %>%
      group_by(cluster) %>%
      sample_n(dplyr::n(), replace = TRUE) %>%
      ungroup() %>%
      dplyr::select("id")
  }))
  return(index)
}

这个generate_boots函数的目的是为单细胞数据分析生成bootstrap索引。函数接受两个参数:celltype和n。celltype是一个向量,包含每个细胞的类型或簇标签;n是一个整数,表示要生成的bootstrap样本的数量。函数的输出是一个矩阵,其中每一列代表一个bootstrap样本的索引

get_ave_exp

#
#' Calculate mean expression for one bootstrap
#'
#' @param i index
#' @param myseurat single cell Seurat object.METAFlux will calculate on "data" slot
#' @param samples generated bootstrap index data
#' @param myident Seurat idents.This will be a character string indicating the grouping of the seurat object
#'
#' @return
#' @import Seurat
#'
#' @examples

get_ave_exp <- function(i, myseurat, samples,myident) {
  meta.data=myseurat@meta.data[samples[,i],]
  #从Seurat对象的元数据中提取出第 i 列的bootstrap样本索引对应的样本数据
  sample <-myseurat@assays$RNA@counts[,samples[,i]]
  #从Seurat对象的RNA矩阵中提取出第 i 列的bootstrap样本索引对应的表达数据
  SeuratObject<-suppressWarnings(
    CreateSeuratObject(count=sample,meta.data = meta.data))
  #使用提取的样本数据和表达量数据创建一个新的Seurat对象
  SeuratObject<-NormalizeData(SeuratObject,verbose = FALSE)
  #新创建的Seurat对象进行数据标准化
  ave<-AverageExpression(SeuratObject,group.by = myident,return.seurat = T)[["RNA"]]@data
  #计算新Seurat对象的平均表达量,按照 myident 指定的分组进行,然后从结果中提取RNA检测的平均表达量数据
  return(ave)
}

这个函数get_ave_exp用于计算每一次bootstrap样本的平均表达量

calculate_avg_exp

#' Calculate bootstrap mean expression for single cell data
#'
#' @param n_bootstrap number of bootstrap
#' @param seed random seed
#' @param myseurat Seurat object. METAFlux will calculate on "data" slot
#' @param myident Seurat idents for grouping.This will be a character string indicating the grouping of the seurat object
#'
#' @return mean expression data
#' @import Seurat
#' @export
#'
#' @examples
calculate_avg_exp <- function(myseurat,myident,n_bootstrap,seed) {
  set.seed(seed)#保证计算的可重复性
  samples=generate_boots(myseurat@meta.data[,myident],n_bootstrap)
  #函数生成bootstrap索引数据。这里使用Seurat对象的样本数据和指定的分组idents来生成索引
  exp <- lapply(1:n_bootstrap,get_ave_exp,myseurat,samples,myident)
  #对每个bootstrap样本调用 get_ave_exp 函数计算平均表达量
  exp <- do.call(cbind, exp)
  #将exp中的所有平均表达量矩阵按列合并,形成一个矩阵,其中每一列代表一个bootstrap样本的平均表达量
  return(exp)
}

统计理论知识

在统计分析中,bootstrap是一种重采样方法,用于估计一个统计量的分布(例如均值、中位数、方差等)。通过从原始数据集中多次有放回地随机抽取样本,可以创建多个“bootstrap样本”,然后对每个样本计算感兴趣的统计量。这个过程可以帮助评估统计量的不确定性和置信区间。这里是示例练习,所以bootstrap设置为3;真实分析过程时bootstrap应该设置为几百bootstrap样本的定义是指从原始单细胞数据集中有放回地随机抽取的子集。这个过程模拟了多次实验或测量,目的是为了估计统计量的分布,如平均表达量、方差等。


本期的内容就到这里啦,我们下期再见~

关注公众号,下回更新不迷路




生信宝库
本公众号只用于生信知识的收集与传播,以及生信人之间互相交流和学习,不会涉及任何商业利益。本公众号各小编平时忙于科研,更新文章较其它同类型公众号较慢,但保持宁缺毋滥的本心,只更新对大家有用的推文。
 最新文章