Digital Trials pipeline configured for WES
Source-only snapshot of the cluster branch for WES execution. Large reference files (HPA/MANE/ensemble FASTA, model weights, ~597 MB) are omitted: they are baked into the container images at build time and mounted from the dreamdock-data PVC at runtime, and exceed the Gitea request size limit. Pipeline entry point is main.nf, which orchestrates the biotransformer, conplex and tissue modules as a single workflow. Ligand inputs are read from the eureka workspace; protein_zarr and chembl_db come from the dreamdock-data PVC.
This commit is contained in:
248
app_network/network.r
Normal file
248
app_network/network.r
Normal file
@@ -0,0 +1,248 @@
|
||||
#!/usr/bin/env Rscript
|
||||
#
|
||||
# Network enrichment and interaction analysis using STRINGdb package.
|
||||
# R equivalent of the NETWORK_ENRICHMENT Nextflow process.
|
||||
#
|
||||
|
||||
suppressPackageStartupMessages({
|
||||
library(STRINGdb)
|
||||
library(data.table)
|
||||
library(argparse)
|
||||
})
|
||||
|
||||
#' Main function
|
||||
main <- function() {
|
||||
# Parse command line arguments
|
||||
parser <- ArgumentParser(
|
||||
description = "Network enrichment analysis using STRINGdb package"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"interactions_file",
|
||||
type = "character",
|
||||
help = "Input TSV file with significant interactions (conplex output)"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--threshold",
|
||||
type = "double",
|
||||
default = 0.65,
|
||||
help = "ConPlex score threshold for protein network"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--max-proteins",
|
||||
type = "integer",
|
||||
default = 450L,
|
||||
help = "Maximum number of proteins to analyze (STRING API limitation)"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--output-dir",
|
||||
type = "character",
|
||||
default = ".",
|
||||
help = "Output directory for results"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--species",
|
||||
type = "integer",
|
||||
default = 9606L,
|
||||
help = "NCBI taxon ID (default: 9606 for Homo sapiens)"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--score-threshold",
|
||||
type = "integer",
|
||||
default = 400L,
|
||||
help = "STRING combined score threshold (0-1000, default: 400)"
|
||||
)
|
||||
|
||||
args <- parser$parse_args()
|
||||
|
||||
# Initialize STRINGdb
|
||||
cat("Initializing STRINGdb...\n")
|
||||
string_db <- STRINGdb$new(
|
||||
version = "12.0",
|
||||
species = args$species,
|
||||
score_threshold = args$score_threshold,
|
||||
network_type = "full",
|
||||
input_directory = "/app"
|
||||
)
|
||||
|
||||
# Load interaction data
|
||||
cat(sprintf("Loading interaction data from %s...\n", args$interactions_file))
|
||||
interaction_data <- fread(args$interactions_file, sep = "\t")
|
||||
interaction_data <- interaction_data[order(-conplex_score)]
|
||||
|
||||
# Remove duplicates and limit (replicates Nextflow logic)
|
||||
interaction_data <- unique(interaction_data, by = "transcipt")
|
||||
|
||||
if (nrow(interaction_data) > args$max_proteins) {
|
||||
cat(sprintf("Limiting to top %d proteins (STRING API limitation)\n", args$max_proteins))
|
||||
interaction_data <- interaction_data[1:args$max_proteins]
|
||||
}
|
||||
|
||||
# Filter by threshold
|
||||
filtered_data <- interaction_data[conplex_score > args$threshold]
|
||||
cat(sprintf("Found %d proteins above threshold %.2f\n",
|
||||
nrow(filtered_data), args$threshold))
|
||||
|
||||
if (nrow(filtered_data) == 0) {
|
||||
stop("ERROR: No proteins above threshold!")
|
||||
}
|
||||
|
||||
# Prepare data for STRING mapping
|
||||
gene_list <- data.frame(
|
||||
gene = filtered_data$transcipt,
|
||||
stringsAsFactors = FALSE
|
||||
)
|
||||
|
||||
# Map genes to STRING IDs
|
||||
cat("Mapping genes to STRING database...\n")
|
||||
mapped <- string_db$map(gene_list, "gene", removeUnmappedRows = TRUE)
|
||||
|
||||
if (nrow(mapped) == 0) {
|
||||
stop("ERROR: No genes could be mapped to STRING database!")
|
||||
}
|
||||
|
||||
cat(sprintf("Successfully mapped %d/%d genes to STRING IDs\n",
|
||||
nrow(mapped), nrow(gene_list)))
|
||||
|
||||
# Get STRING IDs
|
||||
string_ids <- mapped$STRING_id
|
||||
|
||||
# Get enrichment analysis
|
||||
cat("\nPerforming enrichment analysis...\n")
|
||||
enrichment_results <- string_db$get_enrichment(string_ids, category = "all", methodMT = "fdr")
|
||||
|
||||
if (nrow(enrichment_results) == 0) {
|
||||
cat("WARNING: No enrichment results found\n")
|
||||
enrichment_df <- data.table()
|
||||
} else {
|
||||
cat(sprintf("Found %d enriched terms\n", nrow(enrichment_results)))
|
||||
|
||||
# Convert to data.table and rename columns to match Python output
|
||||
enrichment_df <- as.data.table(enrichment_results)
|
||||
|
||||
# The STRINGdb enrichment output has columns:
|
||||
# category, term, number_of_genes, number_of_genes_in_background,
|
||||
# ncbiTaxonId, inputGenes, preferredNames, p_value, fdr, description
|
||||
# This should already match the Python output format
|
||||
}
|
||||
|
||||
# Get network interactions with detailed scores
|
||||
cat("\nExtracting network interactions...\n")
|
||||
interactions <- string_db$get_interactions(string_ids)
|
||||
|
||||
if (nrow(interactions) == 0) {
|
||||
cat("WARNING: No interactions found\n")
|
||||
interactions_df <- data.table()
|
||||
} else {
|
||||
cat(sprintf("Found %d protein-protein interactions\n", nrow(interactions)))
|
||||
|
||||
# Convert to data.table
|
||||
interactions_df <- as.data.table(interactions)
|
||||
|
||||
# The get_interactions() function returns:
|
||||
# from, to, combined_score, and potentially other columns
|
||||
# We need to get detailed scores separately
|
||||
|
||||
# Get the interaction network with all score types
|
||||
# Use get_png to access the full network data including detailed scores
|
||||
network_image <- string_db$plot_network(string_ids)
|
||||
|
||||
# Alternative: Access the graph directly for detailed scores
|
||||
# The STRINGdb object stores the full network internally
|
||||
graph <- string_db$get_graph()
|
||||
|
||||
# Extract edge attributes which contain detailed scores
|
||||
if (!is.null(graph)) {
|
||||
edge_data <- igraph::as_data_frame(graph, what = "edges")
|
||||
edge_dt <- as.data.table(edge_data)
|
||||
|
||||
# Filter for our query proteins
|
||||
edge_dt <- edge_dt[from %in% string_ids & to %in% string_ids]
|
||||
|
||||
# Rename columns to match Python output format
|
||||
if ("from" %in% names(edge_dt)) setnames(edge_dt, "from", "stringId_A")
|
||||
if ("to" %in% names(edge_dt)) setnames(edge_dt, "to", "stringId_B")
|
||||
|
||||
# Add preferred names
|
||||
edge_dt[, preferredName_A := string_db$get_aliases(stringId_A)$alias[1], by = stringId_A]
|
||||
edge_dt[, preferredName_B := string_db$get_aliases(stringId_B)$alias[1], by = stringId_B]
|
||||
|
||||
# Normalize scores to 0-1 range if they're in 0-1000 range
|
||||
score_cols <- c("combined_score", "neighborhood", "fusion", "cooccurence",
|
||||
"coexpression", "experimental", "database", "textmining")
|
||||
target_cols <- c("score", "nscore", "fscore", "pscore",
|
||||
"ascore", "escore", "dscore", "tscore")
|
||||
|
||||
for (i in seq_along(score_cols)) {
|
||||
src <- score_cols[i]
|
||||
tgt <- target_cols[i]
|
||||
if (src %in% names(edge_dt)) {
|
||||
# Check if scores are in 0-1000 range (need normalization)
|
||||
max_val <- max(edge_dt[[src]], na.rm = TRUE)
|
||||
if (max_val > 1) {
|
||||
edge_dt[, (tgt) := get(src) / 1000.0]
|
||||
} else {
|
||||
edge_dt[, (tgt) := get(src)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Add ncbiTaxonId
|
||||
edge_dt[, ncbiTaxonId := args$species]
|
||||
|
||||
# Select and order columns to match Python output
|
||||
output_cols <- c("stringId_A", "stringId_B", "preferredName_A", "preferredName_B",
|
||||
"ncbiTaxonId", "score", "nscore", "fscore", "pscore",
|
||||
"ascore", "escore", "dscore", "tscore")
|
||||
|
||||
# Keep only columns that exist
|
||||
existing_cols <- intersect(output_cols, names(edge_dt))
|
||||
interactions_df <- edge_dt[, ..existing_cols]
|
||||
|
||||
} else {
|
||||
# Fallback: use the interactions data from get_interactions()
|
||||
# Rename columns to match output format
|
||||
if ("from" %in% names(interactions_df)) {
|
||||
setnames(interactions_df, "from", "stringId_A")
|
||||
}
|
||||
if ("to" %in% names(interactions_df)) {
|
||||
setnames(interactions_df, "to", "stringId_B")
|
||||
}
|
||||
if ("combined_score" %in% names(interactions_df)) {
|
||||
interactions_df[, score := combined_score / 1000.0]
|
||||
}
|
||||
|
||||
interactions_df[, ncbiTaxonId := args$species]
|
||||
}
|
||||
}
|
||||
|
||||
# Generate output filename
|
||||
output_name <- gsub("_significant_interactions", "",
|
||||
tools::file_path_sans_ext(basename(args$interactions_file)))
|
||||
|
||||
# Create output directory if needed
|
||||
dir.create(args$output_dir, showWarnings = FALSE, recursive = TRUE)
|
||||
|
||||
# Save results
|
||||
enrichment_file <- file.path(args$output_dir,
|
||||
sprintf("%s_network_enrichment.tsv", output_name))
|
||||
interactions_file <- file.path(args$output_dir,
|
||||
sprintf("%s_network_interactions.tsv", output_name))
|
||||
|
||||
fwrite(enrichment_df, enrichment_file, sep = "\t")
|
||||
fwrite(interactions_df, interactions_file, sep = "\t")
|
||||
|
||||
cat("\nResults saved:\n")
|
||||
cat(sprintf(" Enrichment: %s\n", enrichment_file))
|
||||
cat(sprintf(" Interactions: %s\n", interactions_file))
|
||||
}
|
||||
|
||||
# Run main function
|
||||
if (!interactive()) {
|
||||
main()
|
||||
}
|
||||
376
app_network/network2.r
Normal file
376
app_network/network2.r
Normal file
@@ -0,0 +1,376 @@
|
||||
#!/usr/bin/env Rscript
|
||||
#
|
||||
# Offline network enrichment and interaction analysis using local STRING-DB files.
|
||||
# Replicates the NETWORK_ENRICHMENT Nextflow process without internet connection.
|
||||
# R equivalent of the Python script using STRINGdb package.
|
||||
#
|
||||
|
||||
suppressPackageStartupMessages({
|
||||
library(STRINGdb)
|
||||
library(data.table)
|
||||
library(argparse)
|
||||
})
|
||||
|
||||
#' Load STRING-DB data from local files
|
||||
#'
|
||||
#' @param string_db_dir Directory containing STRING-DB files
|
||||
#' @return List containing mappings and data frames
|
||||
load_string_data <- function(string_db_dir) {
|
||||
cat("Loading STRING-DB files...\n")
|
||||
|
||||
# Load alias mapping (ENST -> ENSP)
|
||||
aliases <- fread(
|
||||
file.path(string_db_dir, "9606.protein.aliases.enst.v12.0.tsv"),
|
||||
sep = "\t",
|
||||
header = FALSE,
|
||||
col.names = c("string_protein_id", "alias", "source")
|
||||
)
|
||||
|
||||
enst_to_ensp <- aliases[source == "Ensembl_transcript",
|
||||
.(string_protein_id, alias)]
|
||||
setkey(enst_to_ensp, alias)
|
||||
|
||||
# Load protein info (for preferred names)
|
||||
info <- fread(
|
||||
file.path(string_db_dir, "9606.protein.info.v12.0.txt"),
|
||||
sep = "\t",
|
||||
header = FALSE,
|
||||
col.names = c("string_protein_id", "preferred_name", "protein_size", "annotation")
|
||||
)
|
||||
setkey(info, string_protein_id)
|
||||
|
||||
# Load protein links - USE DETAILED FILE
|
||||
links <- fread(
|
||||
file.path(string_db_dir, "9606.protein.links.detailed.v12.0.txt"),
|
||||
sep = " "
|
||||
)
|
||||
|
||||
# Load enrichment terms
|
||||
enrichment <- fread(
|
||||
file.path(string_db_dir, "9606.protein.enrichment.terms.v12.0.txt"),
|
||||
sep = "\t",
|
||||
header = FALSE,
|
||||
col.names = c("string_protein_id", "category", "term", "description")
|
||||
)
|
||||
|
||||
cat(sprintf("Loaded %d ENST->ENSP mappings\n", nrow(enst_to_ensp)))
|
||||
cat(sprintf("Loaded %d protein-protein interactions\n", nrow(links)))
|
||||
cat(sprintf("Loaded %d enrichment annotations\n", nrow(enrichment)))
|
||||
|
||||
list(
|
||||
enst_to_ensp = enst_to_ensp,
|
||||
ensp_to_name = info,
|
||||
links = links,
|
||||
enrichment = enrichment
|
||||
)
|
||||
}
|
||||
|
||||
#' Perform enrichment analysis using Fisher's exact test
|
||||
#'
|
||||
#' @param query_proteins Vector of ENSP protein IDs
|
||||
#' @param enrichment_df Data table with enrichment annotations
|
||||
#' @param ensp_to_name Data table mapping ENSP to preferred names
|
||||
#' @param ensp_to_input_map Named vector mapping ENSP back to ENST
|
||||
#' @return Data table with enrichment results
|
||||
perform_enrichment_analysis <- function(query_proteins, enrichment_df,
|
||||
ensp_to_name, ensp_to_input_map) {
|
||||
query_set <- unique(query_proteins)
|
||||
n_query <- length(query_set)
|
||||
|
||||
cat(sprintf("Analyzing enrichment for %d proteins...\n", n_query))
|
||||
|
||||
results_list <- list()
|
||||
|
||||
# Group by category
|
||||
categories <- unique(enrichment_df$category)
|
||||
|
||||
for (cat in categories) {
|
||||
cat_group <- enrichment_df[category == cat]
|
||||
|
||||
# Background: Total unique proteins annotated in this category
|
||||
category_universe <- unique(cat_group$string_protein_id)
|
||||
background_size <- length(category_universe)
|
||||
|
||||
# Iterate over terms within this category
|
||||
terms <- unique(cat_group$term)
|
||||
|
||||
for (tm in terms) {
|
||||
term_group <- cat_group[term == tm]
|
||||
term_proteins <- unique(term_group$string_protein_id)
|
||||
|
||||
# Intersection: proteins in both query and term
|
||||
intersection <- intersect(query_set, term_proteins)
|
||||
n_intersection <- length(intersection)
|
||||
|
||||
if (n_intersection == 0) next
|
||||
|
||||
# Background term count
|
||||
n_term_background <- length(term_proteins)
|
||||
|
||||
# Fisher's exact test
|
||||
# Contingency table:
|
||||
# In term Not in term
|
||||
# In query a b
|
||||
# Not in query c d
|
||||
|
||||
a <- n_intersection
|
||||
b <- n_query - a
|
||||
c <- n_term_background - a
|
||||
d <- background_size - n_term_background - b
|
||||
|
||||
# Ensure no negative values
|
||||
if (c < 0 || d < 0) next
|
||||
|
||||
# One-sided test for enrichment
|
||||
contingency_matrix <- matrix(c(a, b, c, d), nrow = 2, byrow = TRUE)
|
||||
fisher_result <- fisher.test(contingency_matrix, alternative = "greater")
|
||||
pvalue <- fisher_result$p.value
|
||||
|
||||
# Get description
|
||||
description <- term_group$description[1]
|
||||
|
||||
# Get input genes (map ENSP back to ENST input)
|
||||
intersection_sorted <- sort(intersection)
|
||||
|
||||
input_genes_list <- sapply(intersection_sorted, function(ensp) {
|
||||
if (ensp %in% names(ensp_to_input_map)) {
|
||||
ensp_to_input_map[ensp]
|
||||
} else {
|
||||
ensp
|
||||
}
|
||||
})
|
||||
|
||||
input_genes <- paste(input_genes_list, collapse = ",")
|
||||
|
||||
# Get preferred names
|
||||
preferred_names_list <- sapply(intersection_sorted, function(p) {
|
||||
name <- ensp_to_name[string_protein_id == p, preferred_name]
|
||||
if (length(name) > 0) name[1] else strsplit(p, "\\.")[[1]][length(strsplit(p, "\\.")[[1]])]
|
||||
})
|
||||
preferred_names <- paste(preferred_names_list, collapse = ",")
|
||||
|
||||
results_list[[length(results_list) + 1]] <- data.table(
|
||||
category = cat,
|
||||
term = tm,
|
||||
number_of_genes = n_intersection,
|
||||
number_of_genes_in_background = n_term_background,
|
||||
ncbiTaxonId = 9606,
|
||||
inputGenes = input_genes,
|
||||
preferredNames = preferred_names,
|
||||
p_value = pvalue,
|
||||
description = description
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (length(results_list) == 0) {
|
||||
return(data.table())
|
||||
}
|
||||
|
||||
# Combine results
|
||||
results_df <- rbindlist(results_list)
|
||||
|
||||
# Benjamini-Hochberg FDR correction
|
||||
results_df <- results_df[order(p_value)]
|
||||
n_tests <- nrow(results_df)
|
||||
results_df[, fdr := p_value * n_tests / seq_len(n_tests)]
|
||||
results_df[fdr > 1.0, fdr := 1.0]
|
||||
|
||||
# Reorder columns to match API output
|
||||
setcolorder(results_df, c(
|
||||
"category", "term", "number_of_genes", "number_of_genes_in_background",
|
||||
"ncbiTaxonId", "inputGenes", "preferredNames", "p_value", "fdr", "description"
|
||||
))
|
||||
|
||||
return(results_df)
|
||||
}
|
||||
|
||||
#' Extract network interactions for query proteins
|
||||
#'
|
||||
#' @param query_proteins Vector of ENSP protein IDs
|
||||
#' @param links_df Data table with protein-protein interactions
|
||||
#' @param ensp_to_name Data table mapping ENSP to preferred names
|
||||
#' @return Data table with network interactions
|
||||
get_network_interactions <- function(query_proteins, links_df, ensp_to_name) {
|
||||
query_set <- unique(query_proteins)
|
||||
|
||||
# Filter links where both proteins are in query set
|
||||
interactions <- links_df[protein1 %in% query_set & protein2 %in% query_set]
|
||||
|
||||
if (nrow(interactions) == 0) {
|
||||
return(data.table())
|
||||
}
|
||||
|
||||
# Normalize scores (STRING files are integers 0-1000, API is float 0-1)
|
||||
score_cols <- c("combined_score", "neighborhood", "fusion", "cooccurence",
|
||||
"coexpression", "experimental", "database", "textmining")
|
||||
target_cols <- c("score", "nscore", "fscore", "pscore",
|
||||
"ascore", "escore", "dscore", "tscore")
|
||||
|
||||
for (i in seq_along(score_cols)) {
|
||||
src <- score_cols[i]
|
||||
tgt <- target_cols[i]
|
||||
if (src %in% names(interactions)) {
|
||||
interactions[, (tgt) := get(src) / 1000.0]
|
||||
} else {
|
||||
interactions[, (tgt) := 0.0]
|
||||
}
|
||||
}
|
||||
|
||||
# Add preferred names
|
||||
interactions[, preferredName_A := sapply(protein1, function(p) {
|
||||
name <- ensp_to_name[string_protein_id == p, preferred_name]
|
||||
if (length(name) > 0) name[1] else strsplit(p, "\\.")[[1]][length(strsplit(p, "\\.")[[1]])]
|
||||
})]
|
||||
|
||||
interactions[, preferredName_B := sapply(protein2, function(p) {
|
||||
name <- ensp_to_name[string_protein_id == p, preferred_name]
|
||||
if (length(name) > 0) name[1] else strsplit(p, "\\.")[[1]][length(strsplit(p, "\\.")[[1]])]
|
||||
})]
|
||||
|
||||
# Rename columns to match STRING API output
|
||||
setnames(interactions, c("protein1", "protein2"), c("stringId_A", "stringId_B"))
|
||||
|
||||
interactions[, ncbiTaxonId := 9606]
|
||||
|
||||
# Select and order columns to match original output
|
||||
column_order <- c(
|
||||
"stringId_A", "stringId_B", "preferredName_A", "preferredName_B",
|
||||
"ncbiTaxonId", "score", "nscore", "fscore", "pscore",
|
||||
"ascore", "escore", "dscore", "tscore"
|
||||
)
|
||||
|
||||
interactions <- interactions[, ..column_order]
|
||||
|
||||
return(interactions)
|
||||
}
|
||||
|
||||
#' Main function
|
||||
main <- function() {
|
||||
# Parse command line arguments
|
||||
parser <- ArgumentParser(
|
||||
description = "Offline network enrichment analysis using local STRING-DB files"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"interactions_file",
|
||||
type = "character",
|
||||
help = "Input TSV file with significant interactions (conplex output)"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--string-db-dir",
|
||||
type = "character",
|
||||
default = "/app",
|
||||
help = "Directory containing STRING-DB files"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--threshold",
|
||||
type = "double",
|
||||
default = 0.65,
|
||||
help = "ConPlex score threshold for protein network"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--max-proteins",
|
||||
type = "integer",
|
||||
default = 450L,
|
||||
help = "Maximum number of proteins to analyze"
|
||||
)
|
||||
|
||||
parser$add_argument(
|
||||
"--output-dir",
|
||||
type = "character",
|
||||
default = ".",
|
||||
help = "Output directory for results"
|
||||
)
|
||||
|
||||
args <- parser$parse_args()
|
||||
|
||||
# Load STRING-DB data
|
||||
string_data <- load_string_data(args$string_db_dir)
|
||||
|
||||
# Load interaction data
|
||||
cat(sprintf("\nLoading interaction data from %s...\n", args$interactions_file))
|
||||
interaction_data <- fread(args$interactions_file, sep = "\t")
|
||||
interaction_data <- interaction_data[order(-conplex_score)]
|
||||
|
||||
# Remove duplicates and limit
|
||||
# Replicates Nextflow logic: drops duplicates on transcript BEFORE limit
|
||||
interaction_data <- unique(interaction_data, by = "transcipt")
|
||||
|
||||
if (nrow(interaction_data) > args$max_proteins) {
|
||||
cat(sprintf("Limiting to top %d proteins\n", args$max_proteins))
|
||||
interaction_data <- interaction_data[1:args$max_proteins]
|
||||
}
|
||||
|
||||
# Filter by threshold
|
||||
filtered_data <- interaction_data[conplex_score > args$threshold]
|
||||
cat(sprintf("Found %d proteins above threshold %.2f\n",
|
||||
nrow(filtered_data), args$threshold))
|
||||
|
||||
# Map ENST to ENSP and keep track of mapping for output
|
||||
enst_list <- filtered_data$transcipt
|
||||
ensp_list <- character()
|
||||
ensp_to_input_map <- character()
|
||||
|
||||
for (enst in enst_list) {
|
||||
ensp <- string_data$enst_to_ensp[alias == enst, string_protein_id]
|
||||
if (length(ensp) > 0) {
|
||||
ensp <- ensp[1]
|
||||
ensp_list <- c(ensp_list, ensp)
|
||||
ensp_to_input_map[ensp] <- enst
|
||||
}
|
||||
}
|
||||
|
||||
cat(sprintf("Mapped %d ENST IDs to ENSP IDs\n", length(ensp_list)))
|
||||
|
||||
if (length(ensp_list) == 0) {
|
||||
stop("ERROR: No valid ENSP mappings found!")
|
||||
}
|
||||
|
||||
# Perform enrichment analysis
|
||||
cat("\nPerforming enrichment analysis...\n")
|
||||
enrichment_results <- perform_enrichment_analysis(
|
||||
ensp_list,
|
||||
string_data$enrichment,
|
||||
string_data$ensp_to_name,
|
||||
ensp_to_input_map
|
||||
)
|
||||
cat(sprintf("Found %d enriched terms\n", nrow(enrichment_results)))
|
||||
|
||||
# Get network interactions
|
||||
cat("\nExtracting network interactions...\n")
|
||||
network_interactions <- get_network_interactions(
|
||||
ensp_list,
|
||||
string_data$links,
|
||||
string_data$ensp_to_name
|
||||
)
|
||||
cat(sprintf("Found %d protein-protein interactions\n", nrow(network_interactions)))
|
||||
|
||||
# Generate output filename
|
||||
output_name <- gsub("_significant_interactions", "",
|
||||
tools::file_path_sans_ext(basename(args$interactions_file)))
|
||||
|
||||
# Create output directory if needed
|
||||
dir.create(args$output_dir, showWarnings = FALSE, recursive = TRUE)
|
||||
|
||||
# Save results
|
||||
enrichment_file <- file.path(args$output_dir,
|
||||
sprintf("%s_network_enrichment.tsv", output_name))
|
||||
interactions_file <- file.path(args$output_dir,
|
||||
sprintf("%s_network_interactions.tsv", output_name))
|
||||
|
||||
fwrite(enrichment_results, enrichment_file, sep = "\t")
|
||||
fwrite(network_interactions, interactions_file, sep = "\t")
|
||||
|
||||
cat("\nResults saved:\n")
|
||||
cat(sprintf(" Enrichment: %s\n", enrichment_file))
|
||||
cat(sprintf(" Interactions: %s\n", interactions_file))
|
||||
}
|
||||
|
||||
# Run main function
|
||||
if (!interactive()) {
|
||||
main()
|
||||
}
|
||||
283
app_network/network_enrich.py
Normal file
283
app_network/network_enrich.py
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Offline network enrichment and interaction analysis using local STRING-DB files.
|
||||
Replicates the NETWORK_ENRICHMENT Nextflow process without internet connection.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from scipy import stats
|
||||
import numpy as np
|
||||
|
||||
|
||||
def load_string_data(string_db_dir):
|
||||
"""Load all required STRING-DB files."""
|
||||
string_db_dir = Path(string_db_dir)
|
||||
|
||||
print("Loading STRING-DB files...")
|
||||
|
||||
# Load alias mapping (ENST -> ENSP)
|
||||
aliases = pd.read_csv(
|
||||
string_db_dir / "9606.protein.aliases.enst.v12.0.tsv",
|
||||
sep='\t',
|
||||
comment='#',
|
||||
names=['string_protein_id', 'alias', 'source']
|
||||
)
|
||||
enst_to_ensp = aliases[aliases['source'] == 'Ensembl_transcript'].set_index('alias')['string_protein_id'].to_dict()
|
||||
|
||||
# Load protein info (for preferred names)
|
||||
info = pd.read_csv(
|
||||
string_db_dir / "9606.protein.info.v12.0.txt",
|
||||
sep='\t',
|
||||
comment='#',
|
||||
names=['string_protein_id', 'preferred_name', 'protein_size', 'annotation']
|
||||
)
|
||||
ensp_to_name = info.set_index('string_protein_id')['preferred_name'].to_dict()
|
||||
|
||||
# Load protein links
|
||||
# Load protein links - USE DETAILED FILE
|
||||
links = pd.read_csv(
|
||||
string_db_dir / "9606.protein.links.detailed.v12.0.txt",
|
||||
sep=' '
|
||||
)
|
||||
|
||||
# Load enrichment terms
|
||||
enrichment = pd.read_csv(
|
||||
string_db_dir / "9606.protein.enrichment.terms.v12.0.txt",
|
||||
sep='\t',
|
||||
comment='#',
|
||||
names=['string_protein_id', 'category', 'term', 'description']
|
||||
)
|
||||
|
||||
print(f"Loaded {len(enst_to_ensp)} ENST->ENSP mappings")
|
||||
print(f"Loaded {len(links)} protein-protein interactions")
|
||||
print(f"Loaded {len(enrichment)} enrichment annotations")
|
||||
|
||||
return enst_to_ensp, ensp_to_name, links, enrichment
|
||||
|
||||
|
||||
def perform_enrichment_analysis(query_proteins, enrichment_df, ensp_to_name, background_size=20000):
|
||||
"""
|
||||
Perform enrichment analysis similar to STRING-DB API.
|
||||
Uses Fisher's exact test for each term.
|
||||
"""
|
||||
results = []
|
||||
|
||||
query_set = set(query_proteins)
|
||||
n_query = len(query_set)
|
||||
|
||||
# Group by category and term
|
||||
for (category, term), group in enrichment_df.groupby(['category', 'term']):
|
||||
term_proteins = set(group['string_protein_id'])
|
||||
|
||||
# Intersection: proteins in both query and term
|
||||
intersection = query_set & term_proteins
|
||||
n_intersection = len(intersection)
|
||||
|
||||
if n_intersection == 0:
|
||||
continue
|
||||
|
||||
# Background: total proteins with this term
|
||||
n_term_background = len(term_proteins)
|
||||
|
||||
# Fisher's exact test
|
||||
# Contingency table:
|
||||
# In term Not in term
|
||||
# In query a b
|
||||
# Not in query c d
|
||||
|
||||
a = n_intersection
|
||||
b = n_query - a
|
||||
c = n_term_background - a
|
||||
d = background_size - n_term_background - b
|
||||
|
||||
# One-sided test for enrichment
|
||||
oddsratio, pvalue = stats.fisher_exact([[a, b], [c, d]], alternative='greater')
|
||||
|
||||
# Get description (take first)
|
||||
description = group['description'].iloc[0]
|
||||
|
||||
# Get gene names
|
||||
intersection_list = list(intersection)
|
||||
input_genes = ','.join(intersection_list)
|
||||
preferred_names = ','.join([ensp_to_name.get(p, p.split('.')[-1]) for p in intersection_list])
|
||||
|
||||
results.append({
|
||||
'category': category,
|
||||
'term': term,
|
||||
'number_of_genes': n_intersection,
|
||||
'number_of_genes_in_background': n_term_background,
|
||||
'ncbiTaxonId': 9606,
|
||||
'inputGenes': input_genes,
|
||||
'preferredNames': preferred_names,
|
||||
'p_value': pvalue,
|
||||
'description': description
|
||||
})
|
||||
|
||||
if not results:
|
||||
return pd.DataFrame()
|
||||
|
||||
# Create DataFrame and calculate FDR
|
||||
results_df = pd.DataFrame(results)
|
||||
results_df = results_df.sort_values('p_value')
|
||||
|
||||
# Benjamini-Hochberg FDR correction
|
||||
n_tests = len(results_df)
|
||||
results_df['fdr'] = results_df['p_value'] * n_tests / (np.arange(1, n_tests + 1))
|
||||
results_df['fdr'] = results_df['fdr'].clip(upper=1.0)
|
||||
|
||||
# Sort by p-value
|
||||
results_df = results_df.sort_values('p_value').reset_index(drop=True)
|
||||
|
||||
return results_df
|
||||
|
||||
|
||||
def get_network_interactions(query_proteins, links_df, ensp_to_name):
|
||||
"""
|
||||
Extract network interactions for query proteins.
|
||||
Converts combined_score to normalized scores similar to STRING API.
|
||||
"""
|
||||
query_set = set(query_proteins)
|
||||
|
||||
# Filter links where both proteins are in query set
|
||||
interactions = links_df[
|
||||
links_df['protein1'].isin(query_set) &
|
||||
links_df['protein2'].isin(query_set)
|
||||
].copy()
|
||||
|
||||
if len(interactions) == 0:
|
||||
return pd.DataFrame()
|
||||
|
||||
interactions['score'] = interactions['combined_score'] / 1000.0
|
||||
interactions['nscore'] = interactions['neighborhood'] / 1000.0
|
||||
interactions['fscore'] = interactions['fusion'] / 1000.0
|
||||
interactions['pscore'] = interactions['cooccurence'] / 1000.0 # Note: typo in STRING file
|
||||
interactions['ascore'] = interactions['coexpression'] / 1000.0
|
||||
interactions['escore'] = interactions['experimental'] / 1000.0
|
||||
interactions['dscore'] = interactions['database'] / 1000.0
|
||||
interactions['tscore'] = interactions['textmining'] / 1000.0
|
||||
|
||||
# Add preferred names
|
||||
interactions['preferredName_A'] = interactions['protein1'].map(
|
||||
lambda x: ensp_to_name.get(x, x.split('.')[-1])
|
||||
)
|
||||
interactions['preferredName_B'] = interactions['protein2'].map(
|
||||
lambda x: ensp_to_name.get(x, x.split('.')[-1])
|
||||
)
|
||||
|
||||
# Rename columns to match STRING API output
|
||||
interactions = interactions.rename(columns={
|
||||
'protein1': 'stringId_A',
|
||||
'protein2': 'stringId_B'
|
||||
})
|
||||
|
||||
interactions['ncbiTaxonId'] = 9606
|
||||
|
||||
# Select and order columns to match original output
|
||||
column_order = [
|
||||
'stringId_A', 'stringId_B', 'preferredName_A', 'preferredName_B',
|
||||
'ncbiTaxonId', 'score', 'nscore', 'fscore', 'pscore',
|
||||
'ascore', 'escore', 'dscore', 'tscore'
|
||||
]
|
||||
|
||||
interactions = interactions[column_order].reset_index(drop=True)
|
||||
|
||||
return interactions
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Offline network enrichment analysis using local STRING-DB files'
|
||||
)
|
||||
parser.add_argument(
|
||||
'interactions_file',
|
||||
type=str,
|
||||
help='Input TSV file with significant interactions (conplex output)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--string-db-dir',
|
||||
type=str,
|
||||
default='/data/bugra/digital_trials/app_network',
|
||||
help='Directory containing STRING-DB files'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--threshold',
|
||||
type=float,
|
||||
default=0.0,
|
||||
help='ConPlex score threshold for protein network'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--max-proteins',
|
||||
type=int,
|
||||
default=450,
|
||||
help='Maximum number of proteins to analyze'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output-dir',
|
||||
type=str,
|
||||
default='.',
|
||||
help='Output directory for results'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load STRING-DB data
|
||||
enst_to_ensp, ensp_to_name, links, enrichment = load_string_data(args.string_db_dir)
|
||||
|
||||
# Load interaction data
|
||||
print(f"\nLoading interaction data from {args.interactions_file}...")
|
||||
interaction_data = pd.read_csv(args.interactions_file, sep='\t')
|
||||
interaction_data = interaction_data.sort_values('conplex_score', ascending=False)
|
||||
|
||||
# Remove duplicates and limit
|
||||
interaction_data = interaction_data.drop_duplicates('transcipt')
|
||||
if interaction_data.shape[0] > args.max_proteins:
|
||||
print(f"Limiting to top {args.max_proteins} proteins")
|
||||
interaction_data = interaction_data.iloc[:args.max_proteins]
|
||||
|
||||
# Filter by threshold
|
||||
filtered_data = interaction_data[interaction_data['conplex_score'] > args.threshold]
|
||||
print(f"Found {len(filtered_data)} proteins above threshold {args.threshold}")
|
||||
|
||||
# Convert ENST to ENSP
|
||||
enst_list = filtered_data['transcipt'].tolist()
|
||||
ensp_list = [enst_to_ensp.get(enst) for enst in enst_list]
|
||||
ensp_list = [e for e in ensp_list if e is not None]
|
||||
|
||||
print(f"Mapped {len(ensp_list)} ENST IDs to ENSP IDs")
|
||||
|
||||
if len(ensp_list) == 0:
|
||||
print("ERROR: No valid ENSP mappings found!")
|
||||
return
|
||||
|
||||
# Perform enrichment analysis
|
||||
print("\nPerforming enrichment analysis...")
|
||||
enrichment_results = perform_enrichment_analysis(ensp_list, enrichment, ensp_to_name)
|
||||
print(f"Found {len(enrichment_results)} enriched terms")
|
||||
|
||||
# Get network interactions
|
||||
print("\nExtracting network interactions...")
|
||||
network_interactions = get_network_interactions(ensp_list, links, ensp_to_name)
|
||||
print(f"Found {len(network_interactions)} protein-protein interactions")
|
||||
|
||||
# Generate output filename
|
||||
input_path = Path(args.interactions_file)
|
||||
output_name = input_path.stem.replace('_significant_interactions', '')
|
||||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Save results
|
||||
enrichment_file = output_dir / f"{output_name}_network_enrichment.tsv"
|
||||
interactions_file = output_dir / f"{output_name}_network_interactions.tsv"
|
||||
|
||||
enrichment_results.to_csv(enrichment_file, sep='\t')
|
||||
network_interactions.to_csv(interactions_file, sep='\t')
|
||||
|
||||
print(f"\nResults saved:")
|
||||
print(f" Enrichment: {enrichment_file}")
|
||||
print(f" Interactions: {interactions_file}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
332
app_network/network_enrich_offline.py
Normal file
332
app_network/network_enrich_offline.py
Normal file
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Offline network enrichment and interaction analysis using local STRING-DB files.
|
||||
Replicates the NETWORK_ENRICHMENT Nextflow process without internet connection.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from scipy import stats
|
||||
import numpy as np
|
||||
|
||||
|
||||
def load_string_data(string_db_dir):
|
||||
"""Load all required STRING-DB files."""
|
||||
string_db_dir = Path(string_db_dir)
|
||||
|
||||
print("Loading STRING-DB files...")
|
||||
|
||||
# Load alias mapping (ENST -> ENSP)
|
||||
aliases = pd.read_csv(
|
||||
string_db_dir / "9606.protein.aliases.enst.v12.0.tsv",
|
||||
sep='\t',
|
||||
comment='#',
|
||||
names=['string_protein_id', 'alias', 'source']
|
||||
)
|
||||
enst_to_ensp = aliases[aliases['source'] == 'Ensembl_transcript'].set_index('alias')['string_protein_id'].to_dict()
|
||||
|
||||
# Load protein info (for preferred names)
|
||||
info = pd.read_csv(
|
||||
string_db_dir / "9606.protein.info.v12.0.txt",
|
||||
sep='\t',
|
||||
comment='#',
|
||||
names=['string_protein_id', 'preferred_name', 'protein_size', 'annotation']
|
||||
)
|
||||
ensp_to_name = info.set_index('string_protein_id')['preferred_name'].to_dict()
|
||||
|
||||
# Load protein links - USE DETAILED FILE
|
||||
links = pd.read_csv(
|
||||
string_db_dir / "9606.protein.links.detailed.v12.0.txt",
|
||||
sep=' '
|
||||
)
|
||||
|
||||
# Load enrichment terms
|
||||
# Note: STRING files usually don't have headers, or have specific comment lines.
|
||||
# We ensure we capture string_protein_id, category, term, description
|
||||
enrichment = pd.read_csv(
|
||||
string_db_dir / "9606.protein.enrichment.terms.v12.0.txt",
|
||||
sep='\t',
|
||||
comment='#',
|
||||
names=['string_protein_id', 'category', 'term', 'description']
|
||||
)
|
||||
|
||||
print(f"Loaded {len(enst_to_ensp)} ENST->ENSP mappings")
|
||||
print(f"Loaded {len(links)} protein-protein interactions")
|
||||
print(f"Loaded {len(enrichment)} enrichment annotations")
|
||||
|
||||
return enst_to_ensp, ensp_to_name, links, enrichment
|
||||
|
||||
|
||||
def perform_enrichment_analysis(query_proteins, enrichment_df, ensp_to_name, ensp_to_input_map):
|
||||
"""
|
||||
Perform enrichment analysis similar to STRING-DB API.
|
||||
Uses Fisher's exact test for each term.
|
||||
"""
|
||||
results = []
|
||||
|
||||
query_set = set(query_proteins)
|
||||
n_query = len(query_set)
|
||||
|
||||
print(f"Analyzing enrichment for {n_query} proteins...")
|
||||
|
||||
# Group by category to calculate background size per category
|
||||
for category, cat_group in enrichment_df.groupby('category'):
|
||||
|
||||
# Background: Total unique proteins annotated in this category
|
||||
category_universe = set(cat_group['string_protein_id'])
|
||||
background_size = len(category_universe)
|
||||
|
||||
# Now iterate over terms within this category
|
||||
for term, group in cat_group.groupby('term'):
|
||||
term_proteins = set(group['string_protein_id'])
|
||||
|
||||
# Intersection: proteins in both query and term
|
||||
intersection = query_set & term_proteins
|
||||
n_intersection = len(intersection)
|
||||
|
||||
if n_intersection == 0:
|
||||
continue
|
||||
|
||||
# Background term count
|
||||
n_term_background = len(term_proteins)
|
||||
|
||||
# Fisher's exact test
|
||||
# Contingency table:
|
||||
# In term Not in term
|
||||
# In query a b
|
||||
# Not in query c d
|
||||
|
||||
a = n_intersection
|
||||
b = n_query - a
|
||||
c = n_term_background - a
|
||||
d = background_size - n_term_background - b
|
||||
|
||||
# Ensure no negative values (safety check)
|
||||
if c < 0 or d < 0:
|
||||
continue
|
||||
|
||||
# One-sided test for enrichment
|
||||
oddsratio, pvalue = stats.fisher_exact([[a, b], [c, d]], alternative='greater')
|
||||
|
||||
# Get description (take first)
|
||||
description = group['description'].iloc[0]
|
||||
|
||||
# Get input genes (map ENSP back to ENST input)
|
||||
intersection_list = sorted(list(intersection))
|
||||
|
||||
# Reconstruct the 'inputGenes' list using the original ENST IDs
|
||||
# If an ENSP maps to multiple ENSTs in the input, we list them all
|
||||
input_genes_list = []
|
||||
for ensp in intersection_list:
|
||||
if ensp in ensp_to_input_map:
|
||||
# Append the ENST that mapped to this ENSP
|
||||
input_genes_list.append(ensp_to_input_map[ensp])
|
||||
else:
|
||||
input_genes_list.append(ensp)
|
||||
|
||||
input_genes = ','.join(input_genes_list)
|
||||
|
||||
preferred_names = ','.join([ensp_to_name.get(p, p.split('.')[-1]) for p in intersection_list])
|
||||
|
||||
results.append({
|
||||
'category': category,
|
||||
'term': term,
|
||||
'number_of_genes': n_intersection,
|
||||
'number_of_genes_in_background': n_term_background,
|
||||
'ncbiTaxonId': 9606,
|
||||
'inputGenes': input_genes,
|
||||
'preferredNames': preferred_names,
|
||||
'p_value': pvalue,
|
||||
'description': description
|
||||
})
|
||||
|
||||
if not results:
|
||||
return pd.DataFrame()
|
||||
|
||||
# Create DataFrame and calculate FDR
|
||||
results_df = pd.DataFrame(results)
|
||||
|
||||
# Benjamini-Hochberg FDR correction
|
||||
results_df = results_df.sort_values('p_value')
|
||||
n_tests = len(results_df)
|
||||
results_df['fdr'] = results_df['p_value'] * n_tests / (np.arange(1, n_tests + 1))
|
||||
results_df['fdr'] = results_df['fdr'].clip(upper=1.0)
|
||||
|
||||
# Sort by p-value
|
||||
results_df = results_df.sort_values('p_value').reset_index(drop=True)
|
||||
|
||||
# Reorder columns to match API output
|
||||
# API Order: category, term, number_of_genes, number_of_genes_in_background, ncbiTaxonId, inputGenes, preferredNames, p_value, fdr, description
|
||||
cols = [
|
||||
'category', 'term', 'number_of_genes', 'number_of_genes_in_background',
|
||||
'ncbiTaxonId', 'inputGenes', 'preferredNames', 'p_value', 'fdr', 'description'
|
||||
]
|
||||
results_df = results_df[cols]
|
||||
|
||||
return results_df
|
||||
|
||||
|
||||
def get_network_interactions(query_proteins, links_df, ensp_to_name):
|
||||
"""
|
||||
Extract network interactions for query proteins.
|
||||
Converts combined_score to normalized scores similar to STRING API.
|
||||
"""
|
||||
query_set = set(query_proteins)
|
||||
|
||||
# Filter links where both proteins are in query set
|
||||
interactions = links_df[
|
||||
links_df['protein1'].isin(query_set) &
|
||||
links_df['protein2'].isin(query_set)
|
||||
].copy()
|
||||
|
||||
if len(interactions) == 0:
|
||||
return pd.DataFrame()
|
||||
|
||||
# Normalize scores (STRING files are integers 0-1000, API is float 0-1)
|
||||
score_cols = ['combined_score', 'neighborhood', 'fusion', 'cooccurence',
|
||||
'coexpression', 'experimental', 'database', 'textmining']
|
||||
|
||||
target_cols = ['score', 'nscore', 'fscore', 'pscore',
|
||||
'ascore', 'escore', 'dscore', 'tscore']
|
||||
|
||||
for src, tgt in zip(score_cols, target_cols):
|
||||
if src in interactions.columns:
|
||||
interactions[tgt] = interactions[src] / 1000.0
|
||||
else:
|
||||
interactions[tgt] = 0.0
|
||||
|
||||
# Add preferred names
|
||||
interactions['preferredName_A'] = interactions['protein1'].map(
|
||||
lambda x: ensp_to_name.get(x, x.split('.')[-1])
|
||||
)
|
||||
interactions['preferredName_B'] = interactions['protein2'].map(
|
||||
lambda x: ensp_to_name.get(x, x.split('.')[-1])
|
||||
)
|
||||
|
||||
# Rename columns to match STRING API output
|
||||
interactions = interactions.rename(columns={
|
||||
'protein1': 'stringId_A',
|
||||
'protein2': 'stringId_B'
|
||||
})
|
||||
|
||||
interactions['ncbiTaxonId'] = 9606
|
||||
|
||||
# Select and order columns to match original output
|
||||
column_order = [
|
||||
'stringId_A', 'stringId_B', 'preferredName_A', 'preferredName_B',
|
||||
'ncbiTaxonId', 'score', 'nscore', 'fscore', 'pscore',
|
||||
'ascore', 'escore', 'dscore', 'tscore'
|
||||
]
|
||||
|
||||
interactions = interactions[column_order].reset_index(drop=True)
|
||||
|
||||
return interactions
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Offline network enrichment analysis using local STRING-DB files'
|
||||
)
|
||||
parser.add_argument(
|
||||
'interactions_file',
|
||||
type=str,
|
||||
help='Input TSV file with significant interactions (conplex output)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--string-db-dir',
|
||||
type=str,
|
||||
default='/data/bugra/digital_trials/app_network',
|
||||
help='Directory containing STRING-DB files'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--threshold',
|
||||
type=float,
|
||||
default=0.65,
|
||||
help='ConPlex score threshold for protein network'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--max-proteins',
|
||||
type=int,
|
||||
default=450,
|
||||
help='Maximum number of proteins to analyze'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output-dir',
|
||||
type=str,
|
||||
default='.',
|
||||
help='Output directory for results'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load STRING-DB data
|
||||
enst_to_ensp, ensp_to_name, links, enrichment = load_string_data(args.string_db_dir)
|
||||
|
||||
# Load interaction data
|
||||
print(f"\nLoading interaction data from {args.interactions_file}...")
|
||||
interaction_data = pd.read_csv(args.interactions_file, sep='\t')
|
||||
interaction_data = interaction_data.sort_values('conplex_score', ascending=False)
|
||||
|
||||
# Remove duplicates and limit
|
||||
# Replicates Nextflow logic: drops duplicates on transcript BEFORE limit
|
||||
interaction_data = interaction_data.drop_duplicates('transcipt')
|
||||
if interaction_data.shape[0] > args.max_proteins:
|
||||
print(f"Limiting to top {args.max_proteins} proteins")
|
||||
interaction_data = interaction_data.iloc[:args.max_proteins]
|
||||
|
||||
# Filter by threshold
|
||||
filtered_data = interaction_data[interaction_data['conplex_score'] > args.threshold]
|
||||
print(f"Found {len(filtered_data)} proteins above threshold {args.threshold}")
|
||||
|
||||
# Map ENST to ENSP and keep track of mapping for output
|
||||
enst_list = filtered_data['transcipt'].tolist()
|
||||
ensp_list = []
|
||||
ensp_to_input_map = {} # Map ENSP back to ENST for output generation
|
||||
|
||||
for enst in enst_list:
|
||||
ensp = enst_to_ensp.get(enst)
|
||||
if ensp:
|
||||
ensp_list.append(ensp)
|
||||
# We map ENSP back to the ENST. If multiple ENSTs map to one ENSP,
|
||||
# this simple dict keeps the last one.
|
||||
# However, since input is deduped on transcript, this is mostly 1-to-1
|
||||
# for the query set.
|
||||
ensp_to_input_map[ensp] = enst
|
||||
|
||||
print(f"Mapped {len(ensp_list)} ENST IDs to ENSP IDs")
|
||||
|
||||
if len(ensp_list) == 0:
|
||||
print("ERROR: No valid ENSP mappings found!")
|
||||
return
|
||||
|
||||
# Perform enrichment analysis
|
||||
print("\nPerforming enrichment analysis...")
|
||||
enrichment_results = perform_enrichment_analysis(ensp_list, enrichment, ensp_to_name, ensp_to_input_map)
|
||||
print(f"Found {len(enrichment_results)} enriched terms")
|
||||
|
||||
# Get network interactions
|
||||
print("\nExtracting network interactions...")
|
||||
network_interactions = get_network_interactions(ensp_list, links, ensp_to_name)
|
||||
print(f"Found {len(network_interactions)} protein-protein interactions")
|
||||
|
||||
# Generate output filename
|
||||
input_path = Path(args.interactions_file)
|
||||
output_name = input_path.stem.replace('_significant_interactions', '')
|
||||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Save results
|
||||
enrichment_file = output_dir / f"{output_name}_network_enrichment.tsv"
|
||||
interactions_file = output_dir / f"{output_name}_network_interactions.tsv"
|
||||
|
||||
enrichment_results.to_csv(enrichment_file, sep='\t', index=False)
|
||||
network_interactions.to_csv(interactions_file, sep='\t', index=False)
|
||||
|
||||
print(f"\nResults saved:")
|
||||
print(f" Enrichment: {enrichment_file}")
|
||||
print(f" Interactions: {interactions_file}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user