1 load libraries

# Data Processing
library(dplyr)
library(Seurat)
library(tibble)
library(tidyr)
library(stringr)

# Visualization
library(ggplot2)
library(ComplexHeatmap)
library(patchwork)
library(SCpubr)

# Regulatory Network Inference
library(decoupleR)
library(dorothea)
data(dorothea_hs, package = "dorothea")
library(tictoc)

# Create output directories to keep project clean
dir.create("Output_Figures", showWarnings = FALSE)
dir.create("Output_Tables", showWarnings = FALSE)
dir.create("Output_Objects", showWarnings = FALSE)

2 Load Seurat Object


# Load your Seurat Object
seurat_obj <- readRDS("../0-Seurat_RDS_OBJECT_FINAL/All_samples_Merged_with_Renamed_Clusters_Cell_state-03-12-2025.rds.rds")

Idents(seurat_obj) <- "seurat_clusters"
print("Object Loaded.")

3 Infer TF Activity (Run Weighted Mean)

# Select Assay: SCT or RNA
assay_to_use <- "SCT" 

# 1. Load Local DoRothEA Network
library(dorothea)
data(dorothea_hs, package = "dorothea")

# Filter for High/Medium Confidence (A, B, C)
network <- dorothea_hs %>%
  dplyr::filter(confidence %in% c("A", "B", "C"))

print(paste("Running TF inference using", assay_to_use, "assay..."))

# --- OPTIMIZATION: Filter for Shared Genes to Save Memory ---
# Identify genes present in both the Seurat object and the Network
network_genes <- unique(c(network$tf, network$target))
genes_to_keep <- intersect(rownames(seurat_obj@assays[[assay_to_use]]@data), network_genes)

print(paste("Original features:", nrow(seurat_obj)))
print(paste("Features optimized for inference:", length(genes_to_keep)))

# Create smaller matrix (only relevant genes)
expression_matrix <- as.matrix(seurat_obj@assays[[assay_to_use]]@data[genes_to_keep, ])

# Load Parallel Library
library(BiocParallel)

# Set up Parallel Backend
# If on Windows, use SnowParam(). If Linux/Mac, MulticoreParam() is better.
# Let's use SnowParam as it is safer for stability.
n_cores <- 6  # Start conservative. Don't use all cores yet.
bpparam <- SnowParam(workers = n_cores, progressbar = TRUE)

print(paste("Running DecoupleR on", n_cores, "cores..."))

gc()  # Clean memory before running inference)

# 2. Run Weighted Mean (VIPER-like method)
tic("Run DecoupleR")
activities <- decoupleR::run_wmean(mat = expression_matrix,
                                   network = network,
                                   .source = "tf",       # Must match DoRothEA column name
                                   .target = "target",
                                   .mor = "mor",
                                   times = 100,
                                   minsize = 5)
gc()  # Clean memory before running inference)
toc()
gc()  # Clean memory before running inference)
# 3. Reshape and Add as a New Assay ('dorothea')
# Transform long-format to matrix (Features x Cells)
tf_activity_matrix <- activities %>%
  filter(statistic == "norm_wmean") %>%
  pivot_wider(id_cols = "source", names_from = "condition", values_from = "score") %>%
  column_to_rownames("source") %>%
  as.matrix()

# Ensure strict column alignment with Seurat object cells
# (Note: Using expression_matrix colnames ensures perfect match)
common_cells <- colnames(expression_matrix)
tf_activity_matrix <- tf_activity_matrix[, common_cells]

# Add to Seurat Object
seurat_obj[["dorothea"]] <- CreateAssayObject(data = tf_activity_matrix)

# Scale the data (Required for Heatmaps)
seurat_obj <- ScaleData(seurat_obj, assay = "dorothea")

# 4. SAVE OBJECT WITH TF ACTIVITY
saveRDS(seurat_obj, file = "Output_Objects/Seurat_Object_With_TF_Activity.rds")
print("TF Assay added and Object Saved.")
LS0tCnRpdGxlOiAiVEYgQWN0aXZpdHkgSW5mZXJlbmNlIEFuYWx5c2lzIChEZWNvdXBsZVIgKyBEb1JvdGhFQSkiCmF1dGhvcjogIk5hc2lyIE1haG1vb2QgQWJiYXNpIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB0cnVlCiAgICB0aGVtZTogam91cm5hbAotLS0KCgojIGxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBpbmNsdWRlPVRSVUV9CiMgRGF0YSBQcm9jZXNzaW5nCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeSh0aWR5cikKbGlicmFyeShzdHJpbmdyKQoKIyBWaXN1YWxpemF0aW9uCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShDb21wbGV4SGVhdG1hcCkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoU0NwdWJyKQoKIyBSZWd1bGF0b3J5IE5ldHdvcmsgSW5mZXJlbmNlCmxpYnJhcnkoZGVjb3VwbGVSKQpsaWJyYXJ5KGRvcm90aGVhKQpkYXRhKGRvcm90aGVhX2hzLCBwYWNrYWdlID0gImRvcm90aGVhIikKbGlicmFyeSh0aWN0b2MpCgojIENyZWF0ZSBvdXRwdXQgZGlyZWN0b3JpZXMgdG8ga2VlcCBwcm9qZWN0IGNsZWFuCmRpci5jcmVhdGUoIk91dHB1dF9GaWd1cmVzIiwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCmRpci5jcmVhdGUoIk91dHB1dF9UYWJsZXMiLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKZGlyLmNyZWF0ZSgiT3V0cHV0X09iamVjdHMiLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKCmBgYAoKCiMgTG9hZCBTZXVyYXQgT2JqZWN0IApgYGB7cn0KCiMgTG9hZCB5b3VyIFNldXJhdCBPYmplY3QKc2V1cmF0X29iaiA8LSByZWFkUkRTKCIuLi8wLVNldXJhdF9SRFNfT0JKRUNUX0ZJTkFML0FsbF9zYW1wbGVzX01lcmdlZF93aXRoX1JlbmFtZWRfQ2x1c3RlcnNfQ2VsbF9zdGF0ZS0wMy0xMi0yMDI1LnJkcy5yZHMiKQoKSWRlbnRzKHNldXJhdF9vYmopIDwtICJzZXVyYXRfY2x1c3RlcnMiCnByaW50KCJPYmplY3QgTG9hZGVkLiIpCmBgYAoKCiMgIEluZmVyIFRGIEFjdGl2aXR5IChSdW4gV2VpZ2h0ZWQgTWVhbikKYGBge3J9CiMgU2VsZWN0IEFzc2F5OiBTQ1Qgb3IgUk5BCmFzc2F5X3RvX3VzZSA8LSAiU0NUIiAKCiMgMS4gTG9hZCBMb2NhbCBEb1JvdGhFQSBOZXR3b3JrCmxpYnJhcnkoZG9yb3RoZWEpCmRhdGEoZG9yb3RoZWFfaHMsIHBhY2thZ2UgPSAiZG9yb3RoZWEiKQoKIyBGaWx0ZXIgZm9yIEhpZ2gvTWVkaXVtIENvbmZpZGVuY2UgKEEsIEIsIEMpCm5ldHdvcmsgPC0gZG9yb3RoZWFfaHMgJT4lCiAgZHBseXI6OmZpbHRlcihjb25maWRlbmNlICVpbiUgYygiQSIsICJCIiwgIkMiKSkKCnByaW50KHBhc3RlKCJSdW5uaW5nIFRGIGluZmVyZW5jZSB1c2luZyIsIGFzc2F5X3RvX3VzZSwgImFzc2F5Li4uIikpCgojIC0tLSBPUFRJTUlaQVRJT046IEZpbHRlciBmb3IgU2hhcmVkIEdlbmVzIHRvIFNhdmUgTWVtb3J5IC0tLQojIElkZW50aWZ5IGdlbmVzIHByZXNlbnQgaW4gYm90aCB0aGUgU2V1cmF0IG9iamVjdCBhbmQgdGhlIE5ldHdvcmsKbmV0d29ya19nZW5lcyA8LSB1bmlxdWUoYyhuZXR3b3JrJHRmLCBuZXR3b3JrJHRhcmdldCkpCmdlbmVzX3RvX2tlZXAgPC0gaW50ZXJzZWN0KHJvd25hbWVzKHNldXJhdF9vYmpAYXNzYXlzW1thc3NheV90b191c2VdXUBkYXRhKSwgbmV0d29ya19nZW5lcykKCnByaW50KHBhc3RlKCJPcmlnaW5hbCBmZWF0dXJlczoiLCBucm93KHNldXJhdF9vYmopKSkKcHJpbnQocGFzdGUoIkZlYXR1cmVzIG9wdGltaXplZCBmb3IgaW5mZXJlbmNlOiIsIGxlbmd0aChnZW5lc190b19rZWVwKSkpCgojIENyZWF0ZSBzbWFsbGVyIG1hdHJpeCAob25seSByZWxldmFudCBnZW5lcykKZXhwcmVzc2lvbl9tYXRyaXggPC0gYXMubWF0cml4KHNldXJhdF9vYmpAYXNzYXlzW1thc3NheV90b191c2VdXUBkYXRhW2dlbmVzX3RvX2tlZXAsIF0pCgojIExvYWQgUGFyYWxsZWwgTGlicmFyeQpsaWJyYXJ5KEJpb2NQYXJhbGxlbCkKCiMgU2V0IHVwIFBhcmFsbGVsIEJhY2tlbmQKIyBJZiBvbiBXaW5kb3dzLCB1c2UgU25vd1BhcmFtKCkuIElmIExpbnV4L01hYywgTXVsdGljb3JlUGFyYW0oKSBpcyBiZXR0ZXIuCiMgTGV0J3MgdXNlIFNub3dQYXJhbSBhcyBpdCBpcyBzYWZlciBmb3Igc3RhYmlsaXR5LgpuX2NvcmVzIDwtIDYgICMgU3RhcnQgY29uc2VydmF0aXZlLiBEb24ndCB1c2UgYWxsIGNvcmVzIHlldC4KYnBwYXJhbSA8LSBTbm93UGFyYW0od29ya2VycyA9IG5fY29yZXMsIHByb2dyZXNzYmFyID0gVFJVRSkKCnByaW50KHBhc3RlKCJSdW5uaW5nIERlY291cGxlUiBvbiIsIG5fY29yZXMsICJjb3Jlcy4uLiIpKQoKZ2MoKSAgIyBDbGVhbiBtZW1vcnkgYmVmb3JlIHJ1bm5pbmcgaW5mZXJlbmNlKQoKIyAyLiBSdW4gV2VpZ2h0ZWQgTWVhbiAoVklQRVItbGlrZSBtZXRob2QpCnRpYygiUnVuIERlY291cGxlUiIpCmFjdGl2aXRpZXMgPC0gZGVjb3VwbGVSOjpydW5fd21lYW4obWF0ID0gZXhwcmVzc2lvbl9tYXRyaXgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV0d29yayA9IG5ldHdvcmssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnNvdXJjZSA9ICJ0ZiIsICAgICAgICMgTXVzdCBtYXRjaCBEb1JvdGhFQSBjb2x1bW4gbmFtZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50YXJnZXQgPSAidGFyZ2V0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAubW9yID0gIm1vciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXMgPSAxMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluc2l6ZSA9IDUpCmdjKCkgICMgQ2xlYW4gbWVtb3J5IGJlZm9yZSBydW5uaW5nIGluZmVyZW5jZSkKdG9jKCkKZ2MoKSAgIyBDbGVhbiBtZW1vcnkgYmVmb3JlIHJ1bm5pbmcgaW5mZXJlbmNlKQojIDMuIFJlc2hhcGUgYW5kIEFkZCBhcyBhIE5ldyBBc3NheSAoJ2Rvcm90aGVhJykKIyBUcmFuc2Zvcm0gbG9uZy1mb3JtYXQgdG8gbWF0cml4IChGZWF0dXJlcyB4IENlbGxzKQp0Zl9hY3Rpdml0eV9tYXRyaXggPC0gYWN0aXZpdGllcyAlPiUKICBmaWx0ZXIoc3RhdGlzdGljID09ICJub3JtX3dtZWFuIikgJT4lCiAgcGl2b3Rfd2lkZXIoaWRfY29scyA9ICJzb3VyY2UiLCBuYW1lc19mcm9tID0gImNvbmRpdGlvbiIsIHZhbHVlc19mcm9tID0gInNjb3JlIikgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJzb3VyY2UiKSAlPiUKICBhcy5tYXRyaXgoKQoKIyBFbnN1cmUgc3RyaWN0IGNvbHVtbiBhbGlnbm1lbnQgd2l0aCBTZXVyYXQgb2JqZWN0IGNlbGxzCiMgKE5vdGU6IFVzaW5nIGV4cHJlc3Npb25fbWF0cml4IGNvbG5hbWVzIGVuc3VyZXMgcGVyZmVjdCBtYXRjaCkKY29tbW9uX2NlbGxzIDwtIGNvbG5hbWVzKGV4cHJlc3Npb25fbWF0cml4KQp0Zl9hY3Rpdml0eV9tYXRyaXggPC0gdGZfYWN0aXZpdHlfbWF0cml4WywgY29tbW9uX2NlbGxzXQoKIyBBZGQgdG8gU2V1cmF0IE9iamVjdApzZXVyYXRfb2JqW1siZG9yb3RoZWEiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QoZGF0YSA9IHRmX2FjdGl2aXR5X21hdHJpeCkKCiMgU2NhbGUgdGhlIGRhdGEgKFJlcXVpcmVkIGZvciBIZWF0bWFwcykKc2V1cmF0X29iaiA8LSBTY2FsZURhdGEoc2V1cmF0X29iaiwgYXNzYXkgPSAiZG9yb3RoZWEiKQoKIyA0LiBTQVZFIE9CSkVDVCBXSVRIIFRGIEFDVElWSVRZCnNhdmVSRFMoc2V1cmF0X29iaiwgZmlsZSA9ICJPdXRwdXRfT2JqZWN0cy9TZXVyYXRfT2JqZWN0X1dpdGhfVEZfQWN0aXZpdHkucmRzIikKcHJpbnQoIlRGIEFzc2F5IGFkZGVkIGFuZCBPYmplY3QgU2F2ZWQuIikKYGBgCgo=