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()
|
||||
}
|
||||
Reference in New Issue
Block a user