From 0b111fff9ce4d9ed94d81b65942c83c28e35ff88 Mon Sep 17 00:00:00 2001 From: Gram-S Date: Wed, 17 Jun 2026 18:49:07 -0600 Subject: [PATCH 1/5] temporarily remove all files i dont want to touch and document --- NAMESPACE | 30 - R/CytoscapeGraphingFunctions.R | 983 ------------------ R/PathwayCrosstalkNetwork.R | 249 ----- R/RawDataProcessing.R | 183 ---- man/BuildPathwayCrosstalkNetwork.Rd | 37 - man/ReadBioplanetFile.Rd | 19 - man/filter.edges.0.Rd | 289 ----- man/fix.excel.Rd | 22 - man/grapes-w-slash-o-grapes.Rd | 23 - man/make.genepep.edges.Rd | 11 - man/merge2cols.Rd | 26 - man/name.peptide.Rd | 32 - man/ptms_to_cfn.Rd | 35 - man/remove.autophos.Rd | 11 - man/setCorrEdgeAppearance.Rd | 11 - man/setNodeSizeColorIndependently.Rd | 68 -- .../test_buildpathwaycrosstalknetwork.R | 56 - vignettes/BRCANetworks.Rmd | 524 ---------- vignettes/CreatingNetworks.Rmd | 568 ---------- vignettes/CytoscapeGraphing.Rmd | 365 ------- vignettes/RawDataProcessing.Rmd | 421 -------- 21 files changed, 3963 deletions(-) delete mode 100644 R/CytoscapeGraphingFunctions.R delete mode 100644 R/PathwayCrosstalkNetwork.R delete mode 100644 R/RawDataProcessing.R delete mode 100644 man/BuildPathwayCrosstalkNetwork.Rd delete mode 100644 man/ReadBioplanetFile.Rd delete mode 100644 man/filter.edges.0.Rd delete mode 100644 man/fix.excel.Rd delete mode 100644 man/grapes-w-slash-o-grapes.Rd delete mode 100644 man/make.genepep.edges.Rd delete mode 100644 man/merge2cols.Rd delete mode 100644 man/name.peptide.Rd delete mode 100644 man/ptms_to_cfn.Rd delete mode 100644 man/remove.autophos.Rd delete mode 100644 man/setCorrEdgeAppearance.Rd delete mode 100644 man/setNodeSizeColorIndependently.Rd delete mode 100644 tests/testthat/test_buildpathwaycrosstalknetwork.R delete mode 100644 vignettes/BRCANetworks.Rmd delete mode 100644 vignettes/CreatingNetworks.Rmd delete mode 100644 vignettes/CytoscapeGraphing.Rmd delete mode 100644 vignettes/RawDataProcessing.Rmd diff --git a/NAMESPACE b/NAMESPACE index 58ba02f6..9c3696b8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,45 +1,15 @@ # Generated by roxygen2: do not edit by hand -export("%w/o%") export(BuildClusterFilteredNetwork) -export(BuildPathwayCrosstalkNetwork) export(EvaluateClusters) export(GetGeneMANIA.edges) export(GetKinsub.edges) export(GetSTRINGdb.edges) -export(GraphCfn) export(MakeClusterList) export(MakeCorrelationNetwork) export(MakeDBInput) -export(NodeEdgeKey) -export(ReadBioplanetFile) export(StandardizeGeneSymbols) -export(connectNodes.all) -export(cytoscape.graph.PCN.pathways) -export(filter.edges.0) -export(filter.edges.1) -export(filter.edges.between) -export(fix.excel) -export(get.co.clustered.ptms) -export(getCyEdgeNames) export(graph.ptm.by.cluster) -export(harmonize_cfs) -export(make.cytoscape.node.file) -export(make.gene.data.from.ptmtable) -export(make.genepep.edges) -export(merge2cols) -export(mergeEdges) -export(name.peptide) -export(outersect) -export(ptms_to_cfn) -export(remove.autophos) -export(setCorrEdgeAppearance) -export(setEdgeWidths) -export(setNodeColorToRatios) -export(setNodeColorToRowz) -export(setNodeMapping) -export(setNodeSizeColorIndependently) -export(strip.cy.goo) importFrom(gplots,heatmap.2) importFrom(grDevices,colorRampPalette) importFrom(grDevices,hcl) diff --git a/R/CytoscapeGraphingFunctions.R b/R/CytoscapeGraphingFunctions.R deleted file mode 100644 index 79d02a2e..00000000 --- a/R/CytoscapeGraphingFunctions.R +++ /dev/null @@ -1,983 +0,0 @@ -#' @title Graphing and Manipulating Cluster Filtered Network -#' -#' @description Creates a cytoscape graph of the cluster filtered network. Ensure that you have the Cytoscape app open and the RCy3 package downloaded. -#' The package RCy3 is required for many of the functions in this script. To download, run: -#' - if (!require("BiocManager", quietly = TRUE)) install.packages("BiocManager") -#' - BiocManager::install("RCy3") -#' It is also recommended (but not required) that users load RCy3 with library(RCy3) to access its functions directly. -#' -#' @details Graph Key -#' - Node Size -#' - Greater the node size, larger the absolute value of the score -#' - Blue Node -#' - Negative score -#' - Yellow Node -#' - Positive score -#' - Green Node -#' - Approximately zero score -#' Node Shapes: -#' - "ELLIPSE" -#' - unknown -#' - "ROUND_RECTANGLE" -#' - receptor tyrosine kinase -#' - "VEE" -#' - SH2 protein -#' or -#' - SH2-SH3 protein -#' - "TRIANGLE" -#' - SH3 protein -#' - "HEXAGON" -#' - tyrosine kinase -#' - "DIAMOND" -#' - SRC-family kinase -#' - "OCTAGON" -#' - kinase -#' or -#' - phosphatase -#' - "PARALLELOGRAM" -#' - transcription factor -#' - "RECTANGLE" -#' - RNA binding protein -#' Node Border Colors: -#' - Orange -#' - deacetylase -#' or -#' - acetyltransferase -#' - Blue -#' - demethylase -#' or -#' - methyltransferase -#' - Royal Purple -#' - membrane protein -#' - Red -#' - kinase -#' or -#' - tyrosine kinase -#' or -#' - SRC-family kinase -#' - Yellow -#' - phosphatase -#' or -#' - tyrosine phosphatase -#' - Lilac -#' - G protein-coupled receptor -#' or -#' - receptor tyrosine kinase -#' - Grey -#' - default -#' - Edge Thickness -#' - Stronger correlation -#'Edge Colors: -#' - Red -#' - Phosphorylation -#' or -#' - pp -#' or -#' - controls-phosphorylation-of -#' - Bright Magenta -#' - controls-expression-of -#' - Dull Magenta -#' - controls-transport-of -#' - Purple -#' - controls-state-change-of -#' - Blood Orange -#' - Acetylation -#' - Lime Green -#' - Phystical interactions -#' - Green -#' - BioPlex -#' - Dull Green -#' - in-complex-with -#' - Seafoam Green -#' - experiments -#' or -#' - experiments_transferred -#' - Cyan -#' - database -#' or -#' - database_transferred -#' - Teal -#' - Pathway -#' or -#' - Predicted -#' - Dark Turquoise -#' - Genetic interactions -#' - Yellow-Orange -#' - correlation -#' - Royal Blue -#' - negative correlation -#' - Bright Yellow -#' - positive correlation -#' - Grey -#' - combined_score -#' - Dark Grey -#' - merged -#' - Light Grey -#' - intersect -#' - Black -#' - peptide -#' - Orange -#' - homology -#' - Dull Orange -#' - Shared protein domains -#' - White -#' - Default -#' Arrow Types: -#' - Arrow -#' - Phosphorylation -#' or -#' - pp -#' or -#' - controls-phosphorylation-of -#' or -#' - controls-expression-of -#' or -#' - controls-transport-of -#' or -#' - controls-state-change-of -#' or -#' - Acetylation -#' - No Arrow -#' - Default -#' -#' @param cfn A version of ppi.network with only the edges that exist in cccn.matrix and have non-zero weights -#' @param ptmtable A dataset for post-translational modifications. Formatted with numbered rows, and the first column containing PTM names. The rest of the column names should be drugs. Values are numeric values that represent how much the PTM has reacted to the drug. -#' @param funckey A table graphing gene names to type of protein; defaults to the internal dataset `PTMsToPathways::function_key` -#' @param Network.title Desired title for the created Cytoscape Network; defaults to "cfn" -#' @param Network.collection Desired name for the collection created on Cytoscape in which the network will reside; defaults to "PTMsToPathways" -#' @param visual.style.name Desired name for the visual style created on Cytoscape; defaults to "PTMsToPathways.style" -#' -#' @param background.color Hex code of background color of graph; defaults to '#fcf3cf' -#' @param edge.label.color Hex code of edge label color of graph; defaults to '#17202a' -#' @param node.label.color Hex code of node label color of graph; defaults to '#145a32' -#' -#' @param default.font Font style of edge and node names; defaults to "Times New Roman" -#' @param node.font.size Font size of the node name; defaults to 12 -#' @param edge.font.size Font size of the edge name; defaults to 8 -#' -#' @param edge.line.style Type of edge style; defaults to "SOLID"; options include: "PARALLEL_LINES", "MARQUEE_EQUAL", "DOT", "EQUAL_DASH", "LONG_DASH", "CONTIGUOUS_ARROW", "MARQUEE_DASH", "DASH_DOT", "BACKWARD_SLASH", "FORWARD_SLASH", "VERTICAL_SLASH", "SOLID", "SEPARATE_ARROW", "MARQUEE_DASH_DOT", "ZIGZAG", "SINEWAVE" -#' -#' @param edge.opacity Opacity of the edge line on a scale of 0 - 255 with 0 being transparent; defaults to 175 -#' @param edge.label.opacity Opacity of the edge label on a scale of 0 - 255 with 0 being transparent; defaults to 255 -#' @param border.opacity Opacity of the node border on a scale of 0 - 255 with 0 being transparent; defaults to 255 -#' @param node.label.opacity Opacity of the node label on a scale of 0 - 255 with 0 being transparent; defaults to 255 -#' @param node.fill.opacity Opacity of the node fill on a scale of 0 - 255 with 0 being transparent; defaults to 255 -#' -#' @return A cytoscape graph of the cluster filtered network -#' @export -#' -#' @examples -#' # GraphCFN(ex.cfn) -#' # See vignette for default graph -#' -# - -# helper functions for networks in R: - -# function to filter networks to include only selected nodes and those with edges to them -#' @param nodenames -#' -#' @param edge.file -#' -#' @export -filter.edges.0 <- function(nodenames, edge.file) { - nodenames <-as.character(nodenames) - a = as.character(edge.file[,1]) - b = as.character(edge.file[,2]) - edgefile.nodes <- unique(c(a,b)) - sel.edges <- edge.file[edge.file[,1] %in% nodenames & edge.file[,2] %in% nodenames,] - if(dim(sel.edges)[1] == 0) {return(NA)} else return(sel.edges) -} -# -#function to filter networks and to get first order connected nodes -#' @param nodenames -#' -#' @param edge.file -#' -#' @export -filter.edges.1 <- function(nodenames, edge.file) { - nodenames <-as.character(nodenames) - a = as.character(edge.file[,1]) - b = as.character(edge.file[,2]) - edgefile.nodes <- unique(c(a,b)) - sel.edges.1 <- edge.file[edge.file[,1] %in% nodenames,] - sel.edges.2 <- edge.file[edge.file[,2] %in% nodenames,] - sel.edges <- rbind(sel.edges.1, sel.edges.2) - if(dim(sel.edges)[1] == 0) {return(NA)} else { - return(unique(sel.edges)) } -} - -# This function narrows the search only for edges between two sets of nodes -#' @param nodes1 -#' -#' @param nodes2 -#' @param edge.file -#' @param convert -#' -#' @export -filter.edges.between <- function(nodes1, nodes2, edge.file, convert=FALSE) { - sel.edges1 <- edge.file[edge.file[,1] %in% nodes1 & edge.file[,2]%in% nodes2,] - sel.edges2 <- edge.file[edge.file[,1] %in% nodes2 & edge.file[,2]%in% nodes1,] - sel.edges <- rbind(sel.edges1, sel.edges2) - if(dim(sel.edges)[1] == 0) {return(NA)} else return(sel.edges) -} - -# connectNodes.all uses all_shortest_paths and returns just the edge file -#' @param nodepair -#' -#' @param ig.graph -#' @param edgefile -#' @param newgraph -#' -#' @export -connectNodes.all <- function(nodepair, ig.graph=NULL, edgefile, newgraph=FALSE) { - if (newgraph==TRUE) { - ig.graph <- igraph::graph_from_data_frame(edgefile, directed=FALSE) } - sp <- igraph::all_shortest_paths(graph= ig.graph, from=nodepair[1], to=nodepair[2], mode="all") - path.nodeslist <- unique(lapply(sp[[1]], names)) - edges.list <- lapply(path.nodeslist, filter.edges.0, edge.file=edgefile) - path.edges <- unique(plyr::ldply(edges.list)) - return(path.edges) -} -# This function names the edges the way Cytoscape does so they can be selected: -#' @param edgefile -#' -#' @export -getCyEdgeNames <- function(edgefile) { - cyedges <- mapply(paste, edgefile $source, " (", edgefile $interaction, ") ", edgefile $target, sep="") - return(cyedges) -} -# Function to extract node names from, e.g.: -# "ValidatedObjectAndEditString: validatedObject=ERBB3, editString=null" -#' @param test -#' -#' @export -strip.cy.goo <- function(test) { - t1 <- unlist(strsplit(test, "Object=")) - t2 <- sapply(t1[2:length(t1)], function (x) (strsplit(x, ", "))) - return(plyr::ldply(t2)$V1) -} - - -# For graphing Pathway Crosstalk Networks (PCNs) in cytoscape -#' @param PCN -#' -#' @param net.name -#' @param Jaccard.edges -#' -#' @export -cytoscape.graph.PCN.pathways <- function(PCN = pathway.crosstalk.network, net.name, Jaccard.edges=TRUE) { - PCN.df <- data.frame(id=unique(c(PCN$source, PCN$target))) - if (Jaccard.edges== FALSE) {PCN = PCN[-which (PCN$interaction=="pathway Jaccard similarity"),]} - # Get rid of zero weight edges - PCN.edges <- PCN[PCN$Weight>0, ] - no.windows <- length(RCy3::getNetworkList()) - PCN.suid <- RCy3::createNetworkFromDataFrames(PCN.df, PCN.edges, title=paste(net.name, "PCN", (1+no.windows), sep=" "), collection = "Pathway Interactions") - RCy3::setEdgeSelectionColorDefault (gplots::col2hex("chartreuse")) # still doesn't work - RCy3::setNodeColorDefault("#33FFFF" ) # bright Cyan - # edgeColors <- c(col2hex(alpha("purple",0.33333)), col2hex(alpha("magenta",0.33333)), col2hex("green")) - # edgeColors <- c("#9966FF", col2hex("green")) - edgeColors <- c(gplots::col2hex(ggplot2::alpha("darkorchid1",0.33333)), gplots::col2hex(ggplot2::alpha("tomato", 0.25)), gplots::col2hex("green")) - # edgeColors <- c("#9966FF", col2hex("green")) - edgeTypes <- c("PTM_cluster_evidence", "Protein_cluster_evidence", "pathway_Jaccard_similarity") - RCy3::setEdgeColorMapping( 'interaction', edgeTypes, edgeColors, 'd', default.color="#FFFFFF") - setEdgeWidths(ffactor = -1.2, log=TRUE) # Finally works! - style.name <- paste("PCN style", (1+no.windows), sep=" ") - RCy3::copyVisualStyle('default', style.name) - RCy3::setVisualStyle(style.name) -} - -# Two linked functions to generate node file for Cytoscape: -#' @param genes -#' -#' @param ptmtable -#' -#' @export -make.gene.data.from.ptmtable <- function(genes, ptmtable) { - ptmtable.temp <- ptmtable - ptmtable.temp$Gene.Name <- sapply(rownames(ptmtable.temp), function (x) strsplit(x, " ", fixed = TRUE)[[1]][1]) - subset.ptmtable <- ptmtable.temp[ptmtable.temp$Gene.Name %in% unique(genes), ] - gene.data <- subset.ptmtable |> - dplyr::group_by(.data$Gene.Name) |> - dplyr::summarise( - dplyr::across(where(is.numeric), ~sum(.x, na.rm = TRUE)), - .groups = "drop" - ) - - return(as.data.frame(gene.data)) # Ensure base R class -} -#' @param edge.file -#' -#' @param funckey -#' @param ptmtable -#' @param include.gene.data -#' @param include.coclustered.PTMs -#' -#' @export -make.cytoscape.node.file <- function(edge.file, funckey, ptmtable, include.gene.data = FALSE, include.coclustered.PTMs = FALSE) { - # Step 1: get unique nodes from edge file - edge_nodes <- unique(c(as.character(edge.file[, 1]), as.character(edge.file[, 2]))) - # check if there are PTMs in edgefile - peptides <- edge.file[which(edge.file$interaction == "peptide"), "target"] - if(length (peptides) > 0) { - edge_nodes <- edge_nodes %w/o% peptides - } - - - # Step 2: build node data frame from function key - annotation_cols <- c( - "Gene.Name", "Approved.Name", "Hugo.Gene.Family", "HPRD.Function", - "nodeType", "Domains", "Compartment", "Compartment.Overview" - ) - - node_file <- merge( - data.frame(Gene.Name = edge_nodes, stringsAsFactors = FALSE), - funckey[, annotation_cols, drop = FALSE], - by = "Gene.Name", - all.x = TRUE - ) - # For un-annotated genes: - node_file[is.na(node_file)] <- "" - - # Step 3: Optionally merge gene data summed from PTM table - if (include.gene.data == TRUE) { - gene.data <- make.gene.data.from.ptmtable(edge_nodes, ptmtable) - node_file <- merge( - node_file, - gene.data, - by = "Gene.Name", - all.x = TRUE - ) - } - node_file <- cbind(data.frame(id = node_file$Gene.Name), node_file) - # Step 4: Optionally merge PTM CCCN and data - if (include.coclustered.PTMs == TRUE) { - edge.file.with.ptms <- get.co.clustered.ptms(edge.file, ptm.cccn.edges) - if(length (peptides) > 0) { - edge.file.with.ptms <- unique(rbind(edge.file.with.ptms, edge.file[which(edge.file$interaction == "peptide"), ])) - } - node_file <- harmonize_cfs(edge.file.with.ptms, genecf = node_file, ptmtable = ptmtable) - } - return(unique(node_file)) -} -# To remove self-loops -#' Title -#' -#' @param edgefile -#' -#' @returns -#' @export -#' -#' @examples -remove.autophos <- function(edgefile) { - auto <- which (as.character(edgefile$source) == as.character(edgefile$target)) - if (length(auto) > 0) { - newedgefile <- edgefile[-auto,] } else newedgefile <- edgefile - return (newedgefile) -} -# Helper functions for connecting PTMs (called "peptides" with their parent protein nodes (called Gene.Name)) -#' Title -#' -#' @param peptide.edgefile -#' -#' @returns -#' @export -#' -#' @examples -make.genepep.edges <- function(peptide.edgefile) { - peptides <- unique(c(peptide.edgefile$source, peptide.edgefile$target)) - genenames <- sapply(peptides, function (x) unlist(strsplit(x, " ", fixed=TRUE))[1]) - net.gpe <- data.frame(source=genenames, target=peptides, Weight=0.25, interaction="peptide") - net.gpe <- remove.autophos(net.gpe) - return(net.gpe) -} - -# This function takes an edge file, retrieves only co-clustered PTM CCCN edges and links them to their gene nodes, returning an edge file -#' @param edge.file -#' -#' @export -get.co.clustered.ptms <- function (edge.file, ptm.cccn.edges) { - gene_nodes <- unique(c(as.character(edge.file[, 1]), as.character(edge.file[, 2]))) - ptmtable.temp <- ptmtable - ptmtable.temp$Gene.Name <- sapply(rownames(ptmtable.temp), function (x) strsplit(x, " ", fixed = TRUE)[[1]][1]) - subset.ptms <- rownames(ptmtable.temp[ptmtable.temp$Gene.Name %in% unique(gene_nodes), ]) - # Simplify ambiguous names - turned off because it limits their retrieval in the ptm cccn - # subset.ptms <- unique(sapply(subset.ptms, function (x) unlist(strsplit(x, ";", fixed=TRUE))[1])) - subset.ptm.cccn <- filter.edges.0(subset.ptms, ptm.cccn.edges) - pep.edges <- make.genepep.edges(subset.ptm.cccn) - edge.file.with.ptms <- rbind(edge.file, subset.ptm.cccn, pep.edges) - return(edge.file.with.ptms) -} - - -# -# helper functions - -#' Set difference for vectors using custom infix operator -#' -#' Returns the elements in vector x that are not in vector y. -#' -#' @param x A vector. -#' @param y A vector to compare. -#' @return Elements of x that are not in y. -#' @export -#' @examples -#' 1:5 %w/o% c(2, 4) -#' letters[1:5] %w/o% c("b", "d") -"%w/o%" <- function(x, y) { - x[!x %in% y] # x without y -} - - -#' @param x -#' -#' @param y -#' -#' @export -outersect <- function(x, y) { - sort(c(setdiff(x, y), - setdiff(y, x))) -} - -# Function to harmonize gene and peptide data for networks -# - for graphing combined CFN/CCCN graphs -# Enusres that for Cytoscape, "id" is used for node name columns#' @param edge.file.with.ptms -#' -#' @param genecf -#' @param ptmtable -#' -#' @export -harmonize_cfs <- function(edge.file.with.ptms, genecf, ptmtable) { - if(!any(grepl("Gene.Name", names(genecf)))) { - genecf.new <- data.frame(Gene.Name= genecf$id, genecf)} else {genecf.new = genecf} - genecf.new$parent <- "" - genecf.new$Node.ID <- "Gene" - peptides <- edge.file.with.ptms[which(edge.file.with.ptms$interaction == "peptide"), "target"] - if(length (peptides) == 0) { # check if there are PTMs in edgefile - stop("There are no PTMs/peptides in this edge file!") - } - parent.genes <- sapply(peptides, function (x) strsplit(x, " ", fixed = TRUE)[[1]][1]) - # Map peptides to ptmtable rows, handling unmatched by filling with NA or zero - matches <- match(peptides, rownames(ptmtable)) - ptm.rows <- ptmtable[matches, , drop=FALSE] # Will include NAs for unmatched rows - # Optionally replace all NA to 0 in the resulting data.frame - ptm.rows[is.na(ptm.rows)] <- 0 - pepcf <- data.frame( - id = as.character(peptides), - parent = as.character(parent.genes), - Gene.Name = as.character(parent.genes), - ptm.rows - ) - pepcf$Node.ID <- "PTM" - # Add annotation from function key - annotation_cols <- c( - "Gene.Name", "Approved.Name", "Hugo.Gene.Family", "HPRD.Function", - "nodeType", "Domains", "Compartment", "Compartment.Overview" - ) - - pepcf.funcs <- merge( - pepcf, - funckey[, annotation_cols, drop = FALSE], - by = "Gene.Name", - all.x = TRUE - ) - # For un-annotated genes: - pepcf.funcs[is.na(pepcf.funcs)] <- "" - # Harmonize - cf <- merge(genecf.new, pepcf.funcs, all=TRUE) - if(any(grepl("Gene.Name.1", names(cf)))) {cf <- cf[,-which(names(cf)=="Gene.Name.1")]} - if(any(is.na(cf))) {cf[is.na(cf)] <- 0} - # Make sure "id" is in the first column - cf <- cf[,c("id", "Gene.Name", "Node.ID", "parent", names(cf) %w/o% c("id", "Gene.Name", "Node.ID", "parent"))] - return(cf) -} - -# Function to merge edges to declutter networks - -#' @param edgefile -#' -#' @export -mergeEdges <- function(edgefile) { - # Define edge type priorities for directed edges - directed_priority <- c("psp", "controls-phosphorylation-of", "controls-expression-of", - "controls-transport-of", "controls-state-change-of", - "PHOSPHORYLATION", "METHYLATION", "ACETYLATION", "catalysis-precedes") - undirected <- c("Physical interactions", "BioPlex", "in-complex-with", 'experiments', - 'database', "Pathway", "Predicted", "Genetic interactions", - "correlation", "negative correlation", "positive correlation", - 'combined_score', "merged", "intersect", "peptide", 'homology', - "Shared protein domains", "interacts-with") - - # --- UNDIRECTED EDGES --- - undir.edges <- edgefile[!(edgefile$interaction %in% directed_priority), ] - # Sort node pairs for undirected edges - undir.edges[, 1:2] <- t(apply(undir.edges[, 1:2], 1, function(x) sort(x))) - - # Merge by source/target and make informative label - undir.merged <- plyr::ddply(undir.edges, c("source", "target"), function(x) { - data.frame( - Weight = max(x$Weight, na.rm = TRUE), - interaction = paste(sort(unique(as.character(x$interaction))), collapse = " | "), - stringsAsFactors = FALSE - ) - }) - # .. - - # --- DIRECTED EDGES --- - dir.edges <- edgefile[edgefile$interaction %in% directed_priority, ] - # For each directed edge (source, target), choose the top-priority interaction - dir.merged <- plyr::ddply(dir.edges, c("source", "target"), function(x) { - ints <- as.character(x$interaction) - present <- intersect(directed_priority, ints) - best <- if (length(present) > 0) present[1] else ints[1] - all_types <- paste(sort(unique(ints)), collapse = " | ") - data.frame( - Weight = max(x$Weight, na.rm = TRUE), - interaction = if (best != all_types) paste0(best, " [", all_types, "]") else best, - stringsAsFactors = FALSE - ) - }) - - # Combine and clean - edgefile.merged <- rbind(dir.merged, undir.merged) - # Remove self-loops - edgefile.merged <- edgefile.merged[edgefile.merged$source != edgefile.merged$target, ] - if (exists("remove.autophos")) edgefile.merged <- remove.autophos(edgefile.merged) - rownames(edgefile.merged) <- NULL - return(edgefile.merged) -} - -#' Extracts co-clustered PTM and gene network from a cluster filtered network object. -#' -#' This function takes a list of PTM (post-translational modification) site IDs and a cluster-filtered network, -#' extracts the genes from unambiguous and ambiguous PTM entries, then returns the co-clustered subnetwork. -#' -#' @param ptms Character vector of PTM site strings (e.g., \"TP53 p S15\") -#' @param cfn List or data frame representing the cluster filtered network (default: global cfn.merged) -#' @param pepsep Character used to split ambiguous PTM entries (default: \";\") -#' -#' @return Subnetwork object with co-clustered PTMs and genes -#' @export -#' @examples -#' # Example input objects -#' ptms <- c("TP53 p S15", "BRCA1 p S123; BRCA1 p T124", "MDM2 p S200") -#' # Example network -#' # edge list with 'source', 'target', and cluster info, could be data.frame or igraph -#' cfn.merged <- data.frame(source = c("TP53", "BRCA1", "BRCA1", "MDM2"), -#' target = c("BRCA1", "MDM2", "TP53", "TP53"), -#' cluster = c(1,1,2,1)) -#' # Suppose filter.edges.0 and get.co.clustered.ptms are also defined and loaded -#' # The following returns the gene/PTM subnetwork -#' res <- ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") -#' print(res) -ptms_to_cfn <- function(ptms, cfn = cfn.merged, pepsep = ";") { - ambig.ptms <- ptms[grep(";", ptms)] - if (length(ambig.ptms) > 0) { - - sub.ptms <- ptms %w/o% ambig.ptms - all_genes <- unique(sapply(sub.ptms, function (x) unlist(strsplit(x, " ", fixed=TRUE))[1])) - - for (i in 1:length(ambig.ptms) ) { - # Normalize spacing - ptm_entry <- gsub("[;,]\\s*", ";", ambig.ptms) - # Split ambiguous entry - ptm_parts <- strsplit(ptm_entry, pepsep, fixed = TRUE)[[1]] - # Extract gene names (string before first space) - ambig.genes <- sapply(ptm_parts, function(part) strsplit(part, " ", fixed = TRUE)[[1]][1]) - - all_genes <- unique(c(all_genes, ambig.genes)) - } - } else { - all_genes <- unique(sapply(ptms, function (x) unlist(strsplit(x, " ", fixed=TRUE))[1])) - } - - sub.cfn <- filter.edges.0(all_genes, cfn.merged) - sub.cfn.cccn <- get.co.clustered.ptms(sub.cfn, ptm.cccn.edges) - return(sub.cfn.cccn) -} - -#_____________________________________________________________________________ -# Vizprops helper functions: -# Function to set shape and border color according to node type -#' @param cf -#' -#' @export -setNodeMapping <- function(cf=RCy3::getTableColumns('node')) { - # require(RCy3) - RCy3::setBackgroundColorDefault("#949494") # grey 58 - RCy3::setNodeShapeDefault("ELLIPSE") - RCy3::setNodeColorDefault("#F0FFFF") # azure1 - RCy3::setNodeSizeDefault(100) # for grey non-data nodes - RCy3::setNodeFontSizeDefault(22) - RCy3::setNodeLabelColorDefault("#000000") # black - RCy3::setNodeBorderWidthDefault(1.8) - RCy3::setNodeBorderColorDefault("#888888") # gray - RCy3::setNodeSelectionColorDefault("#CC00FF") - molclasses <- c("acetyltransferase", "deacetylase", "demethylase", "G protein-coupled receptor", "kinase", "membrane protein", "methyltransferase", "phosphatase", "receptor tyrosine kinase", "RNA binding and processing protein", "RNA binding and splicing protein", "RNA binding protein", "RNA processing and splicing protein", "RNA processing protein", "RNA splicing protein", "SH2 protein", "SH2-SH3 protein", "SH3 protein", "SRC-family kinase", "transcription factor", "transcription regulator", "tyrosine kinase", "tyrosine phosphatase", "undefined") - nodeshapes <- c("ELLIPSE", "ELLIPSE", "ELLIPSE", "ROUND_RECTANGLE", "OCTAGON", "ROUND_RECTANGLE", "ELLIPSE", "OCTAGON", "ROUND_RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "VEE", "VEE", "TRIANGLE", "DIAMOND", "PARALLELOGRAM", "PARALLELOGRAM", "HEXAGON", "HEXAGON", "ELLIPSE") - nodebordercolors <- gplots::col2hex(c("darkorange", "darkorange3", "blue3", "darkorchid1", "red3", "purple", "blue", "lightgoldenrod1", "violetred", "darkgoldenrod", "burlywood4", "darkgoldenrod3", "burlywood3","darkgoldenrod4", "burlywood3", "deeppink", "hotpink", "rosybrown1", "red2", "springgreen4", "steelblue4", "red2", "yellow", "gray")) - RCy3::setNodeShapeMapping("nodeType", molclasses, nodeshapes, default.shape="ELLIPSE") - RCy3::setNodeBorderColorMapping("nodeType", molclasses, nodebordercolors, mapping.type = "d", default.color=gplots::col2hex("gray")) - nodeborderwidths <- c(12,5,5,16,12,8,12,12,16,6,6,6,6,6,6,12,6,12,16,10,10,12,12,4) - RCy3::setNodeBorderWidthMapping(table.column="nodeType", table.column.values=molclasses, widths=nodeborderwidths, mapping.type = "d", default.width=2) -} - -# Function to set edge appearance -# # Use: setCorrEdgeAppearance() to change cytoscape front window -#This is now modified to handle merged edges and match colors correctly -#' Title -#' -#' @returns -#' @export -#' -#' @examples -setCorrEdgeAppearance <- function() { - # require(RCy3) - RCy3::setEdgeLineWidthDefault(3) - RCy3::setEdgeColorDefault("#FFFFFF") # white - edgevalues <- RCy3::getTableColumns('edge',c('Weight')) - edgevalues['Weight']<-abs(edgevalues['Weight']) - edgevalues['Weight']<-lapply(edgevalues['Weight'], function(x) log2(x * 10) + 2) - names(edgevalues)<-c('Width') - RCy3::loadTableData(edgevalues, table = 'edge', table.key.column = 'SUID') - RCy3::setEdgeLineWidthMapping('Width', mapping.type = 'p', style.name = 'default') - RCy3::setEdgeSelectionColorDefault("#FF69B4") # hotpink - edgecolors <- gplots::col2hex(c("red", "red", "red", "magenta", "violet", "purple", "darkorange1", "green", "green2", "green3", "aquamarine2", "aquamarine2", "cyan","cyan", "turquoise2", "cyan2", "lightseagreen", "gold", "blue", "yellow", "slategrey", "darkslategrey", "grey", "black", "orange", "orange2")) - edgeTypes <- c("PHOSPHORYLATION", "psp", "controls-phosphorylation-of", "controls-expression-of", "controls-transport-of", "controls-state-change-of", "ACETYLATION", "Physical Interactions", "BioPlex", "in-complex-with", 'experimental', 'experimental_transferred', 'database', 'database_transferred', "Pathway", "Predicted", "Genetic interactions", "correlation", "negative correlation", "positive correlation", 'combined_score', "merged" , "intersect", "peptide", 'homology', "Shared protein domains") - myarrows <- c ('Arrow', 'Arrow', 'Arrow', 'Arrow', 'Arrow', 'Arrow', "Arrow", 'None', 'None', 'None', 'None','None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None') - edgevalues2 <- RCy3::getTableColumns('edge',c('interaction','SUID')) - get_main_interaction <- function(intchar) { - components <- unlist(strsplit(as.character(intchar), split = "[|]", fixed = FALSE)) - components <- trimws(components) - found <- edgeTypes[edgeTypes %in% components] - if (length(found)) { - return(found[1]) - } else { - return(components[1]) - } - } - edgevalues2$main_interaction <- sapply(edgevalues2$interaction, get_main_interaction) - RCy3::loadTableData(edgevalues2, table = 'edge', table.key.column = 'SUID') - RCy3::setEdgeTargetArrowMapping('main_interaction', edgeTypes, myarrows, default.shape='None') - RCy3::matchArrowColorToEdge('TRUE') - RCy3::setEdgeColorMapping('main_interaction', edgeTypes, edgecolors, 'd', default.color="#FFFFFF") -} - -# Function to sent node size and color to match ratio data in the Cytoscape node table. -#' @param plotcol -#' -#' @export -setNodeColorToRatios <- function(plotcol){ - require(RCy3) - cf <- RCy3::getTableColumns('node') - if(!(plotcol %in% getTableColumnNames('node'))){ - print (getTableColumnNames('node')) - cat("\n","\n","\t", "Which attribute will set node size and color?") - plotcol <- as.character(readLines(con = stdin(), n = 1)) - } - limits <- range(cf[, plotcol]) - node.sizes = c (135, 130, 108, 75, 35, 75, 108, 130, 135) - # RATIO is plotted - # Blue is negative: Yellow positive, Green in middle - # - size.control.points = c (-100.0, -15.0, -5.0, 0.0, 5.0, 15.0, 100.0) - color.control.points = c (-100.0, -10.0, -5.0, -2.25, 0.0, 2.25, 5.0, 10.0, 100.0) - if(limits[1] < min(size.control.points)) { - size.control.points = c (limits[1], -15.0, -5.0, 0.0, 5.0, 15.0, 100.0) - color.control.points = c (limits[1]-1, -10.0, -5.0, -2.25, 0.0, 2.25, 5.0, 10.0, 100.0) - } - if(limits[2] > max(size.control.points)) { - size.control.points = c (limits[1], -15.0, -5.0, 0.0, 5.0, 15.0, limits[2]) - color.control.points = c (limits[1]-1, -10.0, -5.0, -2.25, 0.0, 2.25, 5.0, 10.0, limits[2]+1) - } - ratio.colors = c ('#0099FF', '#007FFF','#00BFFF', '#00CCFF', '#00FFFF', '#00EE00', '#FFFF7E', '#FFFF00', '#FFE600', '#FFD700', '#FFCC00') - RCy3::setNodeColorMapping (names(cf[plotcol]), color.control.points, ratio.colors, 'c') - RCy3::lockNodeDimensions('TRUE') - RCy3::setNodeSizeMapping (names(cf[plotcol]), size.control.points, node.sizes, 'c') - RCy3::setNodeSelectionColorDefault ( "#CC00FF") -} - -# This function works well with node data that are normalized by row z-scores -#' @param plotcol -#' -#' @export -setNodeColorToRowz <- function(plotcol){ - cf <- getTableColumns('node') - if(!(plotcol %in% getTableColumnNames('node'))){ - print (getTableColumnNames('node')) - cat("\n","\n","\t", "Which attribute will set node size and color?") - plotcol <- as.character(readLines(con = stdin(), n = 1)) - } - limits <- range(cf[, plotcol]) - node.sizes = c (135, 130, 108, 75, 35, 75, 108, 130, 135) - # Row z-score data is plotted - # Blue is negative: Yellow positive, Green in middle - # - size.control.points = c (-log2(100.0), -log2(15.0), -log2(5.0), 0.0, log2(5.0), log2(15.0), log2(100.0)) - color.control.points = c (-log2(100.0), -log2(10.0), -log2(5.0), -log2(2.25), 0.0, log2(2.25), log2(5.0), log2(10.0), log2(100.0)) - ratio.colors = c ('#0099FF', '#007FFF','#00BFFF', '#00CCFF', '#00FFFF', '#00EE00', '#FFFF7E', '#FFFF00', '#FFE600', '#FFD700', '#FFCC00') - RCy3::setNodeColorMapping (names(cf[plotcol]), color.control.points, ratio.colors, 'c') - RCy3::lockNodeDimensions('TRUE') - RCy3::setNodeSizeMapping (names(cf[plotcol]), size.control.points, node.sizes, 'c') - RCy3::setNodeSelectionColorDefault ( "#CC00FF") -} - -#' Set node size and color from independent Cytoscape node attributes -#' -#' Apply independent continuous visual mappings for node size and node color -#' in the active Cytoscape network using columns from the node table. -#' This helper is intended for PTMsToPathways-style networks where one node -#' attribute controls size and a different attribute controls color. -#' -#' If either requested column is not present in the Cytoscape node table, the -#' user is prompted to choose a valid column name interactively. For non-ratio -#' data, color and size control points are derived from the observed ranges of -#' the selected columns. For ratio-style data, fixed log2-based control points -#' are used. -#' -#' @param sizeplotcol A character string naming the node table column to use -#' for node size mapping. -#' @param colorplotcol A character string naming the node table column to use -#' for node color mapping. -#' @param ratio Logical; if `TRUE`, use predefined log2 ratio control points -#' and ratio color palette. If `FALSE`, compute control points from the -#' observed ranges of `sizeplotcol` and `colorplotcol`. -#' -#' @details -#' For `ratio = FALSE`, this function builds separate continuous mappings for -#' node color and node size based on the minimum and maximum values observed -#' in the selected Cytoscape node table columns. Blue-to-white-to-yellow color -#' gradients are used for color mapping. -#' -#' For `ratio = TRUE`, the function assumes ratio-like values centered on zero -#' and applies fixed log2-scaled control points for both node size and node -#' color. In this mode, a blue-to-green-to-yellow palette is used. -#' -#' The function also locks node width and height and sets the default node -#' selection color to magenta. -#' -#' @return -#' This function is called for its side effects in Cytoscape and returns -#' `NULL` invisibly. -#' -#' @seealso -#' [RCy3::setNodeColorMapping()], [RCy3::setNodeSizeMapping()], -#' [RCy3::lockNodeDimensions()], [RCy3::getTableColumns()] -#' -#' @examples -#' \dontrun{ -#' setNodeSizeColorIndependently( -#' sizeplotcol = "mean_expression", -#' colorplotcol = "logFC", -#' ratio = FALSE -#' ) -#' -#' setNodeSizeColorIndependently( -#' sizeplotcol = "abundance_ratio", -#' colorplotcol = "abundance_ratio", -#' ratio = TRUE -#' ) -#' } -#' -#' @export -setNodeSizeColorIndependently <- function(sizeplotcol, colorplotcol, ratio=FALSE){ - cf <- RCy3::getTableColumns('node') - if(!(sizeplotcol %in% RCy3::getTableColumnNames('node'))){ - print (RCy3::getTableColumnNames('node')) - cat("\n","\n","\t", "Which attribute will set node size?") - sizeplotcol <- as.character(readLines(con = stdin(), n = 1)) - } - if(!(colorplotcol %in% RCy3::getTableColumnNames('node'))){ - print (RCy3::getTableColumnNames('node')) - cat("\n","\n","\t", "Which attribute will set node color?") - colorplotcol <- as.character(readLines(con = stdin(), n = 1)) - } - limits <- range(cf[, sizeplotcol]) - node.sizes = c (135, 130, 108, 75, 35, 75, 108, 130, 135) - if (ratio == FALSE) { - Intensity.Values.Color <- cf[, colorplotcol] # set to intensity or normalized intensity - maxint.col <- max(Intensity.Values.Color, na.rm=TRUE) - minint.col <- min(Intensity.Values.Color, na.rm=TRUE) - Intensity.Values.Size <- cf[, sizeplotcol] # set to intensity or normalized intensity - maxint.size <- max(Intensity.Values.Size, na.rm=TRUE) - minint.size <- min(Intensity.Values.Size, na.rm=TRUE) - icolors <- c('#0099FF', '#007FFF','#00BFFF', '#00CCFF', '#00FFFF', '#FFFFFF', '#FFFF7E', '#FFFF00', '#FFE600', '#FFD700', '#FFCC00') - if (maxint.col>=abs(minint.col)) { - color.control.points <- c(-(maxint.col+1), -(maxint.col/5), -(maxint.col/10), -(maxint.col/15), 0.0, (maxint.col/15), (maxint.col/10), (maxint.col/5), (maxint.col+1)) - RCy3::setNodeColorMapping (names(cf[colorplotcol]), color.control.points, icolors) - } - if (maxint.col=abs(minint.size)) { - size.control.points = c (-(maxint.size+1), -(maxint.size*0.3), -(maxint.size/10), 0.0, (maxint.size/10), (maxint.size*0.3), (maxint.size+1)) - RCy3::setNodeSizeMapping (names(cf[sizeplotcol]), size.control.points, node.sizes, 'c') - } - if (maxint.size translucent - RCy3::setEdgeLabelOpacityDefault(edge.label.opacity, visual.style.name) # set opacity of edge label; 0 - 255 w 0 --> translucent - RCy3::setNodeBorderOpacityDefault(border.opacity, visual.style.name) # set opacity of border of node; 0 - 255 w 0 --> translucent - RCy3::setNodeFillOpacityDefault(node.fill.opacity, visual.style.name) # set opacity of interior color of node; 0 - 255 w 0 --> translucent - RCy3::setNodeLabelOpacityDefault(node.label.opacity, visual.style.name) # set opacity of name of node; 0 - 255 w 0 --> translucent - -} - -# NodeEdgeKey function: Cytoscape legend for styles -#' @param visual.style.name -#' -#' @export -NodeEdgeKey <- function(visual.style.name = "PTMsToPathways.style") { - # require(RCy3) - node_types <- c( - "acetyltransferase", "deacetylase", "demethylase", "G protein-coupled receptor", "kinase", "membrane protein", - "methyltransferase", "phosphatase", "receptor tyrosine kinase", "RNA binding and processing protein", - "RNA binding and splicing protein", "RNA binding protein", "RNA processing and splicing protein", - "RNA processing protein", "RNA splicing protein", "SH2 protein", "SH2-SH3 protein", "SH3 protein", - "SRC-family kinase", "transcription factor", "transcription regulator", "tyrosine kinase", - "tyrosine phosphatase", "undefined" - ) - n_nodes <- length(node_types) - edge_types <- c( - "PHOSPHORYLATION", "psp", "controls-phosphorylation-of", "controls-expression-of", "controls-transport-of", - "controls-state-change-of", "ACETYLATION", "Physical Interactions", "BioPlex", "in-complex-with", - "experimental", "experimental_transferred", "database", "database_transferred", "Pathway", "Predicted", - "Genetic interactions", "correlation", "negative correlation", "positive correlation", "combined_score", - "merged", "intersect", "peptide", "homology", "Shared protein domains" - ) - n_edges <- length(edge_types) - demo_nodes <- data.frame( - id = node_types, - nodeType = node_types, - label = node_types, - stringsAsFactors = FALSE - ) - kinase_node <- "kinase" - non_kinase_targets <- node_types[node_types != kinase_node] - edge_sources <- character(n_edges) - edge_targets <- character(n_edges) - for(i in seq_along(edge_types)) { - et <- edge_types[i] - if(et == "PHOSPHORYLATION" || et == "psp") { - edge_sources[i] <- kinase_node - edge_targets[i] <- non_kinase_targets[i] - } else { - edge_sources[i] <- node_types[(i-1) %% n_nodes + 1] - edge_targets[i] <- node_types[i %% n_nodes + 1] - } - } - edge_table <- data.frame( - source = edge_sources, - target = edge_targets, - interaction = edge_types, - label = edge_types, - Weight = seq (from = 2.6, to = 0.1, by = -0.1), - stringsAsFactors = FALSE - ) - stopifnot(all(edge_table$source %in% demo_nodes$id)) - stopifnot(all(edge_table$target %in% demo_nodes$id)) - cy_net <- RCy3::createNetworkFromDataFrames( - nodes = demo_nodes, - edges = edge_table, - title = "Node & Edge Key", - collection = "PTMsToPathways" - ) - RCy3::setEdgeFontSizeDefault(20) - RCy3::setEdgeLabelMapping("label") - setNodeMapping() - setCorrEdgeAppearance() - RCy3::copyVisualStyle("default", visual.style.name) - RCy3::setVisualStyle(visual.style.name) - message("Check the 'Node & Edge Key' network in Cytoscape: kinase sources PHOSPHORYLATION/pp, all edges labelled, all node-edge types illustrated.") -} - - diff --git a/R/PathwayCrosstalkNetwork.R b/R/PathwayCrosstalkNetwork.R deleted file mode 100644 index 1c4d59a2..00000000 --- a/R/PathwayCrosstalkNetwork.R +++ /dev/null @@ -1,249 +0,0 @@ -#' Read Bioplanet File -#' -#' Reads a Bioplanet CSV file and converts it into a named list of pathways, -#' where each list element is a character vector of gene symbols. -#' -#' @param bioplanet.file Path to a delimited Bioplanet text file. -#' Common delimiters such as comma, tab, semicolon, and pipe are supported. -#' -#' @return A named list of pathways and associated gene symbols. -#' @export -ReadBioplanetFile <- function(bioplanet.file = "bioplanet.csv") { - required.cols <- c("PATHWAY_ID", "PATHWAY_NAME", "GENE_ID", "GENE_SYMBOL") - - if (!is.character(bioplanet.file) || length(bioplanet.file) != 1L || is.na(bioplanet.file) || !nzchar(bioplanet.file)) { - stop("bioplanet.file must be a single non-empty file path string.") - } - if (!file.exists(bioplanet.file)) { - stop(paste(bioplanet.file, "not found. Please check your working directory.")) - } - - read_attempts <- list( - function(path) utils::read.csv(path, stringsAsFactors = FALSE), - function(path) utils::read.delim(path, stringsAsFactors = FALSE), - function(path) utils::read.table(path, sep = ";", header = TRUE, stringsAsFactors = FALSE), - function(path) utils::read.table(path, sep = "|", header = TRUE, stringsAsFactors = FALSE) - ) - - bioplanet <- NULL - for (reader in read_attempts) { - parsed <- tryCatch(reader(bioplanet.file), error = function(e) NULL) - if (!is.null(parsed) && is.data.frame(parsed)) { - missing.cols <- setdiff(required.cols, names(parsed)) - if (length(missing.cols) == 0) { - bioplanet <- parsed - break - } - } - } - - if (is.null(bioplanet)) { - stop("Could not parse bioplanet.file with a supported delimiter or required columns are missing.") - } - - # Correct errors from Excel - bioplanet[, "GENE_SYMBOL"] <- sapply(bioplanet$GENE_SYMBOL, fix.excel) - - # Turn Bioplanet table into list of pathways (character vectors of gene symbols) - PATHWAY_NAME <- NULL - pathways.list <- plyr::dlply(bioplanet, plyr::.(PATHWAY_NAME)) - pathways.list <- lapply(pathways.list, `[`, "GENE_SYMBOL") - pathways.list <- lapply(pathways.list, unlist, use.names = FALSE) - - pathways.list -} - -#' Build Pathway Crosstalk Network -#' -#' Converts Bioplanet pathways from () into a list of pathways whose elements are the genes in each pathway. Edge weights are either the PTM Cluster Weight or according to the Jaccard Similarity. -#' -#' @param common.clusters The list of common clusters between all three distance metrics (Euclidean, Spearman, and SED). Can be made in MakeCorrelationNetwork -#' @param bioplanet.file Either the path to a delimited Bioplanet file, or a named list of pathways where each list element is a character vector of gene symbols. -#' @param createfile The path of where to create the edgelist file. Defaults to the working directory, if FALSE is provided, a file will not be created. -#' @param PCN.edgelist.name Name of the PCN edgelist file to be created -#' @return A list with these data structures at the given index: \enumerate{ -#' \item{Contains pathway source-target columns, along with the interaction type.} -#' \item{Contains pathway source-target columns, with edge weights of their jaccard similarity and their Pathway-Pathway Evidence score.} -#' \item{All pathways in the bioplanet database as a named list containing string vectors. Each vector is a pathway with strings associated with the genes in that pathway.} -#' } -#' @export -#' -#' @examples -#' Example_Output <- BuildPathwayCrosstalkNetwork(ex_common_clusters, ex_pathways_list, createfile = FALSE) -#' Example_Output[[1]][[3,]] -#' Example_Output[[3]][[1:3]] -BuildPathwayCrosstalkNetwork <- function(common.clusters, bioplanet.file = "bioplanet.csv", createfile = getwd(), PCN.edgelist.name = "PCN_file"){ - message("Making PCN") - start_time <- Sys.time() - message(start_time) - if(is.character(createfile) && !dir.exists(createfile)) stop(paste("Could not find directory", createfile)) #If createfile is a path but an incorrect one - - # Accept either a file path or pre-built pathways.list - if (is.character(bioplanet.file)) { - pathways.list <- ReadBioplanetFile(bioplanet.file) - } else if (is.list(bioplanet.file) && !is.data.frame(bioplanet.file)) { - pathways.list <- bioplanet.file - } else { - stop(paste(class(bioplanet.file), "is not a supported file type. Please provide a file path or a named list of pathways.")) - } - - if (is.null(names(pathways.list)) || any(names(pathways.list) == "")) { - stop("pathways.list must be a named list with pathway names.") - } - pathways.list <- lapply(pathways.list, as.character) - - - ### Functions to help caluclate the jaccard similarity ### - find.jaccard.val <- function(charvectorrow){ #Helper function to pass into an "apply" function for a matrix that contains character vectors. Matrix dimensions should be 2 columns x any number of rows - p.intersect <- length(intersect(charvectorrow[[1]], charvectorrow[[2]])) #Length of Intersect - p.union <- length(charvectorrow[[1]]) + length(charvectorrow[[2]]) - p.intersect #Length of Union - jaccard.value <- p.intersect/p.union - if(jaccard.value == 0) return(NA) #Return NA if 0 for igraph - return(jaccard.value) #Return the jaccard value - } - - combinations <- utils::combn #Mask the utils::combn with a more descriptive term - - - ### Create the main data structure and add Jaccard Values ### - PCNedgelist <- t(combinations(names(pathways.list), 2)) #Creating the edgelist. First two columns of the data frame like PATHWAY | PATHWAY; Found by finding all pairs pathway names - combn.vector <- combinations(pathways.list, 2) #The values of the above vector, performs the same operation to get pairs of string vectors containing gene names instead of pathway names. Prepares data for analysis - jaccard.values <- apply(combn.vector, 2, find.jaccard.val) #Find the jaccard value for every pair of string vectors in combn.svector - PCNedgelist <- cbind(PCNedgelist, jaccard.values) #Attach the JACCARD VALUE column to PATHWAY | PATHWAY - bioplanetjaccardedges <- as.data.frame(PCNedgelist) - bioplanetjaccardedges <- bioplanetjaccardedges[!is.na(bioplanetjaccardedges$jaccard.values),] - bioplanetjaccardedges$interaction <- "pathway_Jaccard_similarity" - names(bioplanetjaccardedges)[1:2] <- c("source", "target") # For Cytoscape graphing - - ### Pathway Cluster Evidence ### - CPE.matrix <- matrix(NA, nrow = length(common.clusters), ncol = length(pathways.list)) #Initilize empty data structure, Clusters x Pathways - rownames(CPE.matrix) <- names(common.clusters) #Names - colnames(CPE.matrix) <- names(pathways.list) - - #pathways.temp <- as.data.frame(table(bioplanet$GENE_SYMBOL)) #Create table for how many times each gene appears in the pathways list. Needs to be converted into a named vector for efficent runtime. - pathways.temp <- as.data.frame(table(unlist(pathways.list))) #Create table for how many times each gene appears in the pathways list. Needs to be converted into a named vector for efficent runtime. - pathgene.count <- pathways.temp$Freq #Transform into a named vector - names(pathgene.count) <- pathways.temp$Var1 #Now any string vector of genes like pathways.hash[[c("AARS", "ABCA1")]] will return the frequency of how many times those genes appear in the pathway list *in constant time*. Sum() to return the total - get_weighted_gene_counts <- function(ptm_vec, pepsep = ";") { - all_genes <- character() - all_weights <- numeric() - - for (ptm_entry in ptm_vec) { - # Normalize spacing - ptm_entry <- gsub("[;,]\\s*", ";", ptm_entry) - - # Split ambiguous entry - ptm_parts <- strsplit(ptm_entry, pepsep, fixed = TRUE)[[1]] - - # Extract gene names (string before first space) - genes <- sapply(ptm_parts, function(part) strsplit(part, " ", fixed = TRUE)[[1]][1]) - - weight <- 1 / length(genes) - - all_genes <- c(all_genes, genes) - all_weights <- c(all_weights, rep(weight, length(genes))) - } - - # Aggregate weights for duplicated gene names - gene_weights <- tapply(all_weights, all_genes, sum) - - return(gene_weights) - } - - - for (a in 1:nrow(CPE.matrix)) { - - # Step 1: Get weighted gene counts for cluster a - gene.count <- get_weighted_gene_counts(common.clusters[[a]]) # - - cluster.length <- length(common.clusters[[a]]) - - for (b in 1:ncol(CPE.matrix)) { - - # Get genes in this pathway - pathway.genes <- pathways.list[[b]] - - # Numerator: weighted sum of pathway genes in this cluster - num <- gene.count[pathway.genes] - - # Denominator: total frequency across all pathways - den <- pathgene.count[pathway.genes] - - value <- sum(num / den, na.rm = TRUE) / cluster.length - - if (!is.na(value) && value != 0) { - CPE.matrix[a, b] <- value - } - } - } - - ### Generate PCN network ### - # Get a vector of all the PTP weights for every pair of pathways using the CPE weights to filter. For a PTP weight to be non-NA, the PTP weight will be the sum of all clusters both pathways have nonzero CPEs in. - - PTPscore <- apply(PCNedgelist[,1:2], 1, function(x) { - rows <- rowSums(!is.na(CPE.matrix[, x])) == 2 - if (any(rows)) { - sum(CPE.matrix[rows, x], na.rm = TRUE) - } else { - NA - }}) - - - PTPscore[PTPscore== 0] <- NA # Safety check: Turn all 0s created in above line into NAs - - PCNedgelist <- cbind(PCNedgelist, PTPscore) #Bind all the columns together. Now Data structure is PATHWAY | PATHWAY | Jaccard | CPE - PCNedgelist <- PCNedgelist[rowSums(is.na(PCNedgelist)) != 2, ] #Remove all rows that only have NA values for the jaccard and CPE values - PCNedgelist <- as.data.frame(PCNedgelist) - names(PCNedgelist) <- c("source", "target", "pathway_Jaccard_similarity", "PTM_cluster_evidence") - # Sort by the highest PTM cluster evidence - # PCNedgelist <- PCNedgelist[order(PCNedgelist$PTM_cluster_evidence, decreasing = TRUE),] - PCNedgelist$pathway_Jaccard_similarity <- as.numeric(PCNedgelist$pathway_Jaccard_similarity) - PCNedgelist$PTM_cluster_evidence <- as.numeric(PCNedgelist$PTM_cluster_evidence) - # Convert NA to 0 - PCNedgelist[is.na(PCNedgelist)] <- 0 - # Subset data frames - zero_jaccard <- PCNedgelist[PCNedgelist$pathway_Jaccard_similarity == 0, ] - nonzero_jaccard <- PCNedgelist[PCNedgelist$pathway_Jaccard_similarity > 0, ] - - # Sort both by PTM_cluster_evidence in decreasing order - zero_jaccard <- zero_jaccard[order(zero_jaccard$PTM_cluster_evidence, decreasing=TRUE), ] - nonzero_jaccard <- nonzero_jaccard[order(nonzero_jaccard$PTM_cluster_evidence, decreasing=TRUE), ] - - # Combine results: zero-jaccard block on top, then nonzero-jaccard block - PCNedgelist <- rbind(zero_jaccard, nonzero_jaccard) - - # For Cytoscape graphing - #Remove all rows that only have NA values for CPE values - bioplanetCPEedges <- PCNedgelist[!is.na(PCNedgelist[,"PTM_cluster_evidence"]), c("source", "target", "PTM_cluster_evidence")] - # For Cytoscape it's useful to have both types of edges for plotting in different colors - - # Assign interaction, required for Cytoscape - bioplanetCPEedges$interaction <- "PTM_cluster_evidence" - # Create pathway crosstalk network with individual cluster and bioplanet edges - jaccard.net <- bioplanetjaccardedges - names(jaccard.net) <- c("source", "target", "Weight", "interaction") - CPE.net <- bioplanetCPEedges - names(CPE.net) <- c("source", "target", "Weight", "interaction") - pathway.crosstalk.network <- rbind(CPE.net, jaccard.net) - - ### Save edgefile for cytoscape plotting ### - - if(is.character(createfile)){ #Don't need to check if directory exists since was done above - saved.dir <- getwd() - setwd(createfile) - filename <- paste(PCN.edgelist.name, ".csv", sep="") #Name of the file created with .csv appended - utils::write.csv(pathway.crosstalk.network, file = filename, row.names = FALSE) #Save to files for cytoscape... - - cat(filename, "made in directory:", getwd()) #Tell the user where their files got put - setwd(saved.dir) - } - end_time <- Sys.time() - message(end_time) - #calculate difference between start and end time - total_time <- end_time - start_time - message(noquote(paste("Total time: ", total_time, sep=""))) - return(list(pathway.crosstalk.network, PCNedgelist, pathways.list)) -} - - - diff --git a/R/RawDataProcessing.R b/R/RawDataProcessing.R deleted file mode 100644 index 9b83b92b..00000000 --- a/R/RawDataProcessing.R +++ /dev/null @@ -1,183 +0,0 @@ -###================================================================================================================== -# Functions and suggested workflow to help process mass spectrometry data files -###================================================================================================================== - -## We use the following shorthand conventions when describing PTMs. This can be modified if different modification types are under investigation -# The HUGO Gene Name is used to identify the protein/gene -# Phosphorylation = "p" -# Lysine acetylation = "ack" -# Lysine methylation = "kme" -# Arginine methylation = "rme" -# Ubiquitination = "ubi" - - -# Helper functions -#' Correct Excel-Converted Gene Symbols -#' -#' Replaces gene symbols that are commonly converted into date-like strings by -#' spreadsheet software with their expected HUGO symbols. -#' -#' @param cell A character string containing one or more gene symbols, separated -#' by `"; "`. -#' -#' @return A character string with corrected gene symbols. -#' @export -#' -#' @examples -#' fix.excel("1-Sep; CDC2") -# Function to change dates back into gene names - Excel changes many genes into dates and this can't be turned off! -fix.excel <- function(cell) { - fixgenes = c("CDC2", "1-Sep", "2-Sep", "3-Sep", "4-Sep", "5-Sep", "7-Sep", "8-Sep", "9-Sep", "10-Sep", "11-Sep", "15-Sep", "6-Sep", "1-Oct", "2-Oct", "3-Oct", "4-Oct", "6-Oct", "7-Oct", "11-Oct", "1-Mar", "2-Mar", "3-Mar", "4-Mar", "5-Mar", "6-Mar", "7-Mar", "8-Mar", "9-Mar", "10-Mar", "11-Mar", "C11orf58", 'C17orf57', 'C3orf10', 'C7orf51', "C11orf59", "C4orf16", "1-Dec", "14-Sep") - corrects = c("CDK1", "SEPT1", "SEPT2", "SEPT3", "SEPT4", "SEPT5", "SEPT7", "SEPT8", "SEPT9", "SEPT10", "SEPT11", "SEPT15", "SEPT6", "POU2F1", "POU2F2", "POU5F1", "POU5F1", "POU3F1", "POU3F2", "POU2F3", "MARCH1", "MARCH2", "MARCH3", "MARCH4", "MARCH5", "MARCH6", "MARCH7", "MARCH8", "MARCH9", "MARCH10", "MARCH11", "SMAP", "EFCAB13", "BRK1", "NYAP1", "LAMTOR1", 'AP1AR', "DEC1", "SEPT14") - cellv <- unlist(strsplit(as.character(cell), "; ")) - if (any(fixgenes %in% cellv)) { - cellv.new <- gsub(fixgenes[fixgenes %in% cellv], corrects[fixgenes %in% cellv], cellv) - return (paste(cellv.new, collapse="; ")) - } else return(cell) } - -#' Create PTM Peptide Names -#' -#' Builds standardized PTM peptide names from gene symbols, modification type, -#' amino acid, and site information. Supports ambiguous entries separated by a -#' delimiter. -#' -#' @param genes A character string of one or more gene symbols. -#' @param modification A character string indicating modification shorthand. -#' Defaults to `"p"`. -#' @param sites A character string of one or more site positions. -#' @param aa A character string containing the amino acid code. -#' @param pepsep A separator used in `genes` and `sites` for ambiguous entries. -#' Defaults to `";"`. -#' -#' @return A character string containing unique peptide names. -#' @export -#' -#' @examples -#' name.peptide("MAPK1; MAPK3", sites = "185; 204", aa = "T") -# This function will handle ambiguous modification sites (a modification site whose peptide sequence is the same in more than one protein) separated by ";" or another separator -# Make peptide names using this function: -name.peptide <- function (genes, modification="p", sites, aa, pepsep=";") { - genes.v <- unlist(strsplit(genes, pepsep, fixed = TRUE)) - genes.v[which(genes.v == "NA")] <- NA - genes.v <- genes.v[!is.na(genes.v)] - sites.v <- unlist(strsplit(sites, pepsep, fixed = TRUE)) - sites.v[which(sites.v == "NA")] <- NA - sites.v <- sites.v[!is.na(sites.v)] - sites.v <- sapply(sites.v, function (x) paste (aa, x, sep="")) - Peptide.v <- as.character(noquote(paste(genes.v[1:length(genes.v)], modification, sites.v[1:length(sites.v)], sep=" "))) - Peptide <- paste(unique(Peptide.v), collapse="; ") - return(Peptide) -} - -#' Merge Technical Replicate Values -#' -#' Merges two numeric values from technical replicates by returning `NA` when -#' both are missing, returning the observed value when one is missing, or -#' returning their mean when both are present. -#' -#' @param colv1 A numeric value or vector from replicate 1. -#' @param colv2 A numeric value or vector from replicate 2. -#' -#' @return A numeric value/vector or `NA`. -#' @export -#' -#' @examples -#' merge2cols(10, 14) -#' merge2cols(NA, 14) -#' merge2cols(c(NA, 2, 4), c(3, NA, 6)) -# Use this function to average technical replicates. This function ignores NA values in either column and takes the average in the case where there are two values. -merge2cols <- function (colv1, colv2) { - if (length(colv1) != length(colv2)) { - stop("colv1 and colv2 must have the same length.") - } - - colv1 <- as.numeric(colv1) - colv2 <- as.numeric(colv2) - - out <- (colv1 + colv2) / 2 - na1 <- is.na(colv1) - na2 <- is.na(colv2) - - out[na1 & !na2] <- colv2[na1 & !na2] - out[!na1 & na2] <- colv1[!na1 & na2] - out[na1 & na2] <- NA_real_ - - out -} - -# _________________________________________________________________________________ -# # Example data file /Users/markgrimes/Library/CloudStorage/Dropbox/_Work/R_/_LINCS/_KarenGuolin/unnormalized_separate_septm/phospho_cleaned_mapped.txt -# newphos <- read.table(datafilepath ="phospho_cleaned_mapped.txt", sep = "\t", skip = 0, header=TRUE, blank.lines.skip=T, fill=T, quote="\"", dec=".", comment.char = "", stringsAsFactors=F) -# # "Amino Acid" has the modsite, e.g. S,T, etc.; -# # "Position" has the sequence postion; "Positions" and "Positions Within Proteins" have multiple possible postions separated by ";". Assume singular value in Position is for the first gene name. "Modification Type" has Phosphorylation" etc. -# # In our data table, which includes ambigous PTM IDs, the relevant column names are -# # genes = "AllGeneSymbols" -# # positions = "Positions.Within.Proteins" -# # aa = "Amino.Acid" -# # modification = "Modification.Type" -# #### Workflow -# # First remove internal control rows (reverse sequences) -# newphos <- newphos[!is.na(newphos$AllGeneSymbols),] -# # If there are dates in the AllGeneSymbols column, use: -# newphos$AllGeneSymbols <- sapply(newphos$AllGeneSymbols, fix.excel) -# headercols <- c("AllGeneSymbols", "Amino.Acid", "Positions.Within.Proteins", "Modification.Type") -# newphos.head <- newphos[,headercols] -# -# newphos.head$Peptide.Name <- mapply(name.peptide, genes=newphos.head$AllGeneSymbols, sites= newphos.head$Positions.Within.Proteins, aa=newphos.head$Amino.Acid) -# # -# # Define Data columns -# phosdata <- newphos[,grep("Intensity", names(newphos))] -# names(phosdata) <- sapply(names(phosdata), function (x) unlist(strsplit(x, "Intensity."))[2]) -# # make zero into NA, which it is. -# zer0 <- which(phosdata==0, arr.ind = TRUE) -# phosdata <- replace (phosdata, zer0, NA) -# # Define technical replicates -# tr1 <- names(phosdata)[grep(".1", names(phosdata), fixed=TRUE)] -# tr2 <- names(phosdata)[grep(".2", names(phosdata), fixed=TRUE)] -# phosdata <- newphos[,grep("Intensity", names(newphos))] -# names(phosdata) <- sapply(names(phosdata), function (x) unlist(strsplit(x, "Intensity."))[2]) -# # make zero into NA, which it is. -# zer0 <- which(phosdata==0, arr.ind = TRUE) -# phosdata <- replace (phosdata, zer0, NA) -# # Define technical replicates -# tr1 <- names(phosdata)[grep(".1", names(phosdata), fixed=TRUE)] -# tr2 <- names(phosdata)[grep(".2", names(phosdata), fixed=TRUE)] -# # The optimal pulldown columns are straightforward to identify by the pulldown strings present in the sample names (they are also identifiable by zooming out and looking at the patterns of missing data, the optimal pulldowns, as a group, have the least missing data). -# tr1.opt <- tr1[grep("pTyr", tr1)] -# tr2.opt <- tr2[grep("pTyr", tr2)] -# -# -# # Use merge2cols() function to average technical replciates: -# phosdata.merged <- data.frame(matrix(nrow=nrow(phosdata), ncol=18)) -# for(i in 1:length(tr1.opt)) { -# phosdata.merged[,i] <- mapply(merge2cols, colv1=as.numeric(phosdata[, tr1.opt[i]]), colv2=as.numeric(phosdata[,tr2.opt[i]])) -# } -# names(phosdata.merged) <- sapply(tr1.opt, function(x) substr(x, start=1, stop=nchar(x)-2)) -# -# -# # Merge with header -# phosdatafile <- cbind(newphos.head, phosdata.merged) -# # This file could be safed for reference using write.table() -# # For subsequent steps -# rownames(phosdatafile) <- phosdatafile$Peptide.Name -# phosdata.df <- phosdatafile[,6:23] -# # This contains just the data with individual PTMs as rownames -# ###################### -# # Workflow: Repeat with aceltylation or other PTM data -# # ... -# # Then, to put them together: -# # Make column names common. For example: -# kgp <- phosdata.df -# kga <- ackdata.df -# kgu <- ubidata.df -# names(kgp) <- sapply(names(kgp), function (x) paste(unlist(strsplit(x, "_pTyr"))[1], unlist(strsplit(x, "_pTyr"))[2], sep="")) -# names(kga) <- sapply(names(kga), function (x) paste(unlist(strsplit(x, "_AcK"))[1], unlist(strsplit(x, "_AcK"))[2], sep="")) -# names(kgu) <- sapply(names(kgu), function (x) paste(unlist(strsplit(x, "_Ubi"))[1], unlist(strsplit(x, "_Ubi"))[2], sep="")) -# identical(names(kgp), names(kga)) # Check TRUE -# # -# ptmdata <- rbind (kgp, kga, kgu) # 3159 18 -# # Reorder here -# ptmdata <- ptmdata[order(rownames(ptmdata)),] -# # This optional step improves clustering in our hands: -# log2ptmdata <- log2(ptmdata) -# # This is now ready to use as the ptmtable. diff --git a/man/BuildPathwayCrosstalkNetwork.Rd b/man/BuildPathwayCrosstalkNetwork.Rd deleted file mode 100644 index 1c409fa6..00000000 --- a/man/BuildPathwayCrosstalkNetwork.Rd +++ /dev/null @@ -1,37 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/PathwayCrosstalkNetwork.R -\name{BuildPathwayCrosstalkNetwork} -\alias{BuildPathwayCrosstalkNetwork} -\title{Build Pathway Crosstalk Network} -\usage{ -BuildPathwayCrosstalkNetwork( - common.clusters, - bioplanet.file = "bioplanet.csv", - createfile = getwd(), - PCN.edgelist.name = "PCN_file" -) -} -\arguments{ -\item{common.clusters}{The list of common clusters between all three distance metrics (Euclidean, Spearman, and SED). Can be made in MakeCorrelationNetwork} - -\item{bioplanet.file}{Either the path to a delimited Bioplanet file, or a named list of pathways where each list element is a character vector of gene symbols.} - -\item{createfile}{The path of where to create the edgelist file. Defaults to the working directory, if FALSE is provided, a file will not be created.} - -\item{PCN.edgelist.name}{Name of the PCN edgelist file to be created} -} -\value{ -A list with these data structures at the given index: \enumerate{ -\item{Contains pathway source-target columns, along with the interaction type.} -\item{Contains pathway source-target columns, with edge weights of their jaccard similarity and their Pathway-Pathway Evidence score.} -\item{All pathways in the bioplanet database as a named list containing string vectors. Each vector is a pathway with strings associated with the genes in that pathway.} -} -} -\description{ -Converts Bioplanet pathways from (\url{https://tripod.nih.gov/bioplanet/}) into a list of pathways whose elements are the genes in each pathway. Edge weights are either the PTM Cluster Weight or according to the Jaccard Similarity. -} -\examples{ -Example_Output <- BuildPathwayCrosstalkNetwork(ex_common_clusters, ex_pathways_list, createfile = FALSE) -Example_Output[[1]][[3,]] -Example_Output[[3]][[1:3]] -} diff --git a/man/ReadBioplanetFile.Rd b/man/ReadBioplanetFile.Rd deleted file mode 100644 index cefd8c99..00000000 --- a/man/ReadBioplanetFile.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/PathwayCrosstalkNetwork.R -\name{ReadBioplanetFile} -\alias{ReadBioplanetFile} -\title{Read Bioplanet File} -\usage{ -ReadBioplanetFile(bioplanet.file = "bioplanet.csv") -} -\arguments{ -\item{bioplanet.file}{Path to a delimited Bioplanet text file. -Common delimiters such as comma, tab, semicolon, and pipe are supported.} -} -\value{ -A named list of pathways and associated gene symbols. -} -\description{ -Reads a Bioplanet CSV file and converts it into a named list of pathways, -where each list element is a character vector of gene symbols. -} diff --git a/man/filter.edges.0.Rd b/man/filter.edges.0.Rd deleted file mode 100644 index 4f961d03..00000000 --- a/man/filter.edges.0.Rd +++ /dev/null @@ -1,289 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/CytoscapeGraphingFunctions.R -\name{filter.edges.0} -\alias{filter.edges.0} -\title{Graphing and Manipulating Cluster Filtered Network} -\usage{ -filter.edges.0(nodenames, edge.file) -} -\arguments{ -\item{cfn}{A version of ppi.network with only the edges that exist in cccn.matrix and have non-zero weights} - -\item{ptmtable}{A dataset for post-translational modifications. Formatted with numbered rows, and the first column containing PTM names. The rest of the column names should be drugs. Values are numeric values that represent how much the PTM has reacted to the drug.} - -\item{funckey}{A table graphing gene names to type of protein; defaults to the internal dataset \code{PTMsToPathways::function_key}} - -\item{Network.title}{Desired title for the created Cytoscape Network; defaults to "cfn"} - -\item{Network.collection}{Desired name for the collection created on Cytoscape in which the network will reside; defaults to "PTMsToPathways"} - -\item{visual.style.name}{Desired name for the visual style created on Cytoscape; defaults to "PTMsToPathways.style"} - -\item{background.color}{Hex code of background color of graph; defaults to '#fcf3cf'} - -\item{edge.label.color}{Hex code of edge label color of graph; defaults to '#17202a'} - -\item{node.label.color}{Hex code of node label color of graph; defaults to '#145a32'} - -\item{default.font}{Font style of edge and node names; defaults to "Times New Roman"} - -\item{node.font.size}{Font size of the node name; defaults to 12} - -\item{edge.font.size}{Font size of the edge name; defaults to 8} - -\item{edge.line.style}{Type of edge style; defaults to "SOLID"; options include: "PARALLEL_LINES", "MARQUEE_EQUAL", "DOT", "EQUAL_DASH", "LONG_DASH", "CONTIGUOUS_ARROW", "MARQUEE_DASH", "DASH_DOT", "BACKWARD_SLASH", "FORWARD_SLASH", "VERTICAL_SLASH", "SOLID", "SEPARATE_ARROW", "MARQUEE_DASH_DOT", "ZIGZAG", "SINEWAVE"} - -\item{edge.opacity}{Opacity of the edge line on a scale of 0 - 255 with 0 being transparent; defaults to 175} - -\item{edge.label.opacity}{Opacity of the edge label on a scale of 0 - 255 with 0 being transparent; defaults to 255} - -\item{border.opacity}{Opacity of the node border on a scale of 0 - 255 with 0 being transparent; defaults to 255} - -\item{node.label.opacity}{Opacity of the node label on a scale of 0 - 255 with 0 being transparent; defaults to 255} - -\item{node.fill.opacity}{Opacity of the node fill on a scale of 0 - 255 with 0 being transparent; defaults to 255} -} -\value{ -A cytoscape graph of the cluster filtered network -} -\description{ -Creates a cytoscape graph of the cluster filtered network. Ensure that you have the Cytoscape app open and the RCy3 package downloaded. -The package RCy3 is required for many of the functions in this script. To download, run: -\itemize{ -\item if (!require("BiocManager", quietly = TRUE)) install.packages("BiocManager") -\item BiocManager::install("RCy3") -It is also recommended (but not required) that users load RCy3 with library(RCy3) to access its functions directly. -} -} -\details{ -Graph Key -\itemize{ -\item Node Size -\itemize{ -\item Greater the node size, larger the absolute value of the score -} -\item Blue Node -\itemize{ -\item Negative score -} -\item Yellow Node -\itemize{ -\item Positive score -} -\item Green Node -\itemize{ -\item Approximately zero score -Node Shapes: -} -\item "ELLIPSE" -\itemize{ -\item unknown -} -\item "ROUND_RECTANGLE" -\itemize{ -\item receptor tyrosine kinase -} -\item "VEE" -\itemize{ -\item SH2 protein -or -\item SH2-SH3 protein -} -\item "TRIANGLE" -\itemize{ -\item SH3 protein -} -\item "HEXAGON" -\itemize{ -\item tyrosine kinase -} -\item "DIAMOND" -\itemize{ -\item SRC-family kinase -} -\item "OCTAGON" -\itemize{ -\item kinase -or -\item phosphatase -} -\item "PARALLELOGRAM" -\itemize{ -\item transcription factor -} -\item "RECTANGLE" -\itemize{ -\item RNA binding protein -Node Border Colors: -} -\item Orange -\itemize{ -\item deacetylase -or -\item acetyltransferase -} -\item Blue -\itemize{ -\item demethylase -or -\item methyltransferase -} -\item Royal Purple -\itemize{ -\item membrane protein -} -\item Red -\itemize{ -\item kinase -or -\item tyrosine kinase -or -\item SRC-family kinase -} -\item Yellow -\itemize{ -\item phosphatase -or -\item tyrosine phosphatase -} -\item Lilac -\itemize{ -\item G protein-coupled receptor -or -\item receptor tyrosine kinase -} -\item Grey -\itemize{ -\item default -} -\item Edge Thickness -\itemize{ -\item Stronger correlation -Edge Colors: -} -\item Red -\itemize{ -\item Phosphorylation -or -\item pp -or -\item controls-phosphorylation-of -} -\item Bright Magenta -\itemize{ -\item controls-expression-of -} -\item Dull Magenta -\itemize{ -\item controls-transport-of -} -\item Purple -\itemize{ -\item controls-state-change-of -} -\item Blood Orange -\itemize{ -\item Acetylation -} -\item Lime Green -\itemize{ -\item Phystical interactions -} -\item Green -\itemize{ -\item BioPlex -} -\item Dull Green -\itemize{ -\item in-complex-with -} -\item Seafoam Green -\itemize{ -\item experiments -or -\item experiments_transferred -} -\item Cyan -\itemize{ -\item database -or -\item database_transferred -} -\item Teal -\itemize{ -\item Pathway -or -\item Predicted -} -\item Dark Turquoise -\itemize{ -\item Genetic interactions -} -\item Yellow-Orange -\itemize{ -\item correlation -} -\item Royal Blue -\itemize{ -\item negative correlation -} -\item Bright Yellow -\itemize{ -\item positive correlation -} -\item Grey -\itemize{ -\item combined_score -} -\item Dark Grey -\itemize{ -\item merged -} -\item Light Grey -\itemize{ -\item intersect -} -\item Black -\itemize{ -\item peptide -} -\item Orange -\itemize{ -\item homology -} -\item Dull Orange -\itemize{ -\item Shared protein domains -} -\item White -\itemize{ -\item Default -Arrow Types: -} -\item Arrow -\itemize{ -\item Phosphorylation -or -\item pp -or -\item controls-phosphorylation-of -or -\item controls-expression-of -or -\item controls-transport-of -or -\item controls-state-change-of -or -\item Acetylation -} -\item No Arrow -\itemize{ -\item Default -} -} -} -\examples{ -# GraphCFN(ex.cfn) -# See vignette for default graph - -} diff --git a/man/fix.excel.Rd b/man/fix.excel.Rd deleted file mode 100644 index 71f7b759..00000000 --- a/man/fix.excel.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/RawDataProcessing.R -\name{fix.excel} -\alias{fix.excel} -\title{Correct Excel-Converted Gene Symbols} -\usage{ -fix.excel(cell) -} -\arguments{ -\item{cell}{A character string containing one or more gene symbols, separated -by \code{"; "}.} -} -\value{ -A character string with corrected gene symbols. -} -\description{ -Replaces gene symbols that are commonly converted into date-like strings by -spreadsheet software with their expected HUGO symbols. -} -\examples{ -fix.excel("1-Sep; CDC2") -} diff --git a/man/grapes-w-slash-o-grapes.Rd b/man/grapes-w-slash-o-grapes.Rd deleted file mode 100644 index ea7ded98..00000000 --- a/man/grapes-w-slash-o-grapes.Rd +++ /dev/null @@ -1,23 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/CytoscapeGraphingFunctions.R -\name{\%w/o\%} -\alias{\%w/o\%} -\title{Set difference for vectors using custom infix operator} -\usage{ -x \%w/o\% y -} -\arguments{ -\item{x}{A vector.} - -\item{y}{A vector to compare.} -} -\value{ -Elements of x that are not in y. -} -\description{ -Returns the elements in vector x that are not in vector y. -} -\examples{ -1:5 \%w/o\% c(2, 4) -letters[1:5] \%w/o\% c("b", "d") -} diff --git a/man/make.genepep.edges.Rd b/man/make.genepep.edges.Rd deleted file mode 100644 index f0bb0c3b..00000000 --- a/man/make.genepep.edges.Rd +++ /dev/null @@ -1,11 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/CytoscapeGraphingFunctions.R -\name{make.genepep.edges} -\alias{make.genepep.edges} -\title{Title} -\usage{ -make.genepep.edges(peptide.edgefile) -} -\description{ -Title -} diff --git a/man/merge2cols.Rd b/man/merge2cols.Rd deleted file mode 100644 index a85b528f..00000000 --- a/man/merge2cols.Rd +++ /dev/null @@ -1,26 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/RawDataProcessing.R -\name{merge2cols} -\alias{merge2cols} -\title{Merge Technical Replicate Values} -\usage{ -merge2cols(colv1, colv2) -} -\arguments{ -\item{colv1}{A numeric value or vector from replicate 1.} - -\item{colv2}{A numeric value or vector from replicate 2.} -} -\value{ -A numeric value/vector or \code{NA}. -} -\description{ -Merges two numeric values from technical replicates by returning \code{NA} when -both are missing, returning the observed value when one is missing, or -returning their mean when both are present. -} -\examples{ -merge2cols(10, 14) -merge2cols(NA, 14) -merge2cols(c(NA, 2, 4), c(3, NA, 6)) -} diff --git a/man/name.peptide.Rd b/man/name.peptide.Rd deleted file mode 100644 index e316af77..00000000 --- a/man/name.peptide.Rd +++ /dev/null @@ -1,32 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/RawDataProcessing.R -\name{name.peptide} -\alias{name.peptide} -\title{Create PTM Peptide Names} -\usage{ -name.peptide(genes, modification = "p", sites, aa, pepsep = ";") -} -\arguments{ -\item{genes}{A character string of one or more gene symbols.} - -\item{modification}{A character string indicating modification shorthand. -Defaults to \code{"p"}.} - -\item{sites}{A character string of one or more site positions.} - -\item{aa}{A character string containing the amino acid code.} - -\item{pepsep}{A separator used in \code{genes} and \code{sites} for ambiguous entries. -Defaults to \code{";"}.} -} -\value{ -A character string containing unique peptide names. -} -\description{ -Builds standardized PTM peptide names from gene symbols, modification type, -amino acid, and site information. Supports ambiguous entries separated by a -delimiter. -} -\examples{ -name.peptide("MAPK1; MAPK3", sites = "185; 204", aa = "T") -} diff --git a/man/ptms_to_cfn.Rd b/man/ptms_to_cfn.Rd deleted file mode 100644 index c179d6e0..00000000 --- a/man/ptms_to_cfn.Rd +++ /dev/null @@ -1,35 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/CytoscapeGraphingFunctions.R -\name{ptms_to_cfn} -\alias{ptms_to_cfn} -\title{Extracts co-clustered PTM and gene network from a cluster filtered network object.} -\usage{ -ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") -} -\arguments{ -\item{ptms}{Character vector of PTM site strings (e.g., \"TP53 p S15\")} - -\item{cfn}{List or data frame representing the cluster filtered network (default: global cfn.merged)} - -\item{pepsep}{Character used to split ambiguous PTM entries (default: \";\")} -} -\value{ -Subnetwork object with co-clustered PTMs and genes -} -\description{ -This function takes a list of PTM (post-translational modification) site IDs and a cluster-filtered network, -extracts the genes from unambiguous and ambiguous PTM entries, then returns the co-clustered subnetwork. -} -\examples{ -# Example input objects -ptms <- c("TP53 p S15", "BRCA1 p S123; BRCA1 p T124", "MDM2 p S200") -# Example network -# edge list with 'source', 'target', and cluster info, could be data.frame or igraph -cfn.merged <- data.frame(source = c("TP53", "BRCA1", "BRCA1", "MDM2"), - target = c("BRCA1", "MDM2", "TP53", "TP53"), - cluster = c(1,1,2,1)) -# Suppose filter.edges.0 and get.co.clustered.ptms are also defined and loaded -# The following returns the gene/PTM subnetwork -res <- ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") -print(res) -} diff --git a/man/remove.autophos.Rd b/man/remove.autophos.Rd deleted file mode 100644 index 7d8c3550..00000000 --- a/man/remove.autophos.Rd +++ /dev/null @@ -1,11 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/CytoscapeGraphingFunctions.R -\name{remove.autophos} -\alias{remove.autophos} -\title{Title} -\usage{ -remove.autophos(edgefile) -} -\description{ -Title -} diff --git a/man/setCorrEdgeAppearance.Rd b/man/setCorrEdgeAppearance.Rd deleted file mode 100644 index 8e6875a6..00000000 --- a/man/setCorrEdgeAppearance.Rd +++ /dev/null @@ -1,11 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/CytoscapeGraphingFunctions.R -\name{setCorrEdgeAppearance} -\alias{setCorrEdgeAppearance} -\title{Title} -\usage{ -setCorrEdgeAppearance() -} -\description{ -Title -} diff --git a/man/setNodeSizeColorIndependently.Rd b/man/setNodeSizeColorIndependently.Rd deleted file mode 100644 index bd5455ce..00000000 --- a/man/setNodeSizeColorIndependently.Rd +++ /dev/null @@ -1,68 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/CytoscapeGraphingFunctions.R -\name{setNodeSizeColorIndependently} -\alias{setNodeSizeColorIndependently} -\title{Set node size and color from independent Cytoscape node attributes} -\usage{ -setNodeSizeColorIndependently(sizeplotcol, colorplotcol, ratio = FALSE) -} -\arguments{ -\item{sizeplotcol}{A character string naming the node table column to use -for node size mapping.} - -\item{colorplotcol}{A character string naming the node table column to use -for node color mapping.} - -\item{ratio}{Logical; if \code{TRUE}, use predefined log2 ratio control points -and ratio color palette. If \code{FALSE}, compute control points from the -observed ranges of \code{sizeplotcol} and \code{colorplotcol}.} -} -\value{ -This function is called for its side effects in Cytoscape and returns -\code{NULL} invisibly. -} -\description{ -Apply independent continuous visual mappings for node size and node color -in the active Cytoscape network using columns from the node table. -This helper is intended for PTMsToPathways-style networks where one node -attribute controls size and a different attribute controls color. -} -\details{ -If either requested column is not present in the Cytoscape node table, the -user is prompted to choose a valid column name interactively. For non-ratio -data, color and size control points are derived from the observed ranges of -the selected columns. For ratio-style data, fixed log2-based control points -are used. - -For \code{ratio = FALSE}, this function builds separate continuous mappings for -node color and node size based on the minimum and maximum values observed -in the selected Cytoscape node table columns. Blue-to-white-to-yellow color -gradients are used for color mapping. - -For \code{ratio = TRUE}, the function assumes ratio-like values centered on zero -and applies fixed log2-scaled control points for both node size and node -color. In this mode, a blue-to-green-to-yellow palette is used. - -The function also locks node width and height and sets the default node -selection color to magenta. -} -\examples{ -\dontrun{ -setNodeSizeColorIndependently( - sizeplotcol = "mean_expression", - colorplotcol = "logFC", - ratio = FALSE -) - -setNodeSizeColorIndependently( - sizeplotcol = "abundance_ratio", - colorplotcol = "abundance_ratio", - ratio = TRUE -) -} - -} -\seealso{ -\code{\link[RCy3:setNodeColorMapping]{RCy3::setNodeColorMapping()}}, \code{\link[RCy3:setNodeSizeMapping]{RCy3::setNodeSizeMapping()}}, -\code{\link[RCy3:lockNodeDimensions]{RCy3::lockNodeDimensions()}}, \code{\link[RCy3:getTableColumns]{RCy3::getTableColumns()}} -} diff --git a/tests/testthat/test_buildpathwaycrosstalknetwork.R b/tests/testthat/test_buildpathwaycrosstalknetwork.R deleted file mode 100644 index 8b4bc02e..00000000 --- a/tests/testthat/test_buildpathwaycrosstalknetwork.R +++ /dev/null @@ -1,56 +0,0 @@ -# THESE USE DATA GENERATED FROM THE SMALL PTM TABLE - -test_that("BuildPathwayCrosstalkNetwork() gives right answer", { - - output <- suppressMessages(BuildPathwayCrosstalkNetwork(ex_common_clusters, ex_pathways_list, createfile = FALSE)) - - pathway_crosstalk_network <- output[[1]] - - row_7 <- as.list(pathway_crosstalk_network[7,]) - row_25 <- as.list(pathway_crosstalk_network[25,]) - - exp_row_7 <- list("Lipid and lipoprotein metabolism", "Vitamin B12 metabolism", 0.3, "PTM_cluster_evidence") - exp_row_25 <- list("ERBB signaling pathway", "Validated nuclear estrogen receptor alpha network", 0.025974025974026, "pathway_Jaccard_similarity") - - expect_setequal(row_7, exp_row_7) - expect_setequal(row_25, exp_row_25) - - - - PCNedgelist <- output[[2]] - - row_10 <- PCNedgelist[10,] - row_16 <- PCNedgelist[16,] - jaccard_sims <- PCNedgelist[4:8,3] - PTM_ev <- PCNedgelist[10:19,4] # Should be all zeros - - exp_row_10 <- list("Axon guidance", "Class A GPCRs (rhodopsin-like)", 0.00173310225303293, 0) - exp_row_16 <- list("RXR/VDR pathway", "Validated nuclear estrogen receptor alpha network", 0.0344827586206897, 0) - exp_jaccard_sims <- c(0.01391304, 0.53424658, 0.01104972, 0.02075472, 0.02597403) - exp_PTM_ev <- numeric(10) - - expect_setequal(row_10, exp_row_10) - expect_setequal(row_16, exp_row_16) - expect_equal(jaccard_sims, exp_jaccard_sims, tolerance = 1e-6) - expect_setequal(PTM_ev, exp_PTM_ev) - - - - pathways_list <- output[[3]] - - expect_true("CRP" %in% pathways_list$'Selenium pathway') - expect_true("FAM120B" %in% pathways_list$'RXR/VDR pathway') - expect_false("ALB" %in% pathways_list$'Mitochondrial fatty acid beta-oxidation') - - -}) - -test_that("ReadBioplanetFile() gives right answer", { - # read pathways.csv from inst/extdata and compare to ex_pathways_list - bioplanet_pathways <- ReadBioplanetFile(system.file("extdata", "pathway.csv", package = "PTMsToPathways")) - expect_equal(length(bioplanet_pathways), length(ex_pathways_list)) - for (pathway_name in names(ex_pathways_list)) { - expect_true(pathway_name %in% names(bioplanet_pathways)) - expect_equal(sort(bioplanet_pathways[[pathway_name]]), sort(ex_pathways_list[[pathway_name]])) - } -}) \ No newline at end of file diff --git a/vignettes/BRCANetworks.Rmd b/vignettes/BRCANetworks.Rmd deleted file mode 100644 index 6b510ac5..00000000 --- a/vignettes/BRCANetworks.Rmd +++ /dev/null @@ -1,524 +0,0 @@ ---- -title: "BRCA Networks" -date: "2026-05-29" - -author: Nagashree Avabhrath, Mikhail Ukrainetz, Madison Moffett, Grant Smith, Lucia - Williams, Mark Grimes -output: - html_document: - toc: true - toc_float: true - toc_depth: 4 - theme: cosmo - highlight: tango - pdf_document: - toc: true - toc_depth: '4' - word_document: - toc: true - toc_depth: '4' -vignette: > - %\VignetteIndexEntry{PTMsToPathways} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} -editor_options: - markdown: - wrap: 72 ---- - -```{r setup, echo = FALSE} - -knitr::opts_chunk$set( - collapse = TRUE, - comment = ">>", - fig.align = "center", - fig.path = "plots/", - fig.width = 8, - fig.height = 6, - out.width = "100%", - results = "hold" -) -``` - -This tutorial will load and create networks from the breast cancer -cohort (BRCA, N = 122), produced by - -> **Proteogenomic landscape of breast cancer tumorigenesis and targeted -> therapy** Krug, K., Jaehnig, E. J., Satpathy, S., Blumenberg, L., -> Karpova, A., Anurag, M., et al. *Cell* 183, -> 1436PTMsToPathways::name6.e31 - -And downloaded from Supplemental data S2 from - -> **PhosphoDisco: A Toolkit for Co-regulated Phosphorylation Module -> Discovery in Phosphoproteomic Data** Schraink, T., Blumenberg, L., -> Hussey, G., George, S., Miller, B., Mathew, N., Gonzalez-Robles, T.J., -> Sviderskiy, V., Papagiannakopoulos, T., Possemato, R., et al. *Mol -> Cell Proteomics* 22, 100596. 10.1016/j.mcpro.2023.100596 - -First, let's load the PTMsToPathways package so its functions are -available: - -```{r eval = TRUE} -library(PTMsToPathways) -``` - -### Preprocess data for PTMsToPathways functions - -The BRCA data table described above is provided with the PTMsToPathways -package and and can be read in as follows: - -```{r eval = TRUE} -file_path <- system.file("extdata", "PhosphoDiscoData_mmc9.txt", package = "PTMsToPathways") -newphos <- utils::read.table(file_path, header = TRUE, - stringsAsFactors = FALSE, sep = "\t", comment.char = "#", - na.strings = "", quote = "", fill = TRUE) -``` - -It has 4237 rows and 124 columns representing 4237 phosphosites and 122 -samples with phosphoproteomic data. - -```{r eval = TRUE} -dim(newphos) -``` - -The first two columns are `gene_symbol` and `variable_sites_names` which -we will use to create row names for the PTM table to match the expected -input format for the PTMsToPathways functions. The [Raw Data Processing -vignette](RawDataProcessing.html) gives another example of how to -process raw data tables to create the expected input format for the -PTMsToPathways functions. Here are the first few rows and columns of the -data frame: - -```{r eval = TRUE} -head(newphos[, 1:5]) -``` - -Now we will process the first two columns to create row names for the -PTM table. We extract the amino acid and the site number from the -`variable_sites_names` column and remove trailing letters from the site -number if they exist. - -```{r eval = TRUE} - newphos$Amino.Acid <- sapply(newphos$variable_sites_names, function(x) substring (x, 1, 1)) - newphos$Site <- trimws(substring(newphos$variable_sites_names, 2)) - newphos$Site <- sub("[a-z]$", "", newphos$Site) - head(newphos[, c("gene_symbol", "variable_sites_names", "Amino.Acid", "Site")]) -``` - -Now use the PTMsToPathways function `name.peptide` to create peptide -names for the row names of the PTM table. - -```{r eval = TRUE} - newphos$Peptide.Name <- mapply( - name.peptide, genes = newphos$gene_symbol, - sites = newphos$Site, aa = newphos$Amino.Acid) -``` - -Create `ptmtable` with PTMs as rows and samples as columns for use in -the next steps, and remove the columns we used to create the row names. - -```{r eval = TRUE} -phosdata <- newphos[, 3:ncol(newphos), ] -rownames(phosdata) <- newphos$Peptide.Name -phosdata <- phosdata[, !(names(phosdata) %in% c("gene_symbol", "variable_sites_names", "Amino.Acid", "Site", "Peptide.Name"))] -ptmtable <- phosdata -head(ptmtable[, 1:5]) -``` - -### Create Clusters and Co-Cluster Correlation Networks (CCCNs) - -Next, we create clusters and networks from those clusters as in the -[Creating Networks vignette](CreatingNetworks.html). This takes about 10 -minutes on a laptop, so we provide both the code and the pre-computed -results for this step. To re-run the analysis, run, the following: - -```{r eval = FALSE} -set.seed(88) -clusterlist.data <- MakeClusterList(ptmtable, - keeplength = 3, toolong = 3.5) - -``` - -Or load in pre-computed results from within the PTMsToPathways package: - -```{r eval = TRUE} -clusterlist.data <- brca_clusterlist_data -CCCN.data <- brca_CCCN_data -``` - -Whether computed or loaded, the `cluster.data` and `CCCN.data` are lists -that contain the following elements: - -```{r eval = TRUE} -common.clusters <- clusterlist.data[[1]] -adj.consensus.matrix <- clusterlist.data[[2]] -ptm.correlation.matrix <- clusterlist.data[[3]] -``` - -These are required for the next step. - -```{r eval = TRUE} -CCCN.data <- MakeCorrelationNetwork(adj.consensus.matrix, - ptm.correlation.matrix) -ptm.cccn.edges <- CCCN.data[[1]] -gene.cccn.edges <- CCCN.data[[2]] -gene.cccn.nodes <- CCCN.data[[3]] -``` - -We expect >200 common clusters: - -```{r eval = TRUE} -length(common.clusters) -``` - -If desired, the clusters can be trimmed to those \> 4. This reduces the -number of clusters from 231 to 204. - -```{r eval = TRUE} -cclength <- sapply(common.clusters, length) -common.clusters4 <- common.clusters[which(cclength>3)] -length(common.clusters4) -``` - -We can use [graph.ptm.by.cluster](references/graph.ptm.by.cluster.html) -to visualize these in a heatmap. To demonstrate, let's look at the -output for the first 3 clusters. PTMs are rows and samples are columns, -and color represents the value of the PTM in that sample. Black -indicates missing values. - -```{r eval = TRUE, echo = FALSE} -fig_dir <- knitr::opts_current$get("fig.path") -``` - -```{r eval = TRUE} -dir.create(fig_dir, recursive = TRUE, showWarnings = FALSE) -output <- file.path(fig_dir, "ptm_all_clusters_l4.pdf") -res <- graph.ptm.by.cluster( - ptmtable = ptmtable, - common.clusters = common.clusters4[1:3], # use all clusters > 4, only first 3 - filename = output, - order.rows = "slope", - zlim = 3, - show.row.labels = FALSE, - show.col.labels = TRUE, - col_cex = 0.7 - ) -knitr::include_graphics(output) -``` - -PTMsToPathways provides the function -[`EvaluateClusters`](references/EvaluateClusters.html) which computes -the following for each cluster: - -- `intensity` = total signal after removing the NA fraction of samples - for this cluster -- `realsamples` = samples that are not single-gene/PTM samples for this - cluster -- `cleargenes` = genes/PTMs that fit a pattern that ranks by decreasing - total signal -- `percent.NA` = percentage of missing values in the cluster sub-table - -It also computes an `index` value for every cluster, which is a -composite value computed from the above. The output is ordered by this -`index` value, so we examine the top 10 clusters here: - -```{r eval = TRUE} -eval_brca <- EvaluateClusters( - common.clusters4, ptmtable, - data.type = "ratio", - use.slope = FALSE, - index.mode = "density", - verbose = FALSE - ) -eval_brca[1:10, ] - -``` - -### Build Cluster Filtered Networks (CFNs) and Pathway Crosstalk Networks (PCNs) - -For PPI edges, the code below demonstrates how to get the STRING-db and -GeneMANIA edges from the static human PPI data downloaded as local files. [todo: instructions -to download once these are available.] Alternatively, the PPI data -can be obtained from STRINGdb and GeneMANIA websites as demonstrated in the [Creating Networks vignette](CreatingNetworks.html). - -```{r eval = FALSE} -string_db_filepath <- "your/filepath/here.tsv" - -# optional check that the nodenames are consistent with STRINGdb -sym.map <- StandardizeGeneSymbols(gene.cccn.nodes) -identical (unique(sym.map$standard_symbol), gene.cccn.nodes) -# TRUE so no further action is required. -# If there were differences, replace symbol.map = NULL with symbol.map = sym.map - -stringdb.edges <- GetSTRINGdb.edges( gene.cccn.edges, - gene.cccn.nodes, - local = TRUE, - string.local.path = string_db_filepath, - combined.score.threshold = 400, - include.transferred = TRUE, - symbol.map = NULL) - -``` - -To avoid downloading the large STRINGdb edge file, edges from the BRCA gene set can be loaded from within the package: - -```{r eval = TRUE} -stringdb.edges <- BRCA_stringdb.edges -head(stringdb.edges) -``` - -The GeneMANIA human PPI edge file contains the following types of interactions: -"Genetic Interactions", "Pathway", "Physical Interactions", and "Predicted." - -We choose all but "Genetic Interactions" to include using gm.interaction.types in the following function. - -```{r eval = FALSE} -genemania_db_filepath <- "your/filepath/here.tsv" - -genemania.edges <- GetGeneMANIA.edges (gm.all.edges.path, - gene.cccn.nodes, - local = TRUE, - genemania.local.path = genemania_db_filepath, - gm.interaction.types = c("Pathway", "Physical Interactions", "Predicted")) -``` - -And again, to avoid downloading the large GeneMANIA edge file, BRCA gene edges can be loaded -from within the package: - -```{r eval = TRUE} -genemania.edges <- BRCA_genemania.edges -head(genemania.edges) -``` - -Next, we retrieve kinase-substrate edges, then obain the cluster filtered network, retaining PPIs only for proteins whose PTMs co-cluster, as demonstrated in the [Creating Networks vignette](CreatingNetworks.html). - -```{r eval = TRUE} -file_path <- system.file("extdata", "Kinase_Substrate_Dataset.txt", package = "PTMsToPathways") -kinsub.edges <- GetKinsub.edges(file_path, gene.cccn.nodes) - -# Now we can build the CFN. -network.list <- BuildClusterFilteredNetwork(gene.cccn.edges, - stringdb.edges, - genemania.edges, - kinsub.edges, - db.filepaths = c()) - -combined.PPIs <- network.list[[1]] -cfn <- network.list[[2]] -dim(cfn) - -cfn.merged <- mergeEdges(cfn) -dim(cfn.merged) -``` - -We build the PCN from the BioPlanet pathways as done previously. This -takes about a few minutes, so we provide both the code and the -pre-computed results for this step. To re-run the analysis, run the -following: - -```{r eval = FALSE} -bioplanet.file <- system.file("extdata", "bioplanet_pathway_June2025.csv", package = "PTMsToPathways") -PCN.data <- BuildPathwayCrosstalkNetwork(common.clusters, bioplanet.file, - createfile = FALSE) -``` - -Or load in pre-computed results from within the PTMsToPathways package: - -```{r eval = TRUE} -PCN.data <- BRCA_PCN.data -pathway.crosstalk.network <- PCN.data[[1]] # 679707 edges -PCNedgelist <- PCN.data[[2]] -pathways.list <- PCN.data[[3]] -dim(pathway.crosstalk.network) -``` - -Now we can explore these networks. - -### Preprocess modules from Shraink et al. - -We will examine the modules from Schraink, et al., 2023, Supplemental -Table S4. Per their description, the `HDBSCAN;min_cluster_size-4` column -assigns a module number to each phosphosite. - -```{r eval = TRUE} -PD_module.file <- system.file("extdata", "PhosDiscoModules_mmc11.txt", package = "PTMsToPathways") - -PD_module.df <- utils::read.table(PD_module.file, header = TRUE, - stringsAsFactors = FALSE, sep = "\t", comment.char = "#", - na.strings = "", quote = "", fill = TRUE) -dim(PD_module.df) # should be 1017 rows -head(PD_module.df) -``` - -We note that there are differences from the PTM table imported above. We will work with those sites that match between ptmtable and PD_module.df - -```{r eval = TRUE} -length(intersect(newphos$variable_sites_names, PD_module.df$variableSites)) # 530 -``` - -```{r eval = TRUE} -length(intersect(newphos$gene_symbol, PD_module.df$geneSymbol)) # 161 -``` - -Make peptide names as above: - -```{r eval = TRUE} - -PD_module.df$Amino.Acid <- sapply(PD_module.df$variableSites, function(x) substring (x, 1, 1)) -PD_module.df$Site <- trimws(substring(PD_module.df$variableSites, 2)) -PD_module.df$Site <- sub("[a-z]$", "", PD_module.df$Site) - -PD_module.df$Peptide.Name <- mapply( - name.peptide, genes = PD_module.df$geneSymbol, - sites = PD_module.df$Site, aa = PD_module.df$Amino.Acid) -head(PD_module.df) -``` - -Treat modules like our clusters: - -```{r eval=TRUE} -PD.module.list <- split(PD_module.df$Peptide.Name, PD_module.df$HDBSCAN.min_cluster_size.4) -length(PD.module.list) -PD.module.list$`68` -``` - -Let's get the unique genes in each module to compare to the unique genes -in our clusters. - -```{r eval = TRUE} -PD.module.genes.unique <- lapply( - PD.module.list, - function(x) unique(sub(" .*", "", x))) -PD.module.genes.unique$`68` -``` - -### Compare P2P clusters and Schraink, et al. modules - -Let's see which of our clusters have intersections with module 63. We -will use this as an example to show how to explore the networks around a -particular module of interest. - -```{r eval = TRUE} -mod63.intersect <- Filter(length, Map(intersect, common.clusters, list(PD.module.list$`63`))) -mod63.intersect -``` - -Interesctions of more than one PTM were found in several ConsensusClusters. - -There are >200 PTMs in the P2P clusters that intersect with module 63: - -```{r eval = TRUE} -mod63.clust.ptms <- unlist(c(common.clusters$ConsensusCluster1, common.clusters$ConsensusCluster3, common.clusters$ConsensusCluster4, common.clusters$ConsensusCluster5, common.clusters$ConsensusCluster6)) -length(mod63.clust.ptms) -``` - -P2P provides functions to prepare visualizations of these PTMs in -Cytoscape. This code graphs the CFN/CCCN from all these PTMs: - -```{r eval = TRUE} - funckey <- function_key -cfn.cccn <- ptms_to_cfn(mod63.clust.ptms, cfn = cfn.merged, pepsep = ";") -cfn_cccn.nodes <- make.cytoscape.node.file(cfn.cccn, funckey, ptmtable, - include.gene.data = TRUE, - include.coclustered.PTMs = TRUE) -``` - -To graph in Cytoscape: - -```{r eval = FALSE} -g1 <- GraphCfn(cfn.edges = cfn.cccn, cfn.nodes = cfn_cccn.nodes, - Network.title = "CFN/CCCN All PTMs 1", Network.collection = "PTMsToPathways") - -``` - -The above would create a graph using Cytoscape, which would look like: - -```{r eval = TRUE, echo = FALSE} -knitr::include_graphics("vig_figs/Mod63_AllPTMs_X03BR011.png") -``` - -This is a complex graph that shows PTMs clusters as cliques connected by -yellow correlation edges surrounding a CFN of interconnected gene nodes. - -Let's focus on CDK1 substrates to complement the work done in Schraink, -et al., 2023. There are 82 CDK1 edges. - -```{r eval = TRUE} -cdk1.kinsub <- filter.edges.1("CDK1", kinsub.edges) -dim(cdk1.kinsub) -head(cdk1.kinsub) -``` - -Now let's get the needed information about these edges: - -```{r eval = TRUE} -cdk1.substrates <- cdk1.kinsub [which(cdk1.kinsub$source=="CDK1"), "target"] -cfn_cccn.nodes.cdksubs <- cfn_cccn.nodes[cfn_cccn.nodes$Gene.Name %in% cdk1.substrates, ] - -``` - -Two ways to select for CDK1 substrates are presented. Method 1: Using -RCy3. - -```{r eval = FALSE} -library(RCy3) -selectNodes(cfn_cccn.nodes.cdksubs$id, by = "id", preserve=FALSE) -selectEdgesConnectingSelectedNodes() -createSubnetwork(nodes = getSelectedNodes(), edges = getSelectedEdges(), nodes.by.col = "id", edges.by.col = "name") -``` - -Method 2: Using P2P functions. - -```{r eval = FALSE} -cfn_cccn.nodes.cdksubs.edges <- filter.edges.0(nodenames = cfn_cccn.nodes.cdksubs$id, edge.file = cfn.cccn) - -g2 <- GraphCfn(cfn.edges = cfn_cccn.nodes.cdksubs.edges, cfn.nodes = cfn_cccn.nodes.cdksubs, - Network.title = "CFN/CCCN All PTMs 5", Network.collection = "PTMsToPathways") -``` - -Both methods give the same result, which looks like this: - -```{r eval = TRUE, echo = FALSE} -knitr::include_graphics("vig_figs/CDKsub63_allPTMs_X03BR011.png") -``` - -Now further simplify the graph to show only CCCN PTMs. - -```{r eval = FALSE} -cdksubs.genes <- unique(cfn_cccn.nodes.cdksubs$id) -cdksubs.cfn <- filter.edges.0(cdksubs.genes, cfn.merged) -cdksubs.cfn.cccn <- get.co.clustered.ptms(cdksubs.cfn, ptm.cccn.edges=ptm.cccn.edges) -cdksubs.cfn.cccn.nodes <- make.cytoscape.node.file(cdksubs.cfn.cccn, funckey, ptmtable, - include.gene.data = TRUE, - include.coclustered.PTMs = TRUE) -g3 <- GraphCfn(cfn.edges = cdksubs.cfn.cccn, cfn.nodes = cdksubs.cfn.cccn.nodes, - Network.title = "CFN/CCCN All PTMs 6", Network.collection = "PTMsToPathways") -``` - -This gives the following graph: - -```{r eval = TRUE, echo = FALSE} -knitr::include_graphics("vig_figs/CDKsub63_CCPTMs_X03BR011.png") -``` - -Each of these graphs can be modified to set node size and shape using -the following P2P functions that act on the front window in Cytoscape. -Note the differences that reflect activation of different cell signaling -pathways in tumors with different mutations: X02BR011 (AKT1 missense -mutant); X21BR010 (PIK3CA missense mutant); and X05BR045 (TP53 nonsense -and MLLT4 frameshift mutants). - -```{r eval = FALSE} -library(RCy3) -setNodeColorToRatios(plotcol="X03BR011") -setNodeColorToRowz(plotcol="X03BR011") # This exaggerates the node size and shape somewhat. -setNodeColorToRatios(plotcol="X21BR010") -setNodeColorToRowz(plotcol="X21BR010") -setNodeColorToRatios(plotcol="X05BR045") -setNodeColorToRowz(plotcol="X05BR045") -``` - -Note that different samples have dramatically different differences in -PTMs that are up or down, which is reflected also in total in gene -nodes. diff --git a/vignettes/CreatingNetworks.Rmd b/vignettes/CreatingNetworks.Rmd deleted file mode 100644 index bdabaf6b..00000000 --- a/vignettes/CreatingNetworks.Rmd +++ /dev/null @@ -1,568 +0,0 @@ ---- -title: 'Using the P2P Package: A Step-by-Step Tutorial' -author: Nagashree Avabhrath, Mikhail Ukrainetz, Madison Moffett, Grant Smith, Lucia - Williams, Mark Grimes -output: - html_document: - toc: true - toc_float: true - toc_depth: 4 - theme: cosmo - highlight: tango - pdf_document: - toc: true - toc_depth: '4' - word_document: - toc: true - toc_depth: '4' -vignette: > - %\VignetteIndexEntry{PTMsToPathways} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} -editor_options: - markdown: - wrap: 72 ---- - -```{r setup, echo = FALSE} - -knitr::opts_chunk$set( - collapse = TRUE, - comment = ">>", - fig.align = "center", - fig.path = "plots/", - fig.width = 8, - fig.height = 6, - out.width = "100%", - results = "hold" -) -``` - -This tutorial is intended to be a step-by-step guide to walk users -through the process of using the P2P package. It includes descriptions -of each function and must be run in order as subsequent steps require -the data produced in previous steps. Example code and example outputs as -well as estimated run-times are included with each description and are -based on a preliminary dataset of \~9000 PTMs and 69 experimental -conditions processed with a 12th Gen i7 processor and 16GB of RAM. - -**An important note:** The returned outputs from the functions are data -that may be saved in an RData object so that the user may reload the -data, which may take a while to generate, and pick up where they left -off later. See the bottom of this document for code to save your data -efficiently. - -# Installing the Package - -You will need to install the devtools package, which can be installed -with: - -```{r eval = FALSE, echo = TRUE} -install.packages("devtools") -``` - -Next, install the package with: - -```{r eval = FALSE, echo = TRUE} -devtools::install_github("UM-Applied-Algorithms-Lab/PTMsToPathways") -``` - -And load the package: - -```{r eval = TRUE, echo = TRUE} -library(PTMsToPathways) -``` - -# Starting Data - -For the tutorial, we will be using two example datasets: a smaller -dataset consisting of 933 PTMs and 18 experimental conditions (the -example used in the [Raw Data Processing -vignette](RawDataProcessing.html)) and a larger dataset containing -around 9000 PTMs and 69 experimental conditions. These datasets are -available with the package. Alternatively, the larger dataset can be -downloaded -[here](https://github.com/UM-Applied-Algorithms-Lab/PTMsToPathways/raw/refs/heads/main/inst/extdata/AlldataPTMs.txt) -to be inspected locally. - -To see all data that is provided with the package, run: - -```{r eval = FALSE, echo = TRUE} -data(package = "PTMsToPathways") -``` - -```{r eval = TRUE, echo = FALSE} -dat <- as.data.frame(data(package = "PTMsToPathways")$results) -dat <- dat[, c("Item", "Title")] -knitr::kable(dat, col.names = c("Dataset Name", "Description"), align = 'l') -``` - -If you are using the smaller dataset, use the following code to view the -dimensions of the dataset and a small portion of it: - -```{r eval = TRUE, echo = TRUE} -dim(ex_small_ptm_table) -ex_small_ptm_table[38:50, 1:2] -``` - -If you want to use the bigger dataset, the following code shows the -dimensions and a snippet of the dataset: - -```{r eval = TRUE, echo = TRUE} -dim(ex_full_ptm_table) -ex_full_ptm_table[38:50, 1:2] -``` - -If you have downloaded the larger dataset locally, you can read it into -R using the following code: - -```{r eval = FALSE, echo = TRUE} -allptmtable <- utils::read.table("AlldataPTMs.txt", sep = "\t", skip = 0, - fill = T, quote = "\"", dec = ".", - comment.char = "", stringsAsFactors = F) -``` - -## Using Your Own Data - -To use your own MS data, you will need to transform it into a dataframe -with PTMs and row names, experimental conditions as column names, and -numeric data as the entries. Please refer to the [Raw Data Processing -vignette](RawDataProcessing.html) for a tutorial showing all steps -needed to transform an MS output file into a P2P package input -dataframe. - -# Step 1: Make Cluster List - -`MakeClusterList` is the first step in the P2P process. This function -takes the dataframe `ptmtable` and runs it through three calculations of -statistical measures of distance: Euclidean Distance, Spearman -Dissimilarity (1- \|Spearman Correlation\|), and SED (the average of -both Spearman Dissimilarity (1- Spearman Correlation) and Euclidean -Distance). Combining the two dissimilarities leads to better resolution -of the data and is useful in pattern recognition. A correlation table--- -`ptm.correlation.matrix`---is generated based on the distances -calculated for each pair of PTMs. The function then runs the matrices -through t-SNE to generate clusters based on the previously calculated -distance and provides you with a cluster list, `common.clusters`. The -returned `adj.consensus.matrix` (which identifies which PTMs cluster -together with a 'short distance' between them) and -`ptm.correlation.matrix` are also used in the next step to create -co-cluster correlation networks (CCCNs). These three outputs are -returned as a list. - -The `keeplength` paramter defines the minimum number of PTMs that must -be in a cluster for it to be retained in the final output. The `toolong` -parameter defines the maximum distance between two PTMs for them to be -considered as clustering together. - -`MakeClusterList` can be run like so: - -```{r eval = TRUE, echo = TRUE} -set.seed(88) -clusterlist.data <- MakeClusterList(ex_small_ptm_table, - keeplength = 2, toolong = 3.5) -``` - -The following unpacks the output into the separate objects discussed -above: - -```{r eval = TRUE, echo = TRUE} -common.clusters <- clusterlist.data[[1]] -adj.consensus.matrix <- clusterlist.data[[2]] -ptm.correlation.matrix <- clusterlist.data[[3]] -``` - -Now we can view the objects. First, here is an example of a cluster: - -```{r eval = TRUE, echo = TRUE} -common.clusters[1] -``` - -Next, we look at a piece of the adjacency matrix. Ones represent a pair -that cluster and zeroes represent a pair that doesn't: - -```{r eval = TRUE, echo = TRUE} -adj.consensus.matrix[7:10, 7:10] -``` - -Here is a part of the PTM correlation matrix. Values for pairs of PTMs -are Spearman correlation coefficients ranging from -1 to 1. If two PTMs -had no experimental conditions in common, their correlation value will -be NA. - -```{r eval = TRUE, echo = TRUE} -ptm.correlation.matrix[38:43, 1:2] -``` - -#### Estimated run-time (for large dataset) - -\~60min - -# Step 2: Make Co-Cluster Correlation Networks (PTM and Gene) - -The data generated in the previous step is next used to create a new -network of PTMs that have strong associations called the Co-cluster -Correlation Network (CCCN). The Spearman correlations between -co-clustered PTMs are used as edge-weights in this network. The -MakeCorrelationNetwork function groups the PTM correlation matrices by -PTMs that co-cluster together to create a PTM CCCN. It then defines a -relationship between proteins modified by PTMs and creates a gene CCCN -with sum of the PTM correlations serving as edge weights. - -```{r eval = TRUE, echo = TRUE} -CCCN.data <- MakeCorrelationNetwork(adj.consensus.matrix, - ptm.correlation.matrix) -ptm.cccn.edges <- CCCN.data[[1]] -gene.cccn.edges <- CCCN.data[[2]] -gene.cccn.nodes <- CCCN.data[[3]] -``` - -We can view a portion of the PTM CCCN edges: - -```{r eval = TRUE, echo = TRUE} -ptm.cccn.edges[18:22,] -``` - -And a portion of the gene CCCN edges: - -```{r eval = TRUE, echo = TRUE} -gene.cccn.edges[1:5,] -``` - -Finally, we can view a portion of the gene CCCN nodes, which are used to -map to external PPI databases in the next step: - -```{r eval = TRUE, echo = TRUE} -gene.cccn.nodes[1:5] -``` - -Because this step can take a long time to run on larger datasets, the -output may be saved as an RData object for later use. - -```{r eval = FALSE, echo = TRUE} -save.image(file = "filepath/name.RData") -# All objects in the environment are saved -``` - -#### Estimated run-time (for large dataset) - -\~10min - -# Step 3: Retrieve Database Edgefiles - -The third step of the P2P package is to gather data from multiple -existing protein-protein interaction (PPI) databases which will be -integrated with the data generated in steps 1 and 2. The P2P package -explicitly allows the users to integrate data from three external -databases: STRING, GeneMANIA, and PhosphoSite Plus. Other databases can -also be downloaded and added to the PPI network. All three external -databases have different interfaces for downloading data, so we show how -to retrieve data from each of them below. - -For this tutorial, we query STRINGdb and GeneMANIA directly. We also -provide the option (using the switch local = TRUE) to retrive edges from -human PPIs from these sources in pre-assembled files; the [BRCANetworks -vignette](BCRANetworks.html) demonstrates how to get the STRING-db and -GeneMANIA edges from the static downloaded networks. - -### 1. STRINGdb - -[STRINGdb](https://string-db.org/) can be queried directly from R using -the `STRINGdb` package. We wrap this query in a function called -`GetSTRINGdb.edges`, which queries only for the genes found in clusters -in previous steps, and filters the returned by interaction type so only -`experimental`, `database`, `experimental_transferred`, and -`database_transferred` are retained. This ensures that only interactions -with more substantial evidence are used in this analysis. - -```{r eval = TRUE, echo = TRUE} -stringdb.edges <- GetSTRINGdb.edges(gene.cccn.edges, gene.cccn.nodes) -stringdb.edges[1:5,] -``` - -### 2. GeneMANIA - -To our knowledge, no R package exists to programmatically query -[GeneMANIA](https://genemania.org/). Thus, we recommend using the -GeneMANIA Cytoscape App to retrieve PPI data as follows. - -First, create an input file for GeneMANIA using the `MakeDBInput` -function provided within P2P (note that this creates a text file in your -working directory): - -```{r eval = TRUE, echo = TRUE} -MakeDBInput(gene.cccn.nodes, file.path.name = "db_nodes.txt") -``` - -Next, ensure that you have -[Cystoscape](https://cytoscape.org/download.html) and the [GeneMANIA -extension](https://apps.cytoscape.org/apps/genemania) installed. - -Copy the contents of the `db_nodes.txt` file into the GeneMANIA App's -"Genes of Interest" box and run query. - -To save the results, click on the three lines in the upper right corner. -This should be under the GeneMANIA side window beside the species. Click -"Export Results". The path to this file is the gm.results.path: - -![](vig_figs/GeneMANIA-image-1.png)\ -![](vig_figs/GeneMANIA-image-2.png) - -The `GetGeneMANIA.edges` function then processes the output file -produced by GeneMANIA itself. For example, we have saved -`ex_gm_results.txt` as an example output file from GeneMANIA within the -package. The following code shows how to use this file as input to the -function. - -```{r eval = TRUE, echo = TRUE} -gm.results.path <- system.file("extdata", "ex_gm_results.txt", - package = "PTMsToPathways") -genemania.edges <- GetGeneMANIA.edges(gm.results.path, gene.cccn.nodes) -``` - -We can see an example of the GeneMANIA edges below: - -```{r eval = TRUE, echo = TRUE} -genemania.edges[1:5,] -``` - -### 3. Phosphosite Plus - -The kinase-substrate data can be downloaded from [Phosphosite -Plus](https://www.phosphosite.org/staticDownloads) database. The users -will be required to create an account and sign in to download the data. -The `GetKinsub.edges` function reads this downloaded data in and formats -it so that all the PPI edge data frames are in the same format for the -next step. - -```{r eval = TRUE, echo = TRUE} -input.filename <- system.file("extdata", "Kinase_Substrate_Dataset.txt", - package = "PTMsToPathways") -``` - -```{r eval = TRUE, echo = TRUE} -kinsub.edges <- GetKinsub.edges(input.filename, - gene.cccn.nodes) -``` - -# Step 4: Build PPI Network and Cluster Filtered Network - -The `BuildClusterFilteredNetwork` function allows the users to filter -protein-protein interaction networks using the previously generated -co-cluster correlation networks. PPIs are retained in the cluster -filtered network (CFN) only if the interacting proteins share -statistically correlated PTMs identified via t-SNE clusters. The -`BuildClusterFilteredNetwork` function combines all the PPI data -downloaded in step 3 as efficiently as possible while retaining the -desired edge weights. It then normalizes the weights on a scale of 0-1 -and gives an output cluster filter network that will only retain -interacting proteins whose genes are within the co-cluster correlation -network created in step 2. - -We first run the function: - -```{r eval = TRUE, echo = TRUE} -network.list <- BuildClusterFilteredNetwork(gene.cccn.edges, - stringdb.edges, - genemania.edges, - kinsub.edges, - db.filepaths = c()) -``` - -And then unpack the outputs into separate variables: - -```{r eval = TRUE, echo = TRUE} -combined.PPIs <- network.list[[1]] -cfn <- network.list[[2]] -``` - -We can view a portion of the CFN below: - -```{r eval = TRUE, echo = TRUE} -cfn[1:5,] -``` - -To reduce clutter on graphs, the CFN edges can be merged. This collapses -two or more edges between two nodes into a single edge, combining edge -names: - -```{r eval = TRUE, echo = TRUE} -cfn.merged <- mergeEdges(cfn) -``` - -# Step 5: Pathway Crosstalk Network - -The final step is the creation of the Pathway Crosstalk Network (PCN), which -creates a set of pathway-pathway edges that have two weights: a Jaccard similarity -and a Cluster-Pathway Evidence score. -This step requires input of an external database from [NCATS -BioPlanet](https://tripod.nih.gov/bioplanet/download/pathway.csv) that -contains groups of genes (proteins) involved in various cellular -processes known as pathways. P2P provides a function [`ReadBioplanetFile`](references/ReadBioplanetFile.html) -that reads in the BioPlanet file and converts it into a list of pathways. The first pathways is displayed below: - -```{r eval = TRUE, echo = TRUE} -bioplanet.file <- system.file("extdata", "pathway.csv", - package = "PTMsToPathways") -pathways.list <- ReadBioplanetFile(bioplanet.file) -pathways.list[[5]] -``` - -The function [`BuildPathwayCrosstalkNetwork`](references/BuildPathwayCrosstalkNetwork.html) -takes the list of pathways (or a path to the pathway file) and the list of clusters generated in step 1 and returns -the relationships between pathways in two forms: a dataframe of edges that can be uploaded to Cytoscape (the first element of the returned list) -and a dataframe with columns for the two edge weights (the second element of the returned list). The third element of the returned list is the list of pathways -(in case the user provided a path to the pathway file rather than a list of pathways). The function can be run as follows: -```{r eval = TRUE, echo = TRUE} -PCN.data <- BuildPathwayCrosstalkNetwork(common.clusters, bioplanet.file, - createfile = FALSE) -pathway.crosstalk.network <- PCN.data[[1]] -PCNedgelist <- PCN.data[[2]] -pathways.list <- PCN.data[[3]] -``` -The required columns for Cytoscape are: -```{r eval = TRUE, echo = TRUE} -names(pathway.crosstalk.network) -``` - -And we can see some of the pathway crosstalk network edges below: - -```{r eval = FALSE, echo = TRUE} -pathway.crosstalk.network[1:5,] -``` - -```{r eval = TRUE, echo = FALSE} -dat <- pathway.crosstalk.network[1:5,] -knitr::kable(dat, align = 'l', digits = 2) -``` - - -To understand the edge weights calculations, let's consider a pair of pathways, `Axon Guidance` and -`ERBB signaling pathway`. We'll look at the `PCNedgelist` dataframe this time. - -```{r eval = FALSE, echo = TRUE} -PCNedgelist[PCNedgelist$source == "Axon guidance" & PCNedgelist$target == "ERBB signaling pathway", ] -``` -```{r eval = TRUE, echo = FALSE} -dat <- PCNedgelist[PCNedgelist$source == "Axon guidance" & PCNedgelist$target == "ERBB signaling pathway", ] -knitr::kable(dat, align = 'l', digits = 2) -``` - -## Metric calculations - -### Jaccard Similarity - The Jaccard similarity is defined as the size of -the intersection of the two pathways (the number of genes that are in both -pathways) divided by the size of the union of the two pathways (the total number -of unique genes that are in either pathway). - -```{r eval = TRUE, echo = TRUE} -size_union <- length(union(pathways.list[["Axon guidance"]], pathways.list[["ERBB signaling pathway"]])) -size_union -size_intersection <- length(intersect(pathways.list[["Axon guidance"]], pathways.list[["ERBB signaling pathway"]])) -size_intersection -jaccard_similarity <- size_intersection / size_union -jaccard_similarity -``` - -Notice that the found value is the same as the `pathway_Jaccard_similarity` value in the `PCNedgelist` dataframe. - -### Cluster-Pathway Evidence (CPE) Score - -For a given pathway $j$ ($pw_j$) and a given cluster $i$ ($cl_i$), we define the Cluster-Pathway Evidence (CPE), -$$ -CPE(i,j) = \Sigma_{pro_k \in pw_j} \frac{|\{PTM_x: PTM_x \in pro_k \land PTM_x \in cl_i\}|}{|\{pw_y : pro_k \in pw_y\}| \cdot |cl_i|} -$$ -where $pro_k$ is a protein in pathway $j$ and $PTM_x$ is a PTM. - -(Note that $\in$ means "in" and $\land$ means "and".) - -Let's break down the CPE calculation single cluster $i$, the first cluster found, and a single pathway $j$, the `Axon guidance` pathway. - -```{r eval = TRUE, echo = TRUE} -c_i <- common.clusters[[1]] -p_j <- pathways.list[["Axon guidance"]] -length(c_i) -length(p_j) -``` -Notice that the numerator of the summands in $CPE(i,j)$ counts the number of -PTMs that are in both the protein and the cluster. Intuitively, if a pathway contains many proteins that have PTMs in this cluster, then this sum will be larger. -Of the 325 proteins in the `Axon guidance` pathway, only `MYH9` and `PTK2` have PTMs that occur in the first cluster, so -out of the 325 summands in $CPE(i,j)$, only two are non-zero. And in fact, both numerators are 1, since there is only one PTM on `MYH9` in our cluster: -```{r eval = TRUE, echo = TRUE} -c_i[grepl("MYH9", c_i)] -``` -And also only one PTM on`PTK2`: -```{r eval = TRUE, echo = TRUE} -c_i[grepl("PTK2", c_i)] -``` -The denominators of the summands in $CPE(i,j)$ are the product of the number of pathways that the protein is in and the number of PTMs in the cluster. From -above, there are 8 PTMs in this cluster. `MYH9` is in 1 pathway and `PTK2` is in 2 pathways, as seen below: -```{r eval = TRUE, echo = TRUE} -sum(sapply(pathways.list, function(x) "MYH9" %in% x)) -sum(sapply(pathways.list, function(x) "PTK2" %in% x)) -``` -(Note that the `pathways.list` here is a smaller version of the full BioPlanet pathways list.) - -So CPE for the first cluster and the `Axon guidance` pathway is: -```{r eval = TRUE, echo = TRUE} -1/(1*8) + 1/(2*8) -``` - -Overall, two pathways are considered to be related if they both have positive CPE with the same cluster. -In that case, the CPE score for the edge between those two pathways is the sum of the CPE scores for all -clusters that contain - -For example, here are the number of clusters that contain PTMs on one or more proteins in the `Axon guidance` pathway: -```{r eval = TRUE, echo = TRUE} -ag_clusts <- sapply(common.clusters, function(x) any(sapply(pathways.list[["Axon guidance"]], grepl,x=x))) -sum(ag_clusts) -``` -And here are the number of clusters that contain PTMs on one or more proteins in the `ERBB signaling pathway`: -```{r eval = TRUE, echo = TRUE} -erbb_clusts <- sapply(common.clusters, function(x) any(sapply(pathways.list[["ERBB signaling pathway"]], grepl,x=x))) -sum(erbb_clusts) -``` -And overall, here are the number of clustesr that contain PTMs on at least one protein from both the `Axon guidance` pathway and the `ERBB signaling pathway`: -```{r eval = TRUE, echo = TRUE} -sum(ag_clusts & erbb_clusts) -``` -The CPE scores from these clusters to the two pathways would be summed to get the final CPE score for the edge between the two pathways. - -For more detail on the CPE calculation, see ["Ross et al., -2023"](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1010690). - -# Saving Data - -If you want to save your data to a file, all data structures can either -be exported with the save function and loaded later or saved to a csv -file with the write.csv function. - -To save one object: - -```{r eval = FALSE, echo = TRUE} -save(object, filename = "filepath/name.rda") # Saves object as an .rda -load("filepath/name.rda") # Loads object saved to a file -``` - -For multiple objects: Note the objects are saved as an .RData rather -than an .rda - -```{r eval = FALSE, echo = TRUE} -save(object1, object2, object.ect, filename="NewFile.RData") -``` - -To save one object as a csv: - -```{r eval = FALSE, echo = TRUE} -utils::write.csv(object, file = "filepath/name.csv") # Saves object as a .csv -utils::read.csv(file = "filepath/name.csv") # Loads object from .csv -``` - -You may also save your entire Global Environment namespace using the -save.image function as shown below: - -```{r eval = FALSE, echo = TRUE} -save.image(file = "filepath/name.RData") -# All objects in the environment are saved -``` diff --git a/vignettes/CytoscapeGraphing.Rmd b/vignettes/CytoscapeGraphing.Rmd deleted file mode 100644 index 75a379fc..00000000 --- a/vignettes/CytoscapeGraphing.Rmd +++ /dev/null @@ -1,365 +0,0 @@ ---- -title: "Cytoscape Graphing" -author: "Mark Grimes" -date: "2025-09-25" -output: - html_document: - toc: true - toc_float: true - toc_depth: 4 - theme: cosmo - highlight: tango - pdf_document: - toc: true - toc_depth: '4' - word_document: - toc: true - toc_depth: '4' -vignette: > - %\VignetteIndexEntry{Cytoscape Graphing} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} -editor_options: - markdown: - wrap: 72 ---- - -```{r setup, echo = FALSE} - -knitr::opts_chunk$set( - collapse = TRUE, - comment = ">>", - echo = TRUE, - fig.align = "center", - fig.path = "plots/", - fig.width = 8, - fig.height = 6, - out.width = "100%", - results = "hold" -) - -``` - -### Top-down approach starting with pathways - -It is possible to graph the entire PCN, CFN, and CCCNs in their entirety, though -very large graphs take a long time to graph. One approach to navigating these -data structures is to select nodes from the large networks in Cytoscape (or in R -using `RCy3::selectNodes`) and select nearest neighbors or shortest paths and -create a subnetwork in a new window (see the [Cytoscape Manual](https://manual.cytoscape.org/en/stable/)). - -The alternative approach described below is to identify pathways, genes, and -PTMs of interest in the R data objects, then make smaller, more interpretable -graphs in Cytoscape using RCy3. - -For example, we will find names of all pathways in Bioplanet that contain EGFR. -To get started, load the PTMsToPathways package and read the Bioplanet file using the -built-in P2P function [ReadBioplanetFile()](reference/ReadBioplanetFile.html). - -```{r eval = TRUE, echo = FALSE} -library(PTMsToPathways) -pathways.list <- ReadBioplanetFile(system.file("extdata", "bioplanet_pathway_June2025.csv", package = "PTMsToPathways")) -``` - -The data object `pathways.list` is a list, where the name of the list element is -the name of a Bioplanet pathway and each element is a character vector of the -genes in that pathway. - -```{r eval = TRUE} -egfr_pathways <- names(pathways.list)[sapply(1:length(pathways.list), function(x) - {"EGFR" %in% pathways.list[[x]]})] -``` - -We expect 83 pathways that contain EGFR, so let's check: -```{r eval = TRUE} -head(egfr_pathways) -length(egfr_pathways) -``` -Then we want to find interactions between the pathway -"Transmembrane transport of small molecules" and those pathways. - -```{r eval = TRUE} -egfr_transporter.pcn <- filter.edges.between( - "Transmembrane transport of small molecules", - egfr_pathways, ex_PCNedgelist) -``` - -```{r eval = FALSE} -egfr_transporter_pcn.cy <- filter.edges.between( - "Transmembrane transport of small molecules", - egfr_pathways, pathway.crosstalk.network) - -head(egfr_transporter.pcn) -``` - -These two versions of the PCN show cluster evidence and Jaccard smilarity in -adjacent columns (the first case) or as distinct edges (the second case, which -can be used to plot this network in cytoscape). - -```{r eval = FALSE} -# Graph PCN -pcn.graph.1 <- cytoscape.graph.PCN.pathways( - PCN = egfr_transporter_pcn.cy, - net.name = "EGFR signaling and transmembrane transporters", - Jaccard.edges = TRUE) -``` - -Let's zero in on interactions between proteins in the two pathways "EGF/EGFR signaling pathway" and "Transmembrane transport of small molecules" because they have no genes in common, yet the cluster evidence for their interaction is strong. First we extract a network of interactions between the genes in the two pathways. Then we generate a node file for cytoscape. In the following case we include the data extracted from the ptmtable. This is optional, useful if node size and color is used later to indicate values in data. - -```{r eval = FALSE} -egfr_transporter.cfn <- filter.edges.0(c( - pathways_list[["EGF/EGFR signaling pathway"]], - pathways_list[["Transmembrane transport of small molecules"]]), cfn) - -egfr_transporter.nodes <- make.cytoscape.node.file( - egfr_transporter.cfn, funckey, ptmtable, include.gene.data = TRUE) - -``` - -The function GraphCfn creates a graph using the cluster filtered network in the Cytoscape app. When graphed, Cytoscape provides an interactive interface to view the data. This function requires the edge list file (egfr_transporter.cfn in the example), and node data file (egfr_transporter.nodes). - -#### Generating the graph and setting node size and color - -```{r eval = FALSE} -GraphCfn(cfn.edges = egfr_transporter.cfn, cfn.nodes = egfr_transporter.nodes, - Network.title = "CFN", Network.collection = "PTMsToPathways") - -# Choose a ratio data column to show which proteins' PTMs were inhibited by a drug -setNodeColorToRatios(plotcol="PC9_ErlotinibRatio") - -# There are a lot of edges! To simplify the graph, use the mergeEdges() function. -# This can be done to the entire cfn: -cfn.merged <- mergeEdges(cfn) - -# Or just to the cfn made above: -egfr_transporter.cfn.merged <- mergeEdges(egfr_transporter.cfn) - -# Graph to compare: -GraphCfn(cfn.edges = egfr_transporter.cfn.merged, - cfn.nodes = egfr_transporter.nodes, Network.title = "CFN", - Network.collection = "PTMsToPathways") - -# Choose a ratio data column to show which proteins' PTMs were inhibited by a drug -setNodeColorToRatios(plotcol="PC9_ErlotinibRatio") - -# Note that within Cytoscape you can change the column for node size and color -# (two separate tings) in the "Styles" tab - -head(egfr_transporter.cfn.merged) -``` - -#### Asking questions about signaling pathways that connect proteins - -Another example of how to use the network is to ask, what are the paths between two nodes (two proteins)?. We use the function connectNodes.all() to identify all shortest paths between two nodes. - -Having identified the pathways, let's also zoom in further on PTMs to examine which PTMs co-cluster, as indicated by yellow edges between them. - -```{r eval = FALSE} -sp1 <- connectNodes.all(c("FYN", 'MET'), ig.graph=NULL, - edgefile = cfn.merged, newgraph = TRUE) #. *** - -# To include co-clustered PTMs in the network an extra step is necessary: -sp1_plus <- get.co.clustered.ptms(sp1) -sp1_plus.nodes <- make.cytoscape.node.file(sp1_plus, function_key, ptmtable, - include.gene.data = TRUE, - include.coclustered.PTMs = TRUE) - -# Now, graph in cytoscape -GraphCfn(cfn.edges = sp1_plus, cfn.nodes = sp1_plus.nodes, - Network.title = "CFN/CCCN", Network.collection = "PTMsToPathways") - -# Choose a ratio data column to show which proteins' PTMs were inhibited by a drug -setNodeColorToRatios(plotcol = "H3122CrizotinibRatio") - -head(sp1_plus) -``` - -#### Bottom-up approach to investigate how dasatinib affects proteins invovled in focal adhesion - -Dasatinib exhibits strong binding and inhibitory effects on multiple focal adhesion-associated genes from the BioPlanet list: - -• SRC (proto-oncogene tyrosine-protein kinase Src) - -• FYN (tyrosine-protein kinase Fyn) - -• EGFR (epidermal growth factor receptor) - -• ERBB2 (receptor tyrosine-protein kinase erbB-2) - -All these genes are directly implicated in focal adhesion signaling regulation. We hypothesize that ptms on proteins involved in focal adhesion will be downregulated by dasatinib. - -```{r eval = FALSE} -pt.sub <- ptmtable[, grep("DasatinibRatio", names (ptmtable))] -pt.sub$Sum.Dasat <- rowSums(pt.sub, na.rm = TRUE) -pt.sub <- pt.sub[order(pt.sub$Sum.Dasat, decreasing = FALSE), ] - -pt.sub$Gene.Name <- sapply(rownames(pt.sub), function(x){ - unlist(strsplit(x, " ", fixed=TRUE))[1]}) - -fa.genes <- pathways.list[["Focal adhesion"]] -pt.sub.fa <- pt.sub[pt.sub$Gene.Name %in% fa.genes,] -pt.sub.fa.topz <- pt.sub.fa[pt.sub.fa$Sum.Dasat < -2,] -ptms = rownames(pt.sub.fa.topz) - -# Employ a helper function to derive a CFN starting with a list of PTMs -cfn.cccn <- ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") -cfn_cccn.nodes <- make.cytoscape.node.file(cfn.cccn, funckey, ptmtable, - include.gene.data = TRUE, - include.coclustered.PTMs = TRUE) - -# Let's see what it looks like. -GraphCfn(cfn.edges = cfn.cccn, cfn.nodes = cfn_cccn.nodes, - Network.title = "CFN/CCCN", Network.collection = "PTMsToPathways") - -# Choose a ratio data column to show which proteins' PTMs were inhibited by a dasatinib -setNodeColorToRatios(plotcol="H366_DasatinibRatio") -setNodeColorToRatios(plotcol="H2286_DasatinibRatio") - -# Note that within Cytoscape you can change the column for node size and color -# (two separate things) in the "Styles" tab -``` - -#### Node and Edge Key - -We adopt the following standards for visualizing nodes and edges in Cytoscape. The border and shape of the node represent the type of protein this gene is, based on the function key (funckey). - -Edges represent different types of interactions from PPI databases, correlations, or links between proteins and their PTMs. The thicker the edge is, the stronger the interaction weight. - -Node Size: - -- Greater the node size, larger the absolute value of the amount or ratio - -Node Color: - -- Blue Node - - Negative amount or ratio\ -- Yellow Node - - Positive amount or ratio\ -- Green Node - - Approximately zero amount or ratio - -Node Shapes: - -- "ELLIPSE" - - unknown\ -- "ROUND_RECTANGLE" - - receptor tyrosine kinase\ -- "VEE" - - SH2 protein\ - - SH2-SH3 protein\ -- "TRIANGLE" - - SH3 protein\ -- "HEXAGON" - - tyrosine kinase\ -- "DIAMOND" - - SRC-family kinase\ -- "OCTAGON" - - kinase\ - - phosphatase\ -- "PARALLELOGRAM" - - transcription factor\ -- "RECTANGLE" - - RNA binding protein - -Node Border Colors: - -- Orange - - deacetylase\ - - acetyltransferase\ -- Blue - - demethylase\ - - methyltransferase\ -- Royal Purple - - membrane protein\ -- Red - - kinase\ - - tyrosine kinase\ - - SRC-family kinase\ -- Yellow - - phosphatase\ - - tyrosine phosphatase\ -- Lilac - - G protein-coupled receptor\ - - receptor tyrosine kinase\ -- Grey - - default - -Edge Colors: - -- Red - - Phosphorylation\ - - pp\ - - controls-phosphorylation-of\ -- Bright Magenta - - controls-expression-of\ -- Dull Magenta - - controls-transport-of\ -- Purple - - controls-state-change-of\ -- Blood Orange - - Acetylation\ -- Lime Green - - Physical interactions\ -- Green - - BioPlex\ -- Dull Green - - in-complex-with\ -- Seafoam Green - - experiments\ - - experiments_transferred\ -- Cyan - - database\ - - database_transferred\ -- Teal - - Pathway\ - - Predicted\ -- Dark Turquoise - - Genetic interactions\ -- Yellow-Orange - - correlation\ -- Royal Blue - - negative correlation\ -- Bright Yellow - - positive correlation\ -- Grey - - combined_amount or ratio\ -- Dark Grey - - merged\ -- Light Grey - - intersect\ -- Black - - peptide\ -- Orange - - homology\ -- Dull Orange - - Shared protein domains\ -- White - - Default - -Arrow Types: - -- Arrow - - Phosphorylation\ - - pp\ - - controls-phosphorylation-of\ - - controls-expression-of\ - - controls-transport-of\ - - controls-state-change-of\ - - Acetylation\ - -- No Arrow - - Default - - These properties can be visualized in Cytoscape using the NodeEdgeKey() function: - -```{r eval = FALSE} -NodeEdgeKey() - -setEdgeLineWidthMapping('Weight') - -# The edges have different weights, large weights can be too thick and -# smaller weights can result in very thin lines. -# Edge widths can be adjusted using the following function. - -setEdgeWidths(ffactor = 6, log= FALSE) -``` diff --git a/vignettes/RawDataProcessing.Rmd b/vignettes/RawDataProcessing.Rmd deleted file mode 100644 index 3f7c3336..00000000 --- a/vignettes/RawDataProcessing.Rmd +++ /dev/null @@ -1,421 +0,0 @@ ---- -title: "Processing Raw Data for PTMsToPathways" -author: "Lucia Williams, Nagashree Avabhrath, Mikhail Ukrainetz, Madison Moffett, Grant Smith, Mark Grimes" -output: - html_document: - toc: true - toc_float: true - toc_depth: 4 - theme: cosmo - highlight: tango - pdf_document: - toc: true - toc_depth: '4' - word_document: - toc: true - toc_depth: '4' -vignette: > - %\VignetteIndexEntry{Raw Data Processing} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} -editor_options: - markdown: - wrap: 72 ---- - -```{r setup, echo = FALSE} - -knitr::opts_chunk$set( - collapse = TRUE, - comment = ">>", - fig.align = "center", - fig.path = "plots/", - fig.width = 8, - fig.height = 6, - out.width = "100%", - results = "hold" -) -``` - -## Purpose - -This vignette intends to help -users produce a matrix with PTM names as row names (e.g. `FYN p Y411`) and -numeric data in the columns. -The numeric values are the mass -spectrometer output, and `NA`s represent missing data rather than zeroes. -Ambiguous PTMs, where a PTM could match several proteins, must be separated by -semicolons (for example, `"AARS ubi k747; AMBLIL p U123"`). - -Mass spectrometry data output will vary depending on the experimental -design, source of data, and software used to process the raw spectra. R -supports many file types and can automatically convert them into a data -frame. For example, `read.csv()` will take a csv file and convert it into -a data frame (`read.csv()` is a variation of `read.table()`). We start with -data in tab-delimited spreadsheet format. - -## Naming conventions - -In this vignette, we use the following shorthand conventions when describing -PTMs based on the modifications present in the example data set. Your -data will dictate the names of modifications. - -* *Gene.Name* = The HUGO Gene Name is used to identify the protein/gene -* *Phosphorylation* = "p" -* *Lysine acetylation* = "ack" -* *Lysine methylation* = "kme" -* *Arginine methylation* = "rme" -* *Ubiquitination* = "ubi" - -## Preprocessing data - -First, let's load the P2P package, since it contains some helpful pre-processing functions. -```{r eval = TRUE} -library(PTMsToPathways) -``` - -The example raw data file for this -vignette (downloadable [here](https://github.com/UM-Applied-Algorithms-Lab/PTMsToPathways/raw/refs/heads/main/inst/extdata/phospho_cleaned_mapped.txt), -or load directly into R using the commands below) -contains only phosphorylation sites. - -To prepare data for input to PTMsTo pathways, we first read in the data file. -To use your own data file, -replace `file_path` variable with your own path to file, as in the commented line below. - -```{r} -# file_path <- "path/to/your/file.txt" -file_path <- system.file("extdata", "phospho_cleaned_mapped.txt", - package = "PTMsToPathways") - -newphos <- utils::read.table(file_path, sep = "\t", skip = 0, header = TRUE, - blank.lines.skip = T, fill = T, quote = "\"", dec = ".", - comment.char = "", stringsAsFactors = F) -dim(newphos) -``` - -As we can see, this table has 933 rows and 170 columns. - -First remove internal control rows (reverse sequences), which should yield 908 remaining rows. - -```{r} -newphos <- newphos[!is.na(newphos$AllGeneSymbols),] -dim(newphos) -``` - -Many investigators inspect data in Microsoft Excel, which can export -tab- or comma-delimited files. Unfortunately, Excel can silently convert -some gene names into dates when they appear in a cell by themselves. We -reverse that with the P2P helper function [`fix.excel()`](reference/fix.excel.html). -If there are dates in the `AllGeneSymbols` column, we can apply the `fix.excel()` function -to each of them to convert them back to gene names. -```{r} -newphos$AllGeneSymbols <- sapply(newphos$AllGeneSymbols, fix.excel) -``` -Investigators will need to identify which columns contain key -information for analysis of PTMs. - -In this example, the key columns are: - -- `Amino.Acid`: the modified amino acid, such as S or T -- `Positions.Within.Proteins`: the amino acid number in the protein sequence -- `Modification.Type`: the PTM class, such as phosphorylation -- `AllGeneSymbols`: the HUGO gene name(s) of the protein(s) containing the PTM, separated by ";" if more than one - -```{r eval = TRUE} -newphos.header <- newphos[,c("AllGeneSymbols", "Amino.Acid", - "Positions.Within.Proteins", "Modification.Type")] - -head(newphos.header) -``` -We provide another helper function, [`name.peptide()`](reference/name.peptide.html), -to handle ambiguous modification sites (a modification site -whose peptide sequence is the same in more than one protein) separated by ";" or -another separator. -```{r} -newphos.header$Peptide.Name <- mapply( - name.peptide, genes = newphos$AllGeneSymbols, - sites = newphos$Positions.Within.Proteins, aa = newphos$Amino.Acid) -head(newphos.header) -``` - -### Data columns - -Next we identify the data columns, which contain the string `Intensity`. -The example data file is from a multi-PTM study and the data in this -table are from just the phosphorylation pulldown (other tables are for -other PTM types). The optimal pulldown columns are straightforward to -identify by the pulldown strings present in the sample names, `pTyr` in -this case. They are also identifiable by zooming out and looking at the -patterns of missing data, the optimal pulldowns, as a group, have the -least missing data. In this data, the following abbreviations are used: -**C** = Crizotinib, -**D** = DMSO, -**E** = Erlotinib, -**Pr** = PR171. -So for example the first four columns can be interpreted as: -```{r eval = TRUE} -names(newphos)[grep("Intensity", names(newphos))][1] -``` -*C1.1*: Crizotinib biological replicate 1- technical replicate 1 -```{r eval = TRUE} -names(newphos)[grep("Intensity", names(newphos))][2] -``` -*C1.2*: Crizotinib biological replicate 1- technical replicate 2 -```{r eval = TRUE} -names(newphos)[grep("Intensity", names(newphos))][3] -``` -*C2.1*: Crizotinib biological replicate 2- technical replicate 1 -```{r eval = TRUE} -names(newphos)[grep("Intensity", names(newphos))][4] -``` -*C2.2*: Crizotinib biological replicate 2- technical replicate 2 - -So, we first select columns that contain the string `Intensity`. There should be 108 of these: -```{r eval = TRUE} -data.cols <- newphos[,grep("Intensity", names(newphos))] -dim(data.cols) -``` -And then select colums that have `pTyr`. There should be 36 of these: -```{r eval = TRUE} -data.cols <- newphos[,grep("pTyr", names(newphos))] -dim(data.cols) -``` - -Now simplify column names (remove `Intensity`): -```{r eval = TRUE} -names(data.cols) <- sapply(names(data.cols), function(x){ - unlist(strsplit(x, "Intensity."))[2] - }) -head(names(data.cols)) -``` - -Make zero into `NA`, which it is. (Note that this may not apply if you are -confident that zero means actual zero, which is possible with certain technical -advances like DIA.) We should have 16265 `NA` values. -```{r eval = TRUE} -zer0 <- which(data.cols==0, arr.ind = TRUE) -data.cols <- replace (data.cols, zer0, NA) -sum(is.na(data.cols)) -``` - -### Merge techincal replicates - -These data have technical replicates, which means that the same samples -were run twice. Due to the stochastic selection of peptides for -detection, the pattern of missing values is slightly different between -technical replicates. We therefore merge the technical replicates taking -the value of either replicate where it's missing in the other, and -averaging values detected in both, using the P2P function [merge2cols()](reference/merge2cols.html). - -Define technical replicates: -```{r eval = TRUE} -tr1.opt <- names(data.cols)[grep(".1", names(data.cols), fixed=TRUE)] -tr2.opt <- names(data.cols)[grep(".2", names(data.cols), fixed=TRUE)] -tr1.opt[1] -tr2.opt[1] -``` - -Use `merge2cols()` to average technical replicates. This function ignores NA -values in either column and takes the average in the case where there are two -values. In this case, the resulting data frame should have 18 total columns, one for each biological replicate. -```{r eval = TRUE} -phosdata.merged <- data.frame(matrix(nrow = nrow(data.cols), ncol = length(tr1.opt))) -for (i in seq_along(tr1.opt)) { -phosdata.merged[, i] <- merge2cols( -data.cols[, tr1.opt[i]], -data.cols[, tr2.opt[i]] -)} -dim(phosdata.merged) - -``` -And fix up the names of the merged columns to remove the technical replicate numbers: -```{r eval = TRUE} -names(phosdata.merged) <- sapply(tr1.opt, function(x){ - substr(x, start=1, stop=nchar(x)-2) - }) -names(phosdata.merged)[1] -``` -Merge the header and the data together to make a complete data set: -```{r eval = TRUE} -phosdatafile <- cbind(newphos.header, phosdata.merged) -dim(phosdatafile) -``` -This file could be saved for reference using `write.table()`: -```{r eval = FALSE} -write.table(phosdatafile, file = "phosdatafile.txt", - row.names = FALSE, sep = "\t") -``` - -For input into P2P, we want the row names to be the PTM names and the columns -just to be the merged technical replicate columns, so we just use that data and set the row names to -the `Peptide.Name` column: -```{r eval = TRUE} -phosdata.df <- phosdata.merged -rownames(phosdata.df) <- phosdatafile$Peptide.Name -``` -Notice that this data is the same as the `ex_small_ptm_table` that is provided with the package (though the column names are slightly -different): -```{r eval = TRUE} -all.equal(setNames(phosdata.df, NULL), setNames(ex_small_ptm_table, NULL)) -``` - -Log base 2 transformation improves clustering. -```{r eval = TRUE} -log2phosdata <- log2(phosdata.merged) -``` - -The [Creating Networks vignette](vignettes/CreatingNetworks.html) show how to use the functions provided in PTMsToPathways to analyze -data stored in a variable called `ptmtable`. -```{r eval = FALSE} -ptmtable <- log2phosdata -``` - -## Optional data processing steps - -For experiments where treatment with drugs is compared to control -samples, adding treatment/control ratios as additional data column can -improve clustering. This optional step adds dimensions to the data set -that enhance focus on the changes in response to drug treatments. - - Simplify column names first: -```{r Example ratio calculations} -names(phosdata.df) <- sapply(names(phosdata.df), function(x){ - paste(unlist(strsplit(x, "SEPTM_pTyr"))[1], - unlist(strsplit(x, "SEPTM_pTyr"))[2], sep = "") - }) -``` - -Explore using ratios where control=rowMeans (D1, D2, D3): -```{r} -H3122control <- rowMeans(phosdata.df[, names(phosdata.df) - [grep("H3122.D", names(phosdata.df))]], - na.rm=TRUE) -``` - -Change NaN to NA -```{r} -H3122control[is.nan(H3122control)] <- NA - -PC9control <- rowMeans(phosdata.df[, names(phosdata.df) - [grep("PC9.D", names(phosdata.df))]], - na.rm=TRUE) - -PC9control[is.nan(PC9control)] <- NA -``` - -Calculate treatment/control ratios -```{r} -# H3122 cells -H3122.C1.ratio <- phosdata.df$H3122.C1/H3122control -H3122.C2.ratio <- phosdata.df$H3122.C2/H3122control -H3122.C3.ratio <- phosdata.df$H3122.C3/H3122control -H3122.PR1.ratio <- phosdata.df$H3122.PR1/H3122control -H3122.PR2.ratio <- phosdata.df$H3122.PR2/H3122control -H3122.PR3.ratio <- phosdata.df$H3122.PR3/H3122control - -# PC9 cells -PC9.E1.ratio <- phosdata.df$PC9.E1/PC9control -PC9.E2.ratio <- phosdata.df$PC9.E2/PC9control -PC9.E3.ratio <- phosdata.df$PC9.E3/PC9control -PC9.PR1.ratio <- phosdata.df$PC9.PR1/PC9control -PC9.PR2.ratio <- phosdata.df$PC9.PR2/PC9control -PC9.PR3.ratio <- phosdata.df$PC9.PR3/PC9control -``` - -Put these columns in a data frame: -```{r} -phos_ratios <- data.frame(H3122.C1.ratio, H3122.C2.ratio, H3122.C3.ratio, - H3122.PR1.ratio, H3122.PR2.ratio, H3122.PR3.ratio, - PC9.E1.ratio, PC9.E2.ratio, PC9.E3.ratio, - PC9.PR1.ratio, PC9.PR2.ratio, PC9.PR3.ratio) -``` -Check (should be `TRUE`): -```{r} -identical(rownames(phosdata.df), rownames(phos_ratios)) -``` - -Make limits to unweight extreme values. This has been shown to improve clustering, and a ratio of 1000 is biologically not really functional different than a ratio of 100. -```{r} -hi.ratio <- which(phos_ratios >= 100, arr.ind = TRUE) -low.ratio <- which(phos_ratios <= 1/100, arr.ind = TRUE) -phos_ratios.lim <- replace (phos_ratios, hi.ratio, 100) -phos_ratios.lim <- replace (phos_ratios.lim, low.ratio, 1/100) -``` -log2 transformation improves clustering: -```{r} -phos_ratios.lim.log2 <- log2(phos_ratios.lim) -phosdata_plus_ratios <- cbind(log2phosdata, phos_ratios.lim.log2) -``` -And plot to check: - -```{r} -boxplot(phosdata_plus_ratios) -``` -And do one more check: -```{r} -identical(rownames(phos_ratios.lim.log2), rownames(log2phosdata)) -``` - -This can be used as the example ptmtable for subsequent testing. -```{r} -# ptmtable <- phosdata_plus_ratios -``` - -## Combining data from multiple PTM experiments - -For experiments involving multiple PTMs, or if investigators wish to -combine several data sets, the data can be combined. For example, -pulldowns were made to isolate acetylated and ubiquitinated peptides -from the same experimental samples. Combining the data is simply -repeating the above steps using the correct optimum data columns, then -making the column names the same, and binding all the rows together. - -Suppose you had acetylation data in `ackdata.df` and ubiquitination data in -`ubidata.df`, both formatted as above for phosphorylation data in -`phosdata.df`. You could combine them as follows. - -First, make sure the column names are the same: - -```{r eval = FALSE} -kgp <- phosdata.df -kga <- ackdata.df -kgu <- ubidata.df - -names(kgp) <- sapply(names(kgp), function(x){ - paste(unlist(strsplit(x, "_pTyr"))[1], unlist(strsplit(x, "_pTyr"))[2], - sep = "") - }) - -names(kga) <- sapply(names(kga), function(x){ - paste(unlist(strsplit(x, "_AcK"))[1], unlist(strsplit(x, "_AcK"))[2], - sep = "") - }) - -names(kgu) <- sapply(names(kgu), function(x){ - paste(unlist(strsplit(x, "_Ubi"))[1], unlist(strsplit(x, "_Ubi"))[2], - sep = "") - }) - -identical(names(kgp), names(kga)) # Check TRUE -``` - -Then `rbind` them: -```{r eval = FALSE} -ptmdata <- rbind (kgp, kga, kgu) -``` - -Reorder: -```{r eval = FALSE} -ptmdata <- ptmdata[order(rownames(ptmdata)),] -``` - -This optional step improves clustering in our hands: -```{r eval = FALSE} -log2ptmdata <- log2(ptmdata) -``` - -Finally, this dataframe could be used as the example `ptmtable` for the P2P functions. -```{r eval = FALSE} -# ptmtable <- log2ptmdata -``` From ce2e3b3309b72ef2a0cacef5f9c9b25c4b5357ea Mon Sep 17 00:00:00 2001 From: Gram-S Date: Wed, 17 Jun 2026 18:51:14 -0600 Subject: [PATCH 2/5] remove brca helpers (temporarily) --- R/BRCAHelpers.R | 341 ------------------------------------------------ 1 file changed, 341 deletions(-) delete mode 100644 R/BRCAHelpers.R diff --git a/R/BRCAHelpers.R b/R/BRCAHelpers.R deleted file mode 100644 index d9fc7023..00000000 --- a/R/BRCAHelpers.R +++ /dev/null @@ -1,341 +0,0 @@ -# ============================================================ -# graph.ptm.by.cluster() -# Large phosphopeptide heatmap, grouped by consensus cluster -# - Blue (down) -> light grey (neutral/zero) -> Yellow (up) -# - NA values drawn in black -# - Color key and cluster legend saved as separate PDFs -# - Cluster side-bar colors are high-contrast, non-rainbow -# ============================================================ - -#' Heatmap of Phosphopeptides Grouped by Cluster -#' -#' Creates a large phosphopeptide heatmap colored by signal intensity, grouped -#' by consensus cluster with high-contrast cluster color bars. Supports custom -#' row ordering and optional legend/heatkey output files. -#' -#' @param ptmtable A numeric matrix with PTMs as rows and samples as columns. -#' @param common.clusters A named list where each element is a character vector -#' of PTM identifiers belonging to that cluster. -#' @param filename Output filename for the main heatmap PDF. Defaults to -#' `"ptm_by_cluster_heatmap.pdf"`. -#' @param legend.filename Optional filename for a separate PDF cluster legend. -#' If `NULL`, no legend file is written. -#' @param heatkey.filename Optional filename for a separate PDF color scale key. -#' If `NULL`, no heatkey file is written. -#' @param cluster.order Character vector specifying the order in which clusters -#' are displayed. Defaults to `names(common.clusters)`. -#' @param order.rows Method to order rows within clusters: `"as.is"` (no reordering), -#' `"mean"` (by row-wise mean), `"slope"` (by linear trend), or `"hclust"` -#' (hierarchical clustering). Defaults to `"as.is"`. -#' @param cluster.cols Logical; if `TRUE`, columns are also hierarchically -#' clustered. Defaults to `FALSE`. -#' @param zlim Numeric; the upper/lower limit for the heatmap color scale -#' (values are clipped to `[-zlim, zlim]`). Defaults to `3`. -#' @param min.shared Minimum number of shared finite values required to compute -#' distance between two samples. Defaults to `2`. -#' @param show.row.labels Logical; show row labels (PTM identifiers). -#' Defaults to `FALSE`. -#' @param show.col.labels Logical; show column labels (sample names). -#' Defaults to `TRUE`. -#' @param row_cex Character expansion factor for row labels. Defaults to `0.2`. -#' @param col_cex Character expansion factor for column labels. Defaults to `0.8`. -#' @param main Plot title. Defaults to `"Phosphopeptides grouped by cluster"`. -#' -#' @return Invisibly returns a list containing: -#' - `mat`: The clustered expression matrix. -#' - `blocks`: List of ordered sub-matrices, one per cluster. -#' - `rowsep`: Row indices separating clusters. -#' - `cluster.palette`: Named vector of cluster colors. -#' -#' @export -#' -#' @examples -#' \dontrun{ -#' res <- graph.ptm.by.cluster( -#' ptmtable = ptmtable, -#' common.clusters = common.clusters, -#' filename = "ptm_heatmap.pdf", -#' order.rows = "hclust" -#' ) -#' } -#' -#' @importFrom gplots heatmap.2 -#' @importFrom grDevices hcl colorRampPalette -#' -graph.ptm.by.cluster <- function( - ptmtable, - common.clusters, - filename = "ptm_by_cluster_heatmap.pdf", - legend.filename = NULL, # separate cluster-color legend PDF - heatkey.filename = NULL, # separate blue-grey-yellow scale PDF - cluster.order = names(common.clusters), - order.rows = c("as.is", "mean", "slope", "hclust"), - cluster.cols = FALSE, - zlim = 3, - min.shared = 2, - show.row.labels = FALSE, - show.col.labels = TRUE, - row_cex = 0.2, - col_cex = 0.8, - main = "Phosphopeptides grouped by cluster" -) { - - # ---- helpers ------------------------------------------------------- - - order.rows <- match.arg(order.rows) - - ## NA-aware Euclidean distance (pairwise column overlap) - safe_dist2 <- function(m, min.shared = 2) { - m <- as.matrix(m) - nr <- nrow(m) - if (nr <= 1) return(as.dist(matrix(0, nr, nr))) - dmat <- matrix(0, nr, nr) - rownames(dmat) <- colnames(dmat) <- rownames(m) - for (i in seq_len(nr - 1)) { - for (j in (i + 1):nr) { - ok <- is.finite(m[i, ]) & is.finite(m[j, ]) - d <- if (sum(ok) >= min.shared) sqrt(sum((m[i,ok] - m[j,ok])^2)) else NA_real_ - dmat[i,j] <- dmat[j,i] <- d - } - } - fv <- dmat[upper.tri(dmat) & is.finite(dmat)] - fallback <- if (length(fv)) 2 * max(fv) else 1 - dmat[!is.finite(dmat)] <- fallback - diag(dmat) <- 0 - as.dist(dmat) - } - - ## row-wise linear slope helper for order.rows = "slope" - row_slope <- function(v) { - ok <- which(is.finite(v)) - if (length(ok) < 2) return(NA_real_) - coef(lm(v[ok] ~ ok))[2] - } - - ## high-contrast qualitative palette for cluster sidebar - ## Strategy: fixed set of maximally-distinct hues, then when n > set size - ## fall back to HCL with large hue steps so adjacent bars never look similar. - make_cluster_colors <- function(n) { - # 22 hand-picked colors spanning very different hues AND luminances - base.cols <- c( - "#E41A1C", # red - "#377EB8", # blue - "#4DAF4A", # green - "#FF7F00", # orange - "#984EA3", # purple - "#A65628", # brown - "#00CED1", # dark turquoise - "#F781BF", # pink - "#1B9E77", # teal - "#FFD92F", # yellow - "#7570B3", # lavender - "#E7298A", # hot pink - "#66A61E", # olive green - "#E6AB02", # gold - "#D95F02", # burnt orange - "#66C2A5", # mint - "#FC8D62", # salmon - "#8DA0CB", # periwinkle - "#A6D854", # lime - "#999999", # grey - "#A6761D", # khaki - "#666666" # dark grey - ) - if (n <= length(base.cols)) { - return(base.cols[seq_len(n)]) - } - # For very large n: generate HCL colors stepping by large hue angle - # (golden-angle spacing ~137.5 deg) to maximise adjacent contrast - hues <- ((seq_len(n) - 1) * 137.508) %% 360 - grDevices::hcl(h = hues, c = 70, l = 55) - } - - ## standalone cluster-color legend PDF - write_cluster_legend <- function(cluster.palette, filename, cex = 0.9) { - n <- length(cluster.palette) - ncol <- if (n > 18) 2L else 1L - pdf(filename, - width = max(5, 3 * ncol), - height = max(4, 0.32 * ceiling(n / ncol) + 1)) - on.exit(dev.off()) - par(mar = c(0.3, 0.3, 0.3, 0.3), xpd = NA) - plot.new() - legend("topleft", - inset = 0.01, - legend = names(cluster.palette), - fill = cluster.palette, - border = NA, - bty = "n", - cex = cex, - ncol = ncol) - } - - ## standalone heat-scale (colour key) PDF - ## image() requires: nrow(z) == length(x)-1, ncol(z) == length(y)-1 - write_heat_key <- function(filename, zlim = 3) { - n <- 256L # number of colour cells - cols <- colorRampPalette(c("#3333FF","#E6E6E6","#FFFF00"), - space = "rgb", interpolate = "linear")(n) - # breakpoints: length n+1 so the z matrix is (1 x n) - brks <- seq(-zlim, zlim, length.out = n + 1L) - z <- matrix(seq_len(n), nrow = 1L, ncol = n) # 1 x n - - pdf(filename, width = 1.8, height = 4.5) - on.exit(dev.off()) - # left margin wide enough for the bar; right margin for axis labels - par(mar = c(2, 1.0, 1.5, 2.8)) - # x: 2 breakpoints = 1 column; y: n+1 breakpoints = n rows - image(x = c(0, 1), y = brks, z = z, - col = cols, xaxt = "n", yaxt = "n", xlab = "", ylab = "") - axis(4, at = pretty(c(-zlim, zlim)), las = 2, cex.axis = 0.85) - mtext("Signal", side = 4, line = 1.8, cex = 0.85) - box() - } - - # ---- data prep ----------------------------------------------------- - - ptmtable <- as.matrix(ptmtable) - rownames(ptmtable) <- trimws(rownames(ptmtable)) - names(common.clusters) <- trimws(names(common.clusters)) - common.clusters <- lapply(common.clusters, trimws) - - # drop rows that are entirely non-finite - ptmtable <- ptmtable[rowSums(is.finite(ptmtable)) > 0, , drop = FALSE] - - cluster.order <- intersect(cluster.order, names(common.clusters)) - - ordered.blocks <- list() - cluster.id <- character(0) - - for (cl in cluster.order) { - pep <- unique(common.clusters[[cl]]) - pep <- pep[pep %in% rownames(ptmtable)] - if (!length(pep)) next - subm <- ptmtable[pep, , drop = FALSE] - - if (nrow(subm) > 1) { - subm <- switch(order.rows, - mean = subm[order(rowMeans(subm, na.rm=TRUE), decreasing=TRUE, na.last=TRUE), , drop=FALSE], - slope = { sl <- apply(subm, 1, row_slope) - subm[order(sl, decreasing=TRUE, na.last=TRUE), , drop=FALSE] }, - hclust = subm[hclust(safe_dist2(subm, min.shared))$order, , drop=FALSE], - subm # "as.is" - ) - } - - ordered.blocks[[cl]] <- subm - cluster.id <- c(cluster.id, rep(cl, nrow(subm))) - } - - if (!length(ordered.blocks)) stop("No peptides from common.clusters matched rownames(ptmtable).") - - mat <- do.call(rbind, ordered.blocks) - - if (cluster.cols && ncol(mat) > 1) { - mat <- mat[, hclust(safe_dist2(t(mat), min.shared))$order, drop=FALSE] - } - - cluster.sizes <- sapply(ordered.blocks, nrow) - rowsep <- cumsum(cluster.sizes)[-length(cluster.sizes)] # no sep after last block - - # ---- colour setup -------------------------------------------------- - - heatcols <- colorRampPalette(c("#3333FF","#E6E6E6","#FFFF00"), - space="rgb", interpolate="linear")(255) - breaks <- seq(-zlim, zlim, length.out = 256) - - cluster.palette <- make_cluster_colors(length(ordered.blocks)) - names(cluster.palette) <- names(ordered.blocks) - row.side.colors <- unname(cluster.palette[cluster.id]) - - # ---- device sizing ------------------------------------------------- - # Width: accommodate column labels; Height: 1 px per row ~ 1/72 inch - # Give at least 10 inches height so the plot region is never microscopic. - - pdf.w <- max(14, ncol(mat) / 5) - pdf.h <- max(10, nrow(mat) / 40) - - # ---- heatmap (key = FALSE → no internal key panel) --------------- - - pdf(filename, width = pdf.w, height = pdf.h) - gplots::heatmap.2( - mat, - Rowv = FALSE, - Colv = FALSE, - dendrogram = "none", - trace = "none", - scale = "none", - col = heatcols, - breaks = breaks, - na.color = "black", - key = FALSE, # << prevents the "margins too large" crash - symbreaks = FALSE, - density.info = "none", - labRow = if (show.row.labels) rownames(mat) else NA, - labCol = if (show.col.labels) colnames(mat) else NA, - RowSideColors = row.side.colors, - # Narrow the RowSideColors sidebar: lwid[1]=sidebar, lwid[2]=body - lwid = c(0.1, 8), - lhei = c(0.5, 4), - rowsep = rowsep, - sepcolor = "white", - sepwidth = c(0.003, 0.003), - cexRow = row_cex, - cexCol = col_cex, - margins = c(10, 6), - main = main - ) - dev.off() - message("Heatmap written to: ", filename) - - # ---- optional legend files ---------------------------------------- - - if (!is.null(legend.filename)) { - write_cluster_legend(cluster.palette, legend.filename) - message("Cluster legend written to: ", legend.filename) - } - - if (!is.null(heatkey.filename)) { - write_heat_key(heatkey.filename, zlim = zlim) - message("Heat key written to: ", heatkey.filename) - } - - invisible(list( - mat = mat, - blocks = ordered.blocks, - rowsep = rowsep, - cluster.palette = cluster.palette - )) -} - - -# ============================================================ -# Example call — adjust paths/names to match your session -# ============================================================ -# -# res <- graph.ptm.by.cluster( -# ptmtable = ptmtable, -# common.clusters = common.clusters, # use all clusters -# filename = "ptm_all_clusters.pdf", -# legend.filename = "ptm_legend.pdf", -# heatkey.filename = "ptm_heatkey.pdf", -# order.rows = "hclust", -# zlim = 3, -# show.row.labels = FALSE, -# show.col.labels = TRUE, -# col_cex = 0.7 -# ) -# -# Inspect colour mapping: -# res$cluster.palette -# -# Subset to first 3 clusters only: -# res3 <- graph.ptm.by.cluster( -# ptmtable = ptmtable, -# common.clusters = common.clusters[1:3], -# filename = "ptm_clusters_1_3.pdf", -# legend.filename = "ptm_clusters_1_3_legend.pdf", -# heatkey.filename= "ptm_clusters_1_3_heatkey.pdf", -# order.rows = "hclust", -# zlim = 3 -# ) From b1687760aaf1e24e9294d8feae7468d78aa567ad Mon Sep 17 00:00:00 2001 From: Gram-S Date: Wed, 17 Jun 2026 18:51:42 -0600 Subject: [PATCH 3/5] devtools::document() --- NAMESPACE | 4 -- man/graph.ptm.by.cluster.Rd | 92 ------------------------------------- 2 files changed, 96 deletions(-) delete mode 100644 man/graph.ptm.by.cluster.Rd diff --git a/NAMESPACE b/NAMESPACE index 9c3696b8..693d1ba8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,7 +9,3 @@ export(MakeClusterList) export(MakeCorrelationNetwork) export(MakeDBInput) export(StandardizeGeneSymbols) -export(graph.ptm.by.cluster) -importFrom(gplots,heatmap.2) -importFrom(grDevices,colorRampPalette) -importFrom(grDevices,hcl) diff --git a/man/graph.ptm.by.cluster.Rd b/man/graph.ptm.by.cluster.Rd deleted file mode 100644 index 25938da7..00000000 --- a/man/graph.ptm.by.cluster.Rd +++ /dev/null @@ -1,92 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/BRCAHelpers.R -\name{graph.ptm.by.cluster} -\alias{graph.ptm.by.cluster} -\title{Heatmap of Phosphopeptides Grouped by Cluster} -\usage{ -graph.ptm.by.cluster( - ptmtable, - common.clusters, - filename = "ptm_by_cluster_heatmap.pdf", - legend.filename = NULL, - heatkey.filename = NULL, - cluster.order = names(common.clusters), - order.rows = c("as.is", "mean", "slope", "hclust"), - cluster.cols = FALSE, - zlim = 3, - min.shared = 2, - show.row.labels = FALSE, - show.col.labels = TRUE, - row_cex = 0.2, - col_cex = 0.8, - main = "Phosphopeptides grouped by cluster" -) -} -\arguments{ -\item{ptmtable}{A numeric matrix with PTMs as rows and samples as columns.} - -\item{common.clusters}{A named list where each element is a character vector -of PTM identifiers belonging to that cluster.} - -\item{filename}{Output filename for the main heatmap PDF. Defaults to -\code{"ptm_by_cluster_heatmap.pdf"}.} - -\item{legend.filename}{Optional filename for a separate PDF cluster legend. -If \code{NULL}, no legend file is written.} - -\item{heatkey.filename}{Optional filename for a separate PDF color scale key. -If \code{NULL}, no heatkey file is written.} - -\item{cluster.order}{Character vector specifying the order in which clusters -are displayed. Defaults to \code{names(common.clusters)}.} - -\item{order.rows}{Method to order rows within clusters: \code{"as.is"} (no reordering), -\code{"mean"} (by row-wise mean), \code{"slope"} (by linear trend), or \code{"hclust"} -(hierarchical clustering). Defaults to \code{"as.is"}.} - -\item{cluster.cols}{Logical; if \code{TRUE}, columns are also hierarchically -clustered. Defaults to \code{FALSE}.} - -\item{zlim}{Numeric; the upper/lower limit for the heatmap color scale -(values are clipped to \verb{[-zlim, zlim]}). Defaults to \code{3}.} - -\item{min.shared}{Minimum number of shared finite values required to compute -distance between two samples. Defaults to \code{2}.} - -\item{show.row.labels}{Logical; show row labels (PTM identifiers). -Defaults to \code{FALSE}.} - -\item{show.col.labels}{Logical; show column labels (sample names). -Defaults to \code{TRUE}.} - -\item{row_cex}{Character expansion factor for row labels. Defaults to \code{0.2}.} - -\item{col_cex}{Character expansion factor for column labels. Defaults to \code{0.8}.} - -\item{main}{Plot title. Defaults to \code{"Phosphopeptides grouped by cluster"}.} -} -\value{ -Invisibly returns a list containing: -\itemize{ -\item \code{mat}: The clustered expression matrix. -\item \code{blocks}: List of ordered sub-matrices, one per cluster. -\item \code{rowsep}: Row indices separating clusters. -\item \code{cluster.palette}: Named vector of cluster colors. -} -} -\description{ -Creates a large phosphopeptide heatmap colored by signal intensity, grouped -by consensus cluster with high-contrast cluster color bars. Supports custom -row ordering and optional legend/heatkey output files. -} -\examples{ -\dontrun{ -res <- graph.ptm.by.cluster( - ptmtable = ptmtable, - common.clusters = common.clusters, - filename = "ptm_heatmap.pdf", - order.rows = "hclust" -) -} - -} From 6381c60f14f6c1b9623d490111b157ecc85339a7 Mon Sep 17 00:00:00 2001 From: Gram-S Date: Wed, 17 Jun 2026 19:01:30 -0600 Subject: [PATCH 4/5] run styler::style_dir with biocthis transformer --- R/BuildClusterFilteredNetwork.R | 65 ++- R/EvaluateClusters.R | 404 +++++++++-------- R/GatherPPIData.R | 772 ++++++++++++++++---------------- R/MakeClusterList.R | 328 +++++++------- R/MakeCorrelationNetwork.R | 262 +++++------ R/data_documentation.R | 91 ++-- 6 files changed, 973 insertions(+), 949 deletions(-) diff --git a/R/BuildClusterFilteredNetwork.R b/R/BuildClusterFilteredNetwork.R index b832b449..0b384c7a 100644 --- a/R/BuildClusterFilteredNetwork.R +++ b/R/BuildClusterFilteredNetwork.R @@ -20,43 +20,42 @@ #' @examples #' Example_Output <- BuildClusterFilteredNetwork(ex.gene.cccn, ex.stringdb.edges, ex.gm.edges) #' utils::head(Example_Output) - BuildClusterFilteredNetwork <- function(gene.cccn.edges, stringdb.edges = NULL, genemania.edges = NULL, kinsub.edges = NULL, db.filepaths = c(NULL)) { - - # Combine PPIs from different databases - # First Normalize Weights - if (!is.null(stringdb.edges)) { - stringdb.edges$Weight <- 100 * stringdb.edges$Weight / max(stringdb.edges$Weight, na.rm = TRUE) } # this returns a range of 0 to 100 - if (!is.null(genemania.edges)) { - genemania.edges$Weight <- 100 * genemania.edges$Weight / max(genemania.edges$Weight, na.rm = TRUE) } # this returns a range of 0 to 100 - - # Note: if additional PPI data is desired, duplicate the normalization of Weights above and add the additional edge file(s) here (and in the function header) - # Done see below - - # Combine gathered PPI edges into one data frame - combined.PPIs <- rbind(stringdb.edges, genemania.edges, kinsub.edges) - - if(!is.null(db.filepaths)){ - for(path in db.filepaths){ - db.edges <- utils::read.table(path) - db.edges$Weight <- 100 * db.edges$Weight / max(db.edges$Weight, na.rm = TRUE) # return a range of 0 to 100 - combined.PPIs <- rbind(combined.PPIs, db.edges) # add it to combined.PPIs - + # Combine PPIs from different databases + # First Normalize Weights + if (!is.null(stringdb.edges)) { + stringdb.edges$Weight <- 100 * stringdb.edges$Weight / max(stringdb.edges$Weight, na.rm = TRUE) + } # this returns a range of 0 to 100 + if (!is.null(genemania.edges)) { + genemania.edges$Weight <- 100 * genemania.edges$Weight / max(genemania.edges$Weight, na.rm = TRUE) + } # this returns a range of 0 to 100 + + # Note: if additional PPI data is desired, duplicate the normalization of Weights above and add the additional edge file(s) here (and in the function header) + # Done see below + + # Combine gathered PPI edges into one data frame + combined.PPIs <- rbind(stringdb.edges, genemania.edges, kinsub.edges) + + if (!is.null(db.filepaths)) { + for (path in db.filepaths) { + db.edges <- utils::read.table(path) + db.edges$Weight <- 100 * db.edges$Weight / max(db.edges$Weight, na.rm = TRUE) # return a range of 0 to 100 + combined.PPIs <- rbind(combined.PPIs, db.edges) # add it to combined.PPIs + } } - } - cfn1 <- merge(gene.cccn.edges[,c("source", "target")], combined.PPIs, by=c("source", "target")) + cfn1 <- merge(gene.cccn.edges[, c("source", "target")], combined.PPIs, by = c("source", "target")) - # Undirected edges may be reversed in their order so merge the other way around. - reversed <- combined.PPIs - reversed <- reversed[ , c("target", "source", setdiff(names(gene.cccn.edges), c("source", "target")))] - colnames(reversed)[seq_len(2)] <- c("source", "target") # Rename for merge compatibility - cfn2 <- merge(reversed[,c("source", "target")], combined.PPIs, by=c("source", "target")) + # Undirected edges may be reversed in their order so merge the other way around. + reversed <- combined.PPIs + reversed <- reversed[, c("target", "source", setdiff(names(gene.cccn.edges), c("source", "target")))] + colnames(reversed)[seq_len(2)] <- c("source", "target") # Rename for merge compatibility + cfn2 <- merge(reversed[, c("source", "target")], combined.PPIs, by = c("source", "target")) - # Combine both (removing redundant rows if needed) - cfn <- rbind(cfn1, cfn2) - cfn <- unique(cfn) + # Combine both (removing redundant rows if needed) + cfn <- rbind(cfn1, cfn2) + cfn <- unique(cfn) - return(list(combined.PPIs, cfn)) + return(list(combined.PPIs, cfn)) } -#______________________________________________________________________________________________________________________________ +# ______________________________________________________________________________________________________________________________ diff --git a/R/EvaluateClusters.R b/R/EvaluateClusters.R index f89d9ec9..2697e12d 100644 --- a/R/EvaluateClusters.R +++ b/R/EvaluateClusters.R @@ -113,220 +113,232 @@ #' #' # Unordered cohort data (e.g. BRCA): disable slope filter #' eval_brca <- EvaluateClusters( -#' brca_clusters, brca_tbl, -#' data.type = "ratio", -#' use.slope = FALSE, -#' index.mode = "density" +#' brca_clusters, brca_tbl, +#' data.type = "ratio", +#' use.slope = FALSE, +#' index.mode = "density" #' ) #' #' # LINCS data with ratio columns to strip, size-based index: #' eval_lincs <- EvaluateClusters( -#' lincs_clusters, lincs_tbl, -#' data.type = "ratio", -#' index.mode = "size", -#' use.slope = TRUE, -#' ratio.col.pattern = "to" +#' lincs_clusters, lincs_tbl, +#' data.type = "ratio", +#' index.mode = "size", +#' use.slope = TRUE, +#' ratio.col.pattern = "to" #' ) EvaluateClusters <- function( - clusterlist, - tbl.sc, - data.type = c("ratio", "intensity", "count"), - index.mode = c("density", "size"), - use.slope = TRUE, - ratio.col.pattern = NULL, - verbose = TRUE + clusterlist, + tbl.sc, + data.type = c("ratio", "intensity", "count"), + index.mode = c("density", "size"), + use.slope = TRUE, + ratio.col.pattern = NULL, + verbose = TRUE ) { + # ---- argument validation ------------------------------------------------ + data.type <- match.arg(data.type) + index.mode <- match.arg(index.mode) - # ---- argument validation ------------------------------------------------ - data.type <- match.arg(data.type) - index.mode <- match.arg(index.mode) - - if (!is.list(clusterlist) || length(clusterlist) == 0L) { - stop("'clusterlist' must be a non-empty list.") - } - if (!is.data.frame(tbl.sc) && !is.matrix(tbl.sc)) { - stop("'tbl.sc' must be a data frame or matrix.") - } - if (is.null(rownames(tbl.sc))) { - stop("'tbl.sc' must have row names identifying genes / PTMs.") - } - - # ---- helper: number of non-missing values in a vector ------------------ - .filled <- function(x) sum(!is.na(x)) - .nmissing <- function(x) sum( is.na(x)) - - # ---- helper: extract gene names from one cluster element --------------- - # Accepts either a character vector of names or a data frame with a - # PTMnames column (MakeClusterList output format). - .gene.names <- function(cl.el) { - if (is.data.frame(cl.el)) { - if ("PTMnames" %in% names(cl.el)) return(cl.el[["PTMnames"]]) - if ("Gene.Name" %in% names(cl.el)) return(cl.el[["Gene.Name"]]) - stop("Cluster data frame must contain a 'PTMnames' or 'Gene.Name' column.") + if (!is.list(clusterlist) || length(clusterlist) == 0L) { + stop("'clusterlist' must be a non-empty list.") } - if (is.character(cl.el)) return(cl.el) - stop("Each element of 'clusterlist' must be a character vector or data frame.") - } - - # ---- helper: linear slope of a numeric vector against its index -------- - # Returns NA if fewer than 2 non-missing values are available. - .get.slope <- function(x) { - idx <- which(!is.na(x)) - if (length(idx) < 2L) return(NA_real_) - stats::coef(stats::lm(x[idx] ~ idx))[2L] - } - - # ---- initialise results data frame ------------------------------------- - n.clusters <- length(clusterlist) - evaluation <- data.frame( - Group = seq_len(n.clusters), - no.genes = NA_integer_, - culled.by.slope = NA_integer_, - percent.singlesamplegenes = NA_real_, - no.samples = NA_integer_, - percent.singlegenesamples = NA_real_, - total.signal = NA_real_, - percent.NA = NA_real_, - intensity = NA_real_, - Index = NA_real_, - stringsAsFactors = FALSE - ) - - # ---- main loop ---------------------------------------------------------- - for (i in seq_len(n.clusters)) { - - if (verbose) message("Starting Group ", i) - - gene.names <- .gene.names(clusterlist[[i]]) - evaluation$no.genes[i] <- length(gene.names) - - # Extract the cluster sub-table. - # When the cluster has a single gene, data.frame() is needed to prevent - # drop to a vector. - if (length(gene.names) == 1L) { - at <- data.frame(tbl.sc[gene.names, , drop = FALSE]) - } else { - at <- data.frame(tbl.sc[rownames(tbl.sc) %in% gene.names, , drop = FALSE]) + if (!is.data.frame(tbl.sc) && !is.matrix(tbl.sc)) { + stop("'tbl.sc' must be a data frame or matrix.") } - - # Optionally remove ratio descriptor columns (e.g. LINCS "H1_to_control"). - if (!is.null(ratio.col.pattern)) { - ratio.cols <- grep(ratio.col.pattern, names(at)) - if (length(ratio.cols) > 0L) at <- at[, -ratio.cols, drop = FALSE] + if (is.null(rownames(tbl.sc))) { + stop("'tbl.sc' must have row names identifying genes / PTMs.") } - # Apply absolute value for ratio data so that signal magnitude is - # assessed without sign cancellation. - if (data.type == "ratio") at <- abs(at) - - # Restrict to samples that have at least one non-missing value. - has.data <- which(vapply(at, .filled, integer(1L)) > 0L) - n.has.data <- length(has.data) - - if (n.has.data > 1L) { - acol <- names(at)[has.data] - } else { - acol <- if (n.has.data == 1L) names(at)[has.data] else character(0L) + # ---- helper: number of non-missing values in a vector ------------------ + .filled <- function(x) sum(!is.na(x)) + .nmissing <- function(x) sum(is.na(x)) + + # ---- helper: extract gene names from one cluster element --------------- + # Accepts either a character vector of names or a data frame with a + # PTMnames column (MakeClusterList output format). + .gene.names <- function(cl.el) { + if (is.data.frame(cl.el)) { + if ("PTMnames" %in% names(cl.el)) { + return(cl.el[["PTMnames"]]) + } + if ("Gene.Name" %in% names(cl.el)) { + return(cl.el[["Gene.Name"]]) + } + stop("Cluster data frame must contain a 'PTMnames' or 'Gene.Name' column.") + } + if (is.character(cl.el)) { + return(cl.el) + } + stop("Each element of 'clusterlist' must be a character vector or data frame.") } - evaluation$no.samples[i] <- length(acol) - # Restrict columns to those with data before further calculations. - if (length(acol) > 0L) { - at <- at[, acol, drop = FALSE] + # ---- helper: linear slope of a numeric vector against its index -------- + # Returns NA if fewer than 2 non-missing values are available. + .get.slope <- function(x) { + idx <- which(!is.na(x)) + if (length(idx) < 2L) { + return(NA_real_) + } + stats::coef(stats::lm(x[idx] ~ idx))[2L] } - evaluation$total.signal[i] <- sum(at, na.rm = TRUE) - - # ---- degenerate case: single sample or single gene ------------------ - # Diagnostics cannot be computed meaningfully; mark as 100 % single. - if (length(acol) <= 1L || length(gene.names) == 1L) { - evaluation$culled.by.slope[i] <- length(gene.names) - evaluation$percent.NA[i] <- 0 - evaluation$percent.singlesamplegenes[i] <- 100 - evaluation$percent.singlegenesamples[i] <- 100 - + # ---- initialise results data frame ------------------------------------- + n.clusters <- length(clusterlist) + evaluation <- data.frame( + Group = seq_len(n.clusters), + no.genes = NA_integer_, + culled.by.slope = NA_integer_, + percent.singlesamplegenes = NA_real_, + no.samples = NA_integer_, + percent.singlegenesamples = NA_real_, + total.signal = NA_real_, + percent.NA = NA_real_, + intensity = NA_real_, + Index = NA_real_, + stringsAsFactors = FALSE + ) + + # ---- main loop ---------------------------------------------------------- + for (i in seq_len(n.clusters)) { + if (verbose) message("Starting Group ", i) + + gene.names <- .gene.names(clusterlist[[i]]) + evaluation$no.genes[i] <- length(gene.names) + + # Extract the cluster sub-table. + # When the cluster has a single gene, data.frame() is needed to prevent + # drop to a vector. + if (length(gene.names) == 1L) { + at <- data.frame(tbl.sc[gene.names, , drop = FALSE]) + } else { + at <- data.frame(tbl.sc[rownames(tbl.sc) %in% gene.names, , drop = FALSE]) + } + + # Optionally remove ratio descriptor columns (e.g. LINCS "H1_to_control"). + if (!is.null(ratio.col.pattern)) { + ratio.cols <- grep(ratio.col.pattern, names(at)) + if (length(ratio.cols) > 0L) at <- at[, -ratio.cols, drop = FALSE] + } + + # Apply absolute value for ratio data so that signal magnitude is + # assessed without sign cancellation. + if (data.type == "ratio") at <- abs(at) + + # Restrict to samples that have at least one non-missing value. + has.data <- which(vapply(at, .filled, integer(1L)) > 0L) + n.has.data <- length(has.data) + + if (n.has.data > 1L) { + acol <- names(at)[has.data] + } else { + acol <- if (n.has.data == 1L) names(at)[has.data] else character(0L) + } + evaluation$no.samples[i] <- length(acol) + + # Restrict columns to those with data before further calculations. + if (length(acol) > 0L) { + at <- at[, acol, drop = FALSE] + } + + evaluation$total.signal[i] <- sum(at, na.rm = TRUE) + + # ---- degenerate case: single sample or single gene ------------------ + # Diagnostics cannot be computed meaningfully; mark as 100 % single. + if (length(acol) <= 1L || length(gene.names) == 1L) { + evaluation$culled.by.slope[i] <- length(gene.names) + evaluation$percent.NA[i] <- 0 + evaluation$percent.singlesamplegenes[i] <- 100 + evaluation$percent.singlegenesamples[i] <- 100 + } else { + # ---- full diagnostic block ---------------------------------------- + + evaluation$percent.NA[i] <- + 100 * sum(vapply(at, .nmissing, integer(1L))) / + (nrow(at) * ncol(at)) + + # Genes present in only one sample. + single.sample.genes <- at[vapply(seq_len(nrow(at)), function(r) { + .filled(as.numeric(at[r, ])) == 1L + }, logical(1L)), , drop = FALSE] + evaluation$percent.singlesamplegenes[i] <- + 100 * nrow(single.sample.genes) / nrow(at) + + # Samples that contain only one gene. + single.gene.samples <- + sum(vapply(at, .filled, integer(1L)) == 1L) + evaluation$percent.singlegenesamples[i] <- + 100 * single.gene.samples / ncol(at) + + # ---- slope filter ------------------------------------------------- + if (use.slope) { + # Sort columns by decreasing total signal, then rows by decreasing + # total signal, to create a monotonically organised matrix for slope + # assessment. + col.order <- order(-vapply( + at, function(x) sum(x, na.rm = TRUE), + numeric(1L) + )) + row.order <- order(-vapply(seq_len(nrow(at)), function(r) { + sum(as.numeric(at[r, ]), na.rm = TRUE) + }, numeric(1L))) + cluster.mo <- at[row.order, col.order, drop = FALSE] + + slopes <- apply(cluster.mo, 1, .get.slope) + bad.slope <- names(which(is.na(slopes) | slopes > 0)) + + if (verbose && length(bad.slope) > 0L) { + message(" ", length(bad.slope), " gene(s) culled by slope filter.") + } + + evaluation$culled.by.slope[i] <- length(bad.slope) + } else { + evaluation$culled.by.slope[i] <- 0L + } + } # end full diagnostic block + } # end cluster loop + + # ---- compute index for all clusters ------------------------------------ + + cleargenes <- evaluation$no.genes - evaluation$culled.by.slope + realsamples <- evaluation$no.samples - + (evaluation$no.samples * evaluation$percent.singlegenesamples / 100) + intensity <- evaluation$total.signal - + (evaluation$total.signal * evaluation$percent.NA / 100) + + evaluation$intensity <- intensity + + if (index.mode == "density") { + # Reward data density per gene: dividing by no.genes penalises large + # diffuse clusters relative to compact, information-rich ones. + evaluation$Index <- + (1 + realsamples) * (1 + cleargenes) / + ((1 + evaluation$percent.NA) * evaluation$no.genes) } else { - # ---- full diagnostic block ---------------------------------------- - - evaluation$percent.NA[i] <- - 100 * sum(vapply(at, .nmissing, integer(1L))) / - (nrow(at) * ncol(at)) - - # Genes present in only one sample. - single.sample.genes <- at[vapply(seq_len(nrow(at)), function(r) - .filled(as.numeric(at[r, ])) == 1L, logical(1L)), , drop = FALSE] - evaluation$percent.singlesamplegenes[i] <- - 100 * nrow(single.sample.genes) / nrow(at) - - # Samples that contain only one gene. - single.gene.samples <- - sum(vapply(at, .filled, integer(1L)) == 1L) - evaluation$percent.singlegenesamples[i] <- - 100 * single.gene.samples / ncol(at) - - # ---- slope filter ------------------------------------------------- - if (use.slope) { - # Sort columns by decreasing total signal, then rows by decreasing - # total signal, to create a monotonically organised matrix for slope - # assessment. - col.order <- order(-vapply(at, function(x) sum(x, na.rm = TRUE), - numeric(1L))) - row.order <- order(-vapply(seq_len(nrow(at)), function(r) - sum(as.numeric(at[r, ]), na.rm = TRUE), numeric(1L))) - cluster.mo <- at[row.order, col.order, drop = FALSE] - - slopes <- apply(cluster.mo, 1, .get.slope) - bad.slope <- names(which(is.na(slopes) | slopes > 0)) - - if (verbose && length(bad.slope) > 0L) - message(" ", length(bad.slope), " gene(s) culled by slope filter.") - - evaluation$culled.by.slope[i] <- length(bad.slope) - } else { - evaluation$culled.by.slope[i] <- 0L - } - } # end full diagnostic block - - } # end cluster loop - - # ---- compute index for all clusters ------------------------------------ - - cleargenes <- evaluation$no.genes - evaluation$culled.by.slope - realsamples <- evaluation$no.samples - - (evaluation$no.samples * evaluation$percent.singlegenesamples / 100) - intensity <- evaluation$total.signal - - (evaluation$total.signal * evaluation$percent.NA / 100) - - evaluation$intensity <- intensity - - if (index.mode == "density") { - # Reward data density per gene: dividing by no.genes penalises large - # diffuse clusters relative to compact, information-rich ones. - evaluation$Index <- - (1 + realsamples) * (1 + cleargenes) / - ((1 + evaluation$percent.NA) * evaluation$no.genes) - } else { - # "size": reward large, signal-dense clusters. - evaluation$Index <- - intensity * (1 + realsamples) * (1 + cleargenes) / - (1 + evaluation$percent.NA) - } + # "size": reward large, signal-dense clusters. + evaluation$Index <- + intensity * (1 + realsamples) * (1 + cleargenes) / + (1 + evaluation$percent.NA) + } - # Drop the intensity column when using the size index (it is already - # embedded in the index formula and clutters the output). - output.cols <- c("Group", "no.genes", "culled.by.slope", - "percent.singlesamplegenes", "no.samples", - "percent.singlegenesamples", "total.signal", - "percent.NA", "Index") - if (index.mode == "density") { - output.cols <- append(output.cols, "intensity", - after = which(output.cols == "percent.NA")) - } + # Drop the intensity column when using the size index (it is already + # embedded in the index formula and clutters the output). + output.cols <- c( + "Group", "no.genes", "culled.by.slope", + "percent.singlesamplegenes", "no.samples", + "percent.singlegenesamples", "total.signal", + "percent.NA", "Index" + ) + if (index.mode == "density") { + output.cols <- append(output.cols, "intensity", + after = which(output.cols == "percent.NA") + ) + } - eval.sort <- evaluation[ - order(-evaluation$Index, evaluation$percent.NA), - output.cols - ] + eval.sort <- evaluation[ + order(-evaluation$Index, evaluation$percent.NA), + output.cols + ] - return(eval.sort) + return(eval.sort) } diff --git a/R/GatherPPIData.R b/R/GatherPPIData.R index 47625169..24725147 100644 --- a/R/GatherPPIData.R +++ b/R/GatherPPIData.R @@ -27,45 +27,44 @@ #' # sym.map <- StandardizeGeneSymbols(c("EPRS", "QARS", "DDR1", "DDR2")) #' # unique(sym.map$standard_symbol) StandardizeGeneSymbols <- function(genes, - species = 9606, - string.version = "12.0", - keep.unmapped = TRUE) { - - if (!requireNamespace("STRINGdb", quietly = TRUE)) { - stop("Please install STRINGdb: BiocManager::install('STRINGdb')") - } - - if (!is.character(genes) || length(genes) == 0) { - stop("`genes` must be a non-empty character vector.") - } - - genes <- unique(as.character(genes)) - input.df <- data.frame(Gene.Names = genes, stringsAsFactors = FALSE) - - string.db <- STRINGdb::STRINGdb$new( - version = string.version, - species = species, - score_threshold = 0, - network_type = "full", - link_data = "full", - input_directory = "" - ) - - mapped <- string.db$map(input.df, "Gene.Names", removeUnmappedRows = FALSE) - - out <- data.frame( - input_symbol = mapped$Gene.Names, - STRING_id = if ("STRING_id" %in% colnames(mapped)) mapped$STRING_id else NA_character_, - standard_symbol = if ("preferred_name" %in% colnames(mapped)) mapped$preferred_name else NA_character_, - mapped = !is.na(if ("STRING_id" %in% colnames(mapped)) mapped$STRING_id else NA_character_), - stringsAsFactors = FALSE - ) - - if (keep.unmapped) { - out$standard_symbol[is.na(out$standard_symbol)] <- out$input_symbol[is.na(out$standard_symbol)] - } - - out + species = 9606, + string.version = "12.0", + keep.unmapped = TRUE) { + if (!requireNamespace("STRINGdb", quietly = TRUE)) { + stop("Please install STRINGdb: BiocManager::install('STRINGdb')") + } + + if (!is.character(genes) || length(genes) == 0) { + stop("`genes` must be a non-empty character vector.") + } + + genes <- unique(as.character(genes)) + input.df <- data.frame(Gene.Names = genes, stringsAsFactors = FALSE) + + string.db <- STRINGdb::STRINGdb$new( + version = string.version, + species = species, + score_threshold = 0, + network_type = "full", + link_data = "full", + input_directory = "" + ) + + mapped <- string.db$map(input.df, "Gene.Names", removeUnmappedRows = FALSE) + + out <- data.frame( + input_symbol = mapped$Gene.Names, + STRING_id = if ("STRING_id" %in% colnames(mapped)) mapped$STRING_id else NA_character_, + standard_symbol = if ("preferred_name" %in% colnames(mapped)) mapped$preferred_name else NA_character_, + mapped = !is.na(if ("STRING_id" %in% colnames(mapped)) mapped$STRING_id else NA_character_), + stringsAsFactors = FALSE + ) + + if (keep.unmapped) { + out$standard_symbol[is.na(out$standard_symbol)] <- out$input_symbol[is.na(out$standard_symbol)] + } + + out } @@ -87,26 +86,26 @@ StandardizeGeneSymbols <- function(genes, #' #' @keywords internal .map_nodes_with_symbol_map <- function(gene.cccn.nodes, symbol.map = NULL) { - nodes <- unique(as.character(gene.cccn.nodes)) - - if (is.null(symbol.map)) { - return(nodes) - } - - required.map.cols <- c("input_symbol", "standard_symbol") - missing.map.cols <- setdiff(required.map.cols, colnames(symbol.map)) - if (length(missing.map.cols) > 0) { - stop( - "`symbol.map` is missing required columns: ", - paste(missing.map.cols, collapse = ", ") - ) - } + nodes <- unique(as.character(gene.cccn.nodes)) + + if (is.null(symbol.map)) { + return(nodes) + } - idx <- match(nodes, symbol.map$input_symbol) - mapped.nodes <- symbol.map$standard_symbol[idx] - mapped.nodes[is.na(mapped.nodes)] <- nodes[is.na(mapped.nodes)] + required.map.cols <- c("input_symbol", "standard_symbol") + missing.map.cols <- setdiff(required.map.cols, colnames(symbol.map)) + if (length(missing.map.cols) > 0) { + stop( + "`symbol.map` is missing required columns: ", + paste(missing.map.cols, collapse = ", ") + ) + } - unique(mapped.nodes) + idx <- match(nodes, symbol.map$input_symbol) + mapped.nodes <- symbol.map$standard_symbol[idx] + mapped.nodes[is.na(mapped.nodes)] <- nodes[is.na(mapped.nodes)] + + unique(mapped.nodes) } #' Make Database Input File #' @@ -120,10 +119,10 @@ StandardizeGeneSymbols <- function(genes, #' @export #' #' @examples -#' #MakeDBInput(ex.nodenames) -#' cat(ex.nodenames[[1]], sep = '\n') +#' # MakeDBInput(ex.nodenames) +#' cat(ex.nodenames[[1]], sep = "\n") MakeDBInput <- function(gene.cccn.nodes, file.path.name = "db_nodes.txt") { - utils::write.table(unique(c(gene.cccn.nodes[[1]], gene.cccn.nodes[[2]])), file = file.path.name, row.names = FALSE, col.names = FALSE, quote = FALSE) + utils::write.table(unique(c(gene.cccn.nodes[[1]], gene.cccn.nodes[[2]])), file = file.path.name, row.names = FALSE, col.names = FALSE, quote = FALSE) } # Pulls nodenames from the gene.cccn # @@ -174,238 +173,245 @@ MakeDBInput <- function(gene.cccn.nodes, file.path.name = "db_nodes.txt") { #' # symbol.map = sym.map #' # ) GetSTRINGdb.edges <- function(gene.cccn.edges, - gene.cccn.nodes, - local = FALSE, - string.local.path = "string_hs_hugo_full.tsv", - combined.score.threshold = 400, - include.transferred = TRUE, - symbol.map = NULL) { - - if (!is.character(gene.cccn.nodes) || length(gene.cccn.nodes) == 0) { - stop("`gene.cccn.nodes` must be a non-empty character vector.") - } - - if (!is.numeric(combined.score.threshold) || - length(combined.score.threshold) != 1 || - is.na(combined.score.threshold) || - combined.score.threshold < 0 || - combined.score.threshold > 1000) { - stop("`combined.score.threshold` must be a single number between 0 and 1000.") - } - - # -------------------------------------------------------------------------- - # Standardize nodes if a symbol map is supplied - # -------------------------------------------------------------------------- - nodes <- .map_nodes_with_symbol_map(gene.cccn.nodes, symbol.map) - - - # -------------------------------------------------------------------------- - # Helper for empty returns - # -------------------------------------------------------------------------- - .empty_edges <- function() { - data.frame( - source = character(0), - target = character(0), - interaction = character(0), - Weight = numeric(0), - stringsAsFactors = FALSE - ) - } - - # -------------------------------------------------------------------------- - # Local mode: fully offline - # -------------------------------------------------------------------------- - if (local) { - if (!file.exists(string.local.path)) { - stop("Local STRING file not found: ", string.local.path, - "\nGenerate it from protein.links.full.v12.0.txt.gz first.") + gene.cccn.nodes, + local = FALSE, + string.local.path = "string_hs_hugo_full.tsv", + combined.score.threshold = 400, + include.transferred = TRUE, + symbol.map = NULL) { + if (!is.character(gene.cccn.nodes) || length(gene.cccn.nodes) == 0) { + stop("`gene.cccn.nodes` must be a non-empty character vector.") } - message("Reading local STRING file: ", string.local.path) - dt <- utils::read.delim(string.local.path, stringsAsFactors = FALSE) + if (!is.numeric(combined.score.threshold) || + length(combined.score.threshold) != 1 || + is.na(combined.score.threshold) || + combined.score.threshold < 0 || + combined.score.threshold > 1000) { + stop("`combined.score.threshold` must be a single number between 0 and 1000.") + } + + # -------------------------------------------------------------------------- + # Standardize nodes if a symbol map is supplied + # -------------------------------------------------------------------------- + nodes <- .map_nodes_with_symbol_map(gene.cccn.nodes, symbol.map) + + + # -------------------------------------------------------------------------- + # Helper for empty returns + # -------------------------------------------------------------------------- + .empty_edges <- function() { + data.frame( + source = character(0), + target = character(0), + interaction = character(0), + Weight = numeric(0), + stringsAsFactors = FALSE + ) + } + + # -------------------------------------------------------------------------- + # Local mode: fully offline + # -------------------------------------------------------------------------- + if (local) { + if (!file.exists(string.local.path)) { + stop( + "Local STRING file not found: ", string.local.path, + "\nGenerate it from protein.links.full.v12.0.txt.gz first." + ) + } + + message("Reading local STRING file: ", string.local.path) + dt <- utils::read.delim(string.local.path, stringsAsFactors = FALSE) + + required.cols <- c( + "source", "target", + "experiments", "database", + "combined_score" + ) + + if (include.transferred) { + required.cols <- c( + required.cols, + "experiments_transferred", + "database_transferred" + ) + } + + missing.cols <- setdiff(required.cols, colnames(dt)) + if (length(missing.cols) > 0) { + stop( + "Local STRING file is missing required columns: ", + paste(missing.cols, collapse = ", ") + ) + } + + dt <- dt[ + dt$combined_score >= combined.score.threshold & + dt$source %in% nodes & + dt$target %in% nodes, + ] + + if (nrow(dt) == 0) { + warning("No local STRING edges passed the filters.") + return(.empty_edges()) + } + + if (include.transferred) { + keep <- dt$experiments > 0 | + dt$experiments_transferred > 0 | + dt$database > 0 | + dt$database_transferred > 0 + } else { + keep <- dt$experiments > 0 | dt$database > 0 + } + + dt <- dt[keep, ] + + if (nrow(dt) == 0) { + warning("No local STRING edges with selected evidence types passed the filters.") + return(.empty_edges()) + } + + dt$edgeType <- "STRINGdb" + + if (include.transferred) { + dt[dt$database_transferred > 0, "edgeType"] <- "database_transferred" + dt[dt$database > 0, "edgeType"] <- "database" + dt[dt$experiments_transferred > 0, "edgeType"] <- "experiments_transferred" + dt[dt$experiments > 0, "edgeType"] <- "experiments" + dt$Weight <- rowSums( + dt[, c( + "experiments", "experiments_transferred", + "database", "database_transferred" + ), drop = FALSE] + ) + } else { + dt[dt$database > 0, "edgeType"] <- "database" + dt[dt$experiments > 0, "edgeType"] <- "experiments" + dt$Weight <- rowSums( + dt[, c("experiments", "database"), drop = FALSE] + ) + } + # After computing dt$edgeType and dt$Weight + + # Canonicalize undirected edges: sort endpoints within each row + dt$u <- pmin(dt$source, dt$target) + dt$v <- pmax(dt$source, dt$target) + + stringdb.edges <- unique(dt[, c("u", "v", "edgeType", "Weight")]) + colnames(stringdb.edges) <- c("source", "target", "interaction", "Weight") + stringdb.edges$source <- as.character(stringdb.edges$source) + stringdb.edges$target <- as.character(stringdb.edges$target) + rownames(stringdb.edges) <- NULL + return(stringdb.edges) + } + + # -------------------------------------------------------------------------- + # Live mode + # -------------------------------------------------------------------------- + if (!requireNamespace("STRINGdb", quietly = TRUE)) { + stop("Please install STRINGdb: BiocManager::install('STRINGdb')") + } - required.cols <- c( - "source", "target", - "experiments", "database", - "combined_score" + nodenames <- data.frame(Gene.Names = nodes, stringsAsFactors = FALSE) + + string.db <- STRINGdb::STRINGdb$new( + version = "12.0", + species = 9606, + score_threshold = 0, + network_type = "full", + link_data = "full", + input_directory = "" ) - if (include.transferred) { - required.cols <- c( - required.cols, - "experiments_transferred", - "database_transferred" - ) + message("Querying STRINGdb for interactions between ", length(nodes), " genes...") + string.proteins <- string.db$get_proteins() + + message("Mapping genes to STRING IDs...") + mapped.genes <- string.db$map(nodenames, "Gene.Names", removeUnmappedRows = TRUE) + + if (nrow(mapped.genes) == 0) { + warning("No input genes could be mapped to STRING.") + return(.empty_edges()) } - missing.cols <- setdiff(required.cols, colnames(dt)) - if (length(missing.cols) > 0) { - stop("Local STRING file is missing required columns: ", - paste(missing.cols, collapse = ", ")) + message("Retrieving interactions for mapped genes...") + interactions <- string.db$get_interactions(mapped.genes$STRING_id) + + if (nrow(interactions) == 0) { + warning("STRINGdb returned no interactions.") + return(.empty_edges()) + } + + message("Formatting...") + interactions$Gene.1 <- vapply(interactions$from, function(x) { + string.proteins[match(x, string.proteins$protein_external_id), "preferred_name"] + }, FUN.VALUE = character(1)) + interactions$Gene.2 <- vapply(interactions$to, function(x) { + string.proteins[match(x, string.proteins$protein_external_id), "preferred_name"] + }, FUN.VALUE = character(1)) + + if (!"combined_score" %in% colnames(interactions)) { + stop("Live STRINGdb interactions did not include a `combined_score` column.") } - dt <- dt[ - dt$combined_score >= combined.score.threshold & - dt$source %in% nodes & - dt$target %in% nodes, + interactions <- interactions[ + interactions$combined_score >= combined.score.threshold & + interactions$Gene.1 %in% nodes & + interactions$Gene.2 %in% nodes, ] - if (nrow(dt) == 0) { - warning("No local STRING edges passed the filters.") - return(.empty_edges()) + if (nrow(interactions) == 0) { + warning("No live STRING edges passed the filters.") + return(.empty_edges()) } if (include.transferred) { - keep <- dt$experiments > 0 | - dt$experiments_transferred > 0 | - dt$database > 0 | - dt$database_transferred > 0 + str.e <- interactions[interactions$experiments > 0, ] + str.et <- interactions[interactions$experiments_transferred > 0, ] + str.d <- interactions[interactions$database > 0, ] + str.dt <- interactions[interactions$database_transferred > 0, ] + combined_interactions <- unique(rbind(str.e, str.et, str.d, str.dt)) } else { - keep <- dt$experiments > 0 | dt$database > 0 + str.e <- interactions[interactions$experiments > 0, ] + str.d <- interactions[interactions$database > 0, ] + combined_interactions <- unique(rbind(str.e, str.d)) } - dt <- dt[keep, ] - - if (nrow(dt) == 0) { - warning("No local STRING edges with selected evidence types passed the filters.") - return(.empty_edges()) + if (nrow(combined_interactions) == 0) { + warning("No live STRING edges with selected evidence types passed the filters.") + return(.empty_edges()) } - dt$edgeType <- "STRINGdb" + combined_interactions$edgeType <- "STRINGdb" if (include.transferred) { - dt[dt$database_transferred > 0, "edgeType"] <- "database_transferred" - dt[dt$database > 0, "edgeType"] <- "database" - dt[dt$experiments_transferred > 0, "edgeType"] <- "experiments_transferred" - dt[dt$experiments > 0, "edgeType"] <- "experiments" - dt$Weight <- rowSums( - dt[, c("experiments", "experiments_transferred", - "database", "database_transferred"), drop = FALSE] - ) + combined_interactions[combined_interactions$database_transferred > 0, "edgeType"] <- "database_transferred" + combined_interactions[combined_interactions$database > 0, "edgeType"] <- "database" + combined_interactions[combined_interactions$experiments_transferred > 0, "edgeType"] <- "experiments_transferred" + combined_interactions[combined_interactions$experiments > 0, "edgeType"] <- "experiments" + + combined_interactions$Weight <- rowSums( + combined_interactions[, c( + "experiments", "experiments_transferred", + "database", "database_transferred" + ), drop = FALSE] + ) } else { - dt[dt$database > 0, "edgeType"] <- "database" - dt[dt$experiments > 0, "edgeType"] <- "experiments" - dt$Weight <- rowSums( - dt[, c("experiments", "database"), drop = FALSE] - ) - } - # After computing dt$edgeType and dt$Weight + combined_interactions[combined_interactions$database > 0, "edgeType"] <- "database" + combined_interactions[combined_interactions$experiments > 0, "edgeType"] <- "experiments" - # Canonicalize undirected edges: sort endpoints within each row - dt$u <- pmin(dt$source, dt$target) - dt$v <- pmax(dt$source, dt$target) + combined_interactions$Weight <- rowSums( + combined_interactions[, c("experiments", "database"), drop = FALSE] + ) + } - stringdb.edges <- unique(dt[, c("u", "v", "edgeType", "Weight")]) + stringdb.edges <- unique(combined_interactions[, c("Gene.1", "Gene.2", "edgeType", "Weight")]) colnames(stringdb.edges) <- c("source", "target", "interaction", "Weight") - stringdb.edges$source <- as.character(stringdb.edges$source) - stringdb.edges$target <- as.character(stringdb.edges$target) rownames(stringdb.edges) <- NULL - return(stringdb.edges) - } - - # -------------------------------------------------------------------------- - # Live mode - # -------------------------------------------------------------------------- - if (!requireNamespace("STRINGdb", quietly = TRUE)) { - stop("Please install STRINGdb: BiocManager::install('STRINGdb')") - } - - nodenames <- data.frame(Gene.Names = nodes, stringsAsFactors = FALSE) - - string.db <- STRINGdb::STRINGdb$new( - version = "12.0", - species = 9606, - score_threshold = 0, - network_type = "full", - link_data = "full", - input_directory = "" - ) - - message("Querying STRINGdb for interactions between ", length(nodes), " genes...") - string.proteins <- string.db$get_proteins() - - message("Mapping genes to STRING IDs...") - mapped.genes <- string.db$map(nodenames, "Gene.Names", removeUnmappedRows = TRUE) - - if (nrow(mapped.genes) == 0) { - warning("No input genes could be mapped to STRING.") - return(.empty_edges()) - } - - message("Retrieving interactions for mapped genes...") - interactions <- string.db$get_interactions(mapped.genes$STRING_id) - - if (nrow(interactions) == 0) { - warning("STRINGdb returned no interactions.") - return(.empty_edges()) - } - - message("Formatting...") - interactions$Gene.1 <- vapply(interactions$from, function(x) { - string.proteins[match(x, string.proteins$protein_external_id), "preferred_name"] - }, FUN.VALUE=character(1)) - interactions$Gene.2 <- vapply(interactions$to, function(x) { - string.proteins[match(x, string.proteins$protein_external_id), "preferred_name"] - }, FUN.VALUE=character(1)) - - if (!"combined_score" %in% colnames(interactions)) { - stop("Live STRINGdb interactions did not include a `combined_score` column.") - } - - interactions <- interactions[ - interactions$combined_score >= combined.score.threshold & - interactions$Gene.1 %in% nodes & - interactions$Gene.2 %in% nodes, - ] - - if (nrow(interactions) == 0) { - warning("No live STRING edges passed the filters.") - return(.empty_edges()) - } - - if (include.transferred) { - str.e <- interactions[interactions$experiments > 0, ] - str.et <- interactions[interactions$experiments_transferred > 0, ] - str.d <- interactions[interactions$database > 0, ] - str.dt <- interactions[interactions$database_transferred > 0, ] - combined_interactions <- unique(rbind(str.e, str.et, str.d, str.dt)) - } else { - str.e <- interactions[interactions$experiments > 0, ] - str.d <- interactions[interactions$database > 0, ] - combined_interactions <- unique(rbind(str.e, str.d)) - } - - if (nrow(combined_interactions) == 0) { - warning("No live STRING edges with selected evidence types passed the filters.") - return(.empty_edges()) - } - - combined_interactions$edgeType <- "STRINGdb" - - if (include.transferred) { - combined_interactions[combined_interactions$database_transferred > 0, "edgeType"] <- "database_transferred" - combined_interactions[combined_interactions$database > 0, "edgeType"] <- "database" - combined_interactions[combined_interactions$experiments_transferred > 0, "edgeType"] <- "experiments_transferred" - combined_interactions[combined_interactions$experiments > 0, "edgeType"] <- "experiments" - - combined_interactions$Weight <- rowSums( - combined_interactions[, c("experiments", "experiments_transferred", - "database", "database_transferred"), drop = FALSE] - ) - } else { - combined_interactions[combined_interactions$database > 0, "edgeType"] <- "database" - combined_interactions[combined_interactions$experiments > 0, "edgeType"] <- "experiments" - - combined_interactions$Weight <- rowSums( - combined_interactions[, c("experiments", "database"), drop = FALSE] - ) - } - - stringdb.edges <- unique(combined_interactions[, c("Gene.1", "Gene.2", "edgeType", "Weight")]) - colnames(stringdb.edges) <- c("source", "target", "interaction", "Weight") - rownames(stringdb.edges) <- NULL - stringdb.edges + stringdb.edges } @@ -440,85 +446,88 @@ GetSTRINGdb.edges <- function(gene.cccn.edges, #' # symbol.map = sym.map #' # ) GetGeneMANIA.edges <- function(gm.results.path, - gene.cccn.nodes, - local = FALSE, - genemania.local.path = "hs_interactions_hugo.tsv", - gm.interaction.types = c("Pathway", "Physical Interactions"), - symbol.map = NULL) { - - nodes <- .map_nodes_with_symbol_map(gene.cccn.nodes, symbol.map) - - if (local) { - if (!file.exists(genemania.local.path)) { - stop("Local GeneMANIA file not found: ", genemania.local.path, - "\nGenerate it with scripts/genemania_hs_download.r or set local = FALSE.") - } - - message("Reading local GeneMANIA file: ", genemania.local.path) - dt <- utils::read.delim(genemania.local.path, stringsAsFactors = FALSE) - - required.cols <- c("Gene1", "Gene2", "Group", "Weight") - missing.cols <- setdiff(required.cols, colnames(dt)) - if (length(missing.cols) > 0) { - stop("Local GeneMANIA file is missing required columns: ", - paste(missing.cols, collapse = ", ")) - } - - dt <- dt[dt$Group %in% gm.interaction.types, ] - dt <- dt[dt$Gene1 %in% nodes & dt$Gene2 %in% nodes, ] - - if (nrow(dt) == 0) { - warning("No local GeneMANIA edges passed the filters.") - return(data.frame( - source = character(0), - target = character(0), - interaction = character(0), - Weight = numeric(0), - stringsAsFactors = FALSE - )) + gene.cccn.nodes, + local = FALSE, + genemania.local.path = "hs_interactions_hugo.tsv", + gm.interaction.types = c("Pathway", "Physical Interactions"), + symbol.map = NULL) { + nodes <- .map_nodes_with_symbol_map(gene.cccn.nodes, symbol.map) + + if (local) { + if (!file.exists(genemania.local.path)) { + stop( + "Local GeneMANIA file not found: ", genemania.local.path, + "\nGenerate it with scripts/genemania_hs_download.r or set local = FALSE." + ) + } + + message("Reading local GeneMANIA file: ", genemania.local.path) + dt <- utils::read.delim(genemania.local.path, stringsAsFactors = FALSE) + + required.cols <- c("Gene1", "Gene2", "Group", "Weight") + missing.cols <- setdiff(required.cols, colnames(dt)) + if (length(missing.cols) > 0) { + stop( + "Local GeneMANIA file is missing required columns: ", + paste(missing.cols, collapse = ", ") + ) + } + + dt <- dt[dt$Group %in% gm.interaction.types, ] + dt <- dt[dt$Gene1 %in% nodes & dt$Gene2 %in% nodes, ] + + if (nrow(dt) == 0) { + warning("No local GeneMANIA edges passed the filters.") + return(data.frame( + source = character(0), + target = character(0), + interaction = character(0), + Weight = numeric(0), + stringsAsFactors = FALSE + )) + } + + dt <- unique(dt[, c("Gene1", "Gene2", "Group", "Weight")]) + colnames(dt) <- c("source", "target", "interaction", "Weight") + rownames(dt) <- NULL + return(dt) } - dt <- unique(dt[, c("Gene1", "Gene2", "Group", "Weight")]) - colnames(dt) <- c("source", "target", "interaction", "Weight") - rownames(dt) <- NULL - return(dt) - } + # ---- Live mode: original Cytoscape export behaviour ---- - # ---- Live mode: original Cytoscape export behaviour ---- + all_lines <- readLines(gm.results.path) - all_lines <- readLines(gm.results.path) + start_line <- grep("Weight\\tType", all_lines) + end_line <- grep("^Gene\\s+GO ids", all_lines) + if (length(end_line) == 0) end_line <- length(all_lines) + 1 - start_line <- grep("Weight\\tType", all_lines) - end_line <- grep("^Gene\\s+GO ids", all_lines) - if (length(end_line) == 0) end_line <- length(all_lines) + 1 + network_lines <- all_lines[start_line[1]:(end_line[1] - 1)] - network_lines <- all_lines[start_line[1]:(end_line[1] - 1)] - - edgetable <- read.table( - text = network_lines, - header = TRUE, - stringsAsFactors = FALSE, - sep = "\t", - comment.char = "#", - na.strings = "", - quote = "", - fill = TRUE - ) + edgetable <- read.table( + text = network_lines, + header = TRUE, + stringsAsFactors = FALSE, + sep = "\t", + comment.char = "#", + na.strings = "", + quote = "", + fill = TRUE + ) - keeper <- edgetable$Type %in% gm.interaction.types - edgetable <- edgetable[keeper, ] - edgetable <- edgetable[, c("Gene.1", "Gene.2", "Type", "Weight")] - colnames(edgetable) <- c("source", "target", "interaction", "Weight") + keeper <- edgetable$Type %in% gm.interaction.types + edgetable <- edgetable[keeper, ] + edgetable <- edgetable[, c("Gene.1", "Gene.2", "Type", "Weight")] + colnames(edgetable) <- c("source", "target", "interaction", "Weight") - keep <- edgetable$source %in% nodes & edgetable$target %in% nodes - genemania.edges <- unique(edgetable[keep, ]) + keep <- edgetable$source %in% nodes & edgetable$target %in% nodes + genemania.edges <- unique(edgetable[keep, ]) - if (nrow(genemania.edges) == 0) { - warning("No GeneMANIA edges passed the filters.") - } + if (nrow(genemania.edges) == 0) { + warning("No GeneMANIA edges passed the filters.") + } - rownames(genemania.edges) <- NULL - return(genemania.edges) + rownames(genemania.edges) <- NULL + return(genemania.edges) } #' Get Kinase-Substrate Edges @@ -544,52 +553,51 @@ GetGeneMANIA.edges <- function(gm.results.path, #' # symbol.map = sym.map #' # ) GetKinsub.edges <- function(kinasesubstrate.filename = "Kinase_Substrate_Dataset.txt", - gene.cccn.nodes, - symbol.map = NULL) { - - nodes <- toupper(.map_nodes_with_symbol_map(gene.cccn.nodes, symbol.map)) - - kinasesubstrateraw <- read.table( - kinasesubstrate.filename, - header = TRUE, - skip = 3, - stringsAsFactors = FALSE, - sep = "\t", - na.strings = "", - fill = TRUE - ) - - kinsub <- kinasesubstrateraw - - if (any(is.na(kinsub$GENE))) { - kinsub[which(is.na(kinsub$GENE)), "GENE"] <- - as.character(kinsub[which(is.na(kinsub$GENE)), "KINASE"]) - } - - if (any(is.na(kinsub$SUB_GENE))) { - kinsub[which(is.na(kinsub$SUB_GENE)), "SUB_GENE"] <- - as.character(kinsub[which(is.na(kinsub$SUB_GENE)), "SUBSTRATE"]) - } - - kinase <- toupper(kinsub$GENE) - substrate <- toupper(kinsub$SUB_GENE) - - kinsub <- unique(data.frame( - source = kinase, - target = substrate, - stringsAsFactors = FALSE - )) - - kinsub.edges <- kinsub[kinsub$source %in% nodes & kinsub$target %in% nodes, ] - kinsub.edges$interaction <- "pp" - kinsub.edges$Weight <- 1 - - kinsub.edges <- remove.autophos(kinsub.edges) - - if (nrow(kinsub.edges) == 0) { - warning("No kinase-substrate edges passed the filters.") - } - - rownames(kinsub.edges) <- NULL - return(kinsub.edges) + gene.cccn.nodes, + symbol.map = NULL) { + nodes <- toupper(.map_nodes_with_symbol_map(gene.cccn.nodes, symbol.map)) + + kinasesubstrateraw <- read.table( + kinasesubstrate.filename, + header = TRUE, + skip = 3, + stringsAsFactors = FALSE, + sep = "\t", + na.strings = "", + fill = TRUE + ) + + kinsub <- kinasesubstrateraw + + if (any(is.na(kinsub$GENE))) { + kinsub[which(is.na(kinsub$GENE)), "GENE"] <- + as.character(kinsub[which(is.na(kinsub$GENE)), "KINASE"]) + } + + if (any(is.na(kinsub$SUB_GENE))) { + kinsub[which(is.na(kinsub$SUB_GENE)), "SUB_GENE"] <- + as.character(kinsub[which(is.na(kinsub$SUB_GENE)), "SUBSTRATE"]) + } + + kinase <- toupper(kinsub$GENE) + substrate <- toupper(kinsub$SUB_GENE) + + kinsub <- unique(data.frame( + source = kinase, + target = substrate, + stringsAsFactors = FALSE + )) + + kinsub.edges <- kinsub[kinsub$source %in% nodes & kinsub$target %in% nodes, ] + kinsub.edges$interaction <- "pp" + kinsub.edges$Weight <- 1 + + kinsub.edges <- remove.autophos(kinsub.edges) + + if (nrow(kinsub.edges) == 0) { + warning("No kinase-substrate edges passed the filters.") + } + + rownames(kinsub.edges) <- NULL + return(kinsub.edges) } diff --git a/R/MakeClusterList.R b/R/MakeClusterList.R index fbe8ebb6..9a425876 100644 --- a/R/MakeClusterList.R +++ b/R/MakeClusterList.R @@ -23,173 +23,177 @@ #' @export #' #' @examples -#' Example_Output <- MakeClusterList(ex_tiny_ptm_table) #Run function -#' Example_Output[[1]][1:3] #Display data -#' #Do we want to have one for adj.consensus? Doesn't seem like it'd be very helpful to view. -#' utils::head(Example_Output[[3]][, c(1,2,3,4,5)]) #Display data -MakeClusterList <- function(ptmtable, keeplength = 2, toolong = 3.5, tsne_perplexity = 15, tsne_max_iter = 5000){ - start_time <- Sys.time() - message("Starting correlation calculations and t-SNE.") - message("This may take a few minutes or hours for large data sets.") - - # Add if statement here to make sure functions are formatted correctly # - # Ensure ptmtable is a data frame with numeric values # - PTMnames <- rownames(ptmtable) - if (!all(vapply(ptmtable, is.numeric, FUN.VALUE = logical(1) ))) { - stop("All columns in 'ptmtable' must be numeric.") - } - - # SPEARMAN CALCULATION - - # Calculate Spearman correlation # - ptm.correlation.matrix <- stats::cor(t(ptmtable), use = "pairwise.complete.obs", method = "spearman") - # Note: this is the slowest step. We found use = "pairwise.complete.obs", - # method = "spearman" to perform the best according to evaluations with data - # with missing values, but it takes longer. - - # Replace diagonal with NA # - diag(ptm.correlation.matrix) <- NA - - # Calculate dissimilarity # - sp.diss.matrix <- 1 - abs(ptm.correlation.matrix) - - # Handle any remaining NA values by setting them to the maximum dissimilarity # - max.dist.sp <- max(sp.diss.matrix, na.rm = TRUE) - - #Set NA values to 100 * the max distance - sp.diss.matrix[is.na(sp.diss.matrix)] <- 100 * max.dist.sp - message("Spearman correlation calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - - # Run t-SNE # - tsne.results <- Rtsne::Rtsne(sp.diss.matrix, dims = 3, perplexity = tsne_perplexity, theta = 0.25, max_iter = tsne_max_iter, check_duplicates = FALSE, pca = FALSE) - spearman.coords <- tsne.results$Y - message("Spearman t-SNE calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - - #EUCLIDEAN CALCULATION - - # Convert the dataframe to a distance matrix using Euclidean distance # - ptmtable.dist <- as.matrix(stats::dist(ptmtable, method = "euclidean")) - - # Compute the maximum distance in the matrix, excluding NA values # - max.dist <- max(ptmtable.dist, na.rm = TRUE) - - # Replace NA values in the distance matrix with 100 times the maximum distance # - ptmtable.dist[is.na(ptmtable.dist)] <- 100 * max.dist - - # Normalize the distance matrix by scaling it to a range from 0 to 100. This becomes the distance matrix for euclidian distance which we will run Rtsne on# - eu.dist.calc <- 100 * ptmtable.dist / max(ptmtable.dist, na.rm = TRUE) - eu.dist.calc <- as.matrix(eu.dist.calc) #Fix eu.dist.calc RQ - message("Euclidean distance calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - - # Run t-SNE # - eu.ptms.tsne.list <- Rtsne::Rtsne(eu.dist.calc, dims = 3, perplexity = tsne_perplexity, theta = 0.25, max_iter = tsne_max_iter, check_duplicates = FALSE, pca = FALSE) - euclidean.coords <- eu.ptms.tsne.list$Y - message("Euclidean t-SNE calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - - #COMBINED CALCULATION - - #fix spearman thing; so do the exact same thing but no absolute value - sp.diss.calc <- 1 - ptm.correlation.matrix # range goes from (-1 to 1) to (0 to 2) - max.diss.sp <- max(sp.diss.calc, na.rm = TRUE) # find the max value (around 2) - sp.diss.calc[is.na(sp.diss.calc)] <- 50 * max.diss.sp # Set every NA value to 50 * the max dissimilarity - sp.diss.calc <- as.matrix(sp.diss.calc) # turn into a matrix - - #find average - combined.distance <- (sp.diss.calc + eu.dist.calc) / 2 - message("Combined distance calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - # Perform t-SNE on the combined distances # - tsne.result <- Rtsne::Rtsne(combined.distance, dims = 3, perplexity = tsne_perplexity, theta = 0.25, max_iter = tsne_max_iter, check_duplicates = FALSE, pca = FALSE) - sed.coords <- tsne.result$Y - message("SED t-SNE calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - - - #Nested function to analyze result - group <- NULL #Gets rid of check note - clustercreate <- function(tsne_coords, distance_name){ - - # Find clusters that are connected - # this returns a vector with cluster assignment for each PTM - result.disc2 <- vegan::distconnected(stats::dist(tsne_coords), toolong = toolong, trace = FALSE) - - # Create a plot of the clusters, with convex hulll around each cluster - # note that tsne_coords is a matrix with 3 columns (the t-SNE dimensions), but only first two will get plotted - vegan::ordiplot(tsne_coords, display = c(), main=paste("t-SNE Cluster Plot", distance_name)) - vegan::ordihull(tsne_coords, result.disc2, col="red", lwd=2) - - # make a dataframe with one column for PTMs and another for group assignment - result.span.df <- data.frame(PTMnames = PTMnames, group= result.disc2) - - # Convert data frame into a list of clusters (check doesn't like group but it's a column name) - result.span.list <- plyr::dlply(result.span.df, plyr::.(group)) # GROUP LIST ! - - end_time <- Sys.time() - total_time <- end_time - start_time - message("Clustering for ", distance_name, " complete after ", round(total_time, 2), " ", units(total_time), " total.") - return(result.span.list) - } #END of nested function - - #Create all tsne coords data sturcture and give it names - all.tsne.coords <- list(euclidean.coords, spearman.coords, sed.coords) - names(all.tsne.coords) <- c("Euclidean", "Spearman", "SED") - clusters.list <- mapply(clustercreate, all.tsne.coords, names(all.tsne.coords)) - - FindCommonClusters <- function(clusters.list, keeplength=3) { # >>>> NEW method - # For each distance metric's clusters: - # 1. Create a square matrix of all PTMs. - # 2. For each cluster, set all PTM–PTM pairs in the cluster to 1 (indicating co-membership). - # 3. The final matrix for a method has 1 for PTM pairs co-clustered in that method; 0 otherwise. - - co_membership_matrix <- function(clusters, all_ptms) { - # 1. square matrix of all PTMs - mat <- matrix(0, nrow = length(PTMnames), ncol = length(PTMnames), - dimnames = list(PTMnames, PTMnames)) - # 2. For each cluster, set all PTM–PTM pairs in the cluster to 1 (indicating co-membership). - for (cluster in clusters) { - ptms <- cluster$PTMnames - if (length(ptms) > 1) { - mat[ptms, ptms] <- 1 - } - } - diag(mat) <- 0 - return(mat) +#' Example_Output <- MakeClusterList(ex_tiny_ptm_table) # Run function +#' Example_Output[[1]][1:3] # Display data +#' # Do we want to have one for adj.consensus? Doesn't seem like it'd be very helpful to view. +#' utils::head(Example_Output[[3]][, c(1, 2, 3, 4, 5)]) # Display data +MakeClusterList <- function(ptmtable, keeplength = 2, toolong = 3.5, tsne_perplexity = 15, tsne_max_iter = 5000) { + start_time <- Sys.time() + message("Starting correlation calculations and t-SNE.") + message("This may take a few minutes or hours for large data sets.") + + # Add if statement here to make sure functions are formatted correctly # + # Ensure ptmtable is a data frame with numeric values # + PTMnames <- rownames(ptmtable) + if (!all(vapply(ptmtable, is.numeric, FUN.VALUE = logical(1)))) { + stop("All columns in 'ptmtable' must be numeric.") } - adjacency_matrices <- purrr::map(clusters.list, co_membership_matrix, all_ptms=all_ptms) - - # Step 2: Sum the Co-Membership Matrices Across Methods - adj.sum <- Reduce("+", adjacency_matrices) # values: 0 (never), 1, 2, 3 (co-clustered in all 3 methods) - - #Step 3: Build a Consensus Network (for Co-Clustering in All 3 Embeddings) - # Edges: Only keep edges where `adj.sum == 3` (meaning the PTM pair is in the same cluster in all three methods). - adj.consensus.matrix <- (adj.sum == 3) * 1 # This is a neat R trick to convert a logical matrix (`TRUE`/`FALSE`) to a numeric matrix (`1`/`0`) via multiplication. - g <- igraph::graph_from_adjacency_matrix(adj.consensus.matrix, mode="undirected", diag=FALSE) - - # Step 4: Extract Cliques (Consensus Clusters): since clusters partitioned the graph, every clique is a connected component - components <- igraph::components(g) - # filter out components smaller than keeplength parameter and create list of co-clusters - # from existing single-metric clusters - keep_ids <- which(components$csize >= keeplength) - clusters_in_all_three <- lapply(keep_ids, - function(id) {igraph::V(g)$name[components$membership == id]} - ) - if (length(clusters_in_all_three) > 0) { - names(clusters_in_all_three) <- paste0("ConsensusCluster", seq_along(clusters_in_all_three)) - } else { - warning("No cliques remain after filtering with keeplength = ", keeplength) - } + # SPEARMAN CALCULATION + + # Calculate Spearman correlation # + ptm.correlation.matrix <- stats::cor(t(ptmtable), use = "pairwise.complete.obs", method = "spearman") + # Note: this is the slowest step. We found use = "pairwise.complete.obs", + # method = "spearman" to perform the best according to evaluations with data + # with missing values, but it takes longer. + + # Replace diagonal with NA # + diag(ptm.correlation.matrix) <- NA + + # Calculate dissimilarity # + sp.diss.matrix <- 1 - abs(ptm.correlation.matrix) + + # Handle any remaining NA values by setting them to the maximum dissimilarity # + max.dist.sp <- max(sp.diss.matrix, na.rm = TRUE) + + # Set NA values to 100 * the max distance + sp.diss.matrix[is.na(sp.diss.matrix)] <- 100 * max.dist.sp + message("Spearman correlation calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + + # Run t-SNE # + tsne.results <- Rtsne::Rtsne(sp.diss.matrix, dims = 3, perplexity = tsne_perplexity, theta = 0.25, max_iter = tsne_max_iter, check_duplicates = FALSE, pca = FALSE) + spearman.coords <- tsne.results$Y + message("Spearman t-SNE calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + + # EUCLIDEAN CALCULATION + + # Convert the dataframe to a distance matrix using Euclidean distance # + ptmtable.dist <- as.matrix(stats::dist(ptmtable, method = "euclidean")) + + # Compute the maximum distance in the matrix, excluding NA values # + max.dist <- max(ptmtable.dist, na.rm = TRUE) + + # Replace NA values in the distance matrix with 100 times the maximum distance # + ptmtable.dist[is.na(ptmtable.dist)] <- 100 * max.dist + + # Normalize the distance matrix by scaling it to a range from 0 to 100. This becomes the distance matrix for euclidian distance which we will run Rtsne on# + eu.dist.calc <- 100 * ptmtable.dist / max(ptmtable.dist, na.rm = TRUE) + eu.dist.calc <- as.matrix(eu.dist.calc) # Fix eu.dist.calc RQ + message("Euclidean distance calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + + # Run t-SNE # + eu.ptms.tsne.list <- Rtsne::Rtsne(eu.dist.calc, dims = 3, perplexity = tsne_perplexity, theta = 0.25, max_iter = tsne_max_iter, check_duplicates = FALSE, pca = FALSE) + euclidean.coords <- eu.ptms.tsne.list$Y + message("Euclidean t-SNE calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + + # COMBINED CALCULATION + + # fix spearman thing; so do the exact same thing but no absolute value + sp.diss.calc <- 1 - ptm.correlation.matrix # range goes from (-1 to 1) to (0 to 2) + max.diss.sp <- max(sp.diss.calc, na.rm = TRUE) # find the max value (around 2) + sp.diss.calc[is.na(sp.diss.calc)] <- 50 * max.diss.sp # Set every NA value to 50 * the max dissimilarity + sp.diss.calc <- as.matrix(sp.diss.calc) # turn into a matrix + + # find average + combined.distance <- (sp.diss.calc + eu.dist.calc) / 2 + message("Combined distance calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + # Perform t-SNE on the combined distances # + tsne.result <- Rtsne::Rtsne(combined.distance, dims = 3, perplexity = tsne_perplexity, theta = 0.25, max_iter = tsne_max_iter, check_duplicates = FALSE, pca = FALSE) + sed.coords <- tsne.result$Y + message("SED t-SNE calculation complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + + + # Nested function to analyze result + group <- NULL # Gets rid of check note + clustercreate <- function(tsne_coords, distance_name) { + # Find clusters that are connected + # this returns a vector with cluster assignment for each PTM + result.disc2 <- vegan::distconnected(stats::dist(tsne_coords), toolong = toolong, trace = FALSE) + + # Create a plot of the clusters, with convex hulll around each cluster + # note that tsne_coords is a matrix with 3 columns (the t-SNE dimensions), but only first two will get plotted + vegan::ordiplot(tsne_coords, display = c(), main = paste("t-SNE Cluster Plot", distance_name)) + vegan::ordihull(tsne_coords, result.disc2, col = "red", lwd = 2) + + # make a dataframe with one column for PTMs and another for group assignment + result.span.df <- data.frame(PTMnames = PTMnames, group = result.disc2) + + # Convert data frame into a list of clusters (check doesn't like group but it's a column name) + result.span.list <- plyr::dlply(result.span.df, plyr::.(group)) # GROUP LIST ! + + end_time <- Sys.time() + total_time <- end_time - start_time + message("Clustering for ", distance_name, " complete after ", round(total_time, 2), " ", units(total_time), " total.") + return(result.span.list) + } # END of nested function + + # Create all tsne coords data sturcture and give it names + all.tsne.coords <- list(euclidean.coords, spearman.coords, sed.coords) + names(all.tsne.coords) <- c("Euclidean", "Spearman", "SED") + clusters.list <- mapply(clustercreate, all.tsne.coords, names(all.tsne.coords)) + + FindCommonClusters <- function(clusters.list, keeplength = 3) { # >>>> NEW method + # For each distance metric's clusters: + # 1. Create a square matrix of all PTMs. + # 2. For each cluster, set all PTM–PTM pairs in the cluster to 1 (indicating co-membership). + # 3. The final matrix for a method has 1 for PTM pairs co-clustered in that method; 0 otherwise. + + co_membership_matrix <- function(clusters, all_ptms) { + # 1. square matrix of all PTMs + mat <- matrix(0, + nrow = length(PTMnames), ncol = length(PTMnames), + dimnames = list(PTMnames, PTMnames) + ) + # 2. For each cluster, set all PTM–PTM pairs in the cluster to 1 (indicating co-membership). + for (cluster in clusters) { + ptms <- cluster$PTMnames + if (length(ptms) > 1) { + mat[ptms, ptms] <- 1 + } + } + diag(mat) <- 0 + return(mat) + } - end_time <- Sys.time() - message("Consensus clustering complete after ", round(end_time - start_time, 2), " ", units(end_time - start_time), " total.") - return(list(adj.consensus.matrix, clusters_in_all_three)) - } + adjacency_matrices <- purrr::map(clusters.list, co_membership_matrix, all_ptms = all_ptms) + + # Step 2: Sum the Co-Membership Matrices Across Methods + adj.sum <- Reduce("+", adjacency_matrices) # values: 0 (never), 1, 2, 3 (co-clustered in all 3 methods) + + # Step 3: Build a Consensus Network (for Co-Clustering in All 3 Embeddings) + # Edges: Only keep edges where `adj.sum == 3` (meaning the PTM pair is in the same cluster in all three methods). + adj.consensus.matrix <- (adj.sum == 3) * 1 # This is a neat R trick to convert a logical matrix (`TRUE`/`FALSE`) to a numeric matrix (`1`/`0`) via multiplication. + g <- igraph::graph_from_adjacency_matrix(adj.consensus.matrix, mode = "undirected", diag = FALSE) + + # Step 4: Extract Cliques (Consensus Clusters): since clusters partitioned the graph, every clique is a connected component + components <- igraph::components(g) + # filter out components smaller than keeplength parameter and create list of co-clusters + # from existing single-metric clusters + keep_ids <- which(components$csize >= keeplength) + clusters_in_all_three <- lapply( + keep_ids, + function(id) { + igraph::V(g)$name[components$membership == id] + } + ) + if (length(clusters_in_all_three) > 0) { + names(clusters_in_all_three) <- paste0("ConsensusCluster", seq_along(clusters_in_all_three)) + } else { + warning("No cliques remain after filtering with keeplength = ", keeplength) + } + + end_time <- Sys.time() + message("Consensus clustering complete after ", round(end_time - start_time, 2), " ", units(end_time - start_time), " total.") + return(list(adj.consensus.matrix, clusters_in_all_three)) + } - # Find common clusters from existing single-metric clusters - clusters.common.list <- FindCommonClusters(clusters.list, keeplength) - adj.consensus.matrix <- clusters.common.list[[1]] - common.clusters <- clusters.common.list[[2]] + # Find common clusters from existing single-metric clusters + clusters.common.list <- FindCommonClusters(clusters.list, keeplength) + adj.consensus.matrix <- clusters.common.list[[1]] + common.clusters <- clusters.common.list[[2]] - message("MakeClusterList complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + message("MakeClusterList complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - return(list(common.clusters, adj.consensus.matrix, ptm.correlation.matrix)) + return(list(common.clusters, adj.consensus.matrix, ptm.correlation.matrix)) } diff --git a/R/MakeCorrelationNetwork.R b/R/MakeCorrelationNetwork.R index b9fb93fd..8760f33e 100644 --- a/R/MakeCorrelationNetwork.R +++ b/R/MakeCorrelationNetwork.R @@ -19,134 +19,136 @@ #' Example_Output[[1]][1:5, 1:5] #' Example_Output[[2]][1:5, 1:5] #' -MakeCorrelationNetwork <- function(adj.consensus.matrix, ptm.correlation.matrix){ - # Two nested functions for creating the PTM and gene CCCN, respectively - # Use the consensus adjacency matrix to filter PTM correlations, then create a graph and edge files for PTMs and genes - - start_time <- Sys.time() - - MakePTMCCCN <- function(adj.consensus.matrix, ptm.correlation.matrix) { - message("Making PTM CCCN") - # Use only PTM pairs that co-clustered in all three methods (adj.consensus.matrix == 1) - ptm.cccn <- ptm.correlation.matrix[sort(rownames(ptm.correlation.matrix)), sort(colnames(ptm.correlation.matrix))] - ptm.cccn.mask <- adj.consensus.matrix[sort(rownames(adj.consensus.matrix)), sort(colnames(adj.consensus.matrix))] - # Find active ptms before setting 0 to NA - active_flags <- (rowSums(adj.consensus.matrix) > 0) | (colSums(adj.consensus.matrix) > 0) - active_ptms <- rownames(adj.consensus.matrix)[active_flags] - ptm.cccn.mask[ptm.cccn.mask == 0] <- NA # Set 0 to NA for masking - - # Apply mask to Spearman correlations - ptm.cccn[is.na(ptm.cccn.mask)] <- NA - - # Subset the adjacency matrix to include only active PTMs - ptm.cccn.active <- ptm.cccn[active_ptms, active_ptms, drop=FALSE] - - # Remove self-loops - if(any(!is.na(diag(ptm.cccn)))) {diag(ptm.cccn) <- NA} - ptm.cccn <- ptm.cccn.active - - # Make edglist file: - # Replace NA with 0 for igraph compatibility - ptm.cccn0 <- ptm.cccn - ptm.cccn0[is.na(ptm.cccn0)] <- 0 - - # Create igraph object from correlation matrix - ptm.cccn.g <- igraph::graph_from_adjacency_matrix(ptm.cccn0, mode = "lower", diag = FALSE, weighted = TRUE) - - # Extract edge list as data.frame - ptm.cccn.edges <- as.data.frame(igraph::as_edgelist(ptm.cccn.g)) - names(ptm.cccn.edges) <- c("source", "target") - ptm.cccn.edges$Weight <- igraph::edge_attr(ptm.cccn.g, "weight") - - # Classify interaction by correlation value - ptm.cccn.edges$interaction <- "correlation" - ptm.cccn.edges$interaction[ptm.cccn.edges$Weight <= -0.5] <- "negative correlation" - ptm.cccn.edges$interaction[ptm.cccn.edges$Weight >= 0.5] <- "positive correlation" - - message("PTM CCCN complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - return(list(ptm.cccn0, ptm.cccn.g, ptm.cccn.edges)) - } - # - ptm.cccn.list <- MakePTMCCCN(adj.consensus.matrix, ptm.correlation.matrix) - ptm.cccn <- ptm.cccn.list[[1]] - ptm.cccn.g <- ptm.cccn.list[[2]] - ptm.cccn.edges <- ptm.cccn.list[[3]] - # Build the Gene CCCN - # Start from igraph object saved from MakePTMCCCN() - # Double ddply Summing: By grouping and summing in both directions, you ensure the aggregation is performed for both genes in each pair, producing a correctly shaped and labeled gene–gene matrix. - MakeGeneCCCN <- function(ptm.cccn){ - message("Making Gene CCCN") - # ptm.cccn was returned above - gene.cccn <- data.frame(ptm.cccn, row.names = rownames(ptm.cccn), check.rows=TRUE, check.names=FALSE, fix.empty.names = FALSE) - # Check: identical(rownames(gene.cccn), colnames(gene.cccn)) # TRUE - gene.cccn$Gene.Name <- vapply(rownames(gene.cccn), function (x) unlist(strsplit(x, " ", fixed=TRUE))[1], FUN.VALUE = character(1)) - # Use only upper triangle so correlations are not duplicated during the next step - gene.cccn[lower.tri(gene.cccn)] <- NA - - # Sum correlations in one dimension, then the other dimension - gene.cccn2 <- dplyr::summarise( - dplyr::group_by(gene.cccn, .data$Gene.Name), - dplyr::across( - tidyselect::where(is.numeric), - ~sum(.x, na.rm = TRUE) - ) - ) - gene.cccn2 <- as.data.frame(gene.cccn2) - rownames(gene.cccn2) <- gene.cccn2$Gene.Name - gene.cccn2 <- gene.cccn2[, 2:ncol(gene.cccn2)] - # Transform to do the other dimension - gene.cccn2 <- data.frame(t(gene.cccn2)) - gene.cccn2$Gene <- vapply(rownames(gene.cccn2), function (x) unlist(strsplit(x, " ", fixed=TRUE))[1], FUN.VALUE = character(1)) - # Now sum the other dimension - gene.cccn3 <- dplyr::summarise( - dplyr::group_by(gene.cccn2, .data$Gene), - dplyr::across( - tidyselect::where(is.numeric), - ~sum(.x, na.rm = TRUE) - ) - ) - # R likes to put dots in column names, which is a problem for ambiguous gene names and gene names with hyphens - # so just work around the problem (once satisfied that the gene names actually match). - names(gene.cccn3)[2:ncol(gene.cccn3)] <- gene.cccn3$Gene - gene.cccn3 <- as.data.frame(gene.cccn3) - rownames(gene.cccn3) <- gene.cccn3$Gene - gene.cccn.matrix <- as.matrix(gene.cccn3[,2:ncol(gene.cccn3)]) - # Replace 0 with NA in the correlation matrix - gene.cccn.matrix[gene.cccn.matrix==0] <- NA - # Remove self-loops by setting diagonal to NA; diag() requires a matrix - diag(gene.cccn.matrix) <- NA - gene.cccn0 <- gene.cccn.matrix - gene.cccn0[is.na(gene.cccn0)] <- 0 # igraph doesn't like NAs - # For Graphing and making edge lists - gene.cccn.g <- igraph::graph_from_adjacency_matrix(gene.cccn0, mode = "lower", diag = FALSE, weighted = TRUE) - # Extract edge list as data.frame - gene.cccn.edges <- as.data.frame(igraph::as_edgelist(gene.cccn.g)) - names(gene.cccn.edges) <- c("source", "target") - gene.cccn.edges$Weight <- igraph::edge_attr(gene.cccn.g, "weight") - - # Classify interaction by correlation value - gene.cccn.edges$interaction <- "correlation" - gene.cccn.edges$interaction[gene.cccn.edges$Weight <= -0.5] <- "negative correlation" - gene.cccn.edges$interaction[gene.cccn.edges$Weight >= 0.5] <- "positive correlation" - - message("Gene CCCN complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") - return(list(gene.cccn.g, gene.cccn.edges, gene.cccn.matrix)) - } - - gene.cccn.list <- MakeGeneCCCN(ptm.cccn) - gene.cccn.g <- gene.cccn.list[[1]] - gene.cccn.edges <- gene.cccn.list[[2]] - gene.cccn <- gene.cccn.list[[3]] - - # Make a list of nodes for gathering PPI data - gene.cccn.nodes <- unique(c(gene.cccn.edges$source, gene.cccn.edges$target)) - if(length(gene.cccn.nodes) == 0) stop("No genes found (gene.cccn.nodes is empty)") - - ### Return Final Data Structure ### - return (list(ptm.cccn.edges, gene.cccn.edges, gene.cccn.nodes)) - - ### Graphing ### - # graph <- igraph::graph_from_adjacency_matrix(gene.cccn, mode = "lower", diag = FALSE, weighted = "Weight") - # plot(graph) - # Note: Cytoscape is recommended for graphing networks +MakeCorrelationNetwork <- function(adj.consensus.matrix, ptm.correlation.matrix) { + # Two nested functions for creating the PTM and gene CCCN, respectively + # Use the consensus adjacency matrix to filter PTM correlations, then create a graph and edge files for PTMs and genes + + start_time <- Sys.time() + + MakePTMCCCN <- function(adj.consensus.matrix, ptm.correlation.matrix) { + message("Making PTM CCCN") + # Use only PTM pairs that co-clustered in all three methods (adj.consensus.matrix == 1) + ptm.cccn <- ptm.correlation.matrix[sort(rownames(ptm.correlation.matrix)), sort(colnames(ptm.correlation.matrix))] + ptm.cccn.mask <- adj.consensus.matrix[sort(rownames(adj.consensus.matrix)), sort(colnames(adj.consensus.matrix))] + # Find active ptms before setting 0 to NA + active_flags <- (rowSums(adj.consensus.matrix) > 0) | (colSums(adj.consensus.matrix) > 0) + active_ptms <- rownames(adj.consensus.matrix)[active_flags] + ptm.cccn.mask[ptm.cccn.mask == 0] <- NA # Set 0 to NA for masking + + # Apply mask to Spearman correlations + ptm.cccn[is.na(ptm.cccn.mask)] <- NA + + # Subset the adjacency matrix to include only active PTMs + ptm.cccn.active <- ptm.cccn[active_ptms, active_ptms, drop = FALSE] + + # Remove self-loops + if (any(!is.na(diag(ptm.cccn)))) { + diag(ptm.cccn) <- NA + } + ptm.cccn <- ptm.cccn.active + + # Make edglist file: + # Replace NA with 0 for igraph compatibility + ptm.cccn0 <- ptm.cccn + ptm.cccn0[is.na(ptm.cccn0)] <- 0 + + # Create igraph object from correlation matrix + ptm.cccn.g <- igraph::graph_from_adjacency_matrix(ptm.cccn0, mode = "lower", diag = FALSE, weighted = TRUE) + + # Extract edge list as data.frame + ptm.cccn.edges <- as.data.frame(igraph::as_edgelist(ptm.cccn.g)) + names(ptm.cccn.edges) <- c("source", "target") + ptm.cccn.edges$Weight <- igraph::edge_attr(ptm.cccn.g, "weight") + + # Classify interaction by correlation value + ptm.cccn.edges$interaction <- "correlation" + ptm.cccn.edges$interaction[ptm.cccn.edges$Weight <= -0.5] <- "negative correlation" + ptm.cccn.edges$interaction[ptm.cccn.edges$Weight >= 0.5] <- "positive correlation" + + message("PTM CCCN complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + return(list(ptm.cccn0, ptm.cccn.g, ptm.cccn.edges)) + } + # + ptm.cccn.list <- MakePTMCCCN(adj.consensus.matrix, ptm.correlation.matrix) + ptm.cccn <- ptm.cccn.list[[1]] + ptm.cccn.g <- ptm.cccn.list[[2]] + ptm.cccn.edges <- ptm.cccn.list[[3]] + # Build the Gene CCCN + # Start from igraph object saved from MakePTMCCCN() + # Double ddply Summing: By grouping and summing in both directions, you ensure the aggregation is performed for both genes in each pair, producing a correctly shaped and labeled gene–gene matrix. + MakeGeneCCCN <- function(ptm.cccn) { + message("Making Gene CCCN") + # ptm.cccn was returned above + gene.cccn <- data.frame(ptm.cccn, row.names = rownames(ptm.cccn), check.rows = TRUE, check.names = FALSE, fix.empty.names = FALSE) + # Check: identical(rownames(gene.cccn), colnames(gene.cccn)) # TRUE + gene.cccn$Gene.Name <- vapply(rownames(gene.cccn), function(x) unlist(strsplit(x, " ", fixed = TRUE))[1], FUN.VALUE = character(1)) + # Use only upper triangle so correlations are not duplicated during the next step + gene.cccn[lower.tri(gene.cccn)] <- NA + + # Sum correlations in one dimension, then the other dimension + gene.cccn2 <- dplyr::summarise( + dplyr::group_by(gene.cccn, .data$Gene.Name), + dplyr::across( + tidyselect::where(is.numeric), + ~ sum(.x, na.rm = TRUE) + ) + ) + gene.cccn2 <- as.data.frame(gene.cccn2) + rownames(gene.cccn2) <- gene.cccn2$Gene.Name + gene.cccn2 <- gene.cccn2[, 2:ncol(gene.cccn2)] + # Transform to do the other dimension + gene.cccn2 <- data.frame(t(gene.cccn2)) + gene.cccn2$Gene <- vapply(rownames(gene.cccn2), function(x) unlist(strsplit(x, " ", fixed = TRUE))[1], FUN.VALUE = character(1)) + # Now sum the other dimension + gene.cccn3 <- dplyr::summarise( + dplyr::group_by(gene.cccn2, .data$Gene), + dplyr::across( + tidyselect::where(is.numeric), + ~ sum(.x, na.rm = TRUE) + ) + ) + # R likes to put dots in column names, which is a problem for ambiguous gene names and gene names with hyphens + # so just work around the problem (once satisfied that the gene names actually match). + names(gene.cccn3)[2:ncol(gene.cccn3)] <- gene.cccn3$Gene + gene.cccn3 <- as.data.frame(gene.cccn3) + rownames(gene.cccn3) <- gene.cccn3$Gene + gene.cccn.matrix <- as.matrix(gene.cccn3[, 2:ncol(gene.cccn3)]) + # Replace 0 with NA in the correlation matrix + gene.cccn.matrix[gene.cccn.matrix == 0] <- NA + # Remove self-loops by setting diagonal to NA; diag() requires a matrix + diag(gene.cccn.matrix) <- NA + gene.cccn0 <- gene.cccn.matrix + gene.cccn0[is.na(gene.cccn0)] <- 0 # igraph doesn't like NAs + # For Graphing and making edge lists + gene.cccn.g <- igraph::graph_from_adjacency_matrix(gene.cccn0, mode = "lower", diag = FALSE, weighted = TRUE) + # Extract edge list as data.frame + gene.cccn.edges <- as.data.frame(igraph::as_edgelist(gene.cccn.g)) + names(gene.cccn.edges) <- c("source", "target") + gene.cccn.edges$Weight <- igraph::edge_attr(gene.cccn.g, "weight") + + # Classify interaction by correlation value + gene.cccn.edges$interaction <- "correlation" + gene.cccn.edges$interaction[gene.cccn.edges$Weight <= -0.5] <- "negative correlation" + gene.cccn.edges$interaction[gene.cccn.edges$Weight >= 0.5] <- "positive correlation" + + message("Gene CCCN complete after ", round(Sys.time() - start_time, 2), " ", units(Sys.time() - start_time), " total.") + return(list(gene.cccn.g, gene.cccn.edges, gene.cccn.matrix)) + } + + gene.cccn.list <- MakeGeneCCCN(ptm.cccn) + gene.cccn.g <- gene.cccn.list[[1]] + gene.cccn.edges <- gene.cccn.list[[2]] + gene.cccn <- gene.cccn.list[[3]] + + # Make a list of nodes for gathering PPI data + gene.cccn.nodes <- unique(c(gene.cccn.edges$source, gene.cccn.edges$target)) + if (length(gene.cccn.nodes) == 0) stop("No genes found (gene.cccn.nodes is empty)") + + ### Return Final Data Structure ### + return(list(ptm.cccn.edges, gene.cccn.edges, gene.cccn.nodes)) + + ### Graphing ### + # graph <- igraph::graph_from_adjacency_matrix(gene.cccn, mode = "lower", diag = FALSE, weighted = "Weight") + # plot(graph) + # Note: Cytoscape is recommended for graphing networks } diff --git a/R/data_documentation.R b/R/data_documentation.R index e2bb8e3f..a9678fbd 100644 --- a/R/data_documentation.R +++ b/R/data_documentation.R @@ -123,11 +123,11 @@ #' PTM CCCN Edgelist -#' +#' #' PTM Co-Cluster Correlation Network as an Edgelist -#' +#' #' @keywords internal -#' +#' #' @format Data Frame of 5710 rows and 4 columns #' \describe{ #' \item{source}{A PTM} @@ -141,11 +141,11 @@ "ex_ptm_cccn_edges" #' Gene CCCN Edgelist -#' +#' #' Gene Co-Cluster Correlation Network as an Edgelist -#' +#' #' @keywords internal -#' +#' #' @format Data Frame of 2279 rows and 4 columns #' \describe{ #' \item{source}{A Gene} @@ -159,11 +159,11 @@ "ex_gene_cccn_edges" #' Gene list (nodes) -#' +#' #' List of Genes gotten from PTMs -#' +#' #' @keywords internal -#' +#' #' @format Character Vector of length 390 #' \describe{ #' \item{String}{Gene} @@ -196,11 +196,11 @@ #' Genemania Edges -#' +#' #' Edges from gene_cccn_nodes that are also found within the Genemania database -#' +#' #' @keywords internal -#' +#' #' @format Dataframe of 5 rows and 4 columns #' \describe{ #' \item{source}{First gene interacting} @@ -213,16 +213,15 @@ #' head(ex_genemania_edges) "ex_genemania_edges" -# DO KINSUB EDGES - +# DO KINSUB EDGES #' Combined PPIs -#' +#' #' Combines all Protein to Protein interaction data and combines their weights into one score from a range of 0 to 1 -#' +#' #' @keywords internal -#' +#' #' @format Dataframe of 8849 rows and 4 columns #' \describe{ #' \item{source}{First gene interacting} @@ -236,11 +235,11 @@ "ex_combined_ppi" #' Cfn -#' +#' #' The Cluster Filtered Network is a filtered version of the Combined PPIs but only for proteins found to interact via t-SNE clusters -#' +#' #' @keywords internal -#' +#' #' @format Dataframe of 196 rows and 4 columns #' \describe{ #' \item{source}{First gene interacting} @@ -256,11 +255,11 @@ #' Bioplanet -#' +#' #' A database file from the bioplanet website -#' +#' #' @keywords internal -#' +#' #' @format Dataframe of 196 rows and 4 columns #' \describe{ #' \item{PATHWAY_ID}{The pathway a gene belongs to} @@ -274,11 +273,11 @@ "ex_bioplanet" #' Pathway Crosstalk Network -#' +#' #' Specific genes from the bioplanet file that we found in Common Clusters and their respective cell signaling pathway relationships -#' +#' #' @keywords internal -#' +#' #' @format Dataframe of 37 rows and 4 columns #' \describe{ #' \item{source}{The name of a pathway interacting} @@ -292,11 +291,11 @@ "ex_pathway_crosstalk_network" #' PCN Edge List -#' +#' #' The Pathway Crosstalk Network but with direct values for both weight types -#' +#' #' @keywords internal -#' +#' #' @format Dataframe of 19 rows and 4 columns #' \describe{ #' \item{source}{The name of a pathway interacting} @@ -310,11 +309,11 @@ "ex_PCNedgelist" #' Pathways list -#' +#' #' A list of pathway names and the genes that make them up -#' +#' #' @keywords internal -#' +#' #' @format list of 9 character vectors #' \describe{ #' \item{Pathway name}{Genes in the pathway} @@ -325,18 +324,18 @@ "ex_pathways_list" #' BRCA Cluster List Data -#' +#' #' Output of MakeClusterList on the BRCA data -#' +#' #' @keywords internal -#' +#' #' @format list containing the 3 outputs of MakeClusterList #' \describe{ #' \item{Common Clusters}{231 lists of PTMs found to cluster, see Common Clusters} #' \item{Adjacency Matrix}{4237 x 4237, see Adjacency Consensus Matrix} #' \item{PTM correlation matrix}{4237 x 4237, see Correlation Matrix} -#' } -#' +#' } +#' #' @source "MakeClusterList on the BRCA data" #' @examples #' head("brca_clusterlist_data[[1]]") @@ -345,29 +344,29 @@ "brca_clusterlist_data" # BRCA CCCN Data -#' +#' #' Output of MakeCorrelationNetwork on the BRCA data -#' +#' #' @keywords internal "brca_CCCN_data" #' BRCA STRINGdb Edges -#' +#' #' Edges from the STRINGdb database for the genes in the BRCA dataset -#' +#' #' @keywords internal "BRCA_stringdb.edges" #' BRCA Genemania Edges -#' +#' #' Edges from the Genemania database for the genes in the BRCA dataset -#' +#' #' @keywords internal "BRCA_genemania.edges" #' BRCA PCN Data -#' +#' #' Output of BuildPathwayCrosstalkNetwork on the BRCA data -#' +#' #' @keywords internal -"BRCA_PCN.data" \ No newline at end of file +"BRCA_PCN.data" From e2f77980ac61495a0ee6aeceb8f2927fc40118c5 Mon Sep 17 00:00:00 2001 From: Gram-S Date: Wed, 17 Jun 2026 19:05:19 -0600 Subject: [PATCH 5/5] readd deleted files --- R/BRCAHelpers.R | 341 ++++++ R/CytoscapeGraphingFunctions.R | 983 ++++++++++++++++++ R/PathwayCrosstalkNetwork.R | 249 +++++ R/RawDataProcessing.R | 183 ++++ man/BuildPathwayCrosstalkNetwork.Rd | 37 + man/ReadBioplanetFile.Rd | 19 + man/filter.edges.0.Rd | 293 ++++++ man/fix.excel.Rd | 22 + man/grapes-w-slash-o-grapes.Rd | 23 + man/graph.ptm.by.cluster.Rd | 92 ++ man/make.genepep.edges.Rd | 14 + man/merge2cols.Rd | 26 + man/name.peptide.Rd | 32 + man/ptms_to_cfn.Rd | 35 + man/remove.autophos.Rd | 14 + man/setCorrEdgeAppearance.Rd | 11 + man/setNodeSizeColorIndependently.Rd | 68 ++ .../test_buildpathwaycrosstalknetwork.R | 56 + vignettes/BRCANetworks.Rmd | 524 ++++++++++ vignettes/CreatingNetworks.Rmd | 463 +++++++++ vignettes/CytoscapeGraphing.Rmd | 365 +++++++ vignettes/RawDataProcessing.Rmd | 421 ++++++++ 22 files changed, 4271 insertions(+) create mode 100644 R/BRCAHelpers.R create mode 100644 R/CytoscapeGraphingFunctions.R create mode 100644 R/PathwayCrosstalkNetwork.R create mode 100644 R/RawDataProcessing.R create mode 100644 man/BuildPathwayCrosstalkNetwork.Rd create mode 100644 man/ReadBioplanetFile.Rd create mode 100644 man/filter.edges.0.Rd create mode 100644 man/fix.excel.Rd create mode 100644 man/grapes-w-slash-o-grapes.Rd create mode 100644 man/graph.ptm.by.cluster.Rd create mode 100644 man/make.genepep.edges.Rd create mode 100644 man/merge2cols.Rd create mode 100644 man/name.peptide.Rd create mode 100644 man/ptms_to_cfn.Rd create mode 100644 man/remove.autophos.Rd create mode 100644 man/setCorrEdgeAppearance.Rd create mode 100644 man/setNodeSizeColorIndependently.Rd create mode 100644 tests/testthat/test_buildpathwaycrosstalknetwork.R create mode 100644 vignettes/BRCANetworks.Rmd create mode 100644 vignettes/CreatingNetworks.Rmd create mode 100644 vignettes/CytoscapeGraphing.Rmd create mode 100644 vignettes/RawDataProcessing.Rmd diff --git a/R/BRCAHelpers.R b/R/BRCAHelpers.R new file mode 100644 index 00000000..d9fc7023 --- /dev/null +++ b/R/BRCAHelpers.R @@ -0,0 +1,341 @@ +# ============================================================ +# graph.ptm.by.cluster() +# Large phosphopeptide heatmap, grouped by consensus cluster +# - Blue (down) -> light grey (neutral/zero) -> Yellow (up) +# - NA values drawn in black +# - Color key and cluster legend saved as separate PDFs +# - Cluster side-bar colors are high-contrast, non-rainbow +# ============================================================ + +#' Heatmap of Phosphopeptides Grouped by Cluster +#' +#' Creates a large phosphopeptide heatmap colored by signal intensity, grouped +#' by consensus cluster with high-contrast cluster color bars. Supports custom +#' row ordering and optional legend/heatkey output files. +#' +#' @param ptmtable A numeric matrix with PTMs as rows and samples as columns. +#' @param common.clusters A named list where each element is a character vector +#' of PTM identifiers belonging to that cluster. +#' @param filename Output filename for the main heatmap PDF. Defaults to +#' `"ptm_by_cluster_heatmap.pdf"`. +#' @param legend.filename Optional filename for a separate PDF cluster legend. +#' If `NULL`, no legend file is written. +#' @param heatkey.filename Optional filename for a separate PDF color scale key. +#' If `NULL`, no heatkey file is written. +#' @param cluster.order Character vector specifying the order in which clusters +#' are displayed. Defaults to `names(common.clusters)`. +#' @param order.rows Method to order rows within clusters: `"as.is"` (no reordering), +#' `"mean"` (by row-wise mean), `"slope"` (by linear trend), or `"hclust"` +#' (hierarchical clustering). Defaults to `"as.is"`. +#' @param cluster.cols Logical; if `TRUE`, columns are also hierarchically +#' clustered. Defaults to `FALSE`. +#' @param zlim Numeric; the upper/lower limit for the heatmap color scale +#' (values are clipped to `[-zlim, zlim]`). Defaults to `3`. +#' @param min.shared Minimum number of shared finite values required to compute +#' distance between two samples. Defaults to `2`. +#' @param show.row.labels Logical; show row labels (PTM identifiers). +#' Defaults to `FALSE`. +#' @param show.col.labels Logical; show column labels (sample names). +#' Defaults to `TRUE`. +#' @param row_cex Character expansion factor for row labels. Defaults to `0.2`. +#' @param col_cex Character expansion factor for column labels. Defaults to `0.8`. +#' @param main Plot title. Defaults to `"Phosphopeptides grouped by cluster"`. +#' +#' @return Invisibly returns a list containing: +#' - `mat`: The clustered expression matrix. +#' - `blocks`: List of ordered sub-matrices, one per cluster. +#' - `rowsep`: Row indices separating clusters. +#' - `cluster.palette`: Named vector of cluster colors. +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' res <- graph.ptm.by.cluster( +#' ptmtable = ptmtable, +#' common.clusters = common.clusters, +#' filename = "ptm_heatmap.pdf", +#' order.rows = "hclust" +#' ) +#' } +#' +#' @importFrom gplots heatmap.2 +#' @importFrom grDevices hcl colorRampPalette +#' +graph.ptm.by.cluster <- function( + ptmtable, + common.clusters, + filename = "ptm_by_cluster_heatmap.pdf", + legend.filename = NULL, # separate cluster-color legend PDF + heatkey.filename = NULL, # separate blue-grey-yellow scale PDF + cluster.order = names(common.clusters), + order.rows = c("as.is", "mean", "slope", "hclust"), + cluster.cols = FALSE, + zlim = 3, + min.shared = 2, + show.row.labels = FALSE, + show.col.labels = TRUE, + row_cex = 0.2, + col_cex = 0.8, + main = "Phosphopeptides grouped by cluster" +) { + + # ---- helpers ------------------------------------------------------- + + order.rows <- match.arg(order.rows) + + ## NA-aware Euclidean distance (pairwise column overlap) + safe_dist2 <- function(m, min.shared = 2) { + m <- as.matrix(m) + nr <- nrow(m) + if (nr <= 1) return(as.dist(matrix(0, nr, nr))) + dmat <- matrix(0, nr, nr) + rownames(dmat) <- colnames(dmat) <- rownames(m) + for (i in seq_len(nr - 1)) { + for (j in (i + 1):nr) { + ok <- is.finite(m[i, ]) & is.finite(m[j, ]) + d <- if (sum(ok) >= min.shared) sqrt(sum((m[i,ok] - m[j,ok])^2)) else NA_real_ + dmat[i,j] <- dmat[j,i] <- d + } + } + fv <- dmat[upper.tri(dmat) & is.finite(dmat)] + fallback <- if (length(fv)) 2 * max(fv) else 1 + dmat[!is.finite(dmat)] <- fallback + diag(dmat) <- 0 + as.dist(dmat) + } + + ## row-wise linear slope helper for order.rows = "slope" + row_slope <- function(v) { + ok <- which(is.finite(v)) + if (length(ok) < 2) return(NA_real_) + coef(lm(v[ok] ~ ok))[2] + } + + ## high-contrast qualitative palette for cluster sidebar + ## Strategy: fixed set of maximally-distinct hues, then when n > set size + ## fall back to HCL with large hue steps so adjacent bars never look similar. + make_cluster_colors <- function(n) { + # 22 hand-picked colors spanning very different hues AND luminances + base.cols <- c( + "#E41A1C", # red + "#377EB8", # blue + "#4DAF4A", # green + "#FF7F00", # orange + "#984EA3", # purple + "#A65628", # brown + "#00CED1", # dark turquoise + "#F781BF", # pink + "#1B9E77", # teal + "#FFD92F", # yellow + "#7570B3", # lavender + "#E7298A", # hot pink + "#66A61E", # olive green + "#E6AB02", # gold + "#D95F02", # burnt orange + "#66C2A5", # mint + "#FC8D62", # salmon + "#8DA0CB", # periwinkle + "#A6D854", # lime + "#999999", # grey + "#A6761D", # khaki + "#666666" # dark grey + ) + if (n <= length(base.cols)) { + return(base.cols[seq_len(n)]) + } + # For very large n: generate HCL colors stepping by large hue angle + # (golden-angle spacing ~137.5 deg) to maximise adjacent contrast + hues <- ((seq_len(n) - 1) * 137.508) %% 360 + grDevices::hcl(h = hues, c = 70, l = 55) + } + + ## standalone cluster-color legend PDF + write_cluster_legend <- function(cluster.palette, filename, cex = 0.9) { + n <- length(cluster.palette) + ncol <- if (n > 18) 2L else 1L + pdf(filename, + width = max(5, 3 * ncol), + height = max(4, 0.32 * ceiling(n / ncol) + 1)) + on.exit(dev.off()) + par(mar = c(0.3, 0.3, 0.3, 0.3), xpd = NA) + plot.new() + legend("topleft", + inset = 0.01, + legend = names(cluster.palette), + fill = cluster.palette, + border = NA, + bty = "n", + cex = cex, + ncol = ncol) + } + + ## standalone heat-scale (colour key) PDF + ## image() requires: nrow(z) == length(x)-1, ncol(z) == length(y)-1 + write_heat_key <- function(filename, zlim = 3) { + n <- 256L # number of colour cells + cols <- colorRampPalette(c("#3333FF","#E6E6E6","#FFFF00"), + space = "rgb", interpolate = "linear")(n) + # breakpoints: length n+1 so the z matrix is (1 x n) + brks <- seq(-zlim, zlim, length.out = n + 1L) + z <- matrix(seq_len(n), nrow = 1L, ncol = n) # 1 x n + + pdf(filename, width = 1.8, height = 4.5) + on.exit(dev.off()) + # left margin wide enough for the bar; right margin for axis labels + par(mar = c(2, 1.0, 1.5, 2.8)) + # x: 2 breakpoints = 1 column; y: n+1 breakpoints = n rows + image(x = c(0, 1), y = brks, z = z, + col = cols, xaxt = "n", yaxt = "n", xlab = "", ylab = "") + axis(4, at = pretty(c(-zlim, zlim)), las = 2, cex.axis = 0.85) + mtext("Signal", side = 4, line = 1.8, cex = 0.85) + box() + } + + # ---- data prep ----------------------------------------------------- + + ptmtable <- as.matrix(ptmtable) + rownames(ptmtable) <- trimws(rownames(ptmtable)) + names(common.clusters) <- trimws(names(common.clusters)) + common.clusters <- lapply(common.clusters, trimws) + + # drop rows that are entirely non-finite + ptmtable <- ptmtable[rowSums(is.finite(ptmtable)) > 0, , drop = FALSE] + + cluster.order <- intersect(cluster.order, names(common.clusters)) + + ordered.blocks <- list() + cluster.id <- character(0) + + for (cl in cluster.order) { + pep <- unique(common.clusters[[cl]]) + pep <- pep[pep %in% rownames(ptmtable)] + if (!length(pep)) next + subm <- ptmtable[pep, , drop = FALSE] + + if (nrow(subm) > 1) { + subm <- switch(order.rows, + mean = subm[order(rowMeans(subm, na.rm=TRUE), decreasing=TRUE, na.last=TRUE), , drop=FALSE], + slope = { sl <- apply(subm, 1, row_slope) + subm[order(sl, decreasing=TRUE, na.last=TRUE), , drop=FALSE] }, + hclust = subm[hclust(safe_dist2(subm, min.shared))$order, , drop=FALSE], + subm # "as.is" + ) + } + + ordered.blocks[[cl]] <- subm + cluster.id <- c(cluster.id, rep(cl, nrow(subm))) + } + + if (!length(ordered.blocks)) stop("No peptides from common.clusters matched rownames(ptmtable).") + + mat <- do.call(rbind, ordered.blocks) + + if (cluster.cols && ncol(mat) > 1) { + mat <- mat[, hclust(safe_dist2(t(mat), min.shared))$order, drop=FALSE] + } + + cluster.sizes <- sapply(ordered.blocks, nrow) + rowsep <- cumsum(cluster.sizes)[-length(cluster.sizes)] # no sep after last block + + # ---- colour setup -------------------------------------------------- + + heatcols <- colorRampPalette(c("#3333FF","#E6E6E6","#FFFF00"), + space="rgb", interpolate="linear")(255) + breaks <- seq(-zlim, zlim, length.out = 256) + + cluster.palette <- make_cluster_colors(length(ordered.blocks)) + names(cluster.palette) <- names(ordered.blocks) + row.side.colors <- unname(cluster.palette[cluster.id]) + + # ---- device sizing ------------------------------------------------- + # Width: accommodate column labels; Height: 1 px per row ~ 1/72 inch + # Give at least 10 inches height so the plot region is never microscopic. + + pdf.w <- max(14, ncol(mat) / 5) + pdf.h <- max(10, nrow(mat) / 40) + + # ---- heatmap (key = FALSE → no internal key panel) --------------- + + pdf(filename, width = pdf.w, height = pdf.h) + gplots::heatmap.2( + mat, + Rowv = FALSE, + Colv = FALSE, + dendrogram = "none", + trace = "none", + scale = "none", + col = heatcols, + breaks = breaks, + na.color = "black", + key = FALSE, # << prevents the "margins too large" crash + symbreaks = FALSE, + density.info = "none", + labRow = if (show.row.labels) rownames(mat) else NA, + labCol = if (show.col.labels) colnames(mat) else NA, + RowSideColors = row.side.colors, + # Narrow the RowSideColors sidebar: lwid[1]=sidebar, lwid[2]=body + lwid = c(0.1, 8), + lhei = c(0.5, 4), + rowsep = rowsep, + sepcolor = "white", + sepwidth = c(0.003, 0.003), + cexRow = row_cex, + cexCol = col_cex, + margins = c(10, 6), + main = main + ) + dev.off() + message("Heatmap written to: ", filename) + + # ---- optional legend files ---------------------------------------- + + if (!is.null(legend.filename)) { + write_cluster_legend(cluster.palette, legend.filename) + message("Cluster legend written to: ", legend.filename) + } + + if (!is.null(heatkey.filename)) { + write_heat_key(heatkey.filename, zlim = zlim) + message("Heat key written to: ", heatkey.filename) + } + + invisible(list( + mat = mat, + blocks = ordered.blocks, + rowsep = rowsep, + cluster.palette = cluster.palette + )) +} + + +# ============================================================ +# Example call — adjust paths/names to match your session +# ============================================================ +# +# res <- graph.ptm.by.cluster( +# ptmtable = ptmtable, +# common.clusters = common.clusters, # use all clusters +# filename = "ptm_all_clusters.pdf", +# legend.filename = "ptm_legend.pdf", +# heatkey.filename = "ptm_heatkey.pdf", +# order.rows = "hclust", +# zlim = 3, +# show.row.labels = FALSE, +# show.col.labels = TRUE, +# col_cex = 0.7 +# ) +# +# Inspect colour mapping: +# res$cluster.palette +# +# Subset to first 3 clusters only: +# res3 <- graph.ptm.by.cluster( +# ptmtable = ptmtable, +# common.clusters = common.clusters[1:3], +# filename = "ptm_clusters_1_3.pdf", +# legend.filename = "ptm_clusters_1_3_legend.pdf", +# heatkey.filename= "ptm_clusters_1_3_heatkey.pdf", +# order.rows = "hclust", +# zlim = 3 +# ) diff --git a/R/CytoscapeGraphingFunctions.R b/R/CytoscapeGraphingFunctions.R new file mode 100644 index 00000000..79d02a2e --- /dev/null +++ b/R/CytoscapeGraphingFunctions.R @@ -0,0 +1,983 @@ +#' @title Graphing and Manipulating Cluster Filtered Network +#' +#' @description Creates a cytoscape graph of the cluster filtered network. Ensure that you have the Cytoscape app open and the RCy3 package downloaded. +#' The package RCy3 is required for many of the functions in this script. To download, run: +#' - if (!require("BiocManager", quietly = TRUE)) install.packages("BiocManager") +#' - BiocManager::install("RCy3") +#' It is also recommended (but not required) that users load RCy3 with library(RCy3) to access its functions directly. +#' +#' @details Graph Key +#' - Node Size +#' - Greater the node size, larger the absolute value of the score +#' - Blue Node +#' - Negative score +#' - Yellow Node +#' - Positive score +#' - Green Node +#' - Approximately zero score +#' Node Shapes: +#' - "ELLIPSE" +#' - unknown +#' - "ROUND_RECTANGLE" +#' - receptor tyrosine kinase +#' - "VEE" +#' - SH2 protein +#' or +#' - SH2-SH3 protein +#' - "TRIANGLE" +#' - SH3 protein +#' - "HEXAGON" +#' - tyrosine kinase +#' - "DIAMOND" +#' - SRC-family kinase +#' - "OCTAGON" +#' - kinase +#' or +#' - phosphatase +#' - "PARALLELOGRAM" +#' - transcription factor +#' - "RECTANGLE" +#' - RNA binding protein +#' Node Border Colors: +#' - Orange +#' - deacetylase +#' or +#' - acetyltransferase +#' - Blue +#' - demethylase +#' or +#' - methyltransferase +#' - Royal Purple +#' - membrane protein +#' - Red +#' - kinase +#' or +#' - tyrosine kinase +#' or +#' - SRC-family kinase +#' - Yellow +#' - phosphatase +#' or +#' - tyrosine phosphatase +#' - Lilac +#' - G protein-coupled receptor +#' or +#' - receptor tyrosine kinase +#' - Grey +#' - default +#' - Edge Thickness +#' - Stronger correlation +#'Edge Colors: +#' - Red +#' - Phosphorylation +#' or +#' - pp +#' or +#' - controls-phosphorylation-of +#' - Bright Magenta +#' - controls-expression-of +#' - Dull Magenta +#' - controls-transport-of +#' - Purple +#' - controls-state-change-of +#' - Blood Orange +#' - Acetylation +#' - Lime Green +#' - Phystical interactions +#' - Green +#' - BioPlex +#' - Dull Green +#' - in-complex-with +#' - Seafoam Green +#' - experiments +#' or +#' - experiments_transferred +#' - Cyan +#' - database +#' or +#' - database_transferred +#' - Teal +#' - Pathway +#' or +#' - Predicted +#' - Dark Turquoise +#' - Genetic interactions +#' - Yellow-Orange +#' - correlation +#' - Royal Blue +#' - negative correlation +#' - Bright Yellow +#' - positive correlation +#' - Grey +#' - combined_score +#' - Dark Grey +#' - merged +#' - Light Grey +#' - intersect +#' - Black +#' - peptide +#' - Orange +#' - homology +#' - Dull Orange +#' - Shared protein domains +#' - White +#' - Default +#' Arrow Types: +#' - Arrow +#' - Phosphorylation +#' or +#' - pp +#' or +#' - controls-phosphorylation-of +#' or +#' - controls-expression-of +#' or +#' - controls-transport-of +#' or +#' - controls-state-change-of +#' or +#' - Acetylation +#' - No Arrow +#' - Default +#' +#' @param cfn A version of ppi.network with only the edges that exist in cccn.matrix and have non-zero weights +#' @param ptmtable A dataset for post-translational modifications. Formatted with numbered rows, and the first column containing PTM names. The rest of the column names should be drugs. Values are numeric values that represent how much the PTM has reacted to the drug. +#' @param funckey A table graphing gene names to type of protein; defaults to the internal dataset `PTMsToPathways::function_key` +#' @param Network.title Desired title for the created Cytoscape Network; defaults to "cfn" +#' @param Network.collection Desired name for the collection created on Cytoscape in which the network will reside; defaults to "PTMsToPathways" +#' @param visual.style.name Desired name for the visual style created on Cytoscape; defaults to "PTMsToPathways.style" +#' +#' @param background.color Hex code of background color of graph; defaults to '#fcf3cf' +#' @param edge.label.color Hex code of edge label color of graph; defaults to '#17202a' +#' @param node.label.color Hex code of node label color of graph; defaults to '#145a32' +#' +#' @param default.font Font style of edge and node names; defaults to "Times New Roman" +#' @param node.font.size Font size of the node name; defaults to 12 +#' @param edge.font.size Font size of the edge name; defaults to 8 +#' +#' @param edge.line.style Type of edge style; defaults to "SOLID"; options include: "PARALLEL_LINES", "MARQUEE_EQUAL", "DOT", "EQUAL_DASH", "LONG_DASH", "CONTIGUOUS_ARROW", "MARQUEE_DASH", "DASH_DOT", "BACKWARD_SLASH", "FORWARD_SLASH", "VERTICAL_SLASH", "SOLID", "SEPARATE_ARROW", "MARQUEE_DASH_DOT", "ZIGZAG", "SINEWAVE" +#' +#' @param edge.opacity Opacity of the edge line on a scale of 0 - 255 with 0 being transparent; defaults to 175 +#' @param edge.label.opacity Opacity of the edge label on a scale of 0 - 255 with 0 being transparent; defaults to 255 +#' @param border.opacity Opacity of the node border on a scale of 0 - 255 with 0 being transparent; defaults to 255 +#' @param node.label.opacity Opacity of the node label on a scale of 0 - 255 with 0 being transparent; defaults to 255 +#' @param node.fill.opacity Opacity of the node fill on a scale of 0 - 255 with 0 being transparent; defaults to 255 +#' +#' @return A cytoscape graph of the cluster filtered network +#' @export +#' +#' @examples +#' # GraphCFN(ex.cfn) +#' # See vignette for default graph +#' +# + +# helper functions for networks in R: + +# function to filter networks to include only selected nodes and those with edges to them +#' @param nodenames +#' +#' @param edge.file +#' +#' @export +filter.edges.0 <- function(nodenames, edge.file) { + nodenames <-as.character(nodenames) + a = as.character(edge.file[,1]) + b = as.character(edge.file[,2]) + edgefile.nodes <- unique(c(a,b)) + sel.edges <- edge.file[edge.file[,1] %in% nodenames & edge.file[,2] %in% nodenames,] + if(dim(sel.edges)[1] == 0) {return(NA)} else return(sel.edges) +} +# +#function to filter networks and to get first order connected nodes +#' @param nodenames +#' +#' @param edge.file +#' +#' @export +filter.edges.1 <- function(nodenames, edge.file) { + nodenames <-as.character(nodenames) + a = as.character(edge.file[,1]) + b = as.character(edge.file[,2]) + edgefile.nodes <- unique(c(a,b)) + sel.edges.1 <- edge.file[edge.file[,1] %in% nodenames,] + sel.edges.2 <- edge.file[edge.file[,2] %in% nodenames,] + sel.edges <- rbind(sel.edges.1, sel.edges.2) + if(dim(sel.edges)[1] == 0) {return(NA)} else { + return(unique(sel.edges)) } +} + +# This function narrows the search only for edges between two sets of nodes +#' @param nodes1 +#' +#' @param nodes2 +#' @param edge.file +#' @param convert +#' +#' @export +filter.edges.between <- function(nodes1, nodes2, edge.file, convert=FALSE) { + sel.edges1 <- edge.file[edge.file[,1] %in% nodes1 & edge.file[,2]%in% nodes2,] + sel.edges2 <- edge.file[edge.file[,1] %in% nodes2 & edge.file[,2]%in% nodes1,] + sel.edges <- rbind(sel.edges1, sel.edges2) + if(dim(sel.edges)[1] == 0) {return(NA)} else return(sel.edges) +} + +# connectNodes.all uses all_shortest_paths and returns just the edge file +#' @param nodepair +#' +#' @param ig.graph +#' @param edgefile +#' @param newgraph +#' +#' @export +connectNodes.all <- function(nodepair, ig.graph=NULL, edgefile, newgraph=FALSE) { + if (newgraph==TRUE) { + ig.graph <- igraph::graph_from_data_frame(edgefile, directed=FALSE) } + sp <- igraph::all_shortest_paths(graph= ig.graph, from=nodepair[1], to=nodepair[2], mode="all") + path.nodeslist <- unique(lapply(sp[[1]], names)) + edges.list <- lapply(path.nodeslist, filter.edges.0, edge.file=edgefile) + path.edges <- unique(plyr::ldply(edges.list)) + return(path.edges) +} +# This function names the edges the way Cytoscape does so they can be selected: +#' @param edgefile +#' +#' @export +getCyEdgeNames <- function(edgefile) { + cyedges <- mapply(paste, edgefile $source, " (", edgefile $interaction, ") ", edgefile $target, sep="") + return(cyedges) +} +# Function to extract node names from, e.g.: +# "ValidatedObjectAndEditString: validatedObject=ERBB3, editString=null" +#' @param test +#' +#' @export +strip.cy.goo <- function(test) { + t1 <- unlist(strsplit(test, "Object=")) + t2 <- sapply(t1[2:length(t1)], function (x) (strsplit(x, ", "))) + return(plyr::ldply(t2)$V1) +} + + +# For graphing Pathway Crosstalk Networks (PCNs) in cytoscape +#' @param PCN +#' +#' @param net.name +#' @param Jaccard.edges +#' +#' @export +cytoscape.graph.PCN.pathways <- function(PCN = pathway.crosstalk.network, net.name, Jaccard.edges=TRUE) { + PCN.df <- data.frame(id=unique(c(PCN$source, PCN$target))) + if (Jaccard.edges== FALSE) {PCN = PCN[-which (PCN$interaction=="pathway Jaccard similarity"),]} + # Get rid of zero weight edges + PCN.edges <- PCN[PCN$Weight>0, ] + no.windows <- length(RCy3::getNetworkList()) + PCN.suid <- RCy3::createNetworkFromDataFrames(PCN.df, PCN.edges, title=paste(net.name, "PCN", (1+no.windows), sep=" "), collection = "Pathway Interactions") + RCy3::setEdgeSelectionColorDefault (gplots::col2hex("chartreuse")) # still doesn't work + RCy3::setNodeColorDefault("#33FFFF" ) # bright Cyan + # edgeColors <- c(col2hex(alpha("purple",0.33333)), col2hex(alpha("magenta",0.33333)), col2hex("green")) + # edgeColors <- c("#9966FF", col2hex("green")) + edgeColors <- c(gplots::col2hex(ggplot2::alpha("darkorchid1",0.33333)), gplots::col2hex(ggplot2::alpha("tomato", 0.25)), gplots::col2hex("green")) + # edgeColors <- c("#9966FF", col2hex("green")) + edgeTypes <- c("PTM_cluster_evidence", "Protein_cluster_evidence", "pathway_Jaccard_similarity") + RCy3::setEdgeColorMapping( 'interaction', edgeTypes, edgeColors, 'd', default.color="#FFFFFF") + setEdgeWidths(ffactor = -1.2, log=TRUE) # Finally works! + style.name <- paste("PCN style", (1+no.windows), sep=" ") + RCy3::copyVisualStyle('default', style.name) + RCy3::setVisualStyle(style.name) +} + +# Two linked functions to generate node file for Cytoscape: +#' @param genes +#' +#' @param ptmtable +#' +#' @export +make.gene.data.from.ptmtable <- function(genes, ptmtable) { + ptmtable.temp <- ptmtable + ptmtable.temp$Gene.Name <- sapply(rownames(ptmtable.temp), function (x) strsplit(x, " ", fixed = TRUE)[[1]][1]) + subset.ptmtable <- ptmtable.temp[ptmtable.temp$Gene.Name %in% unique(genes), ] + gene.data <- subset.ptmtable |> + dplyr::group_by(.data$Gene.Name) |> + dplyr::summarise( + dplyr::across(where(is.numeric), ~sum(.x, na.rm = TRUE)), + .groups = "drop" + ) + + return(as.data.frame(gene.data)) # Ensure base R class +} +#' @param edge.file +#' +#' @param funckey +#' @param ptmtable +#' @param include.gene.data +#' @param include.coclustered.PTMs +#' +#' @export +make.cytoscape.node.file <- function(edge.file, funckey, ptmtable, include.gene.data = FALSE, include.coclustered.PTMs = FALSE) { + # Step 1: get unique nodes from edge file + edge_nodes <- unique(c(as.character(edge.file[, 1]), as.character(edge.file[, 2]))) + # check if there are PTMs in edgefile + peptides <- edge.file[which(edge.file$interaction == "peptide"), "target"] + if(length (peptides) > 0) { + edge_nodes <- edge_nodes %w/o% peptides + } + + + # Step 2: build node data frame from function key + annotation_cols <- c( + "Gene.Name", "Approved.Name", "Hugo.Gene.Family", "HPRD.Function", + "nodeType", "Domains", "Compartment", "Compartment.Overview" + ) + + node_file <- merge( + data.frame(Gene.Name = edge_nodes, stringsAsFactors = FALSE), + funckey[, annotation_cols, drop = FALSE], + by = "Gene.Name", + all.x = TRUE + ) + # For un-annotated genes: + node_file[is.na(node_file)] <- "" + + # Step 3: Optionally merge gene data summed from PTM table + if (include.gene.data == TRUE) { + gene.data <- make.gene.data.from.ptmtable(edge_nodes, ptmtable) + node_file <- merge( + node_file, + gene.data, + by = "Gene.Name", + all.x = TRUE + ) + } + node_file <- cbind(data.frame(id = node_file$Gene.Name), node_file) + # Step 4: Optionally merge PTM CCCN and data + if (include.coclustered.PTMs == TRUE) { + edge.file.with.ptms <- get.co.clustered.ptms(edge.file, ptm.cccn.edges) + if(length (peptides) > 0) { + edge.file.with.ptms <- unique(rbind(edge.file.with.ptms, edge.file[which(edge.file$interaction == "peptide"), ])) + } + node_file <- harmonize_cfs(edge.file.with.ptms, genecf = node_file, ptmtable = ptmtable) + } + return(unique(node_file)) +} +# To remove self-loops +#' Title +#' +#' @param edgefile +#' +#' @returns +#' @export +#' +#' @examples +remove.autophos <- function(edgefile) { + auto <- which (as.character(edgefile$source) == as.character(edgefile$target)) + if (length(auto) > 0) { + newedgefile <- edgefile[-auto,] } else newedgefile <- edgefile + return (newedgefile) +} +# Helper functions for connecting PTMs (called "peptides" with their parent protein nodes (called Gene.Name)) +#' Title +#' +#' @param peptide.edgefile +#' +#' @returns +#' @export +#' +#' @examples +make.genepep.edges <- function(peptide.edgefile) { + peptides <- unique(c(peptide.edgefile$source, peptide.edgefile$target)) + genenames <- sapply(peptides, function (x) unlist(strsplit(x, " ", fixed=TRUE))[1]) + net.gpe <- data.frame(source=genenames, target=peptides, Weight=0.25, interaction="peptide") + net.gpe <- remove.autophos(net.gpe) + return(net.gpe) +} + +# This function takes an edge file, retrieves only co-clustered PTM CCCN edges and links them to their gene nodes, returning an edge file +#' @param edge.file +#' +#' @export +get.co.clustered.ptms <- function (edge.file, ptm.cccn.edges) { + gene_nodes <- unique(c(as.character(edge.file[, 1]), as.character(edge.file[, 2]))) + ptmtable.temp <- ptmtable + ptmtable.temp$Gene.Name <- sapply(rownames(ptmtable.temp), function (x) strsplit(x, " ", fixed = TRUE)[[1]][1]) + subset.ptms <- rownames(ptmtable.temp[ptmtable.temp$Gene.Name %in% unique(gene_nodes), ]) + # Simplify ambiguous names - turned off because it limits their retrieval in the ptm cccn + # subset.ptms <- unique(sapply(subset.ptms, function (x) unlist(strsplit(x, ";", fixed=TRUE))[1])) + subset.ptm.cccn <- filter.edges.0(subset.ptms, ptm.cccn.edges) + pep.edges <- make.genepep.edges(subset.ptm.cccn) + edge.file.with.ptms <- rbind(edge.file, subset.ptm.cccn, pep.edges) + return(edge.file.with.ptms) +} + + +# +# helper functions + +#' Set difference for vectors using custom infix operator +#' +#' Returns the elements in vector x that are not in vector y. +#' +#' @param x A vector. +#' @param y A vector to compare. +#' @return Elements of x that are not in y. +#' @export +#' @examples +#' 1:5 %w/o% c(2, 4) +#' letters[1:5] %w/o% c("b", "d") +"%w/o%" <- function(x, y) { + x[!x %in% y] # x without y +} + + +#' @param x +#' +#' @param y +#' +#' @export +outersect <- function(x, y) { + sort(c(setdiff(x, y), + setdiff(y, x))) +} + +# Function to harmonize gene and peptide data for networks +# - for graphing combined CFN/CCCN graphs +# Enusres that for Cytoscape, "id" is used for node name columns#' @param edge.file.with.ptms +#' +#' @param genecf +#' @param ptmtable +#' +#' @export +harmonize_cfs <- function(edge.file.with.ptms, genecf, ptmtable) { + if(!any(grepl("Gene.Name", names(genecf)))) { + genecf.new <- data.frame(Gene.Name= genecf$id, genecf)} else {genecf.new = genecf} + genecf.new$parent <- "" + genecf.new$Node.ID <- "Gene" + peptides <- edge.file.with.ptms[which(edge.file.with.ptms$interaction == "peptide"), "target"] + if(length (peptides) == 0) { # check if there are PTMs in edgefile + stop("There are no PTMs/peptides in this edge file!") + } + parent.genes <- sapply(peptides, function (x) strsplit(x, " ", fixed = TRUE)[[1]][1]) + # Map peptides to ptmtable rows, handling unmatched by filling with NA or zero + matches <- match(peptides, rownames(ptmtable)) + ptm.rows <- ptmtable[matches, , drop=FALSE] # Will include NAs for unmatched rows + # Optionally replace all NA to 0 in the resulting data.frame + ptm.rows[is.na(ptm.rows)] <- 0 + pepcf <- data.frame( + id = as.character(peptides), + parent = as.character(parent.genes), + Gene.Name = as.character(parent.genes), + ptm.rows + ) + pepcf$Node.ID <- "PTM" + # Add annotation from function key + annotation_cols <- c( + "Gene.Name", "Approved.Name", "Hugo.Gene.Family", "HPRD.Function", + "nodeType", "Domains", "Compartment", "Compartment.Overview" + ) + + pepcf.funcs <- merge( + pepcf, + funckey[, annotation_cols, drop = FALSE], + by = "Gene.Name", + all.x = TRUE + ) + # For un-annotated genes: + pepcf.funcs[is.na(pepcf.funcs)] <- "" + # Harmonize + cf <- merge(genecf.new, pepcf.funcs, all=TRUE) + if(any(grepl("Gene.Name.1", names(cf)))) {cf <- cf[,-which(names(cf)=="Gene.Name.1")]} + if(any(is.na(cf))) {cf[is.na(cf)] <- 0} + # Make sure "id" is in the first column + cf <- cf[,c("id", "Gene.Name", "Node.ID", "parent", names(cf) %w/o% c("id", "Gene.Name", "Node.ID", "parent"))] + return(cf) +} + +# Function to merge edges to declutter networks + +#' @param edgefile +#' +#' @export +mergeEdges <- function(edgefile) { + # Define edge type priorities for directed edges + directed_priority <- c("psp", "controls-phosphorylation-of", "controls-expression-of", + "controls-transport-of", "controls-state-change-of", + "PHOSPHORYLATION", "METHYLATION", "ACETYLATION", "catalysis-precedes") + undirected <- c("Physical interactions", "BioPlex", "in-complex-with", 'experiments', + 'database', "Pathway", "Predicted", "Genetic interactions", + "correlation", "negative correlation", "positive correlation", + 'combined_score', "merged", "intersect", "peptide", 'homology', + "Shared protein domains", "interacts-with") + + # --- UNDIRECTED EDGES --- + undir.edges <- edgefile[!(edgefile$interaction %in% directed_priority), ] + # Sort node pairs for undirected edges + undir.edges[, 1:2] <- t(apply(undir.edges[, 1:2], 1, function(x) sort(x))) + + # Merge by source/target and make informative label + undir.merged <- plyr::ddply(undir.edges, c("source", "target"), function(x) { + data.frame( + Weight = max(x$Weight, na.rm = TRUE), + interaction = paste(sort(unique(as.character(x$interaction))), collapse = " | "), + stringsAsFactors = FALSE + ) + }) + # .. + + # --- DIRECTED EDGES --- + dir.edges <- edgefile[edgefile$interaction %in% directed_priority, ] + # For each directed edge (source, target), choose the top-priority interaction + dir.merged <- plyr::ddply(dir.edges, c("source", "target"), function(x) { + ints <- as.character(x$interaction) + present <- intersect(directed_priority, ints) + best <- if (length(present) > 0) present[1] else ints[1] + all_types <- paste(sort(unique(ints)), collapse = " | ") + data.frame( + Weight = max(x$Weight, na.rm = TRUE), + interaction = if (best != all_types) paste0(best, " [", all_types, "]") else best, + stringsAsFactors = FALSE + ) + }) + + # Combine and clean + edgefile.merged <- rbind(dir.merged, undir.merged) + # Remove self-loops + edgefile.merged <- edgefile.merged[edgefile.merged$source != edgefile.merged$target, ] + if (exists("remove.autophos")) edgefile.merged <- remove.autophos(edgefile.merged) + rownames(edgefile.merged) <- NULL + return(edgefile.merged) +} + +#' Extracts co-clustered PTM and gene network from a cluster filtered network object. +#' +#' This function takes a list of PTM (post-translational modification) site IDs and a cluster-filtered network, +#' extracts the genes from unambiguous and ambiguous PTM entries, then returns the co-clustered subnetwork. +#' +#' @param ptms Character vector of PTM site strings (e.g., \"TP53 p S15\") +#' @param cfn List or data frame representing the cluster filtered network (default: global cfn.merged) +#' @param pepsep Character used to split ambiguous PTM entries (default: \";\") +#' +#' @return Subnetwork object with co-clustered PTMs and genes +#' @export +#' @examples +#' # Example input objects +#' ptms <- c("TP53 p S15", "BRCA1 p S123; BRCA1 p T124", "MDM2 p S200") +#' # Example network +#' # edge list with 'source', 'target', and cluster info, could be data.frame or igraph +#' cfn.merged <- data.frame(source = c("TP53", "BRCA1", "BRCA1", "MDM2"), +#' target = c("BRCA1", "MDM2", "TP53", "TP53"), +#' cluster = c(1,1,2,1)) +#' # Suppose filter.edges.0 and get.co.clustered.ptms are also defined and loaded +#' # The following returns the gene/PTM subnetwork +#' res <- ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") +#' print(res) +ptms_to_cfn <- function(ptms, cfn = cfn.merged, pepsep = ";") { + ambig.ptms <- ptms[grep(";", ptms)] + if (length(ambig.ptms) > 0) { + + sub.ptms <- ptms %w/o% ambig.ptms + all_genes <- unique(sapply(sub.ptms, function (x) unlist(strsplit(x, " ", fixed=TRUE))[1])) + + for (i in 1:length(ambig.ptms) ) { + # Normalize spacing + ptm_entry <- gsub("[;,]\\s*", ";", ambig.ptms) + # Split ambiguous entry + ptm_parts <- strsplit(ptm_entry, pepsep, fixed = TRUE)[[1]] + # Extract gene names (string before first space) + ambig.genes <- sapply(ptm_parts, function(part) strsplit(part, " ", fixed = TRUE)[[1]][1]) + + all_genes <- unique(c(all_genes, ambig.genes)) + } + } else { + all_genes <- unique(sapply(ptms, function (x) unlist(strsplit(x, " ", fixed=TRUE))[1])) + } + + sub.cfn <- filter.edges.0(all_genes, cfn.merged) + sub.cfn.cccn <- get.co.clustered.ptms(sub.cfn, ptm.cccn.edges) + return(sub.cfn.cccn) +} + +#_____________________________________________________________________________ +# Vizprops helper functions: +# Function to set shape and border color according to node type +#' @param cf +#' +#' @export +setNodeMapping <- function(cf=RCy3::getTableColumns('node')) { + # require(RCy3) + RCy3::setBackgroundColorDefault("#949494") # grey 58 + RCy3::setNodeShapeDefault("ELLIPSE") + RCy3::setNodeColorDefault("#F0FFFF") # azure1 + RCy3::setNodeSizeDefault(100) # for grey non-data nodes + RCy3::setNodeFontSizeDefault(22) + RCy3::setNodeLabelColorDefault("#000000") # black + RCy3::setNodeBorderWidthDefault(1.8) + RCy3::setNodeBorderColorDefault("#888888") # gray + RCy3::setNodeSelectionColorDefault("#CC00FF") + molclasses <- c("acetyltransferase", "deacetylase", "demethylase", "G protein-coupled receptor", "kinase", "membrane protein", "methyltransferase", "phosphatase", "receptor tyrosine kinase", "RNA binding and processing protein", "RNA binding and splicing protein", "RNA binding protein", "RNA processing and splicing protein", "RNA processing protein", "RNA splicing protein", "SH2 protein", "SH2-SH3 protein", "SH3 protein", "SRC-family kinase", "transcription factor", "transcription regulator", "tyrosine kinase", "tyrosine phosphatase", "undefined") + nodeshapes <- c("ELLIPSE", "ELLIPSE", "ELLIPSE", "ROUND_RECTANGLE", "OCTAGON", "ROUND_RECTANGLE", "ELLIPSE", "OCTAGON", "ROUND_RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "RECTANGLE", "VEE", "VEE", "TRIANGLE", "DIAMOND", "PARALLELOGRAM", "PARALLELOGRAM", "HEXAGON", "HEXAGON", "ELLIPSE") + nodebordercolors <- gplots::col2hex(c("darkorange", "darkorange3", "blue3", "darkorchid1", "red3", "purple", "blue", "lightgoldenrod1", "violetred", "darkgoldenrod", "burlywood4", "darkgoldenrod3", "burlywood3","darkgoldenrod4", "burlywood3", "deeppink", "hotpink", "rosybrown1", "red2", "springgreen4", "steelblue4", "red2", "yellow", "gray")) + RCy3::setNodeShapeMapping("nodeType", molclasses, nodeshapes, default.shape="ELLIPSE") + RCy3::setNodeBorderColorMapping("nodeType", molclasses, nodebordercolors, mapping.type = "d", default.color=gplots::col2hex("gray")) + nodeborderwidths <- c(12,5,5,16,12,8,12,12,16,6,6,6,6,6,6,12,6,12,16,10,10,12,12,4) + RCy3::setNodeBorderWidthMapping(table.column="nodeType", table.column.values=molclasses, widths=nodeborderwidths, mapping.type = "d", default.width=2) +} + +# Function to set edge appearance +# # Use: setCorrEdgeAppearance() to change cytoscape front window +#This is now modified to handle merged edges and match colors correctly +#' Title +#' +#' @returns +#' @export +#' +#' @examples +setCorrEdgeAppearance <- function() { + # require(RCy3) + RCy3::setEdgeLineWidthDefault(3) + RCy3::setEdgeColorDefault("#FFFFFF") # white + edgevalues <- RCy3::getTableColumns('edge',c('Weight')) + edgevalues['Weight']<-abs(edgevalues['Weight']) + edgevalues['Weight']<-lapply(edgevalues['Weight'], function(x) log2(x * 10) + 2) + names(edgevalues)<-c('Width') + RCy3::loadTableData(edgevalues, table = 'edge', table.key.column = 'SUID') + RCy3::setEdgeLineWidthMapping('Width', mapping.type = 'p', style.name = 'default') + RCy3::setEdgeSelectionColorDefault("#FF69B4") # hotpink + edgecolors <- gplots::col2hex(c("red", "red", "red", "magenta", "violet", "purple", "darkorange1", "green", "green2", "green3", "aquamarine2", "aquamarine2", "cyan","cyan", "turquoise2", "cyan2", "lightseagreen", "gold", "blue", "yellow", "slategrey", "darkslategrey", "grey", "black", "orange", "orange2")) + edgeTypes <- c("PHOSPHORYLATION", "psp", "controls-phosphorylation-of", "controls-expression-of", "controls-transport-of", "controls-state-change-of", "ACETYLATION", "Physical Interactions", "BioPlex", "in-complex-with", 'experimental', 'experimental_transferred', 'database', 'database_transferred', "Pathway", "Predicted", "Genetic interactions", "correlation", "negative correlation", "positive correlation", 'combined_score', "merged" , "intersect", "peptide", 'homology', "Shared protein domains") + myarrows <- c ('Arrow', 'Arrow', 'Arrow', 'Arrow', 'Arrow', 'Arrow', "Arrow", 'None', 'None', 'None', 'None','None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None', 'None') + edgevalues2 <- RCy3::getTableColumns('edge',c('interaction','SUID')) + get_main_interaction <- function(intchar) { + components <- unlist(strsplit(as.character(intchar), split = "[|]", fixed = FALSE)) + components <- trimws(components) + found <- edgeTypes[edgeTypes %in% components] + if (length(found)) { + return(found[1]) + } else { + return(components[1]) + } + } + edgevalues2$main_interaction <- sapply(edgevalues2$interaction, get_main_interaction) + RCy3::loadTableData(edgevalues2, table = 'edge', table.key.column = 'SUID') + RCy3::setEdgeTargetArrowMapping('main_interaction', edgeTypes, myarrows, default.shape='None') + RCy3::matchArrowColorToEdge('TRUE') + RCy3::setEdgeColorMapping('main_interaction', edgeTypes, edgecolors, 'd', default.color="#FFFFFF") +} + +# Function to sent node size and color to match ratio data in the Cytoscape node table. +#' @param plotcol +#' +#' @export +setNodeColorToRatios <- function(plotcol){ + require(RCy3) + cf <- RCy3::getTableColumns('node') + if(!(plotcol %in% getTableColumnNames('node'))){ + print (getTableColumnNames('node')) + cat("\n","\n","\t", "Which attribute will set node size and color?") + plotcol <- as.character(readLines(con = stdin(), n = 1)) + } + limits <- range(cf[, plotcol]) + node.sizes = c (135, 130, 108, 75, 35, 75, 108, 130, 135) + # RATIO is plotted + # Blue is negative: Yellow positive, Green in middle + # + size.control.points = c (-100.0, -15.0, -5.0, 0.0, 5.0, 15.0, 100.0) + color.control.points = c (-100.0, -10.0, -5.0, -2.25, 0.0, 2.25, 5.0, 10.0, 100.0) + if(limits[1] < min(size.control.points)) { + size.control.points = c (limits[1], -15.0, -5.0, 0.0, 5.0, 15.0, 100.0) + color.control.points = c (limits[1]-1, -10.0, -5.0, -2.25, 0.0, 2.25, 5.0, 10.0, 100.0) + } + if(limits[2] > max(size.control.points)) { + size.control.points = c (limits[1], -15.0, -5.0, 0.0, 5.0, 15.0, limits[2]) + color.control.points = c (limits[1]-1, -10.0, -5.0, -2.25, 0.0, 2.25, 5.0, 10.0, limits[2]+1) + } + ratio.colors = c ('#0099FF', '#007FFF','#00BFFF', '#00CCFF', '#00FFFF', '#00EE00', '#FFFF7E', '#FFFF00', '#FFE600', '#FFD700', '#FFCC00') + RCy3::setNodeColorMapping (names(cf[plotcol]), color.control.points, ratio.colors, 'c') + RCy3::lockNodeDimensions('TRUE') + RCy3::setNodeSizeMapping (names(cf[plotcol]), size.control.points, node.sizes, 'c') + RCy3::setNodeSelectionColorDefault ( "#CC00FF") +} + +# This function works well with node data that are normalized by row z-scores +#' @param plotcol +#' +#' @export +setNodeColorToRowz <- function(plotcol){ + cf <- getTableColumns('node') + if(!(plotcol %in% getTableColumnNames('node'))){ + print (getTableColumnNames('node')) + cat("\n","\n","\t", "Which attribute will set node size and color?") + plotcol <- as.character(readLines(con = stdin(), n = 1)) + } + limits <- range(cf[, plotcol]) + node.sizes = c (135, 130, 108, 75, 35, 75, 108, 130, 135) + # Row z-score data is plotted + # Blue is negative: Yellow positive, Green in middle + # + size.control.points = c (-log2(100.0), -log2(15.0), -log2(5.0), 0.0, log2(5.0), log2(15.0), log2(100.0)) + color.control.points = c (-log2(100.0), -log2(10.0), -log2(5.0), -log2(2.25), 0.0, log2(2.25), log2(5.0), log2(10.0), log2(100.0)) + ratio.colors = c ('#0099FF', '#007FFF','#00BFFF', '#00CCFF', '#00FFFF', '#00EE00', '#FFFF7E', '#FFFF00', '#FFE600', '#FFD700', '#FFCC00') + RCy3::setNodeColorMapping (names(cf[plotcol]), color.control.points, ratio.colors, 'c') + RCy3::lockNodeDimensions('TRUE') + RCy3::setNodeSizeMapping (names(cf[plotcol]), size.control.points, node.sizes, 'c') + RCy3::setNodeSelectionColorDefault ( "#CC00FF") +} + +#' Set node size and color from independent Cytoscape node attributes +#' +#' Apply independent continuous visual mappings for node size and node color +#' in the active Cytoscape network using columns from the node table. +#' This helper is intended for PTMsToPathways-style networks where one node +#' attribute controls size and a different attribute controls color. +#' +#' If either requested column is not present in the Cytoscape node table, the +#' user is prompted to choose a valid column name interactively. For non-ratio +#' data, color and size control points are derived from the observed ranges of +#' the selected columns. For ratio-style data, fixed log2-based control points +#' are used. +#' +#' @param sizeplotcol A character string naming the node table column to use +#' for node size mapping. +#' @param colorplotcol A character string naming the node table column to use +#' for node color mapping. +#' @param ratio Logical; if `TRUE`, use predefined log2 ratio control points +#' and ratio color palette. If `FALSE`, compute control points from the +#' observed ranges of `sizeplotcol` and `colorplotcol`. +#' +#' @details +#' For `ratio = FALSE`, this function builds separate continuous mappings for +#' node color and node size based on the minimum and maximum values observed +#' in the selected Cytoscape node table columns. Blue-to-white-to-yellow color +#' gradients are used for color mapping. +#' +#' For `ratio = TRUE`, the function assumes ratio-like values centered on zero +#' and applies fixed log2-scaled control points for both node size and node +#' color. In this mode, a blue-to-green-to-yellow palette is used. +#' +#' The function also locks node width and height and sets the default node +#' selection color to magenta. +#' +#' @return +#' This function is called for its side effects in Cytoscape and returns +#' `NULL` invisibly. +#' +#' @seealso +#' [RCy3::setNodeColorMapping()], [RCy3::setNodeSizeMapping()], +#' [RCy3::lockNodeDimensions()], [RCy3::getTableColumns()] +#' +#' @examples +#' \dontrun{ +#' setNodeSizeColorIndependently( +#' sizeplotcol = "mean_expression", +#' colorplotcol = "logFC", +#' ratio = FALSE +#' ) +#' +#' setNodeSizeColorIndependently( +#' sizeplotcol = "abundance_ratio", +#' colorplotcol = "abundance_ratio", +#' ratio = TRUE +#' ) +#' } +#' +#' @export +setNodeSizeColorIndependently <- function(sizeplotcol, colorplotcol, ratio=FALSE){ + cf <- RCy3::getTableColumns('node') + if(!(sizeplotcol %in% RCy3::getTableColumnNames('node'))){ + print (RCy3::getTableColumnNames('node')) + cat("\n","\n","\t", "Which attribute will set node size?") + sizeplotcol <- as.character(readLines(con = stdin(), n = 1)) + } + if(!(colorplotcol %in% RCy3::getTableColumnNames('node'))){ + print (RCy3::getTableColumnNames('node')) + cat("\n","\n","\t", "Which attribute will set node color?") + colorplotcol <- as.character(readLines(con = stdin(), n = 1)) + } + limits <- range(cf[, sizeplotcol]) + node.sizes = c (135, 130, 108, 75, 35, 75, 108, 130, 135) + if (ratio == FALSE) { + Intensity.Values.Color <- cf[, colorplotcol] # set to intensity or normalized intensity + maxint.col <- max(Intensity.Values.Color, na.rm=TRUE) + minint.col <- min(Intensity.Values.Color, na.rm=TRUE) + Intensity.Values.Size <- cf[, sizeplotcol] # set to intensity or normalized intensity + maxint.size <- max(Intensity.Values.Size, na.rm=TRUE) + minint.size <- min(Intensity.Values.Size, na.rm=TRUE) + icolors <- c('#0099FF', '#007FFF','#00BFFF', '#00CCFF', '#00FFFF', '#FFFFFF', '#FFFF7E', '#FFFF00', '#FFE600', '#FFD700', '#FFCC00') + if (maxint.col>=abs(minint.col)) { + color.control.points <- c(-(maxint.col+1), -(maxint.col/5), -(maxint.col/10), -(maxint.col/15), 0.0, (maxint.col/15), (maxint.col/10), (maxint.col/5), (maxint.col+1)) + RCy3::setNodeColorMapping (names(cf[colorplotcol]), color.control.points, icolors) + } + if (maxint.col=abs(minint.size)) { + size.control.points = c (-(maxint.size+1), -(maxint.size*0.3), -(maxint.size/10), 0.0, (maxint.size/10), (maxint.size*0.3), (maxint.size+1)) + RCy3::setNodeSizeMapping (names(cf[sizeplotcol]), size.control.points, node.sizes, 'c') + } + if (maxint.size translucent + RCy3::setEdgeLabelOpacityDefault(edge.label.opacity, visual.style.name) # set opacity of edge label; 0 - 255 w 0 --> translucent + RCy3::setNodeBorderOpacityDefault(border.opacity, visual.style.name) # set opacity of border of node; 0 - 255 w 0 --> translucent + RCy3::setNodeFillOpacityDefault(node.fill.opacity, visual.style.name) # set opacity of interior color of node; 0 - 255 w 0 --> translucent + RCy3::setNodeLabelOpacityDefault(node.label.opacity, visual.style.name) # set opacity of name of node; 0 - 255 w 0 --> translucent + +} + +# NodeEdgeKey function: Cytoscape legend for styles +#' @param visual.style.name +#' +#' @export +NodeEdgeKey <- function(visual.style.name = "PTMsToPathways.style") { + # require(RCy3) + node_types <- c( + "acetyltransferase", "deacetylase", "demethylase", "G protein-coupled receptor", "kinase", "membrane protein", + "methyltransferase", "phosphatase", "receptor tyrosine kinase", "RNA binding and processing protein", + "RNA binding and splicing protein", "RNA binding protein", "RNA processing and splicing protein", + "RNA processing protein", "RNA splicing protein", "SH2 protein", "SH2-SH3 protein", "SH3 protein", + "SRC-family kinase", "transcription factor", "transcription regulator", "tyrosine kinase", + "tyrosine phosphatase", "undefined" + ) + n_nodes <- length(node_types) + edge_types <- c( + "PHOSPHORYLATION", "psp", "controls-phosphorylation-of", "controls-expression-of", "controls-transport-of", + "controls-state-change-of", "ACETYLATION", "Physical Interactions", "BioPlex", "in-complex-with", + "experimental", "experimental_transferred", "database", "database_transferred", "Pathway", "Predicted", + "Genetic interactions", "correlation", "negative correlation", "positive correlation", "combined_score", + "merged", "intersect", "peptide", "homology", "Shared protein domains" + ) + n_edges <- length(edge_types) + demo_nodes <- data.frame( + id = node_types, + nodeType = node_types, + label = node_types, + stringsAsFactors = FALSE + ) + kinase_node <- "kinase" + non_kinase_targets <- node_types[node_types != kinase_node] + edge_sources <- character(n_edges) + edge_targets <- character(n_edges) + for(i in seq_along(edge_types)) { + et <- edge_types[i] + if(et == "PHOSPHORYLATION" || et == "psp") { + edge_sources[i] <- kinase_node + edge_targets[i] <- non_kinase_targets[i] + } else { + edge_sources[i] <- node_types[(i-1) %% n_nodes + 1] + edge_targets[i] <- node_types[i %% n_nodes + 1] + } + } + edge_table <- data.frame( + source = edge_sources, + target = edge_targets, + interaction = edge_types, + label = edge_types, + Weight = seq (from = 2.6, to = 0.1, by = -0.1), + stringsAsFactors = FALSE + ) + stopifnot(all(edge_table$source %in% demo_nodes$id)) + stopifnot(all(edge_table$target %in% demo_nodes$id)) + cy_net <- RCy3::createNetworkFromDataFrames( + nodes = demo_nodes, + edges = edge_table, + title = "Node & Edge Key", + collection = "PTMsToPathways" + ) + RCy3::setEdgeFontSizeDefault(20) + RCy3::setEdgeLabelMapping("label") + setNodeMapping() + setCorrEdgeAppearance() + RCy3::copyVisualStyle("default", visual.style.name) + RCy3::setVisualStyle(visual.style.name) + message("Check the 'Node & Edge Key' network in Cytoscape: kinase sources PHOSPHORYLATION/pp, all edges labelled, all node-edge types illustrated.") +} + + diff --git a/R/PathwayCrosstalkNetwork.R b/R/PathwayCrosstalkNetwork.R new file mode 100644 index 00000000..1c4d59a2 --- /dev/null +++ b/R/PathwayCrosstalkNetwork.R @@ -0,0 +1,249 @@ +#' Read Bioplanet File +#' +#' Reads a Bioplanet CSV file and converts it into a named list of pathways, +#' where each list element is a character vector of gene symbols. +#' +#' @param bioplanet.file Path to a delimited Bioplanet text file. +#' Common delimiters such as comma, tab, semicolon, and pipe are supported. +#' +#' @return A named list of pathways and associated gene symbols. +#' @export +ReadBioplanetFile <- function(bioplanet.file = "bioplanet.csv") { + required.cols <- c("PATHWAY_ID", "PATHWAY_NAME", "GENE_ID", "GENE_SYMBOL") + + if (!is.character(bioplanet.file) || length(bioplanet.file) != 1L || is.na(bioplanet.file) || !nzchar(bioplanet.file)) { + stop("bioplanet.file must be a single non-empty file path string.") + } + if (!file.exists(bioplanet.file)) { + stop(paste(bioplanet.file, "not found. Please check your working directory.")) + } + + read_attempts <- list( + function(path) utils::read.csv(path, stringsAsFactors = FALSE), + function(path) utils::read.delim(path, stringsAsFactors = FALSE), + function(path) utils::read.table(path, sep = ";", header = TRUE, stringsAsFactors = FALSE), + function(path) utils::read.table(path, sep = "|", header = TRUE, stringsAsFactors = FALSE) + ) + + bioplanet <- NULL + for (reader in read_attempts) { + parsed <- tryCatch(reader(bioplanet.file), error = function(e) NULL) + if (!is.null(parsed) && is.data.frame(parsed)) { + missing.cols <- setdiff(required.cols, names(parsed)) + if (length(missing.cols) == 0) { + bioplanet <- parsed + break + } + } + } + + if (is.null(bioplanet)) { + stop("Could not parse bioplanet.file with a supported delimiter or required columns are missing.") + } + + # Correct errors from Excel + bioplanet[, "GENE_SYMBOL"] <- sapply(bioplanet$GENE_SYMBOL, fix.excel) + + # Turn Bioplanet table into list of pathways (character vectors of gene symbols) + PATHWAY_NAME <- NULL + pathways.list <- plyr::dlply(bioplanet, plyr::.(PATHWAY_NAME)) + pathways.list <- lapply(pathways.list, `[`, "GENE_SYMBOL") + pathways.list <- lapply(pathways.list, unlist, use.names = FALSE) + + pathways.list +} + +#' Build Pathway Crosstalk Network +#' +#' Converts Bioplanet pathways from () into a list of pathways whose elements are the genes in each pathway. Edge weights are either the PTM Cluster Weight or according to the Jaccard Similarity. +#' +#' @param common.clusters The list of common clusters between all three distance metrics (Euclidean, Spearman, and SED). Can be made in MakeCorrelationNetwork +#' @param bioplanet.file Either the path to a delimited Bioplanet file, or a named list of pathways where each list element is a character vector of gene symbols. +#' @param createfile The path of where to create the edgelist file. Defaults to the working directory, if FALSE is provided, a file will not be created. +#' @param PCN.edgelist.name Name of the PCN edgelist file to be created +#' @return A list with these data structures at the given index: \enumerate{ +#' \item{Contains pathway source-target columns, along with the interaction type.} +#' \item{Contains pathway source-target columns, with edge weights of their jaccard similarity and their Pathway-Pathway Evidence score.} +#' \item{All pathways in the bioplanet database as a named list containing string vectors. Each vector is a pathway with strings associated with the genes in that pathway.} +#' } +#' @export +#' +#' @examples +#' Example_Output <- BuildPathwayCrosstalkNetwork(ex_common_clusters, ex_pathways_list, createfile = FALSE) +#' Example_Output[[1]][[3,]] +#' Example_Output[[3]][[1:3]] +BuildPathwayCrosstalkNetwork <- function(common.clusters, bioplanet.file = "bioplanet.csv", createfile = getwd(), PCN.edgelist.name = "PCN_file"){ + message("Making PCN") + start_time <- Sys.time() + message(start_time) + if(is.character(createfile) && !dir.exists(createfile)) stop(paste("Could not find directory", createfile)) #If createfile is a path but an incorrect one + + # Accept either a file path or pre-built pathways.list + if (is.character(bioplanet.file)) { + pathways.list <- ReadBioplanetFile(bioplanet.file) + } else if (is.list(bioplanet.file) && !is.data.frame(bioplanet.file)) { + pathways.list <- bioplanet.file + } else { + stop(paste(class(bioplanet.file), "is not a supported file type. Please provide a file path or a named list of pathways.")) + } + + if (is.null(names(pathways.list)) || any(names(pathways.list) == "")) { + stop("pathways.list must be a named list with pathway names.") + } + pathways.list <- lapply(pathways.list, as.character) + + + ### Functions to help caluclate the jaccard similarity ### + find.jaccard.val <- function(charvectorrow){ #Helper function to pass into an "apply" function for a matrix that contains character vectors. Matrix dimensions should be 2 columns x any number of rows + p.intersect <- length(intersect(charvectorrow[[1]], charvectorrow[[2]])) #Length of Intersect + p.union <- length(charvectorrow[[1]]) + length(charvectorrow[[2]]) - p.intersect #Length of Union + jaccard.value <- p.intersect/p.union + if(jaccard.value == 0) return(NA) #Return NA if 0 for igraph + return(jaccard.value) #Return the jaccard value + } + + combinations <- utils::combn #Mask the utils::combn with a more descriptive term + + + ### Create the main data structure and add Jaccard Values ### + PCNedgelist <- t(combinations(names(pathways.list), 2)) #Creating the edgelist. First two columns of the data frame like PATHWAY | PATHWAY; Found by finding all pairs pathway names + combn.vector <- combinations(pathways.list, 2) #The values of the above vector, performs the same operation to get pairs of string vectors containing gene names instead of pathway names. Prepares data for analysis + jaccard.values <- apply(combn.vector, 2, find.jaccard.val) #Find the jaccard value for every pair of string vectors in combn.svector + PCNedgelist <- cbind(PCNedgelist, jaccard.values) #Attach the JACCARD VALUE column to PATHWAY | PATHWAY + bioplanetjaccardedges <- as.data.frame(PCNedgelist) + bioplanetjaccardedges <- bioplanetjaccardedges[!is.na(bioplanetjaccardedges$jaccard.values),] + bioplanetjaccardedges$interaction <- "pathway_Jaccard_similarity" + names(bioplanetjaccardedges)[1:2] <- c("source", "target") # For Cytoscape graphing + + ### Pathway Cluster Evidence ### + CPE.matrix <- matrix(NA, nrow = length(common.clusters), ncol = length(pathways.list)) #Initilize empty data structure, Clusters x Pathways + rownames(CPE.matrix) <- names(common.clusters) #Names + colnames(CPE.matrix) <- names(pathways.list) + + #pathways.temp <- as.data.frame(table(bioplanet$GENE_SYMBOL)) #Create table for how many times each gene appears in the pathways list. Needs to be converted into a named vector for efficent runtime. + pathways.temp <- as.data.frame(table(unlist(pathways.list))) #Create table for how many times each gene appears in the pathways list. Needs to be converted into a named vector for efficent runtime. + pathgene.count <- pathways.temp$Freq #Transform into a named vector + names(pathgene.count) <- pathways.temp$Var1 #Now any string vector of genes like pathways.hash[[c("AARS", "ABCA1")]] will return the frequency of how many times those genes appear in the pathway list *in constant time*. Sum() to return the total + get_weighted_gene_counts <- function(ptm_vec, pepsep = ";") { + all_genes <- character() + all_weights <- numeric() + + for (ptm_entry in ptm_vec) { + # Normalize spacing + ptm_entry <- gsub("[;,]\\s*", ";", ptm_entry) + + # Split ambiguous entry + ptm_parts <- strsplit(ptm_entry, pepsep, fixed = TRUE)[[1]] + + # Extract gene names (string before first space) + genes <- sapply(ptm_parts, function(part) strsplit(part, " ", fixed = TRUE)[[1]][1]) + + weight <- 1 / length(genes) + + all_genes <- c(all_genes, genes) + all_weights <- c(all_weights, rep(weight, length(genes))) + } + + # Aggregate weights for duplicated gene names + gene_weights <- tapply(all_weights, all_genes, sum) + + return(gene_weights) + } + + + for (a in 1:nrow(CPE.matrix)) { + + # Step 1: Get weighted gene counts for cluster a + gene.count <- get_weighted_gene_counts(common.clusters[[a]]) # + + cluster.length <- length(common.clusters[[a]]) + + for (b in 1:ncol(CPE.matrix)) { + + # Get genes in this pathway + pathway.genes <- pathways.list[[b]] + + # Numerator: weighted sum of pathway genes in this cluster + num <- gene.count[pathway.genes] + + # Denominator: total frequency across all pathways + den <- pathgene.count[pathway.genes] + + value <- sum(num / den, na.rm = TRUE) / cluster.length + + if (!is.na(value) && value != 0) { + CPE.matrix[a, b] <- value + } + } + } + + ### Generate PCN network ### + # Get a vector of all the PTP weights for every pair of pathways using the CPE weights to filter. For a PTP weight to be non-NA, the PTP weight will be the sum of all clusters both pathways have nonzero CPEs in. + + PTPscore <- apply(PCNedgelist[,1:2], 1, function(x) { + rows <- rowSums(!is.na(CPE.matrix[, x])) == 2 + if (any(rows)) { + sum(CPE.matrix[rows, x], na.rm = TRUE) + } else { + NA + }}) + + + PTPscore[PTPscore== 0] <- NA # Safety check: Turn all 0s created in above line into NAs + + PCNedgelist <- cbind(PCNedgelist, PTPscore) #Bind all the columns together. Now Data structure is PATHWAY | PATHWAY | Jaccard | CPE + PCNedgelist <- PCNedgelist[rowSums(is.na(PCNedgelist)) != 2, ] #Remove all rows that only have NA values for the jaccard and CPE values + PCNedgelist <- as.data.frame(PCNedgelist) + names(PCNedgelist) <- c("source", "target", "pathway_Jaccard_similarity", "PTM_cluster_evidence") + # Sort by the highest PTM cluster evidence + # PCNedgelist <- PCNedgelist[order(PCNedgelist$PTM_cluster_evidence, decreasing = TRUE),] + PCNedgelist$pathway_Jaccard_similarity <- as.numeric(PCNedgelist$pathway_Jaccard_similarity) + PCNedgelist$PTM_cluster_evidence <- as.numeric(PCNedgelist$PTM_cluster_evidence) + # Convert NA to 0 + PCNedgelist[is.na(PCNedgelist)] <- 0 + # Subset data frames + zero_jaccard <- PCNedgelist[PCNedgelist$pathway_Jaccard_similarity == 0, ] + nonzero_jaccard <- PCNedgelist[PCNedgelist$pathway_Jaccard_similarity > 0, ] + + # Sort both by PTM_cluster_evidence in decreasing order + zero_jaccard <- zero_jaccard[order(zero_jaccard$PTM_cluster_evidence, decreasing=TRUE), ] + nonzero_jaccard <- nonzero_jaccard[order(nonzero_jaccard$PTM_cluster_evidence, decreasing=TRUE), ] + + # Combine results: zero-jaccard block on top, then nonzero-jaccard block + PCNedgelist <- rbind(zero_jaccard, nonzero_jaccard) + + # For Cytoscape graphing + #Remove all rows that only have NA values for CPE values + bioplanetCPEedges <- PCNedgelist[!is.na(PCNedgelist[,"PTM_cluster_evidence"]), c("source", "target", "PTM_cluster_evidence")] + # For Cytoscape it's useful to have both types of edges for plotting in different colors + + # Assign interaction, required for Cytoscape + bioplanetCPEedges$interaction <- "PTM_cluster_evidence" + # Create pathway crosstalk network with individual cluster and bioplanet edges + jaccard.net <- bioplanetjaccardedges + names(jaccard.net) <- c("source", "target", "Weight", "interaction") + CPE.net <- bioplanetCPEedges + names(CPE.net) <- c("source", "target", "Weight", "interaction") + pathway.crosstalk.network <- rbind(CPE.net, jaccard.net) + + ### Save edgefile for cytoscape plotting ### + + if(is.character(createfile)){ #Don't need to check if directory exists since was done above + saved.dir <- getwd() + setwd(createfile) + filename <- paste(PCN.edgelist.name, ".csv", sep="") #Name of the file created with .csv appended + utils::write.csv(pathway.crosstalk.network, file = filename, row.names = FALSE) #Save to files for cytoscape... + + cat(filename, "made in directory:", getwd()) #Tell the user where their files got put + setwd(saved.dir) + } + end_time <- Sys.time() + message(end_time) + #calculate difference between start and end time + total_time <- end_time - start_time + message(noquote(paste("Total time: ", total_time, sep=""))) + return(list(pathway.crosstalk.network, PCNedgelist, pathways.list)) +} + + + diff --git a/R/RawDataProcessing.R b/R/RawDataProcessing.R new file mode 100644 index 00000000..9b83b92b --- /dev/null +++ b/R/RawDataProcessing.R @@ -0,0 +1,183 @@ +###================================================================================================================== +# Functions and suggested workflow to help process mass spectrometry data files +###================================================================================================================== + +## We use the following shorthand conventions when describing PTMs. This can be modified if different modification types are under investigation +# The HUGO Gene Name is used to identify the protein/gene +# Phosphorylation = "p" +# Lysine acetylation = "ack" +# Lysine methylation = "kme" +# Arginine methylation = "rme" +# Ubiquitination = "ubi" + + +# Helper functions +#' Correct Excel-Converted Gene Symbols +#' +#' Replaces gene symbols that are commonly converted into date-like strings by +#' spreadsheet software with their expected HUGO symbols. +#' +#' @param cell A character string containing one or more gene symbols, separated +#' by `"; "`. +#' +#' @return A character string with corrected gene symbols. +#' @export +#' +#' @examples +#' fix.excel("1-Sep; CDC2") +# Function to change dates back into gene names - Excel changes many genes into dates and this can't be turned off! +fix.excel <- function(cell) { + fixgenes = c("CDC2", "1-Sep", "2-Sep", "3-Sep", "4-Sep", "5-Sep", "7-Sep", "8-Sep", "9-Sep", "10-Sep", "11-Sep", "15-Sep", "6-Sep", "1-Oct", "2-Oct", "3-Oct", "4-Oct", "6-Oct", "7-Oct", "11-Oct", "1-Mar", "2-Mar", "3-Mar", "4-Mar", "5-Mar", "6-Mar", "7-Mar", "8-Mar", "9-Mar", "10-Mar", "11-Mar", "C11orf58", 'C17orf57', 'C3orf10', 'C7orf51', "C11orf59", "C4orf16", "1-Dec", "14-Sep") + corrects = c("CDK1", "SEPT1", "SEPT2", "SEPT3", "SEPT4", "SEPT5", "SEPT7", "SEPT8", "SEPT9", "SEPT10", "SEPT11", "SEPT15", "SEPT6", "POU2F1", "POU2F2", "POU5F1", "POU5F1", "POU3F1", "POU3F2", "POU2F3", "MARCH1", "MARCH2", "MARCH3", "MARCH4", "MARCH5", "MARCH6", "MARCH7", "MARCH8", "MARCH9", "MARCH10", "MARCH11", "SMAP", "EFCAB13", "BRK1", "NYAP1", "LAMTOR1", 'AP1AR', "DEC1", "SEPT14") + cellv <- unlist(strsplit(as.character(cell), "; ")) + if (any(fixgenes %in% cellv)) { + cellv.new <- gsub(fixgenes[fixgenes %in% cellv], corrects[fixgenes %in% cellv], cellv) + return (paste(cellv.new, collapse="; ")) + } else return(cell) } + +#' Create PTM Peptide Names +#' +#' Builds standardized PTM peptide names from gene symbols, modification type, +#' amino acid, and site information. Supports ambiguous entries separated by a +#' delimiter. +#' +#' @param genes A character string of one or more gene symbols. +#' @param modification A character string indicating modification shorthand. +#' Defaults to `"p"`. +#' @param sites A character string of one or more site positions. +#' @param aa A character string containing the amino acid code. +#' @param pepsep A separator used in `genes` and `sites` for ambiguous entries. +#' Defaults to `";"`. +#' +#' @return A character string containing unique peptide names. +#' @export +#' +#' @examples +#' name.peptide("MAPK1; MAPK3", sites = "185; 204", aa = "T") +# This function will handle ambiguous modification sites (a modification site whose peptide sequence is the same in more than one protein) separated by ";" or another separator +# Make peptide names using this function: +name.peptide <- function (genes, modification="p", sites, aa, pepsep=";") { + genes.v <- unlist(strsplit(genes, pepsep, fixed = TRUE)) + genes.v[which(genes.v == "NA")] <- NA + genes.v <- genes.v[!is.na(genes.v)] + sites.v <- unlist(strsplit(sites, pepsep, fixed = TRUE)) + sites.v[which(sites.v == "NA")] <- NA + sites.v <- sites.v[!is.na(sites.v)] + sites.v <- sapply(sites.v, function (x) paste (aa, x, sep="")) + Peptide.v <- as.character(noquote(paste(genes.v[1:length(genes.v)], modification, sites.v[1:length(sites.v)], sep=" "))) + Peptide <- paste(unique(Peptide.v), collapse="; ") + return(Peptide) +} + +#' Merge Technical Replicate Values +#' +#' Merges two numeric values from technical replicates by returning `NA` when +#' both are missing, returning the observed value when one is missing, or +#' returning their mean when both are present. +#' +#' @param colv1 A numeric value or vector from replicate 1. +#' @param colv2 A numeric value or vector from replicate 2. +#' +#' @return A numeric value/vector or `NA`. +#' @export +#' +#' @examples +#' merge2cols(10, 14) +#' merge2cols(NA, 14) +#' merge2cols(c(NA, 2, 4), c(3, NA, 6)) +# Use this function to average technical replicates. This function ignores NA values in either column and takes the average in the case where there are two values. +merge2cols <- function (colv1, colv2) { + if (length(colv1) != length(colv2)) { + stop("colv1 and colv2 must have the same length.") + } + + colv1 <- as.numeric(colv1) + colv2 <- as.numeric(colv2) + + out <- (colv1 + colv2) / 2 + na1 <- is.na(colv1) + na2 <- is.na(colv2) + + out[na1 & !na2] <- colv2[na1 & !na2] + out[!na1 & na2] <- colv1[!na1 & na2] + out[na1 & na2] <- NA_real_ + + out +} + +# _________________________________________________________________________________ +# # Example data file /Users/markgrimes/Library/CloudStorage/Dropbox/_Work/R_/_LINCS/_KarenGuolin/unnormalized_separate_septm/phospho_cleaned_mapped.txt +# newphos <- read.table(datafilepath ="phospho_cleaned_mapped.txt", sep = "\t", skip = 0, header=TRUE, blank.lines.skip=T, fill=T, quote="\"", dec=".", comment.char = "", stringsAsFactors=F) +# # "Amino Acid" has the modsite, e.g. S,T, etc.; +# # "Position" has the sequence postion; "Positions" and "Positions Within Proteins" have multiple possible postions separated by ";". Assume singular value in Position is for the first gene name. "Modification Type" has Phosphorylation" etc. +# # In our data table, which includes ambigous PTM IDs, the relevant column names are +# # genes = "AllGeneSymbols" +# # positions = "Positions.Within.Proteins" +# # aa = "Amino.Acid" +# # modification = "Modification.Type" +# #### Workflow +# # First remove internal control rows (reverse sequences) +# newphos <- newphos[!is.na(newphos$AllGeneSymbols),] +# # If there are dates in the AllGeneSymbols column, use: +# newphos$AllGeneSymbols <- sapply(newphos$AllGeneSymbols, fix.excel) +# headercols <- c("AllGeneSymbols", "Amino.Acid", "Positions.Within.Proteins", "Modification.Type") +# newphos.head <- newphos[,headercols] +# +# newphos.head$Peptide.Name <- mapply(name.peptide, genes=newphos.head$AllGeneSymbols, sites= newphos.head$Positions.Within.Proteins, aa=newphos.head$Amino.Acid) +# # +# # Define Data columns +# phosdata <- newphos[,grep("Intensity", names(newphos))] +# names(phosdata) <- sapply(names(phosdata), function (x) unlist(strsplit(x, "Intensity."))[2]) +# # make zero into NA, which it is. +# zer0 <- which(phosdata==0, arr.ind = TRUE) +# phosdata <- replace (phosdata, zer0, NA) +# # Define technical replicates +# tr1 <- names(phosdata)[grep(".1", names(phosdata), fixed=TRUE)] +# tr2 <- names(phosdata)[grep(".2", names(phosdata), fixed=TRUE)] +# phosdata <- newphos[,grep("Intensity", names(newphos))] +# names(phosdata) <- sapply(names(phosdata), function (x) unlist(strsplit(x, "Intensity."))[2]) +# # make zero into NA, which it is. +# zer0 <- which(phosdata==0, arr.ind = TRUE) +# phosdata <- replace (phosdata, zer0, NA) +# # Define technical replicates +# tr1 <- names(phosdata)[grep(".1", names(phosdata), fixed=TRUE)] +# tr2 <- names(phosdata)[grep(".2", names(phosdata), fixed=TRUE)] +# # The optimal pulldown columns are straightforward to identify by the pulldown strings present in the sample names (they are also identifiable by zooming out and looking at the patterns of missing data, the optimal pulldowns, as a group, have the least missing data). +# tr1.opt <- tr1[grep("pTyr", tr1)] +# tr2.opt <- tr2[grep("pTyr", tr2)] +# +# +# # Use merge2cols() function to average technical replciates: +# phosdata.merged <- data.frame(matrix(nrow=nrow(phosdata), ncol=18)) +# for(i in 1:length(tr1.opt)) { +# phosdata.merged[,i] <- mapply(merge2cols, colv1=as.numeric(phosdata[, tr1.opt[i]]), colv2=as.numeric(phosdata[,tr2.opt[i]])) +# } +# names(phosdata.merged) <- sapply(tr1.opt, function(x) substr(x, start=1, stop=nchar(x)-2)) +# +# +# # Merge with header +# phosdatafile <- cbind(newphos.head, phosdata.merged) +# # This file could be safed for reference using write.table() +# # For subsequent steps +# rownames(phosdatafile) <- phosdatafile$Peptide.Name +# phosdata.df <- phosdatafile[,6:23] +# # This contains just the data with individual PTMs as rownames +# ###################### +# # Workflow: Repeat with aceltylation or other PTM data +# # ... +# # Then, to put them together: +# # Make column names common. For example: +# kgp <- phosdata.df +# kga <- ackdata.df +# kgu <- ubidata.df +# names(kgp) <- sapply(names(kgp), function (x) paste(unlist(strsplit(x, "_pTyr"))[1], unlist(strsplit(x, "_pTyr"))[2], sep="")) +# names(kga) <- sapply(names(kga), function (x) paste(unlist(strsplit(x, "_AcK"))[1], unlist(strsplit(x, "_AcK"))[2], sep="")) +# names(kgu) <- sapply(names(kgu), function (x) paste(unlist(strsplit(x, "_Ubi"))[1], unlist(strsplit(x, "_Ubi"))[2], sep="")) +# identical(names(kgp), names(kga)) # Check TRUE +# # +# ptmdata <- rbind (kgp, kga, kgu) # 3159 18 +# # Reorder here +# ptmdata <- ptmdata[order(rownames(ptmdata)),] +# # This optional step improves clustering in our hands: +# log2ptmdata <- log2(ptmdata) +# # This is now ready to use as the ptmtable. diff --git a/man/BuildPathwayCrosstalkNetwork.Rd b/man/BuildPathwayCrosstalkNetwork.Rd new file mode 100644 index 00000000..1c409fa6 --- /dev/null +++ b/man/BuildPathwayCrosstalkNetwork.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/PathwayCrosstalkNetwork.R +\name{BuildPathwayCrosstalkNetwork} +\alias{BuildPathwayCrosstalkNetwork} +\title{Build Pathway Crosstalk Network} +\usage{ +BuildPathwayCrosstalkNetwork( + common.clusters, + bioplanet.file = "bioplanet.csv", + createfile = getwd(), + PCN.edgelist.name = "PCN_file" +) +} +\arguments{ +\item{common.clusters}{The list of common clusters between all three distance metrics (Euclidean, Spearman, and SED). Can be made in MakeCorrelationNetwork} + +\item{bioplanet.file}{Either the path to a delimited Bioplanet file, or a named list of pathways where each list element is a character vector of gene symbols.} + +\item{createfile}{The path of where to create the edgelist file. Defaults to the working directory, if FALSE is provided, a file will not be created.} + +\item{PCN.edgelist.name}{Name of the PCN edgelist file to be created} +} +\value{ +A list with these data structures at the given index: \enumerate{ +\item{Contains pathway source-target columns, along with the interaction type.} +\item{Contains pathway source-target columns, with edge weights of their jaccard similarity and their Pathway-Pathway Evidence score.} +\item{All pathways in the bioplanet database as a named list containing string vectors. Each vector is a pathway with strings associated with the genes in that pathway.} +} +} +\description{ +Converts Bioplanet pathways from (\url{https://tripod.nih.gov/bioplanet/}) into a list of pathways whose elements are the genes in each pathway. Edge weights are either the PTM Cluster Weight or according to the Jaccard Similarity. +} +\examples{ +Example_Output <- BuildPathwayCrosstalkNetwork(ex_common_clusters, ex_pathways_list, createfile = FALSE) +Example_Output[[1]][[3,]] +Example_Output[[3]][[1:3]] +} diff --git a/man/ReadBioplanetFile.Rd b/man/ReadBioplanetFile.Rd new file mode 100644 index 00000000..cefd8c99 --- /dev/null +++ b/man/ReadBioplanetFile.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/PathwayCrosstalkNetwork.R +\name{ReadBioplanetFile} +\alias{ReadBioplanetFile} +\title{Read Bioplanet File} +\usage{ +ReadBioplanetFile(bioplanet.file = "bioplanet.csv") +} +\arguments{ +\item{bioplanet.file}{Path to a delimited Bioplanet text file. +Common delimiters such as comma, tab, semicolon, and pipe are supported.} +} +\value{ +A named list of pathways and associated gene symbols. +} +\description{ +Reads a Bioplanet CSV file and converts it into a named list of pathways, +where each list element is a character vector of gene symbols. +} diff --git a/man/filter.edges.0.Rd b/man/filter.edges.0.Rd new file mode 100644 index 00000000..deeb68ba --- /dev/null +++ b/man/filter.edges.0.Rd @@ -0,0 +1,293 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CytoscapeGraphingFunctions.R +\name{filter.edges.0} +\alias{filter.edges.0} +\title{Graphing and Manipulating Cluster Filtered Network} +\usage{ +filter.edges.0(nodenames, edge.file) +} +\arguments{ +\item{nodenames}{} + +\item{edge.file}{} + +\item{cfn}{A version of ppi.network with only the edges that exist in cccn.matrix and have non-zero weights} + +\item{ptmtable}{A dataset for post-translational modifications. Formatted with numbered rows, and the first column containing PTM names. The rest of the column names should be drugs. Values are numeric values that represent how much the PTM has reacted to the drug.} + +\item{funckey}{A table graphing gene names to type of protein; defaults to the internal dataset \code{PTMsToPathways::function_key}} + +\item{Network.title}{Desired title for the created Cytoscape Network; defaults to "cfn"} + +\item{Network.collection}{Desired name for the collection created on Cytoscape in which the network will reside; defaults to "PTMsToPathways"} + +\item{visual.style.name}{Desired name for the visual style created on Cytoscape; defaults to "PTMsToPathways.style"} + +\item{background.color}{Hex code of background color of graph; defaults to '#fcf3cf'} + +\item{edge.label.color}{Hex code of edge label color of graph; defaults to '#17202a'} + +\item{node.label.color}{Hex code of node label color of graph; defaults to '#145a32'} + +\item{default.font}{Font style of edge and node names; defaults to "Times New Roman"} + +\item{node.font.size}{Font size of the node name; defaults to 12} + +\item{edge.font.size}{Font size of the edge name; defaults to 8} + +\item{edge.line.style}{Type of edge style; defaults to "SOLID"; options include: "PARALLEL_LINES", "MARQUEE_EQUAL", "DOT", "EQUAL_DASH", "LONG_DASH", "CONTIGUOUS_ARROW", "MARQUEE_DASH", "DASH_DOT", "BACKWARD_SLASH", "FORWARD_SLASH", "VERTICAL_SLASH", "SOLID", "SEPARATE_ARROW", "MARQUEE_DASH_DOT", "ZIGZAG", "SINEWAVE"} + +\item{edge.opacity}{Opacity of the edge line on a scale of 0 - 255 with 0 being transparent; defaults to 175} + +\item{edge.label.opacity}{Opacity of the edge label on a scale of 0 - 255 with 0 being transparent; defaults to 255} + +\item{border.opacity}{Opacity of the node border on a scale of 0 - 255 with 0 being transparent; defaults to 255} + +\item{node.label.opacity}{Opacity of the node label on a scale of 0 - 255 with 0 being transparent; defaults to 255} + +\item{node.fill.opacity}{Opacity of the node fill on a scale of 0 - 255 with 0 being transparent; defaults to 255} +} +\value{ +A cytoscape graph of the cluster filtered network +} +\description{ +Creates a cytoscape graph of the cluster filtered network. Ensure that you have the Cytoscape app open and the RCy3 package downloaded. +The package RCy3 is required for many of the functions in this script. To download, run: +\itemize{ +\item if (!require("BiocManager", quietly = TRUE)) install.packages("BiocManager") +\item BiocManager::install("RCy3") +It is also recommended (but not required) that users load RCy3 with library(RCy3) to access its functions directly. +} +} +\details{ +Graph Key +\itemize{ +\item Node Size +\itemize{ +\item Greater the node size, larger the absolute value of the score +} +\item Blue Node +\itemize{ +\item Negative score +} +\item Yellow Node +\itemize{ +\item Positive score +} +\item Green Node +\itemize{ +\item Approximately zero score +Node Shapes: +} +\item "ELLIPSE" +\itemize{ +\item unknown +} +\item "ROUND_RECTANGLE" +\itemize{ +\item receptor tyrosine kinase +} +\item "VEE" +\itemize{ +\item SH2 protein +or +\item SH2-SH3 protein +} +\item "TRIANGLE" +\itemize{ +\item SH3 protein +} +\item "HEXAGON" +\itemize{ +\item tyrosine kinase +} +\item "DIAMOND" +\itemize{ +\item SRC-family kinase +} +\item "OCTAGON" +\itemize{ +\item kinase +or +\item phosphatase +} +\item "PARALLELOGRAM" +\itemize{ +\item transcription factor +} +\item "RECTANGLE" +\itemize{ +\item RNA binding protein +Node Border Colors: +} +\item Orange +\itemize{ +\item deacetylase +or +\item acetyltransferase +} +\item Blue +\itemize{ +\item demethylase +or +\item methyltransferase +} +\item Royal Purple +\itemize{ +\item membrane protein +} +\item Red +\itemize{ +\item kinase +or +\item tyrosine kinase +or +\item SRC-family kinase +} +\item Yellow +\itemize{ +\item phosphatase +or +\item tyrosine phosphatase +} +\item Lilac +\itemize{ +\item G protein-coupled receptor +or +\item receptor tyrosine kinase +} +\item Grey +\itemize{ +\item default +} +\item Edge Thickness +\itemize{ +\item Stronger correlation +Edge Colors: +} +\item Red +\itemize{ +\item Phosphorylation +or +\item pp +or +\item controls-phosphorylation-of +} +\item Bright Magenta +\itemize{ +\item controls-expression-of +} +\item Dull Magenta +\itemize{ +\item controls-transport-of +} +\item Purple +\itemize{ +\item controls-state-change-of +} +\item Blood Orange +\itemize{ +\item Acetylation +} +\item Lime Green +\itemize{ +\item Phystical interactions +} +\item Green +\itemize{ +\item BioPlex +} +\item Dull Green +\itemize{ +\item in-complex-with +} +\item Seafoam Green +\itemize{ +\item experiments +or +\item experiments_transferred +} +\item Cyan +\itemize{ +\item database +or +\item database_transferred +} +\item Teal +\itemize{ +\item Pathway +or +\item Predicted +} +\item Dark Turquoise +\itemize{ +\item Genetic interactions +} +\item Yellow-Orange +\itemize{ +\item correlation +} +\item Royal Blue +\itemize{ +\item negative correlation +} +\item Bright Yellow +\itemize{ +\item positive correlation +} +\item Grey +\itemize{ +\item combined_score +} +\item Dark Grey +\itemize{ +\item merged +} +\item Light Grey +\itemize{ +\item intersect +} +\item Black +\itemize{ +\item peptide +} +\item Orange +\itemize{ +\item homology +} +\item Dull Orange +\itemize{ +\item Shared protein domains +} +\item White +\itemize{ +\item Default +Arrow Types: +} +\item Arrow +\itemize{ +\item Phosphorylation +or +\item pp +or +\item controls-phosphorylation-of +or +\item controls-expression-of +or +\item controls-transport-of +or +\item controls-state-change-of +or +\item Acetylation +} +\item No Arrow +\itemize{ +\item Default +} +} +} +\examples{ +# GraphCFN(ex.cfn) +# See vignette for default graph + +} diff --git a/man/fix.excel.Rd b/man/fix.excel.Rd new file mode 100644 index 00000000..71f7b759 --- /dev/null +++ b/man/fix.excel.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RawDataProcessing.R +\name{fix.excel} +\alias{fix.excel} +\title{Correct Excel-Converted Gene Symbols} +\usage{ +fix.excel(cell) +} +\arguments{ +\item{cell}{A character string containing one or more gene symbols, separated +by \code{"; "}.} +} +\value{ +A character string with corrected gene symbols. +} +\description{ +Replaces gene symbols that are commonly converted into date-like strings by +spreadsheet software with their expected HUGO symbols. +} +\examples{ +fix.excel("1-Sep; CDC2") +} diff --git a/man/grapes-w-slash-o-grapes.Rd b/man/grapes-w-slash-o-grapes.Rd new file mode 100644 index 00000000..ea7ded98 --- /dev/null +++ b/man/grapes-w-slash-o-grapes.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CytoscapeGraphingFunctions.R +\name{\%w/o\%} +\alias{\%w/o\%} +\title{Set difference for vectors using custom infix operator} +\usage{ +x \%w/o\% y +} +\arguments{ +\item{x}{A vector.} + +\item{y}{A vector to compare.} +} +\value{ +Elements of x that are not in y. +} +\description{ +Returns the elements in vector x that are not in vector y. +} +\examples{ +1:5 \%w/o\% c(2, 4) +letters[1:5] \%w/o\% c("b", "d") +} diff --git a/man/graph.ptm.by.cluster.Rd b/man/graph.ptm.by.cluster.Rd new file mode 100644 index 00000000..25938da7 --- /dev/null +++ b/man/graph.ptm.by.cluster.Rd @@ -0,0 +1,92 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/BRCAHelpers.R +\name{graph.ptm.by.cluster} +\alias{graph.ptm.by.cluster} +\title{Heatmap of Phosphopeptides Grouped by Cluster} +\usage{ +graph.ptm.by.cluster( + ptmtable, + common.clusters, + filename = "ptm_by_cluster_heatmap.pdf", + legend.filename = NULL, + heatkey.filename = NULL, + cluster.order = names(common.clusters), + order.rows = c("as.is", "mean", "slope", "hclust"), + cluster.cols = FALSE, + zlim = 3, + min.shared = 2, + show.row.labels = FALSE, + show.col.labels = TRUE, + row_cex = 0.2, + col_cex = 0.8, + main = "Phosphopeptides grouped by cluster" +) +} +\arguments{ +\item{ptmtable}{A numeric matrix with PTMs as rows and samples as columns.} + +\item{common.clusters}{A named list where each element is a character vector +of PTM identifiers belonging to that cluster.} + +\item{filename}{Output filename for the main heatmap PDF. Defaults to +\code{"ptm_by_cluster_heatmap.pdf"}.} + +\item{legend.filename}{Optional filename for a separate PDF cluster legend. +If \code{NULL}, no legend file is written.} + +\item{heatkey.filename}{Optional filename for a separate PDF color scale key. +If \code{NULL}, no heatkey file is written.} + +\item{cluster.order}{Character vector specifying the order in which clusters +are displayed. Defaults to \code{names(common.clusters)}.} + +\item{order.rows}{Method to order rows within clusters: \code{"as.is"} (no reordering), +\code{"mean"} (by row-wise mean), \code{"slope"} (by linear trend), or \code{"hclust"} +(hierarchical clustering). Defaults to \code{"as.is"}.} + +\item{cluster.cols}{Logical; if \code{TRUE}, columns are also hierarchically +clustered. Defaults to \code{FALSE}.} + +\item{zlim}{Numeric; the upper/lower limit for the heatmap color scale +(values are clipped to \verb{[-zlim, zlim]}). Defaults to \code{3}.} + +\item{min.shared}{Minimum number of shared finite values required to compute +distance between two samples. Defaults to \code{2}.} + +\item{show.row.labels}{Logical; show row labels (PTM identifiers). +Defaults to \code{FALSE}.} + +\item{show.col.labels}{Logical; show column labels (sample names). +Defaults to \code{TRUE}.} + +\item{row_cex}{Character expansion factor for row labels. Defaults to \code{0.2}.} + +\item{col_cex}{Character expansion factor for column labels. Defaults to \code{0.8}.} + +\item{main}{Plot title. Defaults to \code{"Phosphopeptides grouped by cluster"}.} +} +\value{ +Invisibly returns a list containing: +\itemize{ +\item \code{mat}: The clustered expression matrix. +\item \code{blocks}: List of ordered sub-matrices, one per cluster. +\item \code{rowsep}: Row indices separating clusters. +\item \code{cluster.palette}: Named vector of cluster colors. +} +} +\description{ +Creates a large phosphopeptide heatmap colored by signal intensity, grouped +by consensus cluster with high-contrast cluster color bars. Supports custom +row ordering and optional legend/heatkey output files. +} +\examples{ +\dontrun{ +res <- graph.ptm.by.cluster( + ptmtable = ptmtable, + common.clusters = common.clusters, + filename = "ptm_heatmap.pdf", + order.rows = "hclust" +) +} + +} diff --git a/man/make.genepep.edges.Rd b/man/make.genepep.edges.Rd new file mode 100644 index 00000000..b2df0f20 --- /dev/null +++ b/man/make.genepep.edges.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CytoscapeGraphingFunctions.R +\name{make.genepep.edges} +\alias{make.genepep.edges} +\title{Title} +\usage{ +make.genepep.edges(peptide.edgefile) +} +\arguments{ +\item{peptide.edgefile}{} +} +\description{ +Title +} diff --git a/man/merge2cols.Rd b/man/merge2cols.Rd new file mode 100644 index 00000000..a85b528f --- /dev/null +++ b/man/merge2cols.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RawDataProcessing.R +\name{merge2cols} +\alias{merge2cols} +\title{Merge Technical Replicate Values} +\usage{ +merge2cols(colv1, colv2) +} +\arguments{ +\item{colv1}{A numeric value or vector from replicate 1.} + +\item{colv2}{A numeric value or vector from replicate 2.} +} +\value{ +A numeric value/vector or \code{NA}. +} +\description{ +Merges two numeric values from technical replicates by returning \code{NA} when +both are missing, returning the observed value when one is missing, or +returning their mean when both are present. +} +\examples{ +merge2cols(10, 14) +merge2cols(NA, 14) +merge2cols(c(NA, 2, 4), c(3, NA, 6)) +} diff --git a/man/name.peptide.Rd b/man/name.peptide.Rd new file mode 100644 index 00000000..e316af77 --- /dev/null +++ b/man/name.peptide.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RawDataProcessing.R +\name{name.peptide} +\alias{name.peptide} +\title{Create PTM Peptide Names} +\usage{ +name.peptide(genes, modification = "p", sites, aa, pepsep = ";") +} +\arguments{ +\item{genes}{A character string of one or more gene symbols.} + +\item{modification}{A character string indicating modification shorthand. +Defaults to \code{"p"}.} + +\item{sites}{A character string of one or more site positions.} + +\item{aa}{A character string containing the amino acid code.} + +\item{pepsep}{A separator used in \code{genes} and \code{sites} for ambiguous entries. +Defaults to \code{";"}.} +} +\value{ +A character string containing unique peptide names. +} +\description{ +Builds standardized PTM peptide names from gene symbols, modification type, +amino acid, and site information. Supports ambiguous entries separated by a +delimiter. +} +\examples{ +name.peptide("MAPK1; MAPK3", sites = "185; 204", aa = "T") +} diff --git a/man/ptms_to_cfn.Rd b/man/ptms_to_cfn.Rd new file mode 100644 index 00000000..c179d6e0 --- /dev/null +++ b/man/ptms_to_cfn.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CytoscapeGraphingFunctions.R +\name{ptms_to_cfn} +\alias{ptms_to_cfn} +\title{Extracts co-clustered PTM and gene network from a cluster filtered network object.} +\usage{ +ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") +} +\arguments{ +\item{ptms}{Character vector of PTM site strings (e.g., \"TP53 p S15\")} + +\item{cfn}{List or data frame representing the cluster filtered network (default: global cfn.merged)} + +\item{pepsep}{Character used to split ambiguous PTM entries (default: \";\")} +} +\value{ +Subnetwork object with co-clustered PTMs and genes +} +\description{ +This function takes a list of PTM (post-translational modification) site IDs and a cluster-filtered network, +extracts the genes from unambiguous and ambiguous PTM entries, then returns the co-clustered subnetwork. +} +\examples{ +# Example input objects +ptms <- c("TP53 p S15", "BRCA1 p S123; BRCA1 p T124", "MDM2 p S200") +# Example network +# edge list with 'source', 'target', and cluster info, could be data.frame or igraph +cfn.merged <- data.frame(source = c("TP53", "BRCA1", "BRCA1", "MDM2"), + target = c("BRCA1", "MDM2", "TP53", "TP53"), + cluster = c(1,1,2,1)) +# Suppose filter.edges.0 and get.co.clustered.ptms are also defined and loaded +# The following returns the gene/PTM subnetwork +res <- ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") +print(res) +} diff --git a/man/remove.autophos.Rd b/man/remove.autophos.Rd new file mode 100644 index 00000000..d2980675 --- /dev/null +++ b/man/remove.autophos.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CytoscapeGraphingFunctions.R +\name{remove.autophos} +\alias{remove.autophos} +\title{Title} +\usage{ +remove.autophos(edgefile) +} +\arguments{ +\item{edgefile}{} +} +\description{ +Title +} diff --git a/man/setCorrEdgeAppearance.Rd b/man/setCorrEdgeAppearance.Rd new file mode 100644 index 00000000..8e6875a6 --- /dev/null +++ b/man/setCorrEdgeAppearance.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CytoscapeGraphingFunctions.R +\name{setCorrEdgeAppearance} +\alias{setCorrEdgeAppearance} +\title{Title} +\usage{ +setCorrEdgeAppearance() +} +\description{ +Title +} diff --git a/man/setNodeSizeColorIndependently.Rd b/man/setNodeSizeColorIndependently.Rd new file mode 100644 index 00000000..bd5455ce --- /dev/null +++ b/man/setNodeSizeColorIndependently.Rd @@ -0,0 +1,68 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CytoscapeGraphingFunctions.R +\name{setNodeSizeColorIndependently} +\alias{setNodeSizeColorIndependently} +\title{Set node size and color from independent Cytoscape node attributes} +\usage{ +setNodeSizeColorIndependently(sizeplotcol, colorplotcol, ratio = FALSE) +} +\arguments{ +\item{sizeplotcol}{A character string naming the node table column to use +for node size mapping.} + +\item{colorplotcol}{A character string naming the node table column to use +for node color mapping.} + +\item{ratio}{Logical; if \code{TRUE}, use predefined log2 ratio control points +and ratio color palette. If \code{FALSE}, compute control points from the +observed ranges of \code{sizeplotcol} and \code{colorplotcol}.} +} +\value{ +This function is called for its side effects in Cytoscape and returns +\code{NULL} invisibly. +} +\description{ +Apply independent continuous visual mappings for node size and node color +in the active Cytoscape network using columns from the node table. +This helper is intended for PTMsToPathways-style networks where one node +attribute controls size and a different attribute controls color. +} +\details{ +If either requested column is not present in the Cytoscape node table, the +user is prompted to choose a valid column name interactively. For non-ratio +data, color and size control points are derived from the observed ranges of +the selected columns. For ratio-style data, fixed log2-based control points +are used. + +For \code{ratio = FALSE}, this function builds separate continuous mappings for +node color and node size based on the minimum and maximum values observed +in the selected Cytoscape node table columns. Blue-to-white-to-yellow color +gradients are used for color mapping. + +For \code{ratio = TRUE}, the function assumes ratio-like values centered on zero +and applies fixed log2-scaled control points for both node size and node +color. In this mode, a blue-to-green-to-yellow palette is used. + +The function also locks node width and height and sets the default node +selection color to magenta. +} +\examples{ +\dontrun{ +setNodeSizeColorIndependently( + sizeplotcol = "mean_expression", + colorplotcol = "logFC", + ratio = FALSE +) + +setNodeSizeColorIndependently( + sizeplotcol = "abundance_ratio", + colorplotcol = "abundance_ratio", + ratio = TRUE +) +} + +} +\seealso{ +\code{\link[RCy3:setNodeColorMapping]{RCy3::setNodeColorMapping()}}, \code{\link[RCy3:setNodeSizeMapping]{RCy3::setNodeSizeMapping()}}, +\code{\link[RCy3:lockNodeDimensions]{RCy3::lockNodeDimensions()}}, \code{\link[RCy3:getTableColumns]{RCy3::getTableColumns()}} +} diff --git a/tests/testthat/test_buildpathwaycrosstalknetwork.R b/tests/testthat/test_buildpathwaycrosstalknetwork.R new file mode 100644 index 00000000..8b4bc02e --- /dev/null +++ b/tests/testthat/test_buildpathwaycrosstalknetwork.R @@ -0,0 +1,56 @@ +# THESE USE DATA GENERATED FROM THE SMALL PTM TABLE + +test_that("BuildPathwayCrosstalkNetwork() gives right answer", { + + output <- suppressMessages(BuildPathwayCrosstalkNetwork(ex_common_clusters, ex_pathways_list, createfile = FALSE)) + + pathway_crosstalk_network <- output[[1]] + + row_7 <- as.list(pathway_crosstalk_network[7,]) + row_25 <- as.list(pathway_crosstalk_network[25,]) + + exp_row_7 <- list("Lipid and lipoprotein metabolism", "Vitamin B12 metabolism", 0.3, "PTM_cluster_evidence") + exp_row_25 <- list("ERBB signaling pathway", "Validated nuclear estrogen receptor alpha network", 0.025974025974026, "pathway_Jaccard_similarity") + + expect_setequal(row_7, exp_row_7) + expect_setequal(row_25, exp_row_25) + + + + PCNedgelist <- output[[2]] + + row_10 <- PCNedgelist[10,] + row_16 <- PCNedgelist[16,] + jaccard_sims <- PCNedgelist[4:8,3] + PTM_ev <- PCNedgelist[10:19,4] # Should be all zeros + + exp_row_10 <- list("Axon guidance", "Class A GPCRs (rhodopsin-like)", 0.00173310225303293, 0) + exp_row_16 <- list("RXR/VDR pathway", "Validated nuclear estrogen receptor alpha network", 0.0344827586206897, 0) + exp_jaccard_sims <- c(0.01391304, 0.53424658, 0.01104972, 0.02075472, 0.02597403) + exp_PTM_ev <- numeric(10) + + expect_setequal(row_10, exp_row_10) + expect_setequal(row_16, exp_row_16) + expect_equal(jaccard_sims, exp_jaccard_sims, tolerance = 1e-6) + expect_setequal(PTM_ev, exp_PTM_ev) + + + + pathways_list <- output[[3]] + + expect_true("CRP" %in% pathways_list$'Selenium pathway') + expect_true("FAM120B" %in% pathways_list$'RXR/VDR pathway') + expect_false("ALB" %in% pathways_list$'Mitochondrial fatty acid beta-oxidation') + + +}) + +test_that("ReadBioplanetFile() gives right answer", { + # read pathways.csv from inst/extdata and compare to ex_pathways_list + bioplanet_pathways <- ReadBioplanetFile(system.file("extdata", "pathway.csv", package = "PTMsToPathways")) + expect_equal(length(bioplanet_pathways), length(ex_pathways_list)) + for (pathway_name in names(ex_pathways_list)) { + expect_true(pathway_name %in% names(bioplanet_pathways)) + expect_equal(sort(bioplanet_pathways[[pathway_name]]), sort(ex_pathways_list[[pathway_name]])) + } +}) \ No newline at end of file diff --git a/vignettes/BRCANetworks.Rmd b/vignettes/BRCANetworks.Rmd new file mode 100644 index 00000000..6b510ac5 --- /dev/null +++ b/vignettes/BRCANetworks.Rmd @@ -0,0 +1,524 @@ +--- +title: "BRCA Networks" +date: "2026-05-29" + +author: Nagashree Avabhrath, Mikhail Ukrainetz, Madison Moffett, Grant Smith, Lucia + Williams, Mark Grimes +output: + html_document: + toc: true + toc_float: true + toc_depth: 4 + theme: cosmo + highlight: tango + pdf_document: + toc: true + toc_depth: '4' + word_document: + toc: true + toc_depth: '4' +vignette: > + %\VignetteIndexEntry{PTMsToPathways} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +editor_options: + markdown: + wrap: 72 +--- + +```{r setup, echo = FALSE} + +knitr::opts_chunk$set( + collapse = TRUE, + comment = ">>", + fig.align = "center", + fig.path = "plots/", + fig.width = 8, + fig.height = 6, + out.width = "100%", + results = "hold" +) +``` + +This tutorial will load and create networks from the breast cancer +cohort (BRCA, N = 122), produced by + +> **Proteogenomic landscape of breast cancer tumorigenesis and targeted +> therapy** Krug, K., Jaehnig, E. J., Satpathy, S., Blumenberg, L., +> Karpova, A., Anurag, M., et al. *Cell* 183, +> 1436PTMsToPathways::name6.e31 + +And downloaded from Supplemental data S2 from + +> **PhosphoDisco: A Toolkit for Co-regulated Phosphorylation Module +> Discovery in Phosphoproteomic Data** Schraink, T., Blumenberg, L., +> Hussey, G., George, S., Miller, B., Mathew, N., Gonzalez-Robles, T.J., +> Sviderskiy, V., Papagiannakopoulos, T., Possemato, R., et al. *Mol +> Cell Proteomics* 22, 100596. 10.1016/j.mcpro.2023.100596 + +First, let's load the PTMsToPathways package so its functions are +available: + +```{r eval = TRUE} +library(PTMsToPathways) +``` + +### Preprocess data for PTMsToPathways functions + +The BRCA data table described above is provided with the PTMsToPathways +package and and can be read in as follows: + +```{r eval = TRUE} +file_path <- system.file("extdata", "PhosphoDiscoData_mmc9.txt", package = "PTMsToPathways") +newphos <- utils::read.table(file_path, header = TRUE, + stringsAsFactors = FALSE, sep = "\t", comment.char = "#", + na.strings = "", quote = "", fill = TRUE) +``` + +It has 4237 rows and 124 columns representing 4237 phosphosites and 122 +samples with phosphoproteomic data. + +```{r eval = TRUE} +dim(newphos) +``` + +The first two columns are `gene_symbol` and `variable_sites_names` which +we will use to create row names for the PTM table to match the expected +input format for the PTMsToPathways functions. The [Raw Data Processing +vignette](RawDataProcessing.html) gives another example of how to +process raw data tables to create the expected input format for the +PTMsToPathways functions. Here are the first few rows and columns of the +data frame: + +```{r eval = TRUE} +head(newphos[, 1:5]) +``` + +Now we will process the first two columns to create row names for the +PTM table. We extract the amino acid and the site number from the +`variable_sites_names` column and remove trailing letters from the site +number if they exist. + +```{r eval = TRUE} + newphos$Amino.Acid <- sapply(newphos$variable_sites_names, function(x) substring (x, 1, 1)) + newphos$Site <- trimws(substring(newphos$variable_sites_names, 2)) + newphos$Site <- sub("[a-z]$", "", newphos$Site) + head(newphos[, c("gene_symbol", "variable_sites_names", "Amino.Acid", "Site")]) +``` + +Now use the PTMsToPathways function `name.peptide` to create peptide +names for the row names of the PTM table. + +```{r eval = TRUE} + newphos$Peptide.Name <- mapply( + name.peptide, genes = newphos$gene_symbol, + sites = newphos$Site, aa = newphos$Amino.Acid) +``` + +Create `ptmtable` with PTMs as rows and samples as columns for use in +the next steps, and remove the columns we used to create the row names. + +```{r eval = TRUE} +phosdata <- newphos[, 3:ncol(newphos), ] +rownames(phosdata) <- newphos$Peptide.Name +phosdata <- phosdata[, !(names(phosdata) %in% c("gene_symbol", "variable_sites_names", "Amino.Acid", "Site", "Peptide.Name"))] +ptmtable <- phosdata +head(ptmtable[, 1:5]) +``` + +### Create Clusters and Co-Cluster Correlation Networks (CCCNs) + +Next, we create clusters and networks from those clusters as in the +[Creating Networks vignette](CreatingNetworks.html). This takes about 10 +minutes on a laptop, so we provide both the code and the pre-computed +results for this step. To re-run the analysis, run, the following: + +```{r eval = FALSE} +set.seed(88) +clusterlist.data <- MakeClusterList(ptmtable, + keeplength = 3, toolong = 3.5) + +``` + +Or load in pre-computed results from within the PTMsToPathways package: + +```{r eval = TRUE} +clusterlist.data <- brca_clusterlist_data +CCCN.data <- brca_CCCN_data +``` + +Whether computed or loaded, the `cluster.data` and `CCCN.data` are lists +that contain the following elements: + +```{r eval = TRUE} +common.clusters <- clusterlist.data[[1]] +adj.consensus.matrix <- clusterlist.data[[2]] +ptm.correlation.matrix <- clusterlist.data[[3]] +``` + +These are required for the next step. + +```{r eval = TRUE} +CCCN.data <- MakeCorrelationNetwork(adj.consensus.matrix, + ptm.correlation.matrix) +ptm.cccn.edges <- CCCN.data[[1]] +gene.cccn.edges <- CCCN.data[[2]] +gene.cccn.nodes <- CCCN.data[[3]] +``` + +We expect >200 common clusters: + +```{r eval = TRUE} +length(common.clusters) +``` + +If desired, the clusters can be trimmed to those \> 4. This reduces the +number of clusters from 231 to 204. + +```{r eval = TRUE} +cclength <- sapply(common.clusters, length) +common.clusters4 <- common.clusters[which(cclength>3)] +length(common.clusters4) +``` + +We can use [graph.ptm.by.cluster](references/graph.ptm.by.cluster.html) +to visualize these in a heatmap. To demonstrate, let's look at the +output for the first 3 clusters. PTMs are rows and samples are columns, +and color represents the value of the PTM in that sample. Black +indicates missing values. + +```{r eval = TRUE, echo = FALSE} +fig_dir <- knitr::opts_current$get("fig.path") +``` + +```{r eval = TRUE} +dir.create(fig_dir, recursive = TRUE, showWarnings = FALSE) +output <- file.path(fig_dir, "ptm_all_clusters_l4.pdf") +res <- graph.ptm.by.cluster( + ptmtable = ptmtable, + common.clusters = common.clusters4[1:3], # use all clusters > 4, only first 3 + filename = output, + order.rows = "slope", + zlim = 3, + show.row.labels = FALSE, + show.col.labels = TRUE, + col_cex = 0.7 + ) +knitr::include_graphics(output) +``` + +PTMsToPathways provides the function +[`EvaluateClusters`](references/EvaluateClusters.html) which computes +the following for each cluster: + +- `intensity` = total signal after removing the NA fraction of samples + for this cluster +- `realsamples` = samples that are not single-gene/PTM samples for this + cluster +- `cleargenes` = genes/PTMs that fit a pattern that ranks by decreasing + total signal +- `percent.NA` = percentage of missing values in the cluster sub-table + +It also computes an `index` value for every cluster, which is a +composite value computed from the above. The output is ordered by this +`index` value, so we examine the top 10 clusters here: + +```{r eval = TRUE} +eval_brca <- EvaluateClusters( + common.clusters4, ptmtable, + data.type = "ratio", + use.slope = FALSE, + index.mode = "density", + verbose = FALSE + ) +eval_brca[1:10, ] + +``` + +### Build Cluster Filtered Networks (CFNs) and Pathway Crosstalk Networks (PCNs) + +For PPI edges, the code below demonstrates how to get the STRING-db and +GeneMANIA edges from the static human PPI data downloaded as local files. [todo: instructions +to download once these are available.] Alternatively, the PPI data +can be obtained from STRINGdb and GeneMANIA websites as demonstrated in the [Creating Networks vignette](CreatingNetworks.html). + +```{r eval = FALSE} +string_db_filepath <- "your/filepath/here.tsv" + +# optional check that the nodenames are consistent with STRINGdb +sym.map <- StandardizeGeneSymbols(gene.cccn.nodes) +identical (unique(sym.map$standard_symbol), gene.cccn.nodes) +# TRUE so no further action is required. +# If there were differences, replace symbol.map = NULL with symbol.map = sym.map + +stringdb.edges <- GetSTRINGdb.edges( gene.cccn.edges, + gene.cccn.nodes, + local = TRUE, + string.local.path = string_db_filepath, + combined.score.threshold = 400, + include.transferred = TRUE, + symbol.map = NULL) + +``` + +To avoid downloading the large STRINGdb edge file, edges from the BRCA gene set can be loaded from within the package: + +```{r eval = TRUE} +stringdb.edges <- BRCA_stringdb.edges +head(stringdb.edges) +``` + +The GeneMANIA human PPI edge file contains the following types of interactions: +"Genetic Interactions", "Pathway", "Physical Interactions", and "Predicted." + +We choose all but "Genetic Interactions" to include using gm.interaction.types in the following function. + +```{r eval = FALSE} +genemania_db_filepath <- "your/filepath/here.tsv" + +genemania.edges <- GetGeneMANIA.edges (gm.all.edges.path, + gene.cccn.nodes, + local = TRUE, + genemania.local.path = genemania_db_filepath, + gm.interaction.types = c("Pathway", "Physical Interactions", "Predicted")) +``` + +And again, to avoid downloading the large GeneMANIA edge file, BRCA gene edges can be loaded +from within the package: + +```{r eval = TRUE} +genemania.edges <- BRCA_genemania.edges +head(genemania.edges) +``` + +Next, we retrieve kinase-substrate edges, then obain the cluster filtered network, retaining PPIs only for proteins whose PTMs co-cluster, as demonstrated in the [Creating Networks vignette](CreatingNetworks.html). + +```{r eval = TRUE} +file_path <- system.file("extdata", "Kinase_Substrate_Dataset.txt", package = "PTMsToPathways") +kinsub.edges <- GetKinsub.edges(file_path, gene.cccn.nodes) + +# Now we can build the CFN. +network.list <- BuildClusterFilteredNetwork(gene.cccn.edges, + stringdb.edges, + genemania.edges, + kinsub.edges, + db.filepaths = c()) + +combined.PPIs <- network.list[[1]] +cfn <- network.list[[2]] +dim(cfn) + +cfn.merged <- mergeEdges(cfn) +dim(cfn.merged) +``` + +We build the PCN from the BioPlanet pathways as done previously. This +takes about a few minutes, so we provide both the code and the +pre-computed results for this step. To re-run the analysis, run the +following: + +```{r eval = FALSE} +bioplanet.file <- system.file("extdata", "bioplanet_pathway_June2025.csv", package = "PTMsToPathways") +PCN.data <- BuildPathwayCrosstalkNetwork(common.clusters, bioplanet.file, + createfile = FALSE) +``` + +Or load in pre-computed results from within the PTMsToPathways package: + +```{r eval = TRUE} +PCN.data <- BRCA_PCN.data +pathway.crosstalk.network <- PCN.data[[1]] # 679707 edges +PCNedgelist <- PCN.data[[2]] +pathways.list <- PCN.data[[3]] +dim(pathway.crosstalk.network) +``` + +Now we can explore these networks. + +### Preprocess modules from Shraink et al. + +We will examine the modules from Schraink, et al., 2023, Supplemental +Table S4. Per their description, the `HDBSCAN;min_cluster_size-4` column +assigns a module number to each phosphosite. + +```{r eval = TRUE} +PD_module.file <- system.file("extdata", "PhosDiscoModules_mmc11.txt", package = "PTMsToPathways") + +PD_module.df <- utils::read.table(PD_module.file, header = TRUE, + stringsAsFactors = FALSE, sep = "\t", comment.char = "#", + na.strings = "", quote = "", fill = TRUE) +dim(PD_module.df) # should be 1017 rows +head(PD_module.df) +``` + +We note that there are differences from the PTM table imported above. We will work with those sites that match between ptmtable and PD_module.df + +```{r eval = TRUE} +length(intersect(newphos$variable_sites_names, PD_module.df$variableSites)) # 530 +``` + +```{r eval = TRUE} +length(intersect(newphos$gene_symbol, PD_module.df$geneSymbol)) # 161 +``` + +Make peptide names as above: + +```{r eval = TRUE} + +PD_module.df$Amino.Acid <- sapply(PD_module.df$variableSites, function(x) substring (x, 1, 1)) +PD_module.df$Site <- trimws(substring(PD_module.df$variableSites, 2)) +PD_module.df$Site <- sub("[a-z]$", "", PD_module.df$Site) + +PD_module.df$Peptide.Name <- mapply( + name.peptide, genes = PD_module.df$geneSymbol, + sites = PD_module.df$Site, aa = PD_module.df$Amino.Acid) +head(PD_module.df) +``` + +Treat modules like our clusters: + +```{r eval=TRUE} +PD.module.list <- split(PD_module.df$Peptide.Name, PD_module.df$HDBSCAN.min_cluster_size.4) +length(PD.module.list) +PD.module.list$`68` +``` + +Let's get the unique genes in each module to compare to the unique genes +in our clusters. + +```{r eval = TRUE} +PD.module.genes.unique <- lapply( + PD.module.list, + function(x) unique(sub(" .*", "", x))) +PD.module.genes.unique$`68` +``` + +### Compare P2P clusters and Schraink, et al. modules + +Let's see which of our clusters have intersections with module 63. We +will use this as an example to show how to explore the networks around a +particular module of interest. + +```{r eval = TRUE} +mod63.intersect <- Filter(length, Map(intersect, common.clusters, list(PD.module.list$`63`))) +mod63.intersect +``` + +Interesctions of more than one PTM were found in several ConsensusClusters. + +There are >200 PTMs in the P2P clusters that intersect with module 63: + +```{r eval = TRUE} +mod63.clust.ptms <- unlist(c(common.clusters$ConsensusCluster1, common.clusters$ConsensusCluster3, common.clusters$ConsensusCluster4, common.clusters$ConsensusCluster5, common.clusters$ConsensusCluster6)) +length(mod63.clust.ptms) +``` + +P2P provides functions to prepare visualizations of these PTMs in +Cytoscape. This code graphs the CFN/CCCN from all these PTMs: + +```{r eval = TRUE} + funckey <- function_key +cfn.cccn <- ptms_to_cfn(mod63.clust.ptms, cfn = cfn.merged, pepsep = ";") +cfn_cccn.nodes <- make.cytoscape.node.file(cfn.cccn, funckey, ptmtable, + include.gene.data = TRUE, + include.coclustered.PTMs = TRUE) +``` + +To graph in Cytoscape: + +```{r eval = FALSE} +g1 <- GraphCfn(cfn.edges = cfn.cccn, cfn.nodes = cfn_cccn.nodes, + Network.title = "CFN/CCCN All PTMs 1", Network.collection = "PTMsToPathways") + +``` + +The above would create a graph using Cytoscape, which would look like: + +```{r eval = TRUE, echo = FALSE} +knitr::include_graphics("vig_figs/Mod63_AllPTMs_X03BR011.png") +``` + +This is a complex graph that shows PTMs clusters as cliques connected by +yellow correlation edges surrounding a CFN of interconnected gene nodes. + +Let's focus on CDK1 substrates to complement the work done in Schraink, +et al., 2023. There are 82 CDK1 edges. + +```{r eval = TRUE} +cdk1.kinsub <- filter.edges.1("CDK1", kinsub.edges) +dim(cdk1.kinsub) +head(cdk1.kinsub) +``` + +Now let's get the needed information about these edges: + +```{r eval = TRUE} +cdk1.substrates <- cdk1.kinsub [which(cdk1.kinsub$source=="CDK1"), "target"] +cfn_cccn.nodes.cdksubs <- cfn_cccn.nodes[cfn_cccn.nodes$Gene.Name %in% cdk1.substrates, ] + +``` + +Two ways to select for CDK1 substrates are presented. Method 1: Using +RCy3. + +```{r eval = FALSE} +library(RCy3) +selectNodes(cfn_cccn.nodes.cdksubs$id, by = "id", preserve=FALSE) +selectEdgesConnectingSelectedNodes() +createSubnetwork(nodes = getSelectedNodes(), edges = getSelectedEdges(), nodes.by.col = "id", edges.by.col = "name") +``` + +Method 2: Using P2P functions. + +```{r eval = FALSE} +cfn_cccn.nodes.cdksubs.edges <- filter.edges.0(nodenames = cfn_cccn.nodes.cdksubs$id, edge.file = cfn.cccn) + +g2 <- GraphCfn(cfn.edges = cfn_cccn.nodes.cdksubs.edges, cfn.nodes = cfn_cccn.nodes.cdksubs, + Network.title = "CFN/CCCN All PTMs 5", Network.collection = "PTMsToPathways") +``` + +Both methods give the same result, which looks like this: + +```{r eval = TRUE, echo = FALSE} +knitr::include_graphics("vig_figs/CDKsub63_allPTMs_X03BR011.png") +``` + +Now further simplify the graph to show only CCCN PTMs. + +```{r eval = FALSE} +cdksubs.genes <- unique(cfn_cccn.nodes.cdksubs$id) +cdksubs.cfn <- filter.edges.0(cdksubs.genes, cfn.merged) +cdksubs.cfn.cccn <- get.co.clustered.ptms(cdksubs.cfn, ptm.cccn.edges=ptm.cccn.edges) +cdksubs.cfn.cccn.nodes <- make.cytoscape.node.file(cdksubs.cfn.cccn, funckey, ptmtable, + include.gene.data = TRUE, + include.coclustered.PTMs = TRUE) +g3 <- GraphCfn(cfn.edges = cdksubs.cfn.cccn, cfn.nodes = cdksubs.cfn.cccn.nodes, + Network.title = "CFN/CCCN All PTMs 6", Network.collection = "PTMsToPathways") +``` + +This gives the following graph: + +```{r eval = TRUE, echo = FALSE} +knitr::include_graphics("vig_figs/CDKsub63_CCPTMs_X03BR011.png") +``` + +Each of these graphs can be modified to set node size and shape using +the following P2P functions that act on the front window in Cytoscape. +Note the differences that reflect activation of different cell signaling +pathways in tumors with different mutations: X02BR011 (AKT1 missense +mutant); X21BR010 (PIK3CA missense mutant); and X05BR045 (TP53 nonsense +and MLLT4 frameshift mutants). + +```{r eval = FALSE} +library(RCy3) +setNodeColorToRatios(plotcol="X03BR011") +setNodeColorToRowz(plotcol="X03BR011") # This exaggerates the node size and shape somewhat. +setNodeColorToRatios(plotcol="X21BR010") +setNodeColorToRowz(plotcol="X21BR010") +setNodeColorToRatios(plotcol="X05BR045") +setNodeColorToRowz(plotcol="X05BR045") +``` + +Note that different samples have dramatically different differences in +PTMs that are up or down, which is reflected also in total in gene +nodes. diff --git a/vignettes/CreatingNetworks.Rmd b/vignettes/CreatingNetworks.Rmd new file mode 100644 index 00000000..b19f43f1 --- /dev/null +++ b/vignettes/CreatingNetworks.Rmd @@ -0,0 +1,463 @@ +--- +title: 'Using the P2P Package: A Step-by-Step Tutorial' +author: Nagashree Avabhrath, Mikhail Ukrainetz, Madison Moffett, Grant Smith, Lucia + Williams, Mark Grimes +output: + html_document: + toc: true + toc_float: true + toc_depth: 4 + theme: cosmo + highlight: tango + pdf_document: + toc: true + toc_depth: '4' + word_document: + toc: true + toc_depth: '4' +vignette: > + %\VignetteIndexEntry{PTMsToPathways} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +editor_options: + markdown: + wrap: 72 +--- + +```{r setup, echo = FALSE} + +knitr::opts_chunk$set( + collapse = TRUE, + comment = ">>", + fig.align = "center", + fig.path = "plots/", + fig.width = 8, + fig.height = 6, + out.width = "100%", + results = "hold" +) +``` + +This tutorial is intended to be a step-by-step guide to walk users +through the process of using the P2P package. It includes descriptions +of each function and must be run in order as subsequent steps require +the data produced in previous steps. Example code and example outputs as +well as estimated run-times are included with each description and are +based on a preliminary dataset of \~9000 PTMs and 69 experimental +conditions processed with a 12th Gen i7 processor and 16GB of RAM. + +**An important note:** The returned outputs from the functions are data +that may be saved in an RData object so that the user may reload the +data, which may take a while to generate, and pick up where they left +off later. See the bottom of this document for code to save your data +efficiently. + +# Installing the Package + +You will need to install the devtools package, which can be installed +with: + +```{r eval = FALSE, echo = TRUE} +install.packages("devtools") +``` + +Next, install the package with: + +```{r eval = FALSE, echo = TRUE} +devtools::install_github("UM-Applied-Algorithms-Lab/PTMsToPathways") +``` + +And load the package: + +```{r eval = TRUE, echo = TRUE} +library(PTMsToPathways) +``` + +# Starting Data + +For the tutorial, we will be using two example datasets: a smaller +dataset consisting of 933 PTMs and 18 experimental conditions (the +example used in the [Raw Data Processing +vignette](RawDataProcessing.html)) and a larger dataset containing +around 9000 PTMs and 69 experimental conditions. These datasets are +available with the package. Alternatively, the larger dataset can be +downloaded +[here](https://github.com/UM-Applied-Algorithms-Lab/PTMsToPathways/raw/refs/heads/main/inst/extdata/AlldataPTMs.txt) +to be inspected locally. + +To see all data that is provided with the package, run: + +```{r eval = FALSE, echo = TRUE} +data(package = "PTMsToPathways") +``` + +```{r eval = TRUE, echo = FALSE} +dat <- as.data.frame(data(package = "PTMsToPathways")$results) +dat <- dat[, c("Item", "Title")] +knitr::kable(dat, col.names = c("Dataset Name", "Description"), align = 'l') +``` + +If you are using the smaller dataset, use the following code to view the +dimensions of the dataset and a small portion of it: + +```{r eval = TRUE, echo = TRUE} +dim(ex_small_ptm_table) +ex_small_ptm_table[38:50, 1:2] +``` + +If you want to use the bigger dataset, the following code shows the +dimensions and a snippet of the dataset: + +```{r eval = TRUE, echo = TRUE} +dim(ex_full_ptm_table) +ex_full_ptm_table[38:50, 1:2] +``` + +If you have downloaded the larger dataset locally, you can read it into +R using the following code: + +```{r eval = FALSE, echo = TRUE} +allptmtable <- utils::read.table("AlldataPTMs.txt", sep = "\t", skip = 0, + fill = T, quote = "\"", dec = ".", + comment.char = "", stringsAsFactors = F) +``` + +## Using Your Own Data + +To use your own MS data, you will need to transform it into a dataframe +with PTMs and row names, experimental conditions as column names, and +numeric data as the entries. Please refer to the [Raw Data Processing +vignette](RawDataProcessing.html) for a tutorial showing all steps +needed to transform an MS output file into a P2P package input +dataframe. + +# Step 1: Make Cluster List + +`MakeClusterList` is the first step in the P2P process. This function +takes the dataframe `ptmtable` and runs it through three calculations of +statistical measures of distance: Euclidean Distance, Spearman +Dissimilarity (1- \|Spearman Correlation\|), and SED (the average of +both Spearman Dissimilarity (1- Spearman Correlation) and Euclidean +Distance). Combining the two dissimilarities leads to better resolution +of the data and is useful in pattern recognition. A correlation table--- +`ptm.correlation.matrix`---is generated based on the distances +calculated for each pair of PTMs. The function then runs the matrices +through t-SNE to generate clusters based on the previously calculated +distance and provides you with a cluster list, `common.clusters`. The +returned `adj.consensus.matrix` (which identifies which PTMs cluster +together with a 'short distance' between them) and +`ptm.correlation.matrix` are also used in the next step to create +co-cluster correlation networks (CCCNs). These three outputs are +returned as a list. + +The `keeplength` paramter defines the minimum number of PTMs that must +be in a cluster for it to be retained in the final output. The `toolong` +parameter defines the maximum distance between two PTMs for them to be +considered as clustering together. + +`MakeClusterList` can be run like so: + +```{r eval = TRUE, echo = TRUE} +set.seed(88) +clusterlist.data <- MakeClusterList(ex_small_ptm_table, + keeplength = 2, toolong = 3.5) +``` + +The following unpacks the output into the separate objects discussed +above: + +```{r eval = TRUE, echo = TRUE} +common.clusters <- clusterlist.data[[1]] +adj.consensus.matrix <- clusterlist.data[[2]] +ptm.correlation.matrix <- clusterlist.data[[3]] +``` + +Now we can view the objects. First, here is an example of a cluster: + +```{r eval = TRUE, echo = TRUE} +common.clusters[1] +``` + +Next, we look at a piece of the adjacency matrix. Ones represent a pair +that cluster and zeroes represent a pair that doesn't: + +```{r eval = TRUE, echo = TRUE} +adj.consensus.matrix[7:10, 7:10] +``` + +Here is a part of the PTM correlation matrix. Values for pairs of PTMs +are Spearman correlation coefficients ranging from -1 to 1. If two PTMs +had no experimental conditions in common, their correlation value will +be NA. + +```{r eval = TRUE, echo = TRUE} +ptm.correlation.matrix[38:43, 1:2] +``` + +#### Estimated run-time (for large dataset) + +\~60min + +# Step 2: Make Co-Cluster Correlation Networks (PTM and Gene) + +The data generated in the previous step is next used to create a new +network of PTMs that have strong associations called the Co-cluster +Correlation Network (CCCN). The Spearman correlations between +co-clustered PTMs are used as edge-weights in this network. The +MakeCorrelationNetwork function groups the PTM correlation matrices by +PTMs that co-cluster together to create a PTM CCCN. It then defines a +relationship between proteins modified by PTMs and creates a gene CCCN +with sum of the PTM correlations serving as edge weights. + +```{r eval = TRUE, echo = TRUE} +CCCN.data <- MakeCorrelationNetwork(adj.consensus.matrix, + ptm.correlation.matrix) +ptm.cccn.edges <- CCCN.data[[1]] +gene.cccn.edges <- CCCN.data[[2]] +gene.cccn.nodes <- CCCN.data[[3]] +``` + +We can view a portion of the PTM CCCN edges: + +```{r eval = TRUE, echo = TRUE} +ptm.cccn.edges[18:22,] +``` + +And a portion of the gene CCCN edges: + +```{r eval = TRUE, echo = TRUE} +gene.cccn.edges[1:5,] +``` + +Finally, we can view a portion of the gene CCCN nodes, which are used to +map to external PPI databases in the next step: + +```{r eval = TRUE, echo = TRUE} +gene.cccn.nodes[1:5] +``` + +Because this step can take a long time to run on larger datasets, the +output may be saved as an RData object for later use. + +```{r eval = FALSE, echo = TRUE} +save.image(file = "filepath/name.RData") +# All objects in the environment are saved +``` + +#### Estimated run-time (for large dataset) + +\~10min + +# Step 3: Retrieve Database Edgefiles + +The third step of the P2P package is to gather data from multiple +existing protein-protein interaction (PPI) databases which will be +integrated with the data generated in steps 1 and 2. The P2P package +explicitly allows the users to integrate data from three external +databases: STRING, GeneMANIA, and PhosphoSite Plus. Other databases can +also be downloaded and added to the PPI network. All three external +databases have different interfaces for downloading data, so we show how +to retrieve data from each of them below. + +For this tutorial, we query STRINGdb and GeneMANIA directly. We also provide the option (using the switch local = TRUE) to retrive edges from human PPIs from these sources in pre-assembled files; the [BRCANetworks vignette](BCRANetworks.html) demonstrates how to get the STRING-db and GeneMANIA edges from the static downloaded networks. + +### 1. STRINGdb + +[STRINGdb](https://string-db.org/) can be queried directly from R using +the `STRINGdb` package. We wrap this query in a function called +`GetSTRINGdb.edges`, which queries only for the genes found in clusters +in previous steps, and filters the returned by interaction type so only +`experimental`, `database`, `experimental_transferred`, and +`database_transferred` are retained. This ensures that only interactions +with more substantial evidence are used in this analysis. + +```{r eval = TRUE, echo = TRUE} +stringdb.edges <- GetSTRINGdb.edges(gene.cccn.edges, gene.cccn.nodes) +stringdb.edges[1:5,] +``` + +### 2. GeneMANIA + +To our knowledge, no R package exists to programmatically query +[GeneMANIA](https://genemania.org/). Thus, we recommend using the +GeneMANIA Cytoscape App to retrieve PPI data as follows. + +First, create an input file for GeneMANIA using the `MakeDBInput` +function provided within P2P (note that this creates a text file in your +working directory): + +```{r eval = TRUE, echo = TRUE} +MakeDBInput(gene.cccn.nodes, file.path.name = "db_nodes.txt") +``` + +Next, ensure that you have +[Cystoscape](https://cytoscape.org/download.html) and the [GeneMANIA +extension](https://apps.cytoscape.org/apps/genemania) installed. + +Copy the contents of the `db_nodes.txt` file into the GeneMANIA App's +"Genes of Interest" box and run query. + +To save the results, click on the three lines in the upper right corner. +This should be under the GeneMANIA side window beside the species. Click +"Export Results". The path to this file is the gm.results.path: + +![](vig_figs/GeneMANIA-image-1.png)\ +![](vig_figs/GeneMANIA-image-2.png) + +The `GetGeneMANIA.edges` function then processes the output file +produced by GeneMANIA itself. For example, we have saved +`ex_gm_results.txt` as an example output file from GeneMANIA within the +package. The following code shows how to use this file as input to the +function. + +```{r eval = TRUE, echo = TRUE} +gm.results.path <- system.file("extdata", "ex_gm_results.txt", + package = "PTMsToPathways") +genemania.edges <- GetGeneMANIA.edges(gm.results.path, gene.cccn.nodes) +``` + +We can see an example of the GeneMANIA edges below: + +```{r eval = TRUE, echo = TRUE} +genemania.edges[1:5,] +``` + +### 3. Phosphosite Plus + +The kinase-substrate data can be downloaded from [Phosphosite +Plus](https://www.phosphosite.org/staticDownloads) database. The users +will be required to create an account and sign in to download the data. +The `GetKinsub.edges` function reads this downloaded data in and formats +it so that all the PPI edge data frames are in the same format for the +next step. + +```{r eval = TRUE, echo = TRUE} +input.filename <- system.file("extdata", "Kinase_Substrate_Dataset.txt", + package = "PTMsToPathways") +``` + +```{r eval = TRUE, echo = TRUE} +kinsub.edges <- GetKinsub.edges(input.filename, + gene.cccn.nodes) +``` + +# Step 4: Build PPI Network and Cluster Filtered Network + +The `BuildClusterFilteredNetwork` function allows the users to filter +protein-protein interaction networks using the previously generated +co-cluster correlation networks. PPIs are retained in the cluster +filtered network (CFN) only if the interacting proteins share +statistically correlated PTMs identified via t-SNE clusters. The +`BuildClusterFilteredNetwork` function combines all the PPI data +downloaded in step 3 as efficiently as possible while retaining the +desired edge weights. It then normalizes the weights on a scale of 0-1 +and gives an output cluster filter network that will only retain +interacting proteins whose genes are within the co-cluster correlation +network created in step 2. + +We first run the function: + +```{r eval = TRUE, echo = TRUE} +network.list <- BuildClusterFilteredNetwork(gene.cccn.edges, + stringdb.edges, + genemania.edges, + kinsub.edges, + db.filepaths = c()) +``` + +And then unpack the outputs into separate variables: + +```{r eval = TRUE, echo = TRUE} +combined.PPIs <- network.list[[1]] +cfn <- network.list[[2]] +``` + +We can view a portion of the CFN below: + +```{r eval = TRUE, echo = TRUE} +cfn[1:5,] +``` + +To reduce clutter on graphs, the CFN edges can be merged. This collapses +two or more edges between two nodes into a single edge, combining edge +names: + +```{r eval = TRUE, echo = TRUE} +cfn.merged <- mergeEdges(cfn) +``` + +# Step 5: Pathway Crosstalk Network + +The final step is the creation of the Pathway Crosstalk Network (PCN). +This step requires input of an external database from [NCATS +BioPlanet](https://tripod.nih.gov/bioplanet/download/pathway.csv) that +contains groups of genes (proteins) involved in various cellular +processes known as pathways. `BuildPathwayCrosstalkNetwork` turns this +data file into a list of pathways and converts those pathways into a +list of pathway-pathway edges, each of which is assigned a Jaccard +similarity and a Cluster-Pathway Evidence score based on the common +clusters found in the gene co-cluster correlation network. Info about +the Cluster-Pathway Evidence score can be found +[here](https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1010690) +For graphing in Cytoscape, the Cluster-Pathway Evidence and Jaccard +similarity edges are listed separately in the edgelist called +pathway.crosstalk.network. + +```{r eval = TRUE, echo = TRUE} +bioplanet.file <- system.file("extdata", "pathway.csv", + package = "PTMsToPathways") +``` + +```{r eval = TRUE, echo = TRUE} +PCN.data <- BuildPathwayCrosstalkNetwork(common.clusters, bioplanet.file, + createfile = FALSE) +pathway.crosstalk.network <- PCN.data[[1]] +PCNedgelist <- PCN.data[[2]] +pathways.list <- PCN.data[[3]] +``` + +And we can see some of the pathway crosstalk network edges below: + +```{r eval = FALSE, echo = TRUE} +pathway.crosstalk.network[1:5,] +``` + +```{r eval = TRUE, echo = TRUE} +dat <- pathway.crosstalk.network[1:5,] +knitr::kable(dat, align = 'l', digits = 2) +``` + +# Saving Data + +If you want to save your data to a file, all data structures can either +be exported with the save function and loaded later or saved to a csv +file with the write.csv function. + +To save one object: + +```{r eval = FALSE, echo = TRUE} +save(object, filename = "filepath/name.rda") # Saves object as an .rda +load("filepath/name.rda") # Loads object saved to a file +``` + +For multiple objects: Note the objects are saved as an .RData rather +than an .rda + +```{r eval = FALSE, echo = TRUE} +save(object1, object2, object.ect, filename="NewFile.RData") +``` + +To save one object as a csv: + +```{r eval = FALSE, echo = TRUE} +utils::write.csv(object, file = "filepath/name.csv") # Saves object as a .csv +utils::read.csv(file = "filepath/name.csv") # Loads object from .csv +``` + +You may also save your entire Global Environment namespace using the +save.image function as shown below: + +```{r eval = FALSE, echo = TRUE} +save.image(file = "filepath/name.RData") +# All objects in the environment are saved +``` diff --git a/vignettes/CytoscapeGraphing.Rmd b/vignettes/CytoscapeGraphing.Rmd new file mode 100644 index 00000000..0f07a5b8 --- /dev/null +++ b/vignettes/CytoscapeGraphing.Rmd @@ -0,0 +1,365 @@ +--- +title: "Cytoscape Graphing" +author: "Mark Grimes" +date: "2025-09-25" +output: + html_document: + toc: true + toc_float: true + toc_depth: 4 + theme: cosmo + highlight: tango + pdf_document: + toc: true + toc_depth: '4' + word_document: + toc: true + toc_depth: '4' +vignette: > + %\VignetteIndexEntry{Cytoscape Graphing} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +editor_options: + markdown: + wrap: 72 +--- + +```{r setup, echo = FALSE} + +knitr::opts_chunk$set( + collapse = TRUE, + comment = ">>", + echo = TRUE, + fig.align = "center", + fig.path = "plots/", + fig.width = 8, + fig.height = 6, + out.width = "100%", + results = "hold" +) + +``` + +## + +## Graphing Networks in Cytoscape + +### Top-down approach starting with pathways + +It is possible to graph the entire PCN, CFN, and CCCNs in their entirety, though +very large graphs take a long time to graph. One approach to navigating these +data structures is to select nodes from the large networks in Cytoscape (or in R +using `RCy3::selectNodes`) and select nearest neighbors or shortest paths and +create a subnetwork in a new window (see the [Cytoscape Manual](https://manual.cytoscape.org/en/stable/)). + +The alternative approach described below is to identify pathways, genes, and +PTMs of interest in the R data objects, then make smaller, more interpretable +graphs in Cytoscape using RCy3. + +For example, we will find names of all pathways in Bioplanet that contain EGFR. +The data object `pathways.list` is a list, where the name of the list element is +the name of a Bioplanet pathway and each element is a character vector of the +genes in that pathway. Then we want to find interactions between the pathway +"Transmembrane transport of small molecules" and those pathways. The utility +functions used below are in CytoscapeGraphing.R + +```{r eval = TRUE, echo = FALSE} +library(PTMsToPathways) +funckey <- function_key +``` + +```{r eval = TRUE} +egfr_pathways <- names(ex_pathways_list)[sapply(1:length(ex_pathways_list), function(x) + {"EGFR" %in% ex_pathways_list[[x]]})] +``` + +We expect 83 pathways that contain EGFR, so let's check: +```{r eval = TRUE} +length(egfr_pathways) +``` + +```{r eval = TRUE} +egfr_transporter.pcn <- filter.edges.between( + "Transmembrane transport of small molecules", + egfr_pathways, ex_PCNedgelist) +``` + +```{r eval = FALSE} +egfr_transporter_pcn.cy <- filter.edges.between( + "Transmembrane transport of small molecules", + egfr_pathways, pathway.crosstalk.network) + +head(egfr_transporter.pcn) +``` + +These two versions of the PCN show cluster evidence and Jaccard smilarity in +adjacent columns (the first case) or as distinct edges (the second case, which +can be used to plot this network in cytoscape). + +```{r eval = FALSE} +# Graph PCN +pcn.graph.1 <- cytoscape.graph.PCN.pathways( + PCN = egfr_transporter_pcn.cy, + net.name = "EGFR signaling and transmembrane transporters", + Jaccard.edges = TRUE) +``` + +Let's zero in on interactions between proteins in the two pathways "EGF/EGFR signaling pathway" and "Transmembrane transport of small molecules" because they have no genes in common, yet the cluster evidence for their interaction is strong. First we extract a network of interactions between the genes in the two pathways. Then we generate a node file for cytoscape. In the following case we include the data extracted from the ptmtable. This is optional, useful if node size and color is used later to indicate values in data. + +```{r eval = FALSE} +egfr_transporter.cfn <- filter.edges.0(c( + pathways_list[["EGF/EGFR signaling pathway"]], + pathways_list[["Transmembrane transport of small molecules"]]), cfn) + +egfr_transporter.nodes <- make.cytoscape.node.file( + egfr_transporter.cfn, funckey, ptmtable, include.gene.data = TRUE) + +``` + +The function GraphCfn creates a graph using the cluster filtered network in the Cytoscape app. When graphed, Cytoscape provides an interactive interface to view the data. This function requires the edge list file (egfr_transporter.cfn in the example), and node data file (egfr_transporter.nodes). + +#### Generating the graph and setting node size and color + +```{r eval = FALSE} +GraphCfn(cfn.edges = egfr_transporter.cfn, cfn.nodes = egfr_transporter.nodes, + Network.title = "CFN", Network.collection = "PTMsToPathways") + +# Choose a ratio data column to show which proteins' PTMs were inhibited by a drug +setNodeColorToRatios(plotcol="PC9_ErlotinibRatio") + +# There are a lot of edges! To simplify the graph, use the mergeEdges() function. +# This can be done to the entire cfn: +cfn.merged <- mergeEdges(cfn) + +# Or just to the cfn made above: +egfr_transporter.cfn.merged <- mergeEdges(egfr_transporter.cfn) + +# Graph to compare: +GraphCfn(cfn.edges = egfr_transporter.cfn.merged, + cfn.nodes = egfr_transporter.nodes, Network.title = "CFN", + Network.collection = "PTMsToPathways") + +# Choose a ratio data column to show which proteins' PTMs were inhibited by a drug +setNodeColorToRatios(plotcol="PC9_ErlotinibRatio") + +# Note that within Cytoscape you can change the column for node size and color +# (two separate tings) in the "Styles" tab + +head(egfr_transporter.cfn.merged) +``` + +#### Asking questions about signaling pathways that connect proteins + +Another example of how to use the network is to ask, what are the paths between two nodes (two proteins)?. We use the function connectNodes.all() to identify all shortest paths between two nodes. + +Having identified the pathways, let's also zoom in further on PTMs to examine which PTMs co-cluster, as indicated by yellow edges between them. + +```{r eval = FALSE} +sp1 <- connectNodes.all(c("FYN", 'MET'), ig.graph=NULL, + edgefile = cfn.merged, newgraph = TRUE) #. *** + +# To include co-clustered PTMs in the network an extra step is necessary: +sp1_plus <- get.co.clustered.ptms(sp1) +sp1_plus.nodes <- make.cytoscape.node.file(sp1_plus, funckey, ptmtable, + include.gene.data = TRUE, + include.coclustered.PTMs = TRUE) + +# Now, graph in cytoscape +GraphCfn(cfn.edges = sp1_plus, cfn.nodes = sp1_plus.nodes, + Network.title = "CFN/CCCN", Network.collection = "PTMsToPathways") + +# Choose a ratio data column to show which proteins' PTMs were inhibited by a drug +setNodeColorToRatios(plotcol = "H3122CrizotinibRatio") + +head(sp1_plus) +``` + +#### Bottom-up approach to investigate how dasatinib affects proteins invovled in focal adhesion + +Dasatinib exhibits strong binding and inhibitory effects on multiple focal adhesion-associated genes from the BioPlanet list: + +• SRC (proto-oncogene tyrosine-protein kinase Src) + +• FYN (tyrosine-protein kinase Fyn) + +• EGFR (epidermal growth factor receptor) + +• ERBB2 (receptor tyrosine-protein kinase erbB-2) + +All these genes are directly implicated in focal adhesion signaling regulation. We hypothesize that ptms on proteins involved in focal adhesion will be downregulated by dasatinib. + +```{r eval = FALSE} +pt.sub <- ptmtable[, grep("DasatinibRatio", names (ptmtable))] +pt.sub$Sum.Dasat <- rowSums(pt.sub, na.rm = TRUE) +pt.sub <- pt.sub[order(pt.sub$Sum.Dasat, decreasing = FALSE), ] + +pt.sub$Gene.Name <- sapply(rownames(pt.sub), function(x){ + unlist(strsplit(x, " ", fixed=TRUE))[1]}) + +fa.genes <- pathways.list[["Focal adhesion"]] +pt.sub.fa <- pt.sub[pt.sub$Gene.Name %in% fa.genes,] +pt.sub.fa.topz <- pt.sub.fa[pt.sub.fa$Sum.Dasat < -2,] +ptms = rownames(pt.sub.fa.topz) + +# Employ a helper function to derive a CFN starting with a list of PTMs +cfn.cccn <- ptms_to_cfn(ptms, cfn = cfn.merged, pepsep = ";") +cfn_cccn.nodes <- make.cytoscape.node.file(cfn.cccn, funckey, ptmtable, + include.gene.data = TRUE, + include.coclustered.PTMs = TRUE) + +# Let's see what it looks like. +GraphCfn(cfn.edges = cfn.cccn, cfn.nodes = cfn_cccn.nodes, + Network.title = "CFN/CCCN", Network.collection = "PTMsToPathways") + +# Choose a ratio data column to show which proteins' PTMs were inhibited by a dasatinib +setNodeColorToRatios(plotcol="H366_DasatinibRatio") +setNodeColorToRatios(plotcol="H2286_DasatinibRatio") + +# Note that within Cytoscape you can change the column for node size and color +# (two separate things) in the "Styles" tab +``` + +#### Node and Edge Key + +We adopt the following standards for visualizing nodes and edges in Cytoscape. The border and shape of the node represent the type of protein this gene is, based on the function key (funckey). + +Edges represent different types of interactions from PPI databases, correlations, or links between proteins and their PTMs. The thicker the edge is, the stronger the interaction weight. + +Node Size: + +- Greater the node size, larger the absolute value of the amount or ratio + +Node Color: + +- Blue Node + - Negative amount or ratio\ +- Yellow Node + - Positive amount or ratio\ +- Green Node + - Approximately zero amount or ratio + +Node Shapes: + +- "ELLIPSE" + - unknown\ +- "ROUND_RECTANGLE" + - receptor tyrosine kinase\ +- "VEE" + - SH2 protein\ + - SH2-SH3 protein\ +- "TRIANGLE" + - SH3 protein\ +- "HEXAGON" + - tyrosine kinase\ +- "DIAMOND" + - SRC-family kinase\ +- "OCTAGON" + - kinase\ + - phosphatase\ +- "PARALLELOGRAM" + - transcription factor\ +- "RECTANGLE" + - RNA binding protein + +Node Border Colors: + +- Orange + - deacetylase\ + - acetyltransferase\ +- Blue + - demethylase\ + - methyltransferase\ +- Royal Purple + - membrane protein\ +- Red + - kinase\ + - tyrosine kinase\ + - SRC-family kinase\ +- Yellow + - phosphatase\ + - tyrosine phosphatase\ +- Lilac + - G protein-coupled receptor\ + - receptor tyrosine kinase\ +- Grey + - default + +Edge Colors: + +- Red + - Phosphorylation\ + - pp\ + - controls-phosphorylation-of\ +- Bright Magenta + - controls-expression-of\ +- Dull Magenta + - controls-transport-of\ +- Purple + - controls-state-change-of\ +- Blood Orange + - Acetylation\ +- Lime Green + - Physical interactions\ +- Green + - BioPlex\ +- Dull Green + - in-complex-with\ +- Seafoam Green + - experiments\ + - experiments_transferred\ +- Cyan + - database\ + - database_transferred\ +- Teal + - Pathway\ + - Predicted\ +- Dark Turquoise + - Genetic interactions\ +- Yellow-Orange + - correlation\ +- Royal Blue + - negative correlation\ +- Bright Yellow + - positive correlation\ +- Grey + - combined_amount or ratio\ +- Dark Grey + - merged\ +- Light Grey + - intersect\ +- Black + - peptide\ +- Orange + - homology\ +- Dull Orange + - Shared protein domains\ +- White + - Default + +Arrow Types: + +- Arrow + - Phosphorylation\ + - pp\ + - controls-phosphorylation-of\ + - controls-expression-of\ + - controls-transport-of\ + - controls-state-change-of\ + - Acetylation\ + +- No Arrow + - Default + + These properties can be visualized in Cytoscape using the NodeEdgeKey() function: + +```{r eval = FALSE} +NodeEdgeKey() + +setEdgeLineWidthMapping('Weight') + +# The edges have different weights, large weights can be too thick and +# smaller weights can result in very thin lines. +# Edge widths can be adjusted using the following function. + +setEdgeWidths(ffactor = 6, log= FALSE) +``` diff --git a/vignettes/RawDataProcessing.Rmd b/vignettes/RawDataProcessing.Rmd new file mode 100644 index 00000000..3f7c3336 --- /dev/null +++ b/vignettes/RawDataProcessing.Rmd @@ -0,0 +1,421 @@ +--- +title: "Processing Raw Data for PTMsToPathways" +author: "Lucia Williams, Nagashree Avabhrath, Mikhail Ukrainetz, Madison Moffett, Grant Smith, Mark Grimes" +output: + html_document: + toc: true + toc_float: true + toc_depth: 4 + theme: cosmo + highlight: tango + pdf_document: + toc: true + toc_depth: '4' + word_document: + toc: true + toc_depth: '4' +vignette: > + %\VignetteIndexEntry{Raw Data Processing} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +editor_options: + markdown: + wrap: 72 +--- + +```{r setup, echo = FALSE} + +knitr::opts_chunk$set( + collapse = TRUE, + comment = ">>", + fig.align = "center", + fig.path = "plots/", + fig.width = 8, + fig.height = 6, + out.width = "100%", + results = "hold" +) +``` + +## Purpose + +This vignette intends to help +users produce a matrix with PTM names as row names (e.g. `FYN p Y411`) and +numeric data in the columns. +The numeric values are the mass +spectrometer output, and `NA`s represent missing data rather than zeroes. +Ambiguous PTMs, where a PTM could match several proteins, must be separated by +semicolons (for example, `"AARS ubi k747; AMBLIL p U123"`). + +Mass spectrometry data output will vary depending on the experimental +design, source of data, and software used to process the raw spectra. R +supports many file types and can automatically convert them into a data +frame. For example, `read.csv()` will take a csv file and convert it into +a data frame (`read.csv()` is a variation of `read.table()`). We start with +data in tab-delimited spreadsheet format. + +## Naming conventions + +In this vignette, we use the following shorthand conventions when describing +PTMs based on the modifications present in the example data set. Your +data will dictate the names of modifications. + +* *Gene.Name* = The HUGO Gene Name is used to identify the protein/gene +* *Phosphorylation* = "p" +* *Lysine acetylation* = "ack" +* *Lysine methylation* = "kme" +* *Arginine methylation* = "rme" +* *Ubiquitination* = "ubi" + +## Preprocessing data + +First, let's load the P2P package, since it contains some helpful pre-processing functions. +```{r eval = TRUE} +library(PTMsToPathways) +``` + +The example raw data file for this +vignette (downloadable [here](https://github.com/UM-Applied-Algorithms-Lab/PTMsToPathways/raw/refs/heads/main/inst/extdata/phospho_cleaned_mapped.txt), +or load directly into R using the commands below) +contains only phosphorylation sites. + +To prepare data for input to PTMsTo pathways, we first read in the data file. +To use your own data file, +replace `file_path` variable with your own path to file, as in the commented line below. + +```{r} +# file_path <- "path/to/your/file.txt" +file_path <- system.file("extdata", "phospho_cleaned_mapped.txt", + package = "PTMsToPathways") + +newphos <- utils::read.table(file_path, sep = "\t", skip = 0, header = TRUE, + blank.lines.skip = T, fill = T, quote = "\"", dec = ".", + comment.char = "", stringsAsFactors = F) +dim(newphos) +``` + +As we can see, this table has 933 rows and 170 columns. + +First remove internal control rows (reverse sequences), which should yield 908 remaining rows. + +```{r} +newphos <- newphos[!is.na(newphos$AllGeneSymbols),] +dim(newphos) +``` + +Many investigators inspect data in Microsoft Excel, which can export +tab- or comma-delimited files. Unfortunately, Excel can silently convert +some gene names into dates when they appear in a cell by themselves. We +reverse that with the P2P helper function [`fix.excel()`](reference/fix.excel.html). +If there are dates in the `AllGeneSymbols` column, we can apply the `fix.excel()` function +to each of them to convert them back to gene names. +```{r} +newphos$AllGeneSymbols <- sapply(newphos$AllGeneSymbols, fix.excel) +``` +Investigators will need to identify which columns contain key +information for analysis of PTMs. + +In this example, the key columns are: + +- `Amino.Acid`: the modified amino acid, such as S or T +- `Positions.Within.Proteins`: the amino acid number in the protein sequence +- `Modification.Type`: the PTM class, such as phosphorylation +- `AllGeneSymbols`: the HUGO gene name(s) of the protein(s) containing the PTM, separated by ";" if more than one + +```{r eval = TRUE} +newphos.header <- newphos[,c("AllGeneSymbols", "Amino.Acid", + "Positions.Within.Proteins", "Modification.Type")] + +head(newphos.header) +``` +We provide another helper function, [`name.peptide()`](reference/name.peptide.html), +to handle ambiguous modification sites (a modification site +whose peptide sequence is the same in more than one protein) separated by ";" or +another separator. +```{r} +newphos.header$Peptide.Name <- mapply( + name.peptide, genes = newphos$AllGeneSymbols, + sites = newphos$Positions.Within.Proteins, aa = newphos$Amino.Acid) +head(newphos.header) +``` + +### Data columns + +Next we identify the data columns, which contain the string `Intensity`. +The example data file is from a multi-PTM study and the data in this +table are from just the phosphorylation pulldown (other tables are for +other PTM types). The optimal pulldown columns are straightforward to +identify by the pulldown strings present in the sample names, `pTyr` in +this case. They are also identifiable by zooming out and looking at the +patterns of missing data, the optimal pulldowns, as a group, have the +least missing data. In this data, the following abbreviations are used: +**C** = Crizotinib, +**D** = DMSO, +**E** = Erlotinib, +**Pr** = PR171. +So for example the first four columns can be interpreted as: +```{r eval = TRUE} +names(newphos)[grep("Intensity", names(newphos))][1] +``` +*C1.1*: Crizotinib biological replicate 1- technical replicate 1 +```{r eval = TRUE} +names(newphos)[grep("Intensity", names(newphos))][2] +``` +*C1.2*: Crizotinib biological replicate 1- technical replicate 2 +```{r eval = TRUE} +names(newphos)[grep("Intensity", names(newphos))][3] +``` +*C2.1*: Crizotinib biological replicate 2- technical replicate 1 +```{r eval = TRUE} +names(newphos)[grep("Intensity", names(newphos))][4] +``` +*C2.2*: Crizotinib biological replicate 2- technical replicate 2 + +So, we first select columns that contain the string `Intensity`. There should be 108 of these: +```{r eval = TRUE} +data.cols <- newphos[,grep("Intensity", names(newphos))] +dim(data.cols) +``` +And then select colums that have `pTyr`. There should be 36 of these: +```{r eval = TRUE} +data.cols <- newphos[,grep("pTyr", names(newphos))] +dim(data.cols) +``` + +Now simplify column names (remove `Intensity`): +```{r eval = TRUE} +names(data.cols) <- sapply(names(data.cols), function(x){ + unlist(strsplit(x, "Intensity."))[2] + }) +head(names(data.cols)) +``` + +Make zero into `NA`, which it is. (Note that this may not apply if you are +confident that zero means actual zero, which is possible with certain technical +advances like DIA.) We should have 16265 `NA` values. +```{r eval = TRUE} +zer0 <- which(data.cols==0, arr.ind = TRUE) +data.cols <- replace (data.cols, zer0, NA) +sum(is.na(data.cols)) +``` + +### Merge techincal replicates + +These data have technical replicates, which means that the same samples +were run twice. Due to the stochastic selection of peptides for +detection, the pattern of missing values is slightly different between +technical replicates. We therefore merge the technical replicates taking +the value of either replicate where it's missing in the other, and +averaging values detected in both, using the P2P function [merge2cols()](reference/merge2cols.html). + +Define technical replicates: +```{r eval = TRUE} +tr1.opt <- names(data.cols)[grep(".1", names(data.cols), fixed=TRUE)] +tr2.opt <- names(data.cols)[grep(".2", names(data.cols), fixed=TRUE)] +tr1.opt[1] +tr2.opt[1] +``` + +Use `merge2cols()` to average technical replicates. This function ignores NA +values in either column and takes the average in the case where there are two +values. In this case, the resulting data frame should have 18 total columns, one for each biological replicate. +```{r eval = TRUE} +phosdata.merged <- data.frame(matrix(nrow = nrow(data.cols), ncol = length(tr1.opt))) +for (i in seq_along(tr1.opt)) { +phosdata.merged[, i] <- merge2cols( +data.cols[, tr1.opt[i]], +data.cols[, tr2.opt[i]] +)} +dim(phosdata.merged) + +``` +And fix up the names of the merged columns to remove the technical replicate numbers: +```{r eval = TRUE} +names(phosdata.merged) <- sapply(tr1.opt, function(x){ + substr(x, start=1, stop=nchar(x)-2) + }) +names(phosdata.merged)[1] +``` +Merge the header and the data together to make a complete data set: +```{r eval = TRUE} +phosdatafile <- cbind(newphos.header, phosdata.merged) +dim(phosdatafile) +``` +This file could be saved for reference using `write.table()`: +```{r eval = FALSE} +write.table(phosdatafile, file = "phosdatafile.txt", + row.names = FALSE, sep = "\t") +``` + +For input into P2P, we want the row names to be the PTM names and the columns +just to be the merged technical replicate columns, so we just use that data and set the row names to +the `Peptide.Name` column: +```{r eval = TRUE} +phosdata.df <- phosdata.merged +rownames(phosdata.df) <- phosdatafile$Peptide.Name +``` +Notice that this data is the same as the `ex_small_ptm_table` that is provided with the package (though the column names are slightly +different): +```{r eval = TRUE} +all.equal(setNames(phosdata.df, NULL), setNames(ex_small_ptm_table, NULL)) +``` + +Log base 2 transformation improves clustering. +```{r eval = TRUE} +log2phosdata <- log2(phosdata.merged) +``` + +The [Creating Networks vignette](vignettes/CreatingNetworks.html) show how to use the functions provided in PTMsToPathways to analyze +data stored in a variable called `ptmtable`. +```{r eval = FALSE} +ptmtable <- log2phosdata +``` + +## Optional data processing steps + +For experiments where treatment with drugs is compared to control +samples, adding treatment/control ratios as additional data column can +improve clustering. This optional step adds dimensions to the data set +that enhance focus on the changes in response to drug treatments. + + Simplify column names first: +```{r Example ratio calculations} +names(phosdata.df) <- sapply(names(phosdata.df), function(x){ + paste(unlist(strsplit(x, "SEPTM_pTyr"))[1], + unlist(strsplit(x, "SEPTM_pTyr"))[2], sep = "") + }) +``` + +Explore using ratios where control=rowMeans (D1, D2, D3): +```{r} +H3122control <- rowMeans(phosdata.df[, names(phosdata.df) + [grep("H3122.D", names(phosdata.df))]], + na.rm=TRUE) +``` + +Change NaN to NA +```{r} +H3122control[is.nan(H3122control)] <- NA + +PC9control <- rowMeans(phosdata.df[, names(phosdata.df) + [grep("PC9.D", names(phosdata.df))]], + na.rm=TRUE) + +PC9control[is.nan(PC9control)] <- NA +``` + +Calculate treatment/control ratios +```{r} +# H3122 cells +H3122.C1.ratio <- phosdata.df$H3122.C1/H3122control +H3122.C2.ratio <- phosdata.df$H3122.C2/H3122control +H3122.C3.ratio <- phosdata.df$H3122.C3/H3122control +H3122.PR1.ratio <- phosdata.df$H3122.PR1/H3122control +H3122.PR2.ratio <- phosdata.df$H3122.PR2/H3122control +H3122.PR3.ratio <- phosdata.df$H3122.PR3/H3122control + +# PC9 cells +PC9.E1.ratio <- phosdata.df$PC9.E1/PC9control +PC9.E2.ratio <- phosdata.df$PC9.E2/PC9control +PC9.E3.ratio <- phosdata.df$PC9.E3/PC9control +PC9.PR1.ratio <- phosdata.df$PC9.PR1/PC9control +PC9.PR2.ratio <- phosdata.df$PC9.PR2/PC9control +PC9.PR3.ratio <- phosdata.df$PC9.PR3/PC9control +``` + +Put these columns in a data frame: +```{r} +phos_ratios <- data.frame(H3122.C1.ratio, H3122.C2.ratio, H3122.C3.ratio, + H3122.PR1.ratio, H3122.PR2.ratio, H3122.PR3.ratio, + PC9.E1.ratio, PC9.E2.ratio, PC9.E3.ratio, + PC9.PR1.ratio, PC9.PR2.ratio, PC9.PR3.ratio) +``` +Check (should be `TRUE`): +```{r} +identical(rownames(phosdata.df), rownames(phos_ratios)) +``` + +Make limits to unweight extreme values. This has been shown to improve clustering, and a ratio of 1000 is biologically not really functional different than a ratio of 100. +```{r} +hi.ratio <- which(phos_ratios >= 100, arr.ind = TRUE) +low.ratio <- which(phos_ratios <= 1/100, arr.ind = TRUE) +phos_ratios.lim <- replace (phos_ratios, hi.ratio, 100) +phos_ratios.lim <- replace (phos_ratios.lim, low.ratio, 1/100) +``` +log2 transformation improves clustering: +```{r} +phos_ratios.lim.log2 <- log2(phos_ratios.lim) +phosdata_plus_ratios <- cbind(log2phosdata, phos_ratios.lim.log2) +``` +And plot to check: + +```{r} +boxplot(phosdata_plus_ratios) +``` +And do one more check: +```{r} +identical(rownames(phos_ratios.lim.log2), rownames(log2phosdata)) +``` + +This can be used as the example ptmtable for subsequent testing. +```{r} +# ptmtable <- phosdata_plus_ratios +``` + +## Combining data from multiple PTM experiments + +For experiments involving multiple PTMs, or if investigators wish to +combine several data sets, the data can be combined. For example, +pulldowns were made to isolate acetylated and ubiquitinated peptides +from the same experimental samples. Combining the data is simply +repeating the above steps using the correct optimum data columns, then +making the column names the same, and binding all the rows together. + +Suppose you had acetylation data in `ackdata.df` and ubiquitination data in +`ubidata.df`, both formatted as above for phosphorylation data in +`phosdata.df`. You could combine them as follows. + +First, make sure the column names are the same: + +```{r eval = FALSE} +kgp <- phosdata.df +kga <- ackdata.df +kgu <- ubidata.df + +names(kgp) <- sapply(names(kgp), function(x){ + paste(unlist(strsplit(x, "_pTyr"))[1], unlist(strsplit(x, "_pTyr"))[2], + sep = "") + }) + +names(kga) <- sapply(names(kga), function(x){ + paste(unlist(strsplit(x, "_AcK"))[1], unlist(strsplit(x, "_AcK"))[2], + sep = "") + }) + +names(kgu) <- sapply(names(kgu), function(x){ + paste(unlist(strsplit(x, "_Ubi"))[1], unlist(strsplit(x, "_Ubi"))[2], + sep = "") + }) + +identical(names(kgp), names(kga)) # Check TRUE +``` + +Then `rbind` them: +```{r eval = FALSE} +ptmdata <- rbind (kgp, kga, kgu) +``` + +Reorder: +```{r eval = FALSE} +ptmdata <- ptmdata[order(rownames(ptmdata)),] +``` + +This optional step improves clustering in our hands: +```{r eval = FALSE} +log2ptmdata <- log2(ptmdata) +``` + +Finally, this dataframe could be used as the example `ptmtable` for the P2P functions. +```{r eval = FALSE} +# ptmtable <- log2ptmdata +```