From 95b48c5f2c7bc6d886cbee55d8a44387af80f629 Mon Sep 17 00:00:00 2001 From: Karin Schork Date: Thu, 12 Sep 2024 15:45:11 +0200 Subject: [PATCH] overall cleanup --- DESCRIPTION | 2 +- NAMESPACE | 5 +- ...ers_calculate_and_plot_isomorphism_lists.R | 128 ---------- ...rs_calculate_and_plot_isomorphism_lists2.R | 131 ---------- R/generate_graphs_from_FASTA.R | 2 +- R/generate_graphs_from_quantdata.R | 31 ++- R/helpers-add_graph_attributes.R | 3 +- ...s-collapse_nodes_edgelist_quant_pepratio.R | 12 +- R/helpers-generate_edgelist.R | 1 + R/helpers-isomorphisms.R | 10 +- R/helpers-normalization.R | 184 -------------- R/helpers-preprocess_quant_peptide_data.R | 8 +- R/helpers-prototypeList.R | 3 +- R/helpers-small_helper_functions.R | 3 +- R/normalizeCyclicLoess2.R | 231 ------------------ R/plotBipartiteGraph.R | 33 +-- R/tables-subgraph_characteristics.R | 120 ++++++--- R/tables-subgraph_characteristics_NEW.R | 151 ------------ inst/extdata/uniprot_test.fasta | 8 + man/add_average_pep_ratio.Rd | 4 +- man/calculateIsomorphList.Rd | 35 --- man/calculate_peptide_ratios.Rd | 15 +- man/calculate_subgraph_characteristics.Rd | 2 +- man/calculate_subgraph_characteristics_OLD.Rd | 37 --- man/convertToBipartiteGraph.Rd | 4 +- man/direct_bipartite_graph.Rd | 11 +- man/duplicated.dgCMatrix.Rd | 29 --- man/foldChange.Rd | 3 - man/generatePrototypeList.Rd | 11 +- man/generate_edgelist.Rd | 5 +- man/generate_graphs_from_FASTA.Rd | 4 +- man/generate_graphs_from_quant_data.Rd | 16 +- man/generate_quant_graphs.Rd | 14 +- man/geom_mean.Rd | 4 +- man/isomorphic_bipartite.Rd | 5 - man/plotBipartiteGraph.Rd | 10 +- man/plotIsomorphList.Rd | 80 ------ man/read_MQ_peptidetable.Rd | 6 +- tests/testthat/test-graph_generation_FASTA.R | 53 ++-- .../testfiles/digested_proteins_test.RData | Bin 4767 -> 0 bytes .../testfiles/digested_proteins_test.rds | Bin 4700 -> 5058 bytes .../testfiles/edgelist_coll__prot_test.rds | Bin 4835 -> 0 bytes .../edgelist_coll_pept_prot_test.rds | Bin 3844 -> 4136 bytes .../testfiles/edgelist_coll_prot_test.rds | Bin 4831 -> 5192 bytes tests/testthat/testfiles/edgelist_test.rds | Bin 4835 -> 5198 bytes .../testthat/testfiles/edgelist_test.rds.rds | Bin 0 -> 4836 bytes .../testfiles/graphs_coll_pept_prot_test.rds | Bin 4084 -> 4307 bytes .../testfiles/graphs_coll_prot_test.rds | Bin 8559 -> 6965 bytes 48 files changed, 238 insertions(+), 1176 deletions(-) delete mode 100644 R/OLD_helpers_calculate_and_plot_isomorphism_lists.R delete mode 100644 R/OLD_helpers_calculate_and_plot_isomorphism_lists2.R delete mode 100644 R/helpers-normalization.R delete mode 100644 R/normalizeCyclicLoess2.R delete mode 100644 R/tables-subgraph_characteristics_NEW.R delete mode 100644 man/calculateIsomorphList.Rd delete mode 100644 man/calculate_subgraph_characteristics_OLD.Rd delete mode 100644 man/duplicated.dgCMatrix.Rd delete mode 100644 man/plotIsomorphList.Rd delete mode 100644 tests/testthat/testfiles/digested_proteins_test.RData delete mode 100644 tests/testthat/testfiles/edgelist_coll__prot_test.rds create mode 100644 tests/testthat/testfiles/edgelist_test.rds.rds diff --git a/DESCRIPTION b/DESCRIPTION index 12b7d99..183dae2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,7 +9,7 @@ Description: Functionality to create and characterize bipartite graphs that License: BSD_3_clause + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.2 Imports: BBmisc, graphics, diff --git a/NAMESPACE b/NAMESPACE index 8cf1ec7..9f1e91a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,15 +1,12 @@ # Generated by roxygen2: do not edit by hand -S3method(duplicated,dgCMatrix) export(Digest2) export(add_average_pep_ratio) export(add_uniqueness_attributes) export(aggregate_replicates) export(assign_protein_accessions) -export(calculateIsomorphList) export(calculate_peptide_ratios) export(calculate_subgraph_characteristics) -export(calculate_subgraph_characteristics_OLD) export(calculate_summary_table) export(collapse_edgelist) export(collapse_edgelist_quant) @@ -26,5 +23,5 @@ export(generate_quant_graphs) export(geom_mean) export(isomorphic_bipartite) export(plotBipartiteGraph) -export(plotIsomorphList) export(read_MQ_peptidetable) +importFrom(igraph,"%->%") diff --git a/R/OLD_helpers_calculate_and_plot_isomorphism_lists.R b/R/OLD_helpers_calculate_and_plot_isomorphism_lists.R deleted file mode 100644 index 59e4de9..0000000 --- a/R/OLD_helpers_calculate_and_plot_isomorphism_lists.R +++ /dev/null @@ -1,128 +0,0 @@ - -### TODO: neue Version davon, die Repräsentanten ausrechnet und diese dann zuordnet? -### TODO: use isomorphic function that considers node tpes - -#' Calculation of isomorphism lists from submatrizes or subgraphs -#' -#' @param G list of subgraphs -#' #' -#' @return isomorph_list is a list of indizes that belong in the same isomorphism class -#' Graphs are graph representatives -#' @export -#' -#' @examples -#' ### TODO -calculateIsomorphList <- function(G) { - - - isomorph_list <- list() - k <- 1 - - ### TODO: progress bar - - for (i in 1:length(G)) { - print(i) - G_tmp <- G[[i]] - if (k == 1) {isomorph_list[[k]] <- i; k <- k + 1; next} - - for (j in 1:(k-1)) { - iso <- igraph::isomorphic(G_tmp, G[[isomorph_list[[j]][1]]]) # compare graph with 1st element if each isomorphism class - if (iso) { - cG <- igraph::canonical_permutation(G) - cG <- igraph::permute(G_tmp, cG$labeling) - cG2 <- igraph::canonical_permutation(Graphs[[isomorph_list[[j]][1]]]) - cG2 <- igraph::permute(Graphs[[isomorph_list[[j]][1]]], cG2$labeling) - - iso2 <- all(igraph::V(cG)$type == igraph::V(cG2)$type) # test if graphs are really the same (considering the node types) - - if(iso2) { - isomorph_list[[j]] <- c(isomorph_list[[j]], i); break - } - - } - - - # same_nr_of_prot_and_pep <- sum(V(G)$type) == sum(V(Graphs[[isomorph_list[[j]][1]]])$type) # compare number of type 1 nodes - # if(iso&same_nr_of_prot_and_pep){isomorph_list[[j]] <- c(isomorph_list[[j]], i); break} # add graph to group of isomorphic graphs - } - - if (!iso) {isomorph_list[[k]] <- i; k <- k + 1; next} # if it is not isomorphic to an existing class, start a new one - - } - return(list(isomorph_list = isomorph_list, Graphs = Graphs)) -} - - - - - - -#### function that plots list of isomorph classes, sorted by number of occurences -## isomorph_list: result of calculateIsomorphList -## Graphs: list of graphs -## path: path to save plots -## title: if TRUE, title is added with number of occurence and percentage value -## pdf: if TRUE, plot is saved in a single odf, if FALSE as multiple pngs -## cex.title: size of title -## which_graphs: ranks of classes that should be plottet (e.g. 1:10 for top 10 classes) -## mfrow: mfrow agrument of par function to arrance graphs in one plot -## save: if TRUE, graphs will be saved as pdf or png -## ...: further arguments to plotBipartiteGraph - -#' function that plots list of isomorph classes, sorted by number of occurences -#' -#' @param isomorph_list result of calculateIsomorphList -#' @param Graphs list of graphs -#' @param path path to save plots -#' @param title if TRUE, title is added with number of occurence and percentage value -#' @param pdf if TRUE, plot is saved in a single odf, if FALSE as multiple pngs -#' @param cex.title size of title -#' @param which_graphs ranks of classes that should be plottet (e.g. 1:10 for top 10 classes) -#' @param mfrow mfrow agrument of par function to arrance graphs in one plot -#' @param save if TRUE, graphs will be saved as pdf or png -#' @param title_format "times+percent" or "percent" -#' @param ... further arguments to plotBipartiteGraph -#' @param height height of plot -#' @param width width of plot -#' -#' @return plots saved as a pdf or multiple png files -#' @export -#' -#' @examples -#' ### TODO -plotIsomorphList <- function(isomorph_list, Graphs, path, title = TRUE, pdf = TRUE, - cex.title = 1, which_graphs = NULL, mfrow = c(1,1), save = TRUE, - title_format = "times+percent", ..., height = 15, width = 15) { - - ord_le_iso <- order(lengths(isomorph_list), decreasing = TRUE) # order by number of occurrences - if (!is.null(which_graphs)) ord_le_iso <- ord_le_iso[which_graphs] - le_iso <- lengths(isomorph_list) # sizes of isomorph classes - le_iso_total <- sum(le_iso) # total number of graphs - percentages <- round(le_iso/le_iso_total * 100, 2) # percentages (proportion of all graphs) - - - - if(pdf & save) grDevices::pdf(paste0(path, ".pdf")) - graphics::par(mfrow = mfrow) - graphics::par(mai = c(0.1, 0.5, 0.4, 0), oma = c(0,0,0,0), mfrow = mfrow) - j <- 1 - for (i in ord_le_iso) { - if (!pdf & save) grDevices::png(paste0(path, "_", j, ".png"), res = 500, units = "cm", height = height, width = width) - ind <- isomorph_list[[i]][1] # plot first element for each isomorph group - G <- Graphs[[ind]] - types <- igraph::vertex_attr(G)$type # type = 0 peptides, type = 1 proteins - G <- igraph::set_vertex_attr(G, name = "name", value = c(1:sum(!types), LETTERS[1:sum(types)])) - - bppg::plotBipartiteGraph(G, vertex.label.dist = 0, ...) - if(title & title_format == "times+percent") title(paste0(le_iso[i], " times (", - formatC(percentages[i], digits = 2, format = "f"), "%)"), cex.main = cex.title, line = 0.5) - if(title & title_format == "percent") title(paste0(formatC(percentages[i], digits = 2, format = "f"), "%"), cex.main = cex.title, line = 0.5) - if(!pdf & save) grDevices::dev.off() - j <- j + 1 - } - graphics::par(mfrow = c(1,1)) - if(pdf & save) grDevices::dev.off() - - -} - diff --git a/R/OLD_helpers_calculate_and_plot_isomorphism_lists2.R b/R/OLD_helpers_calculate_and_plot_isomorphism_lists2.R deleted file mode 100644 index ca4c196..0000000 --- a/R/OLD_helpers_calculate_and_plot_isomorphism_lists2.R +++ /dev/null @@ -1,131 +0,0 @@ - -### TODO: neue Version davon, die Repräsentanten ausrechnet und diese dann zuordnet? -### TODO: use isomorphic function that considers node tpes - -#' Calculation of isomorphism lists from submatrizes or subgraphs -#' -#' @param Submatrix list of submatrizes or subgraphs -#' @param matrix Is submatrix a list of matrizes? -#' -#' @return isomorph_list is a list of indizes that belong in the same isomorphism class -#' Graphs are graph representatives -#' @export -#' -#' @examples -#' ### TODO -calculateIsomorphList <- function(Submatrix, matrix = TRUE) { - if (matrix) { - Graphs <- lapply(Submatrix, convertToBipartiteGraph) - } else { - Graphs <- Submatrix - } - - isomorph_list <- list() - k <- 1 - - for (i in 1:length(Graphs)) { - print(i) - G <- Graphs[[i]] - if (k == 1) {isomorph_list[[k]] <- i; k <- k + 1; next} - - for (j in 1:(k-1)) { - iso <- igraph::isomorphic(G, Graphs[[isomorph_list[[j]][1]]]) # compare graph with 1st element if each isomorphism class - if (iso) { - cG <- igraph::canonical_permutation(G) - cG <- igraph::permute(G, cG$labeling) - cG2 <- igraph::canonical_permutation(Graphs[[isomorph_list[[j]][1]]]) - cG2 <- igraph::permute(Graphs[[isomorph_list[[j]][1]]], cG2$labeling) - - iso2 <- all(igraph::V(cG)$type == igraph::V(cG2)$type) # test if graphs are really the same (considering the node types) - - if(iso2) { - isomorph_list[[j]] <- c(isomorph_list[[j]], i); break - } - - } - - - # same_nr_of_prot_and_pep <- sum(V(G)$type) == sum(V(Graphs[[isomorph_list[[j]][1]]])$type) # compare number of type 1 nodes - # if(iso&same_nr_of_prot_and_pep){isomorph_list[[j]] <- c(isomorph_list[[j]], i); break} # add graph to group of isomorphic graphs - } - - if (!iso) {isomorph_list[[k]] <- i; k <- k + 1; next} # if it is not isomorphic to an existing class, start a new one - - } - return(list(isomorph_list = isomorph_list, Graphs = Graphs)) -} - - - - - - -#### function that plots list of isomorph classes, sorted by number of occurences -## isomorph_list: result of calculateIsomorphList -## Graphs: list of graphs -## path: path to save plots -## title: if TRUE, title is added with number of occurence and percentage value -## pdf: if TRUE, plot is saved in a single odf, if FALSE as multiple pngs -## cex.title: size of title -## which_graphs: ranks of classes that should be plottet (e.g. 1:10 for top 10 classes) -## mfrow: mfrow agrument of par function to arrance graphs in one plot -## save: if TRUE, graphs will be saved as pdf or png -## ...: further arguments to plotBipartiteGraph - -#' function that plots list of isomorph classes, sorted by number of occurences -#' -#' @param isomorph_list result of calculateIsomorphList -#' @param Graphs list of graphs -#' @param path path to save plots -#' @param title if TRUE, title is added with number of occurence and percentage value -#' @param pdf if TRUE, plot is saved in a single odf, if FALSE as multiple pngs -#' @param cex.title size of title -#' @param which_graphs ranks of classes that should be plottet (e.g. 1:10 for top 10 classes) -#' @param mfrow mfrow agrument of par function to arrance graphs in one plot -#' @param save if TRUE, graphs will be saved as pdf or png -#' @param title_format "times+percent" or "percent" -#' @param ... further arguments to plotBipartiteGraph -#' @param height height of plot -#' @param width width of plot -#' -#' @return plots saved as a pdf or multiple png files -#' @export -#' -#' @examples -#' ### TODO -plotIsomorphList <- function(isomorph_list, Graphs, path, title = TRUE, pdf = TRUE, - cex.title = 1, which_graphs = NULL, mfrow = c(1,1), save = TRUE, - title_format = "times+percent", ..., height = 15, width = 15) { - - ord_le_iso <- order(lengths(isomorph_list), decreasing = TRUE) # order by number of occurrences - if (!is.null(which_graphs)) ord_le_iso <- ord_le_iso[which_graphs] - le_iso <- lengths(isomorph_list) # sizes of isomorph classes - le_iso_total <- sum(le_iso) # total number of graphs - percentages <- round(le_iso/le_iso_total * 100, 2) # percentages (proportion of all graphs) - - - - if(pdf & save) grDevices::pdf(paste0(path, ".pdf")) - graphics::par(mfrow = mfrow) - graphics::par(mai = c(0.1, 0.5, 0.4, 0), oma = c(0,0,0,0), mfrow = mfrow) - j <- 1 - for (i in ord_le_iso) { - if (!pdf & save) grDevices::png(paste0(path, "_", j, ".png"), res = 500, units = "cm", height = height, width = width) - ind <- isomorph_list[[i]][1] # plot first element for each isomorph group - G <- Graphs[[ind]] - types <- igraph::vertex_attr(G)$type # type = 0 peptides, type = 1 proteins - G <- igraph::set_vertex_attr(G, name = "name", value = c(1:sum(!types), LETTERS[1:sum(types)])) - - bppg::plotBipartiteGraph(G, vertex.label.dist = 0, ...) - if(title & title_format == "times+percent") title(paste0(le_iso[i], " times (", - formatC(percentages[i], digits = 2, format = "f"), "%)"), cex.main = cex.title, line = 0.5) - if(title & title_format == "percent") title(paste0(formatC(percentages[i], digits = 2, format = "f"), "%"), cex.main = cex.title, line = 0.5) - if(!pdf & save) grDevices::dev.off() - j <- j + 1 - } - graphics::par(mfrow = c(1,1)) - if(pdf & save) grDevices::dev.off() - - -} - diff --git a/R/generate_graphs_from_FASTA.R b/R/generate_graphs_from_FASTA.R index cfcadac..ddfac91 100644 --- a/R/generate_graphs_from_FASTA.R +++ b/R/generate_graphs_from_FASTA.R @@ -7,7 +7,7 @@ #' @param suffix suffix for saving results #' @param save_intermediate Save intermediate results? #' @param ... additional arguments to bppg::digest_fasta() - +#' @param prot_origin origin of protein, e.g. organism etc. #' #' @return subgraphs (i.e. connected components) from the graph generated from the FASTA file. #' @export diff --git a/R/generate_graphs_from_quantdata.R b/R/generate_graphs_from_quantdata.R index af348e0..2ccfcc5 100644 --- a/R/generate_graphs_from_quantdata.R +++ b/R/generate_graphs_from_quantdata.R @@ -3,6 +3,11 @@ #' @param peptide_ratios table with peptide ratios #' @param id_cols columns with ids, e.g. peptide sequences (everything except the peptide ratios) #' @param fasta_edgelist Edgelist created from the corresponding FASTA file +#' @param outpath output path +#' @param seq_column column name of the peptide sequence +#' @param collapse_protein_nodes if TRUE protein nodes will be collapsed +#' @param collapse_peptide_nodes if TRUE, peptide nodes will be collapsed +#' @param suffix suffix for output files #' #' @return list of list of subgraphs #' @export @@ -72,26 +77,37 @@ generate_quant_graphs <- function(peptide_ratios, id_cols = 1, fasta_edgelist, o #' (e.g. output from bppg::read_MQ_peptidetable) #' @param fasta fasta file used for identification of peptides in D #' @param outpath bla -#' @param normalize currently only loess normalization possible #' @param missed_cleavages bla #' @param min_aa bla #' @param max_aa bla #' @param ... currently not in use +#' @param id_columns column numbers of D that contain ID information (the rest should contain only peptide intensities, properly normalized) +#' @param seq_column column name of the peptide sequence +#' @param collapse_protein_nodes if TRUE protein nodes will be collapsed +#' @param collapse_peptide_nodes if TRUE, peptide nodes will be collapsed +#' @param suffix suffix for output files #' #' @return list of list of graphs #' @export #' #' @examples -generate_graphs_from_quant_data <- function(D, fasta, outpath = NULL, normalize = FALSE, - missed_cleavages = 2, min_aa = 6, max_aa = 50, - id_columns = 1, seq_column = "Sequence", - collapse_protein_nodes = TRUE, collapse_peptide_nodes = FALSE, +generate_graphs_from_quant_data <- function(D, + fasta, + outpath = NULL, + #normalize = FALSE, + missed_cleavages = 2, + min_aa = 6, + max_aa = 50, + id_columns = 1, + seq_column = "Sequence", + collapse_protein_nodes = TRUE, + collapse_peptide_nodes = FALSE, suffix = "", ...) { message("Digesting FASTA file...") digested_proteins <- bppg::digest_fasta(fasta, missed_cleavages = missed_cleavages, - min_aa = min_aa, max_aa = max_aa)#, ...) + min_aa = min_aa, max_aa = max_aa) message("Generating edgelist ...") edgelist <- bppg::generate_edgelist(digested_proteins) @@ -99,12 +115,9 @@ generate_graphs_from_quant_data <- function(D, fasta, outpath = NULL, normalize openxlsx::write.xlsx(edgelist, file = paste0(outpath, "edgelist_fasta_", suffix, ".xlsx"), overwrite = TRUE, keepNA = TRUE) } - # remove peptides outside the desired length range D <- D[nchar(D[, seq_column]) >= min_aa & nchar(D[, seq_column]) <= max_aa,] - - #normalize Intensities intensities <- D[,-id_columns] ### aggregate replicates by calculating the mean diff --git a/R/helpers-add_graph_attributes.R b/R/helpers-add_graph_attributes.R index bce9483..67fadfc 100644 --- a/R/helpers-add_graph_attributes.R +++ b/R/helpers-add_graph_attributes.R @@ -39,7 +39,8 @@ add_uniqueness_attributes <- function(G) { #' Adds average peptide ratios as a attribute to the graphs, if a list of peptide ratios is already present #' -#' @param G +#' @param G graph +#' @param type not used at the moment. Default is 'geom_mean' #' #' @return graphs with added attributes #' @export diff --git a/R/helpers-collapse_nodes_edgelist_quant_pepratio.R b/R/helpers-collapse_nodes_edgelist_quant_pepratio.R index a238838..28da55d 100644 --- a/R/helpers-collapse_nodes_edgelist_quant_pepratio.R +++ b/R/helpers-collapse_nodes_edgelist_quant_pepratio.R @@ -28,11 +28,11 @@ collapse_edgelist_quant <- function(edgelist, ### Calculate list if protein nodes if (collapse_protein_nodes) { ### aggregate peptide sequences that belong to the same protein accession (1 row per protein accession) - protEdges <- aggregate(data = edgelist, x = cbind(peptide, pep_ratio) ~ protein, function(x) paste(sort(unique(x)), collapse = ";")) + protEdges <- stats::aggregate(data = edgelist, x = cbind(peptide, pep_ratio) ~ protein, function(x) paste(sort(unique(x)), collapse = ";")) ### aggregate proteins with the same set of peptides (-> protein nodes) - protNodes <- aggregate(data = protEdges, x = protein ~ peptide+pep_ratio, function(x) paste(sort(unique(x)), collapse = ";")) + protNodes <- stats::aggregate(data = protEdges, x = protein ~ peptide+pep_ratio, function(x) paste(sort(unique(x)), collapse = ";")) } else { - protEdges <- aggregate(data = edgelist, x = peptide ~ protein, function(x) paste(sort(unique(x)), collapse = ";")) + protEdges <- stats::aggregate(data = edgelist, x = peptide ~ protein, function(x) paste(sort(unique(x)), collapse = ";")) protNodes <- protEdges } @@ -40,11 +40,11 @@ collapse_edgelist_quant <- function(edgelist, ### calculate list of peptide nodes if (collapse_peptide_nodes) { ### aggregate protein accessions belonging to the same peptide sequences (1 row per peptide sequence) - pepEdges <- aggregate(data = edgelist, x = protein ~ peptide + pep_ratio, function(x) paste(sort(unique(x)), collapse = ";")) + pepEdges <- stats::aggregate(data = edgelist, x = protein ~ peptide + pep_ratio, function(x) paste(sort(unique(x)), collapse = ";")) ### aggregate peptides with the same set of proteins (-> peptide nodes) - pepNodes <- aggregate(data = pepEdges, x = cbind(peptide, pep_ratio) ~ protein, function(x) paste(sort(unique(x)), collapse = ";")) + pepNodes <- stats::aggregate(data = pepEdges, x = cbind(peptide, pep_ratio) ~ protein, function(x) paste(sort(unique(x)), collapse = ";")) } else { - pepEdges <- aggregate(data = edgelist, x = protein ~ peptide + pep_ratio, function(x) paste(sort(unique(x)), collapse = ";"), simplify = FALSE) + pepEdges <- stats::aggregate(data = edgelist, x = protein ~ peptide + pep_ratio, function(x) paste(sort(unique(x)), collapse = ";"), simplify = FALSE) pepNodes <- pepEdges } diff --git a/R/helpers-generate_edgelist.R b/R/helpers-generate_edgelist.R index 66195b4..209c7e0 100644 --- a/R/helpers-generate_edgelist.R +++ b/R/helpers-generate_edgelist.R @@ -1,6 +1,7 @@ #' Generate edgelist from list of in silico digested proteins #' #' @param digested_proteins Output from digest_fasta() (List of vectors of peptide sequences) +#' @param prot_origin origin of the protein (e.g. organism, spike-in/background etc) #' #' @return edgelist #' @export diff --git a/R/helpers-isomorphisms.R b/R/helpers-isomorphisms.R index 8c9da9d..66e982a 100644 --- a/R/helpers-isomorphisms.R +++ b/R/helpers-isomorphisms.R @@ -28,25 +28,27 @@ isomorphic_bipartite <- function(graph1, graph2, ...) { #' Transform a bipartite graph into a directed graph #' -#' @param bip_graph -#' @param from_type TODO +#' @param bip_graph a bipartite graph +#' @param from_type determines if protein or peptide nodes are the "from" nodes #' #' @return a bipartite graph that is know directed #' @export #' #' @examples #' +#' @importFrom igraph %->% +#' direct_bipartite_graph <- function(bip_graph, from_type = FALSE){ # turn undirected into directed edges - bip_graph <- as.directed(bip_graph, mode = "arbitrary") + bip_graph <- igraph::as.directed(bip_graph, mode = "arbitrary") from_vertices <- igraph::V(bip_graph)[igraph::V(bip_graph)$type == from_type] to_vertices <- igraph::V(bip_graph)[igraph::V(bip_graph)$type == !from_type] # reverse edges going from the "to-group" to the "from-group" - bip_graph <- reverse_edges(bip_graph, igraph::E(bip_graph)[to_vertices %->% from_vertices]) + bip_graph <- igraph::reverse_edges(bip_graph, igraph::E(bip_graph)[to_vertices %->% from_vertices]) return(bip_graph) } diff --git a/R/helpers-normalization.R b/R/helpers-normalization.R deleted file mode 100644 index f7f4eb6..0000000 --- a/R/helpers-normalization.R +++ /dev/null @@ -1,184 +0,0 @@ - -# TODO: LTS Normalisierung einbauen, wie in stat_workflows! - - -automatedNormalization <- function(D, D.name = deparse(substitute(D)), - method = "loess", suffix = method, log = TRUE, id = NULL, - output_path = "", save = FALSE, - groupwise = FALSE, group = NULL) { - - if(method == "loess" | method == "quantile" | method == "median") { - - if(log) { - D <- log2(D) - } - - #### choose normalization function - fun <- switch(method, - "loess" = limma::normalizeBetweenArrays, - "quantile" = limma::normalizeBetweenArrays, - "median" = limma::normalizeBetweenArrays) - - ### choose arguments for normalization function - args <- switch(method, - "loess" = list(object = D, method = "cyclicloess"), - "quantile" = list(object = D, method = "quantile"), - "median" = list(object = D, method = "scale")) - - if (!groupwise) { - D_norm <- do.call(fun, args) - D_norm <- as.data.frame(D_norm) - } else { - D_split <- split.default(D, group) - D_split_norm <- lapply(D_split, limma::normalizeBetweenArrays, method = args$method) - D_norm <- do.call(cbind, D_split_norm) - } - - # if (length(id_columns) >= 1 ) { - D_norm_2 <- data.frame(id, D_norm) - # } - - # tryCatch(expr = { - if (save) { - openxlsx::write.xlsx(x = D_norm_2, file = paste0(output_path, D.name, "_", suffix, ".xlsx"), keepNA = TRUE, overwrite = TRUE) - message("Normalized data successfully saved!") - } - # }, - # error = function(err) { - # error handler picks up where error was generated - # print(paste("MY_ERROR: ",err)) - # beepr::beep(sound = 10) - ## user_input <- readline(prompt = paste0("+++ Do you want to overwrite ", paste0(DATA.name,"_",method,".xlsx"), "? +++ [yes/no] ")) - # if(user_input == "yes"){ - # write.xlsx(x = DATA_norm_2, file = paste0(output_path, DATA.name,"_",suffix,".xlsx"), overwrite = TRUE, keepNA = TRUE) - # message("Normalized data successfully saved!") - # } else { - # message("Overwriting of normalized data failed. Please allow overwriting, remove the data file or choose different normalization method!") - # } - # }) - }else{ # if method == "nonorm" - D_norm <- D - cat("No normalization applied.") - - # if (ncol(id_columns) >= 1 ) { - D_norm_2 <- data.frame(id, D_norm) - # } - openxlsx::write.xlsx(x = D_norm_2, file = paste0(output_path, D.name, "_", suffix, ".xlsx"), keepNA = TRUE) - } - - return(D_norm) -} - - - - - - - - - -#### Function for a single MA-Plot: -# x1: Sample 1 -# x2: Sample 2 -# log: Should data be log-transformed? -# TRUE, if not already log-transformed, FALSE, if already log-transformed -# alpha: Should points be transparent? -# col: colours of the data points -# ...: further arguments for ma.plot -MAPlot_single <- function(x1, x2, log = TRUE, alpha = FALSE, col = "black", ...) { - - if(log) { - x1 <- log2(x1) - x2 <- log2(x2) - } - if(alpha) { - col = alpha(col, 0.5) - } - - M <- stats::na.omit(x1 - x2) - A <- stats::na.omit((x1 + x2)/2) - - if (length(col) > 1) { - na.ind <- attr(M, "na.action") - col <- col[-na.ind] - } - - - affy::ma.plot(A = A, M = M, pch = 16, cex = 0.7, col = col, show.statistics = FALSE, ...) -} - - - -# function to check if user is sure to plot more than 1000 plots --> if yes it return 1 and the plots will be created -MAPlots_check <- function(X, maxPlots, ...){ - number_states <- max(as.integer(as.factor(colnames(X)))) - number_plots <- choose(number_states,2) - return_value <- 2 - - if(number_plots >= maxPlots){ - #beepr::beep(sound = 10) - user_input <- readline(prompt = paste("Are you sure you want to generate",number_plots,"MA-plots? [yes/no]")) - if (user_input == "yes"){ - return_value <- 1 - }else return_value <- 0 - }else return_value <- 1 - - return(return_value) -} - - - -### main function for MA-Plots -# X: Data in wide format -# labels: labels of the samples for the title of the MA-Plot -# labels2: second line in title, e.g. group membership -MAPlots <- function(X, log = TRUE, alpha = FALSE, suffix="nonorm", - labels = 1:ncol(X), labels2 = colnames(X), maxPlots = 5000, - plot_height=15, plot_width=15, output_path = "", ...) { - - require(limma) - require(affy) - require(scales) - require(beepr) - - number_states <- max(as.integer(as.factor(colnames(X)))) - number_plots <- choose(number_states,2) - - if(MAPlots_check(X, maxPlots) == 1){ - - num <- 0 - - print("Generating MA-Plots ...") - - ### TODO: auf pbapply umsteigen - pb <- utils::txtProgressBar(min = 0,max = number_plots,char = "#",style = 3) - - grDevices::pdf(paste0(output_path, "MA_Plots_", suffix, ".pdf"), height = plot_height/2.54, width = plot_width/2.54) - - for(i in 1:(ncol(X)-1)) { - for (j in (i + 1):ncol(X)) { - - if (is.null(labels2)) { - main = paste(labels[i], labels[j]) - } else { - main = paste(labels[i], labels[j], "\n", labels2[i], labels2[j]) - } - - num <- num + 1 - utils::setTxtProgressBar(pb, num) - - MAPlot_single(X[,i], X[, j], log = log, main = main, ...) - } - } - # sound chosen, "treasure", "facebook" also cool :) - #beepr::beep("coin") - close(pb) - print("MA-Plots finished!") - - grDevices::dev.off() - - } - -} - - diff --git a/R/helpers-preprocess_quant_peptide_data.R b/R/helpers-preprocess_quant_peptide_data.R index 9d54ebf..8823fbf 100644 --- a/R/helpers-preprocess_quant_peptide_data.R +++ b/R/helpers-preprocess_quant_peptide_data.R @@ -5,9 +5,10 @@ #' @param remove_contaminants If TRUE, peptide sequences from potential contaminants are removed #' @param rename_columns Rename columns? If TRUE, "Intensity." or "LFQ.intensity." are removed #' @param zeroToNA If TRUE, zeros are converted to NAs. -#' @param remove_empty_rows +#' @param remove_empty_rows If TRUE, rows with only NAs are removed. +#' @param further_columns_to_keep additional columns to keep, except peptide sequence and intensities #' -#' @return Dataframe with sequences and intensities +#' @return Data frame with sequences and intensities #' @export #' #' @examples @@ -31,7 +32,7 @@ read_MQ_peptidetable <- function(path, LFQ = FALSE, remove_contaminants = FALSE, } - ## search for itensity columns or LFQ values + ## search for intensity columns or LFQ values if(LFQ) { intensities <- D[, grep("LFQ", colnames(D))] if (rename_columns) colnames(intensities) <- stringr::str_replace(colnames(intensities), "LFQ.intensity.", "") @@ -151,6 +152,7 @@ foldChange <- function(D, X, Y, useNA = FALSE) { #' @param id_cols column numbers that contain peptide sequences etc (everything except intensities) #' @param group_levels levels of groups in the right order #' @param type "ratio" or "difference". Difference if values are already on log-scale +#' @param log_base log base #' #' @return data set with peptide ratios #' @export diff --git a/R/helpers-prototypeList.R b/R/helpers-prototypeList.R index 42663bd..c8397b7 100644 --- a/R/helpers-prototypeList.R +++ b/R/helpers-prototypeList.R @@ -2,6 +2,7 @@ #' #' #' @param G graph +#' @param sort_by_nr_edges logical, if TRUE, the list of prototypes is sorted by number of edges #' #' @return list of prototype graphs plus count #' @export @@ -41,7 +42,7 @@ generatePrototypeList <- function(G, sort_by_nr_edges = FALSE) { ind <- which(x) - # delete Graphs isomorphic to G_tmp graphs (-> list becomes smallet) + # delete Graphs isomorphic to G_tmp graphs (-> list becomes smaller) # G_tmp itself is a new isomorphism class. if (length(ind) > 0) { G <- G[-(ind+i)] diff --git a/R/helpers-small_helper_functions.R b/R/helpers-small_helper_functions.R index 113b533..bae203a 100644 --- a/R/helpers-small_helper_functions.R +++ b/R/helpers-small_helper_functions.R @@ -1,12 +1,13 @@ #' Geometric mean #' #' @param x vector with numbers +#' @param useprod if TRUE, prod(x)^(1/n) will be calculated, otherwise exp(mean(log(x))) #' #' @return geometric mean of the provided data points #' @export #' #' @examples # TODO -geom_mean <- function(x,useprod = FALSE) { +geom_mean <- function(x, useprod = FALSE) { n <- length(x) if (useprod) { diff --git a/R/normalizeCyclicLoess2.R b/R/normalizeCyclicLoess2.R deleted file mode 100644 index 71a1a52..0000000 --- a/R/normalizeCyclicLoess2.R +++ /dev/null @@ -1,231 +0,0 @@ -# affy::ma.plot -# function (A, M, subset = sample(1:length(M), min(c(10000, length(M)))), -# show.statistics = TRUE, span = 2/3, family.loess = "gaussian", -# cex = 2, plot.method = c("normal", "smoothScatter", -# "add"), add.loess = TRUE, lwd = 1, lty = 1, loess.col = "red", -# ...) -# { -# plot.method <- match.arg(plot.method) -# fn.call <- list(...) -# sigma <- IQR(M) -# mean <- median(M) -# if (!is.element("ylim", names(fn.call))) { -# yloc <- max(M) -# } -# else { -# yloc <- max(fn.call$ylim) -# } -# if (!is.element("xlim", names(fn.call))) { -# xloc <- max(A) -# } -# else { -# xloc <- max(fn.call$xlim) -# } -# if (plot.method == "smoothScatter") { -# plotmethod <- "smoothScatter" -# } -# else if (plot.method == "add") { -# plotmethod <- "add" -# } -# else { -# plotmethod <- "normal" -# } -# aux <- loess(M[subset] ~ A[subset], degree = 1, span = span, -# family = family.loess)$fitted -# if (plotmethod == "smoothScatter") { -# smoothScatter(A, M, ...) -# } -# else if (plotmethod == "add") { -# points(A, M, cex = cex, ...) -# } -# else { -# plot(A, M, cex = cex, ...) -# } -# if (add.loess) { -# o <- order(A[subset]) -# A <- A[subset][o] -# M <- aux[o] -# o <- which(!duplicated(A)) -# lines(approx(A[o], M[o]), col = loess.col, lwd = lwd, -# lty = lty) -# } -# abline(0, 0, col = "blue") -# if (show.statistics) { -# txt <- format(sigma, digits = 3) -# txt2 <- format(mean, digits = 3) -# text(xloc, yloc, paste(paste("Median:", txt2), -# paste("IQR:", txt), sep = "\n"), cex = cex, -# adj = c(1, 1)) -# } -# } - - -################################################################################ -################################################################################ - -loessFit2 <- function (y, x, weights = NULL, span = 0.3, iterations = 4L, - min.weight = 1e-05, max.weight = 1e+05, equal.weights.as.null = TRUE, - method = "weightedLowess") { - n <- length(y) - if (length(x) != n) - stop("y and x have different lengths") - out <- list(fitted = rep(NA, n), residuals = rep(NA, n)) - obs <- is.finite(y) & is.finite(x) - xobs <- x[obs] - yobs <- y[obs] - nobs <- length(yobs) - if (nobs == 0) - return(out) - if (span < 1/nobs) { - out$fitted[obs] <- y[obs] - out$residuals[obs] <- 0 - return(out) - } - if (min.weight < 0) - min.weight <- 0 - if (!is.null(weights)) { - if (length(weights) != n) - stop("y and weights have different lengths") - wobs <- weights[obs] - wobs[is.na(wobs)] <- 0 - wobs <- pmax(wobs, min.weight) - wobs <- pmin(wobs, max.weight) - if (equal.weights.as.null) { - r <- range(wobs) - if (r[2] - r[1] < 1e-15) - weights <- NULL - } - } - if (is.null(weights)) { - o <- order(xobs) - #lo <- lowess(x = xobs, y = yobs, f = span, iter = iterations - - # 1L) - lo <- loess(yobs ~ xobs, span = span, - degree = 1, parametric = FALSE, normalize = FALSE, - statistics = "approximate", surface = "direct", # interpolate - cell = 0.01/span, iterations = iterations, trace.hat = "approximate", - family = "gaussian") - - - # loess(M[subset] ~ A[subset], degree = 1, span = span, - # # family = family.loess) - - - out$fitted[obs][o] <- lo$y - out$residuals[obs] <- yobs - out$fitted[obs] - out$mod <- lo - out <<- out - lo <<- lo - return(out) - } - # if (min.weight > 0) - # nwobs <- nobs - # else nwobs <- sum(wobs > 0) - # if (nwobs < 4 + 1/span) { - # if (nwobs == 1L) { - # out$fitted[obs] <- yobs[wobs > 0] - # out$residuals[obs] <- yobs - out$fitted[obs] - # } - # else { - # fit <- lm.wfit(cbind(1, xobs), yobs, wobs) - # out$fitted[obs] <- fit$fitted - # out$residuals[obs] <- fit$residuals - # } - # return(out) - # } - # method <- match.arg(method, c("weightedLowess", "locfit", - # "loess")) - # switch(method, weightedLowess = { - # fit <- weightedLowess(x = xobs, y = yobs, weights = wobs, - # span = span, iterations = iterations, npts = 200) - # out$fitted[obs] <- fit$fitted - # out$residuals[obs] <- fit$residuals - # out$mod <- fit - # fit <<- fit - # }, locfit = { - # if (!requireNamespace("locfit", quietly = TRUE)) stop("locfit required but is not installed (or can't be loaded)") - # biweights <- rep(1, nobs) - # for (i in 1:iterations) { - # fit <- locfit::locfit(yobs ~ xobs, weights = wobs * - # biweights, alpha = span, deg = 1) - # res <- residuals(fit, type = "raw") - # s <- median(abs(res)) - # biweights <- pmax(1 - (res/(6 * s))^2, 0)^2 - # } - # out$fitted[obs] <- fitted(fit) - # out$residuals[obs] <- res - # out$mod <- fit - # }, loess = { - # oldopt <- options(warn = -1) - # on.exit(options(oldopt)) - # bin <- 0.01 - # fit <- loess(yobs ~ xobs, weights = wobs, span = span, - # degree = 1, parametric = FALSE, normalize = FALSE, - # statistics = "approximate", surface = "interpolate", - # cell = bin/span, iterations = iterations, trace.hat = "approximate") - # out$fitted[obs] <- fit$fitted - # out$residuals[obs] <- fit$residuals - # out$mod <- fit - # }) - # print(fit) - # out -} - - - -################################################################################ -################################################################################ -##affy:: normalizeCyclicLoess - -normalizeCyclicLoess2 <- function (x, weights = NULL, span = 0.7, iterations = 3, method = "fast", subset = NULL) { - if (is.null(subset)) { - subset <- 1:nrow(x) - } - - x <- as.matrix(x) - method <- match.arg(method, c("fast", "affy", - "pairs")) - n <- ncol(x) - if (method == "pairs") { - for (k in 1:iterations) for (i in 1:(n - 1)) for (j in (i + - 1):n) { - m <- x[, j] - x[, i] - a <- 0.5 * (x[, j] + x[, i]) - mod <- loessFit2(m[subset], a[subset], weights = weights, span = span, method = "loess") # "weightedLowess" - f <- stats:::predict.loess(mod$mod, a) # cbind(m, a) - x[, i] <- x[, i] + f/2 - x[, j] <- x[, j] - f/2 - } - } - if (method == "fast") { - for (k in 1:iterations) { - a <- rowMeans(x, na.rm = TRUE) - for (i in 1:n) { - m <- x[, i] - a - mod <- loessFit2(m[subset], a[subset], weights = weights, span = span, method = "loess")#$fitted - mod <<- mod - f <- stats:::predict.loess(mod$mod, a) - print(i) - print(summary(f)) - x[, i] <- x[, i] - f - } - } - } - # if (method == "affy") { - # g <- nrow(x) - # for (k in 1:iterations) { - # adjustment <- matrix(0, g, n) - # for (i in 1:(n - 1)) for (j in (i + 1):n) { - # m <- x[, j] - x[, i] - # a <- 0.5 * (x[, j] + x[, i]) - # mod <- loessFit2(m, a, weights = weights, span = span, method = "loess")#$fitted - # f <- stats:::predict.loess(mod$mod, cbind(m, a)) - # adjustment[, j] <- adjustment[, j] + f - # adjustment[, i] <- adjustment[, i] - f - # } - # x <- x - adjustment/n - # } - # } - x -} - diff --git a/R/plotBipartiteGraph.R b/R/plotBipartiteGraph.R index 8c903c0..5e920e8 100644 --- a/R/plotBipartiteGraph.R +++ b/R/plotBipartiteGraph.R @@ -20,14 +20,16 @@ #' @param node_labels_peptides "numbers" or "pep_ratios" or "pep_ratio_aggr" #' @param round_digits Number of digits to round the peptide ratios to. #' @param use_edge_attributes Use edge attributes for plotting (e.g. deleted edges will be dashed)? +#' @param legend.x x-coordinate of the legend. +#' @param legend.y y-coordinate of the legend. #' #' @return Plot of one bipartite graph. #' @export #' #' @examples #' biadjacency_matrix <- matrix(c(1,1,1,0), nrow = 2) -#' G <- igraph::graph_from_incidence_matrix(biadjacency_matrix) -#' plotBipartiteGraph(G, three_shapes = TRUE, useCanonicalPermutation = TRUE) +#' G <- igraph::graph_from_biadjacency_matrix(biadjacency_matrix) +#' #plotBipartiteGraph(G, three_shapes = TRUE, useCanonicalPermutation = TRUE) plotBipartiteGraph <- function(G, vertex.label.dist = 0, legend = TRUE, vertex.color = c("mediumseagreen", "cadetblue2", "coral1"), vertex.size = 15, vertex.label.cex = 1, edge.width = 1, vertex.size2=15, @@ -35,7 +37,7 @@ plotBipartiteGraph <- function(G, vertex.label.dist = 0, legend = TRUE, node_labels_proteins = "letters", node_labels_peptides = "numbers", round_digits = 2, use_edge_attributes = FALSE, - legend.x = NULL, legend.y = NULL, + legend.x = "bottom", legend.y = NULL, ...) { igraph::V(G)$type <- !igraph::V(G)$type # switch node types so that proteins are at the top @@ -56,7 +58,7 @@ plotBipartiteGraph <- function(G, vertex.label.dist = 0, legend = TRUE, names_G[Layout[,2] == 1] <- LETTERS[rank(pos_proteins)] } if (node_labels_proteins == "accessions") { - names_G[Layout[,2] == 1] <- limma::strsplit2(V(G)$name[Layout[,2] == 1], ";")[,1] + names_G[Layout[,2] == 1] <- limma::strsplit2(igraph::V(G)$name[Layout[,2] == 1], ";")[,1] } # nicht geordnete Zahlen if (node_labels_proteins == "numbers_noord") { @@ -70,11 +72,11 @@ plotBipartiteGraph <- function(G, vertex.label.dist = 0, legend = TRUE, names_G[Layout[,2] == 0] <- names_peptides[rank(pos_peptides)] } if (node_labels_peptides == "pep_ratios") { - pep_ratios <- V(G)$pep_ratio + pep_ratios <- igraph::V(G)$pep_ratio names_G[Layout[,2] == 0] <- round(pep_ratios[Layout[,2] == 0],round_digits) } if (node_labels_peptides == "pep_ratio_aggr") { - pep_ratios <- V(G)$pep_ratio_aggr + pep_ratios <- igraph::V(G)$pep_ratio_aggr names_G[Layout[,2] == 0] <- round(pep_ratios[Layout[,2] == 0],round_digits) } if (node_labels_peptides == "") { @@ -86,23 +88,6 @@ plotBipartiteGraph <- function(G, vertex.label.dist = 0, legend = TRUE, ################################# - - # if (node_labels == "letters+numbers") { - # names_G[Layout[,2] == 1] <- LETTERS[rank(pos_proteins)] - # names_peptides <- 1:sum(Layout[,2] == 0) - # names_G[Layout[,2] == 0] <- names_peptides[rank(pos_peptides)] - # - # G <- igraph::set_vertex_attr(G, name = "name", value = names_G) - # } - # if (node_labels == "peptide_ratios") { - # pep_ratios <- V(G)$pep_ratio - # names_G[Layout[,2] == 1] <- limma::strsplit2(V(G)$name[Layout[,2] == 1], ";")[,1] - # - # # names_peptides <- 1:sum(Layout[,2] == 0) - # names_G[Layout[,2] == 0] <- round(pep_ratios[Layout[,2] == 0],2) - # G <- igraph::set_vertex_attr(G, name = "name", value = names_G) - # } - type <- integer(length(igraph::V(G))) type[!igraph::V(G)$type] <- 1 # "protein" type[igraph::V(G)$type] <- 2 # "shared peptide" @@ -133,7 +118,7 @@ plotBipartiteGraph <- function(G, vertex.label.dist = 0, legend = TRUE, #if (legend) graphics::par(mar = c(10, 4, 4, 2) + 0.1) if (use_edge_attributes) { - edge.lty <- E(G)$deleted + 1 + edge.lty <- igraph::E(G)$deleted + 1 } else { edge.lty <- 1 } diff --git a/R/tables-subgraph_characteristics.R b/R/tables-subgraph_characteristics.R index 993550b..748967b 100644 --- a/R/tables-subgraph_characteristics.R +++ b/R/tables-subgraph_characteristics.R @@ -2,10 +2,8 @@ #' Generates a table with characteristics for each subgraph in a list. #' #' @param S list of subgraphs, where peptide and protein nodes are collapsed -#' @param S2 list of subgraphs, where only protein nodes are collapsed -#' @param S3 list of subgraphs, where nodes are not collapsed #' @param fastalevel Are the subgraphs on fasta level? -#' @param comparison name of comparison, for quantitative level (not in use currently) +#' @param prototype Are the subgraphs part of a prototype list? #' @param file where to save the table. #' #' @@ -14,10 +12,21 @@ #' #' @examples #' # TODO -calculate_subgraph_characteristics_OLD <- function(S, S2, S3, fastalevel = TRUE, comparison = NULL, file = NULL) { +calculate_subgraph_characteristics <- function(S, #S2, S3, + fastalevel = TRUE, + prototype = FALSE, + #comparison = NULL, + file = NULL) { + + if (prototype) { + counter <- S$counter + S <- S$graph + } Data <- NULL + + ### TODO: das kann man auch anders lösen, indem man guckt ob es ne liste ist? Dann würde das Argument wegfallen if (fastalevel) { comparisons <- 1 } else { @@ -28,12 +37,12 @@ calculate_subgraph_characteristics_OLD <- function(S, S2, S3, fastalevel = TRUE, if (fastalevel) { S_tmp <- S - S2_tmp <- S2 - S3_tmp <- S3 + # S2_tmp <- S2 + # S3_tmp <- S3 } else { S_tmp <- S[[j]] - S2_tmp <- S2[[j]] - S3_tmp <- S3[[j]] + # S2_tmp <- S2[[j]] + # S3_tmp <- S3[[j]] } print(comparisons[j]) @@ -46,49 +55,82 @@ calculate_subgraph_characteristics_OLD <- function(S, S2, S3, fastalevel = TRUE, for (i in 1:length(S_tmp)) { G_tmp <- S_tmp[[i]] - G2_tmp <- S2_tmp[[i]] - G3_tmp <- S3_tmp[[i]] - #S_tmp <- igraph::as_incidence_matrix(G_tmp) + nr_protein_nodes <- sum(igraph::V(G_tmp)$type) + nr_peptide_nodes <- sum(!igraph::V(G_tmp)$type) + nr_edges <- igraph::gsize(G_tmp) + + nr_edges_per_pep_node <- igraph::degree(G_tmp)[!igraph::V(G_tmp)$type] + nr_unique_peptides <- sum(nr_edges_per_pep_node == 1) + nr_shared_peptides <- sum(nr_edges_per_pep_node > 1) + + + protein_acc <- igraph::V(G_tmp)$name[igraph::V(G_tmp)$type] + protein_acc <- strsplit(protein_acc, ";") + nr_protein_accessions <- sum(sapply(protein_acc, length)) + + peptide_seq <- igraph::V(G_tmp)$name[!igraph::V(G_tmp)$type] + peptide_seq <- strsplit(peptide_seq, ";") + nr_peptide_sequences <- sum(sapply(peptide_seq, length)) + - nr_proteins <- sum(igraph::V(G_tmp)$type) - nr_peptides <- sum(!igraph::V(G_tmp)$type) - nr_edges <- igraph::gsize(G_tmp) - nr_edges_per_pep_node <- igraph::degree(G_tmp)[!igraph::V(G_tmp)$type] - nr_unique_peptides <- sum(nr_edges_per_pep_node == 1) - nr_shared_peptides <- sum(nr_edges_per_pep_node > 1) + unique_peptide_nodes <- igraph::V(G_tmp)[(igraph::degree(G_tmp) == 1 & !igraph::V(G_tmp)$type)] - nr_protein_accessions <- sum(igraph::V(G3_tmp)$type) - nr_peptide_sequences <- sum(!igraph::V(G2_tmp)$type) - nr_edges_per_pep_node2 <- igraph::degree(G2_tmp)[!igraph::V(G2_tmp)$type] - nr_peptide_sequences_unique <- sum(nr_edges_per_pep_node2 == 1) - nr_peptide_sequences_shared <- sum(nr_edges_per_pep_node2 > 1) + if (length(unique_peptide_nodes) == 0) { # Fall: keine uniquen Peptide im ganzen Graphen + nr_prot_node_only_unique_pep <- 0 + nr_prot_node_unique_and_shared_pep <- 0 + nr_prot_node_only_shared_pep <- nr_protein_nodes + } else { + if (length(unique_peptide_nodes) == 1 & nr_protein_nodes == 1) { # Fall: I-shaped graph + nr_prot_node_only_unique_pep <- 1 + nr_prot_node_unique_and_shared_pep <- 0 + nr_prot_node_only_shared_pep <- 0 + } else { + # neighborhood of the unique peptides (these are proteins with a unique peptide) + NH_of_unique_peptides <- igraph::ego(G_tmp, order = 1, mindist = 1, nodes = unique_peptide_nodes) - D_tmp <- data.frame(graph_ID = i, - nr_protein_nodes = nr_proteins, - nr_peptide_nodes = nr_peptides, - nr_unique_peptide_nodes = nr_unique_peptides, - nr_shared_peptide_nodes = nr_shared_peptides, - nr_edges = nr_edges, - nr_protein_accessions = nr_protein_accessions, - nr_peptide_sequences = nr_peptide_sequences, - nr_peptide_sequences_unique = nr_peptide_sequences_unique, - nr_peptide_sequences_shared = nr_peptide_sequences_shared, - comparison = comparisons[j] + nr_prot_node_only_unique_pep <- 0 + nr_prot_node_unique_and_shared_pep <- length(NH_of_unique_peptides) # = Anzahl uniquer Peptide?? + nr_prot_node_only_shared_pep <- nr_protein_nodes - nr_prot_node_unique_and_shared_pep#length(NH_of_unique_peptides) + } + } - ) + ### TODO: add nr of unique and shared peptide sequences + ### TODO: add info about graph type (isomorphism list!) - Data <- rbind(Data, D_tmp) + D_tmp <- data.frame(graph_ID = i, + nr_protein_nodes = nr_protein_nodes, + nr_peptide_nodes = nr_peptide_nodes, + nr_unique_peptide_nodes = nr_unique_peptides, + nr_shared_peptide_nodes = nr_shared_peptides, + nr_edges = as.integer(nr_edges), + nr_protein_accessions = nr_protein_accessions, + nr_peptide_sequences = as.integer(nr_peptide_sequences), + # nr_peptide_sequences_unique = nr_peptide_sequences_unique, + # nr_peptide_sequences_shared = nr_peptide_sequences_shared, + nr_prot_node_only_unique_pep = nr_prot_node_only_unique_pep, + nr_prot_node_unique_and_shared_pep = nr_prot_node_unique_and_shared_pep, + nr_prot_node_only_shared_pep = nr_prot_node_only_shared_pep, + comparison = comparisons[j] - pbapply::setpb(pb, i) + ) + + Data <- rbind(Data, D_tmp) + + pbapply::setpb(pb, i) + } + #progress bar command + invisible(NULL) } - #progress bar command - invisible(NULL) -} + + if (prototype) { + Data <- cbind(Data, counter = counter) + } + if (!is.null(file)) openxlsx::write.xlsx(Data, file, overwrite = TRUE) return(Data) diff --git a/R/tables-subgraph_characteristics_NEW.R b/R/tables-subgraph_characteristics_NEW.R deleted file mode 100644 index f2d9053..0000000 --- a/R/tables-subgraph_characteristics_NEW.R +++ /dev/null @@ -1,151 +0,0 @@ - -#' Generates a table with characteristics for each subgraph in a list. -#' -#' @param S list of subgraphs, where peptide and protein nodes are collapsed -#' @param fastalevel Are the subgraphs on fasta level? -#' @param prototype Are the subgraphs part of a prototype list? -#' @param file where to save the table. -#' -#' -#' @return table -#' @export -#' -#' @examples -#' # TODO -calculate_subgraph_characteristics <- function(S, #S2, S3, - fastalevel = TRUE, - prototype = FALSE, - #comparison = NULL, - file = NULL) { - - if (prototype) { - counter <- S$counter - S <- S$graph - } - - # print(str(S, 1)) - #print(counter) - - Data <- NULL - - if (fastalevel) { - comparisons <- 1 - } else { - comparisons <- names(S) - } - - for (j in 1:length(comparisons)) { - - if (fastalevel) { - S_tmp <- S - # S2_tmp <- S2 - # S3_tmp <- S3 - } else { - S_tmp <- S[[j]] - # S2_tmp <- S2[[j]] - # S3_tmp <- S3[[j]] - } - - print(comparisons[j]) - - #add progress bar to loop - number_of_iterations <- length(S_tmp) - pb <- pbapply::startpb(0, length(S_tmp)) - on.exit(pbapply::closepb(pb)) - - for (i in 1:length(S_tmp)) { - - G_tmp <- S_tmp[[i]] - # G2_tmp <- S2_tmp[[i]] - # G3_tmp <- S3_tmp[[i]] - - - #S_tmp <- igraph::as_incidence_matrix(G_tmp) - - nr_protein_nodes <- sum(igraph::V(G_tmp)$type) - nr_peptide_nodes <- sum(!igraph::V(G_tmp)$type) - nr_edges <- igraph::gsize(G_tmp) - - nr_edges_per_pep_node <- igraph::degree(G_tmp)[!igraph::V(G_tmp)$type] - nr_unique_peptides <- sum(nr_edges_per_pep_node == 1) - nr_shared_peptides <- sum(nr_edges_per_pep_node > 1) - - - protein_acc <- igraph::V(G_tmp)$name[igraph::V(G_tmp)$type] - protein_acc <- strsplit(protein_acc, ";") - nr_protein_accessions <- sum(sapply(protein_acc, length)) - - peptide_seq <- igraph::V(G_tmp)$name[!igraph::V(G_tmp)$type] - peptide_seq <- strsplit(peptide_seq, ";") - nr_peptide_sequences <- sum(sapply(peptide_seq, length)) - - - - unique_peptide_nodes <- V(G_tmp)[(degree(G_tmp) == 1 & !V(G_tmp)$type)] - - if (length(unique_peptide_nodes) == 0) { # Fall: keine uniquen Peptide im ganzen Graphen - nr_prot_node_only_unique_pep <- 0 - nr_prot_node_unique_and_shared_pep <- 0 - nr_prot_node_only_shared_pep <- nr_protein_nodes - - } else { - if (length(unique_peptide_nodes) == 1 & nr_protein_nodes == 1) { # Fall: I-shaped graph - nr_prot_node_only_unique_pep <- 1 - nr_prot_node_unique_and_shared_pep <- 0 - nr_prot_node_only_shared_pep <- 0 - } else { - - # neighborhood of the unique peptides (these are proteins with a unique peptide) - NH_of_unique_peptides <- ego(G_tmp, order = 1, mindist = 1, nodes = unique_peptide_nodes) - - nr_prot_node_only_unique_pep <- 0 - nr_prot_node_unique_and_shared_pep <- length(NH_of_unique_peptides) # = Anzahl uniquer Peptide?? - nr_prot_node_only_shared_pep <- nr_protein_nodes - nr_prot_node_unique_and_shared_pep#length(NH_of_unique_peptides) - } - } - - - - - - - -### TODO: add nr of unique and shared peptide sequences -### TODO: add infor about graph type (isomorphism list!) - - - D_tmp <- data.frame(graph_ID = i, - nr_protein_nodes = nr_protein_nodes, - nr_peptide_nodes = nr_peptide_nodes, - nr_unique_peptide_nodes = nr_unique_peptides, - nr_shared_peptide_nodes = nr_shared_peptides, - nr_edges = as.integer(nr_edges), - nr_protein_accessions = nr_protein_accessions, - nr_peptide_sequences = as.integer(nr_peptide_sequences), - # nr_peptide_sequences_unique = nr_peptide_sequences_unique, - # nr_peptide_sequences_shared = nr_peptide_sequences_shared, - nr_prot_node_only_unique_pep = nr_prot_node_only_unique_pep, - nr_prot_node_unique_and_shared_pep = nr_prot_node_unique_and_shared_pep, - nr_prot_node_only_shared_pep = nr_prot_node_only_shared_pep, - comparison = comparisons[j] - - ) - - Data <- rbind(Data, D_tmp) - - - - pbapply::setpb(pb, i) - } - #progress bar command - invisible(NULL) - } - - if (prototype) { - Data <- cbind(Data, counter = counter) - } - - if (!is.null(file)) openxlsx::write.xlsx(Data, file, overwrite = TRUE) - - return(Data) -} diff --git a/inst/extdata/uniprot_test.fasta b/inst/extdata/uniprot_test.fasta index 3da137a..7a4571d 100644 --- a/inst/extdata/uniprot_test.fasta +++ b/inst/extdata/uniprot_test.fasta @@ -80,3 +80,11 @@ FLQNLLSDERLCQSEALYAFLSPSPDYLKVIDVQGKKNSFSLSSFLERLPRDFFSHQEEE TEEDSDLSDYGDDVDGRKDALAEPCFMLIGEIFELRGMFKWVRRTLIALVQVTFGRTINK QIRDTVSWIFSEQMLVYYINIFRDAFWPNGKLAPPTTIRSKEQSQETKQRAQQKLLENIP DMLQSLVGQQNARHGIIKIFNALQETRANKHLLYALMELLLIELCPELRVHLDQLKAGQV +>sp|O43402|EMC8_HUMAN ER membrane protein complex subunit 8 OS=Homo sapiens OX=9606 GN=EMC8 PE=1 SV=1 +MPGVKLTTQAYCKMVLHGAKYPHCAVNGLLVAEKQKPRKEHLPLGGPGAHHTLFVDCIPL +FHGTLALAPMLEVALTLIDSWCKDHSYVIAGYYQANERVKDASPNQVAEKVASRIAEGFS +DTALIMVDNTKFTMDCVAPTIHVYEHHENRWRCRDPHHDYCEDWPEAQRISASLLDSRSY +ETLVDFDNHLDDIRNDWTNPEINKAVLHLC +>tr|M0R1B0|M0R1B0_HUMAN ER membrane protein complex subunit 8 (Fragment) OS=Homo sapiens OX=9606 GN=EMC8 PE=1 SV=1 +VASRIAEGFSDTALIMVDNTKFTMDCVAPTIHVYEHHENRWRCRDPHHDYCEDWPEAQRI +SASLLDSRSYETLVDFDNHLDDIRNDWTNPEINKAVLHLC diff --git a/man/add_average_pep_ratio.Rd b/man/add_average_pep_ratio.Rd index 8206568..a42ee7f 100644 --- a/man/add_average_pep_ratio.Rd +++ b/man/add_average_pep_ratio.Rd @@ -7,7 +7,9 @@ add_average_pep_ratio(G, type = "geom_mean") } \arguments{ -\item{G}{} +\item{G}{graph} + +\item{type}{not used at the moment. Default is 'geom_mean'} } \value{ graphs with added attributes diff --git a/man/calculateIsomorphList.Rd b/man/calculateIsomorphList.Rd deleted file mode 100644 index 770fc25..0000000 --- a/man/calculateIsomorphList.Rd +++ /dev/null @@ -1,35 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/helpers_calculate_and_plot_isomorphism_lists.R, -% R/helpers_calculate_and_plot_isomorphism_lists2.R -\name{calculateIsomorphList} -\alias{calculateIsomorphList} -\title{Calculation of isomorphism lists from submatrizes or subgraphs} -\usage{ -calculateIsomorphList(Submatrix, matrix = TRUE) - -calculateIsomorphList(Submatrix, matrix = TRUE) -} -\arguments{ -\item{Submatrix}{list of submatrizes or subgraphs} - -\item{matrix}{Is submatrix a list of matrizes?} - -\item{G}{list of subgraphs -#'} -} -\value{ -isomorph_list is a list of indizes that belong in the same isomorphism class -Graphs are graph representatives - -isomorph_list is a list of indizes that belong in the same isomorphism class -Graphs are graph representatives -} -\description{ -Calculation of isomorphism lists from submatrizes or subgraphs - -Calculation of isomorphism lists from submatrizes or subgraphs -} -\examples{ -### TODO -### TODO -} diff --git a/man/calculate_peptide_ratios.Rd b/man/calculate_peptide_ratios.Rd index 24bb6cb..80c07c2 100644 --- a/man/calculate_peptide_ratios.Rd +++ b/man/calculate_peptide_ratios.Rd @@ -4,7 +4,13 @@ \alias{calculate_peptide_ratios} \title{Calculation of peptide ratios from aggregated intensities} \usage{ -calculate_peptide_ratios(aggr_intensities, id_cols = 1, group_levels = NULL) +calculate_peptide_ratios( + aggr_intensities, + id_cols = 1, + group_levels = NULL, + type = "ratio", + log_base = 10 +) } \arguments{ \item{aggr_intensities}{result from function aggregate_replicates} @@ -12,6 +18,10 @@ calculate_peptide_ratios(aggr_intensities, id_cols = 1, group_levels = NULL) \item{id_cols}{column numbers that contain peptide sequences etc (everything except intensities)} \item{group_levels}{levels of groups in the right order} + +\item{type}{"ratio" or "difference". Difference if values are already on log-scale} + +\item{log_base}{log base} } \value{ data set with peptide ratios @@ -19,6 +29,3 @@ data set with peptide ratios \description{ Calculation of peptide ratios from aggregated intensities } -\examples{ -## TODO -} diff --git a/man/calculate_subgraph_characteristics.Rd b/man/calculate_subgraph_characteristics.Rd index 7efff68..91ae9be 100644 --- a/man/calculate_subgraph_characteristics.Rd +++ b/man/calculate_subgraph_characteristics.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/tables-subgraph_characteristics_NEW.R +% Please edit documentation in R/tables-subgraph_characteristics.R \name{calculate_subgraph_characteristics} \alias{calculate_subgraph_characteristics} \title{Generates a table with characteristics for each subgraph in a list.} diff --git a/man/calculate_subgraph_characteristics_OLD.Rd b/man/calculate_subgraph_characteristics_OLD.Rd deleted file mode 100644 index 8c1949a..0000000 --- a/man/calculate_subgraph_characteristics_OLD.Rd +++ /dev/null @@ -1,37 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/tables-subgraph_characteristics.R -\name{calculate_subgraph_characteristics_OLD} -\alias{calculate_subgraph_characteristics_OLD} -\title{Generates a table with characteristics for each subgraph in a list.} -\usage{ -calculate_subgraph_characteristics_OLD( - S, - S2, - S3, - fastalevel = TRUE, - comparison = NULL, - file = NULL -) -} -\arguments{ -\item{S}{list of subgraphs, where peptide and protein nodes are collapsed} - -\item{S2}{list of subgraphs, where only protein nodes are collapsed} - -\item{S3}{list of subgraphs, where nodes are not collapsed} - -\item{fastalevel}{Are the subgraphs on fasta level?} - -\item{comparison}{name of comparison, for quantitative level (not in use currently)} - -\item{file}{where to save the table.} -} -\value{ -table -} -\description{ -Generates a table with characteristics for each subgraph in a list. -} -\examples{ -# TODO -} diff --git a/man/convertToBipartiteGraph.Rd b/man/convertToBipartiteGraph.Rd index f7cab4c..42436dc 100644 --- a/man/convertToBipartiteGraph.Rd +++ b/man/convertToBipartiteGraph.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/helpers-convertToBipartiteGraph.R \name{convertToBipartiteGraph} \alias{convertToBipartiteGraph} -\title{Conversion of submatrizes to subgraphs.} +\title{Conversion of submatrices to subgraphs.} \usage{ convertToBipartiteGraph(x) } @@ -13,7 +13,7 @@ convertToBipartiteGraph(x) graph as igraph object } \description{ -Conversion of submatrizes to subgraphs. +Conversion of submatrices to subgraphs. } \examples{ M <- matrix(c(1,0,1,1), nrow = 2, byrow = TRUE) diff --git a/man/direct_bipartite_graph.Rd b/man/direct_bipartite_graph.Rd index e2bafcf..2ed441a 100644 --- a/man/direct_bipartite_graph.Rd +++ b/man/direct_bipartite_graph.Rd @@ -2,19 +2,18 @@ % Please edit documentation in R/helpers-isomorphisms.R \name{direct_bipartite_graph} \alias{direct_bipartite_graph} -\title{Title} +\title{Transform a bipartite graph into a directed graph} \usage{ direct_bipartite_graph(bip_graph, from_type = FALSE) } \arguments{ -\item{from_type}{TODO} +\item{bip_graph}{a bipartite graph} + +\item{from_type}{determines if protein or peptide nodes are the "from" nodes} } \value{ a bipartite graph that is know directed } \description{ -Title -} -\examples{ -# TODO +Transform a bipartite graph into a directed graph } diff --git a/man/duplicated.dgCMatrix.Rd b/man/duplicated.dgCMatrix.Rd deleted file mode 100644 index 3ec4dca..0000000 --- a/man/duplicated.dgCMatrix.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/helpers-duplicated.dgCMatrix.R -\name{duplicated.dgCMatrix} -\alias{duplicated.dgCMatrix} -\title{duplicated() but for sparse dgCMatrix objects} -\usage{ -\method{duplicated}{dgCMatrix}(x, incomparables = FALSE, MARGIN, ...) -} -\arguments{ -\item{x}{A dgCMat object (sparse matrix generated by the Matrix package).} - -\item{incomparables}{no functionality} - -\item{MARGIN}{1 = rowwise, 2 = column wise.} - -\item{...}{no functionality} -} -\value{ -Logical vector indicating if the columns or rows of the matrix are duplicated. -} -\description{ -duplicated() but for sparse dgCMatrix objects -} -\examples{ - library(Matrix) - M <- Matrix::Matrix(c(0,0,1,0,0,1,1 ,0, 0,0), byrow = FALSE, nrow = 2) - duplicated(M, MARGIN = 2) - -} diff --git a/man/foldChange.Rd b/man/foldChange.Rd index 6a22de6..c958d9a 100644 --- a/man/foldChange.Rd +++ b/man/foldChange.Rd @@ -21,6 +21,3 @@ fold changes (Y/X) \description{ calculates peptide ratios for pairwise comparisons of groups (Y/X) } -\examples{ -### TODO -} diff --git a/man/generatePrototypeList.Rd b/man/generatePrototypeList.Rd index 3665aac..d341518 100644 --- a/man/generatePrototypeList.Rd +++ b/man/generatePrototypeList.Rd @@ -7,16 +7,13 @@ generatePrototypeList(G, sort_by_nr_edges = FALSE) } \arguments{ -\item{G}{bla} +\item{G}{graph} + +\item{sort_by_nr_edges}{logical, if TRUE, the list of prototypes is sorted by number of edges} } \value{ -bla +list of prototype graphs plus count } \description{ Generates a list of graph prototypes for the different isomorphism classes and } -\examples{ -# TODO - - -} diff --git a/man/generate_edgelist.Rd b/man/generate_edgelist.Rd index ebbe1a1..3cd654a 100644 --- a/man/generate_edgelist.Rd +++ b/man/generate_edgelist.Rd @@ -8,6 +8,8 @@ generate_edgelist(digested_proteins, prot_origin = NULL) } \arguments{ \item{digested_proteins}{Output from digest_fasta() (List of vectors of peptide sequences)} + +\item{prot_origin}{origin of the protein (e.g. organism, spike-in/background etc)} } \value{ edgelist @@ -23,7 +25,4 @@ digested_proteins <- digest_fasta(fasta) edgelist <- generate_edgelist(digested_proteins) - - - } diff --git a/man/generate_graphs_from_FASTA.Rd b/man/generate_graphs_from_FASTA.Rd index cea0af8..f623c05 100644 --- a/man/generate_graphs_from_FASTA.Rd +++ b/man/generate_graphs_from_FASTA.Rd @@ -22,12 +22,14 @@ generate_graphs_from_FASTA( \item{collapse_peptide_nodes}{collapse peptide nodes?} -\item{result_path}{path whereresults are saved. If NULL, results are not saved} +\item{result_path}{path where results are saved. If NULL, results are not saved} \item{suffix}{suffix for saving results} \item{save_intermediate}{Save intermediate results?} +\item{prot_origin}{origin of protein, e.g. organism etc.} + \item{...}{additional arguments to bppg::digest_fasta()} } \value{ diff --git a/man/generate_graphs_from_quant_data.Rd b/man/generate_graphs_from_quant_data.Rd index f3a52aa..40c3b22 100644 --- a/man/generate_graphs_from_quant_data.Rd +++ b/man/generate_graphs_from_quant_data.Rd @@ -8,7 +8,6 @@ generate_graphs_from_quant_data( D, fasta, outpath = NULL, - normalize = FALSE, missed_cleavages = 2, min_aa = 6, max_aa = 50, @@ -28,14 +27,22 @@ generate_graphs_from_quant_data( \item{outpath}{bla} -\item{normalize}{currently only loess normalization possible} - \item{missed_cleavages}{bla} \item{min_aa}{bla} \item{max_aa}{bla} +\item{id_columns}{column numbers of D that contain ID information (the rest should contain only peptide intensities, properly normalized)} + +\item{seq_column}{column name of the peptide sequence} + +\item{collapse_protein_nodes}{if TRUE protein nodes will be collapsed} + +\item{collapse_peptide_nodes}{if TRUE, peptide nodes will be collapsed} + +\item{suffix}{suffix for output files} + \item{...}{currently not in use} } \value{ @@ -44,6 +51,3 @@ list of list of graphs \description{ Generate graphs from quantitative peptide-level data } -\examples{ -# TODO -} diff --git a/man/generate_quant_graphs.Rd b/man/generate_quant_graphs.Rd index 0d2c200..2d1d47a 100644 --- a/man/generate_quant_graphs.Rd +++ b/man/generate_quant_graphs.Rd @@ -21,6 +21,16 @@ generate_quant_graphs( \item{id_cols}{columns with ids, e.g. peptide sequences (everything except the peptide ratios)} \item{fasta_edgelist}{Edgelist created from the corresponding FASTA file} + +\item{outpath}{output path} + +\item{seq_column}{column name of the peptide sequence} + +\item{collapse_protein_nodes}{if TRUE protein nodes will be collapsed} + +\item{collapse_peptide_nodes}{if TRUE, peptide nodes will be collapsed} + +\item{suffix}{suffix for output files} } \value{ list of list of subgraphs @@ -28,7 +38,3 @@ list of list of subgraphs \description{ Generate graphs from peptide ratio table, using an edgelist calculated on the fasta file } -\examples{ -### TODO: Einstellbar, ob Peptid-Knoten auch gemergt werden sollen (dann mit geom. Mittel als peptid-ratio). -#### Das funktioniert noch nicht!!! -} diff --git a/man/geom_mean.Rd b/man/geom_mean.Rd index ef60084..5070bf0 100644 --- a/man/geom_mean.Rd +++ b/man/geom_mean.Rd @@ -4,10 +4,12 @@ \alias{geom_mean} \title{Geometric mean} \usage{ -geom_mean(x) +geom_mean(x, useprod = FALSE) } \arguments{ \item{x}{vector with numbers} + +\item{useprod}{if TRUE, prod(x)^(1/n) will be calculated, otherwise exp(mean(log(x)))} } \value{ geometric mean of the provided data points diff --git a/man/isomorphic_bipartite.Rd b/man/isomorphic_bipartite.Rd index fb673f7..d1f02d3 100644 --- a/man/isomorphic_bipartite.Rd +++ b/man/isomorphic_bipartite.Rd @@ -21,8 +21,3 @@ TRUE if graphs are isomorphic, FALSE if not. Enchanced version of the igraph::isomorphic function that also considers the node type in bipartite graphs, e.g. that W- and M-shaped graphs are NOT isomorphic } -\examples{ -# TODO - - -} diff --git a/man/plotBipartiteGraph.Rd b/man/plotBipartiteGraph.Rd index 5fabc83..f7a62d2 100644 --- a/man/plotBipartiteGraph.Rd +++ b/man/plotBipartiteGraph.Rd @@ -19,6 +19,8 @@ plotBipartiteGraph( node_labels_peptides = "numbers", round_digits = 2, use_edge_attributes = FALSE, + legend.x = "bottom", + legend.y = NULL, ... ) } @@ -51,6 +53,10 @@ plotBipartiteGraph( \item{use_edge_attributes}{Use edge attributes for plotting (e.g. deleted edges will be dashed)?} +\item{legend.x}{x-coordinate of the legend.} + +\item{legend.y}{y-coordinate of the legend.} + \item{...}{Additional arguments for plot.igraph.} } \value{ @@ -61,6 +67,6 @@ Plotting of bipartite peptide-protein graphs. } \examples{ biadjacency_matrix <- matrix(c(1,1,1,0), nrow = 2) -G <- igraph::graph_from_incidence_matrix(biadjacency_matrix) -plotBipartiteGraph(G, three_shapes = TRUE, useCanonicalPermutation = TRUE) +G <- igraph::graph_from_biadjacency_matrix(biadjacency_matrix) +#plotBipartiteGraph(G, three_shapes = TRUE, useCanonicalPermutation = TRUE) } diff --git a/man/plotIsomorphList.Rd b/man/plotIsomorphList.Rd deleted file mode 100644 index a5b006f..0000000 --- a/man/plotIsomorphList.Rd +++ /dev/null @@ -1,80 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/helpers_calculate_and_plot_isomorphism_lists.R, -% R/helpers_calculate_and_plot_isomorphism_lists2.R -\name{plotIsomorphList} -\alias{plotIsomorphList} -\title{function that plots list of isomorph classes, sorted by number of occurences} -\usage{ -plotIsomorphList( - isomorph_list, - Graphs, - path, - title = TRUE, - pdf = TRUE, - cex.title = 1, - which_graphs = NULL, - mfrow = c(1, 1), - save = TRUE, - title_format = "times+percent", - ..., - height = 15, - width = 15 -) - -plotIsomorphList( - isomorph_list, - Graphs, - path, - title = TRUE, - pdf = TRUE, - cex.title = 1, - which_graphs = NULL, - mfrow = c(1, 1), - save = TRUE, - title_format = "times+percent", - ..., - height = 15, - width = 15 -) -} -\arguments{ -\item{isomorph_list}{result of calculateIsomorphList} - -\item{Graphs}{list of graphs} - -\item{path}{path to save plots} - -\item{title}{if TRUE, title is added with number of occurence and percentage value} - -\item{pdf}{if TRUE, plot is saved in a single odf, if FALSE as multiple pngs} - -\item{cex.title}{size of title} - -\item{which_graphs}{ranks of classes that should be plottet (e.g. 1:10 for top 10 classes)} - -\item{mfrow}{mfrow agrument of par function to arrance graphs in one plot} - -\item{save}{if TRUE, graphs will be saved as pdf or png} - -\item{title_format}{"times+percent" or "percent"} - -\item{...}{further arguments to plotBipartiteGraph} - -\item{height}{height of plot} - -\item{width}{width of plot} -} -\value{ -plots saved as a pdf or multiple png files - -plots saved as a pdf or multiple png files -} -\description{ -function that plots list of isomorph classes, sorted by number of occurences - -function that plots list of isomorph classes, sorted by number of occurences -} -\examples{ -### TODO -### TODO -} diff --git a/man/read_MQ_peptidetable.Rd b/man/read_MQ_peptidetable.Rd index d77d362..faba77e 100644 --- a/man/read_MQ_peptidetable.Rd +++ b/man/read_MQ_peptidetable.Rd @@ -25,10 +25,12 @@ read_MQ_peptidetable( \item{zeroToNA}{If TRUE, zeros are converted to NAs.} -\item{remove_empty_rows}{} +\item{remove_empty_rows}{If TRUE, rows with only NAs are removed.} + +\item{further_columns_to_keep}{additional columns to keep, except peptide sequence and intensities} } \value{ -Dataframe with sequences and intensities +Data frame with sequences and intensities } \description{ Import of MaxQuant's peptide.txt-table diff --git a/tests/testthat/test-graph_generation_FASTA.R b/tests/testthat/test-graph_generation_FASTA.R index 8f31d12..b70adb1 100644 --- a/tests/testthat/test-graph_generation_FASTA.R +++ b/tests/testthat/test-graph_generation_FASTA.R @@ -1,6 +1,6 @@ test_that("digestion of a FASTA file", { - digested_proteins <- readRDS(test_path("testfiles/digested_proteins_test.rds")) + digested_proteins <- readRDS(testthat::test_path("testfiles/digested_proteins_test.rds")) file <- system.file("extdata", "uniprot_test.fasta", package = "bppg") fasta <- seqinr::read.fasta(file = file, seqtype = "AA", as.string = TRUE) @@ -12,9 +12,9 @@ test_that("digestion of a FASTA file", { test_that("generation of an edgelist", { - edgelist <- readRDS(test_path("testfiles/edgelist_test.rds")) + edgelist <- readRDS(testthat::test_path("testfiles/edgelist_test.rds")) - digested_proteins <- readRDS(test_path("testfiles/digested_proteins_test.rds")) + digested_proteins <- readRDS(testthat::test_path("testfiles/digested_proteins_test.rds")) res <- generate_edgelist(digested_proteins) expect_equal(res, edgelist) @@ -22,9 +22,9 @@ test_that("generation of an edgelist", { test_that("collapsing of edgelists", { - edgelist_coll_pept_prot <- readRDS(test_path("testfiles/edgelist_coll_pept_prot_test.rds")) + edgelist_coll_pept_prot <- readRDS(testthat::test_path("testfiles/edgelist_coll_pept_prot_test.rds")) - edgelist <- readRDS(test_path("testfiles/edgelist_test.rds")) + edgelist <- readRDS(testthat::test_path("testfiles/edgelist_test.rds")) res <- bppg::collapse_edgelist(edgelist, collapse_protein_nodes = TRUE, collapse_peptide_nodes = TRUE) @@ -32,9 +32,9 @@ test_that("collapsing of edgelists", { expect_equal(res, edgelist_coll_pept_prot) - edgelist_coll_prot <- readRDS(test_path("testfiles/edgelist_coll_prot_test.rds")) + edgelist_coll_prot <- readRDS(testthat::test_path("testfiles/edgelist_coll_prot_test.rds")) - edgelist <- readRDS(test_path("testfiles/edgelist_test.rds")) + edgelist <- readRDS(testthat::test_path("testfiles/edgelist_test.rds")) res2 <- bppg::collapse_edgelist(edgelist, collapse_protein_nodes = TRUE, collapse_peptide_nodes = FALSE) @@ -54,39 +54,36 @@ test_that("generation of graphs from edgelist", { edgelist_coll_pept_prot <- readRDS(testthat::test_path("testfiles/edgelist_coll_pept_prot_test.rds")) res <- bppg::generate_graphs_from_edgelist(edgelist_coll_pept_prot) - expect_true(bppg::isomorphic_bipartite(res[[1]], igraph::upgrade_graph(graphs_coll_pept_prot[[1]]))) - expect_true(bppg::isomorphic_bipartite(res[[2]], igraph::upgrade_graph(graphs_coll_pept_prot[[2]]))) + expect_true(bppg::isomorphic_bipartite(res[[1]], graphs_coll_pept_prot[[1]])) + expect_true(bppg::isomorphic_bipartite(res[[2]], graphs_coll_pept_prot[[2]])) + expect_true(bppg::isomorphic_bipartite(res[[3]], graphs_coll_pept_prot[[3]])) # with collapsing of only protein nodes edgelist_coll_prot <- readRDS(test_path("testfiles/edgelist_coll_prot_test.rds")) res2 <- bppg::generate_graphs_from_edgelist(edgelist_coll_prot) - expect_true(bppg::isomorphic_bipartite(res2[[1]], igraph::upgrade_graph(graphs_coll_prot[[1]]))) - expect_true(bppg::isomorphic_bipartite(res2[[2]], igraph::upgrade_graph(graphs_coll_prot[[2]]))) + expect_true(bppg::isomorphic_bipartite(res2[[1]], graphs_coll_prot[[1]])) + expect_true(bppg::isomorphic_bipartite(res2[[2]], graphs_coll_prot[[2]])) + expect_true(bppg::isomorphic_bipartite(res2[[3]], graphs_coll_prot[[3]])) - - # TODO: evtl ist es nicht ganz ideal hier mit einer Funktio aus bppg (isomorphic_bipartite) den Test zu machen - # evtl wegen alter igraph version (siehe testthat output?) }) test_that("subgraph characteristics table", { - ## TODO: evtl weiteres Beispiel mit einem Graph mit mind. einem Protein ohne uniques Peptid - expected <- data.frame( - graph_ID = c(1L,2L), - nr_protein_nodes = c(7L,1L), - nr_peptide_nodes = c(13L,1L), - nr_unique_peptide_nodes = c(7L,1L), - nr_shared_peptide_nodes = c(6L,0L), - nr_edges = c(22L,1L), - nr_protein_accessions = c(7L,1L), - nr_peptide_sequences = c(476L, 204L), - nr_prot_node_only_unique_pep = c(0,1), - nr_prot_node_unique_and_shared_pep = c(7,0), - nr_prot_node_only_shared_pep = c(0, 0), - comparison = c(1L,1L) + graph_ID = c(1L,2L,3L), + nr_protein_nodes = c(7L,1L,2L), + nr_peptide_nodes = c(13L,1L,2L), + nr_unique_peptide_nodes = c(7L,1L,1L), + nr_shared_peptide_nodes = c(6L,0L,1L), + nr_edges = c(22L,1L,3L), + nr_protein_accessions = c(7L,1L,2L), + nr_peptide_sequences = c(476L, 204L,47L), + nr_prot_node_only_unique_pep = c(0,1,0L), + nr_prot_node_unique_and_shared_pep = c(7L,0L,1L), + nr_prot_node_only_shared_pep = c(0L, 0L, 1L), + comparison = c(1L,1L, 1L) ) G <- readRDS(test_path("testfiles/graphs_coll_pept_prot_test.rds")) diff --git a/tests/testthat/testfiles/digested_proteins_test.RData b/tests/testthat/testfiles/digested_proteins_test.RData deleted file mode 100644 index f5b2fff702cef967a9573a3f16bd22e9be0e8c40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4767 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 diff --git a/tests/testthat/testfiles/digested_proteins_test.rds b/tests/testthat/testfiles/digested_proteins_test.rds index ecd1ea96dd95f4106b2743b58f75c77a9c9794ad..ac07fca20e00c6b93c075463a10336e9dc12755b 100644 GIT binary patch literal 5058 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 literal 4700 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

w7rUA*j>t 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 mhadmt7r&f%n1~Mg$g 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 GPXGX`M2o)w diff --git a/tests/testthat/testfiles/edgelist_coll_prot_test.rds b/tests/testthat/testfiles/edgelist_coll_prot_test.rds index 354c1c0ad34764493b2d063bccd63eb7ce689f9c..731fb6d600c51d8ab202e73e6b1cb54b6c1b316b 100644 GIT binary patch literal 5192 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 Gc=2Bl4GQ%D literal 4835 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

w7rUA*j>t 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 BVhR8N literal 0 HcmV?d00001 diff --git a/tests/testthat/testfiles/graphs_coll_pept_prot_test.rds b/tests/testthat/testfiles/graphs_coll_pept_prot_test.rds index 920e4ffca9fbe0873e0a2283ddd050c9240471d6..eb870a2df2ee84d9cd60d8e6472738ff2287dbcc 100644 GIT binary patch literal 4307 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 BR@wjn literal 4084 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 diff --git a/tests/testthat/testfiles/graphs_coll_prot_test.rds b/tests/testthat/testfiles/graphs_coll_prot_test.rds index 615a4108aaf8a2d61ee5685a5b9aa7f3d18962cc..582f138ee6bcc620e087a92a9b5a4cff2e7e1afa 100644 GIT binary patch literal 6965 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ⅈ_;}#qkO;@?1+G-DZ;^S%Nn{R_(sPB6&_Vaa= z?K-b5wWE8G`ZYJfEth5mxAM>5^*~6+kdNmPpK|NlcD@}aJYg*qS*L7Uf#A82mcCNm z!_NFvG=J^PX=7nWydNKIh3Q}kQ|*Z!r_JiTY;~lNZs@nI$Q3CTM&9>sOY&=o95|h&4Pwc!7 zr_H$wqY6lGcx|bao`gfMWFIWS6?*79$l?v#C(#wpb%An=;(AyQ>eLh3U{tg*%yC>$ zH5fy8a=`-cC!?N=#P|~--!?kuc&^7t#~r0jDX)=zba}U?|BXiJQY4MZUIwN~!F^LX z@{ChJ`Vi)vy=3&=1XS+m{=trR)s2GNJ0Q(DchSd6los4_LGwU3-E9l3Nh(uX4`Nf= zANp9hyU4L=JLzACl0Ecc(9$S41!O|8ecpeoF~>^`mQk9Xs?9iEV>Z{^zX(ef36?%b zk|9>_?;vw{5;qO^jaz*{&KJ}2B_b27FZiEQ4EexEck60~PtWIbefJxg+P}m1Ov((X z0>~5cfh^SX7=|ISjDG6l{4%1@DA*~<+n~GL@s#nh*y8<0@P>bxW3df9PdnvI-b<5I z#CgqKUwQDFiCt8Uda+d!y^-j}4nZlAsv`j_g|0Shpj^>~(WJ6J*U$Hpilk4(hL9+g&@d*~75$x?BLKi*_Ep~x!%$vuYn%yI+Y?Ij|D&dC=)VvPAZM0fO8zT2;jmtfq zP1qzU-M1Q|1hoXf9Dw9C$$n47T*3fq{Wna4?r!peH(8yC&`BXE(+dhR$l=UiX)02q zeVrl1tsDPme|oKp_hA(A%L|7aY|#lTj#rZ?n-)n6!VeHUB;7>ro55dE)yPmuHic-U zfjceX=V{fQ4W=D7gY_$(w?L_e?hoZ{D!L`3&m6<4CV`1MgJWf~zPUQ8Kythf3Mk*! z4_RZNHGZF)k@$%}x069ru+U_^&;CU^JN|i9jN^-NXZQ>hQ;TE#3;AduTep;sm0d%X zVvKHz7hQ#wYqz=8uId^`nVqNT&B(~w-eQG_v~+`Ozgu6k(dhRQ&sgDsGaiqkbck=; z;$y|99a2qS;iRCEX-CD|QPSR{u!3$#c+HJ+)t^6p7Im9DOFUP~-zVLl0)@-AnjaCe z&X0K9-dbJXCAikh-_Kqk zNq52zcfv`*ckicvfcY$1ThoDCO@^!wFXGQ_!9N{_>5$nrLL|^nc(Q{*s({54KEKx8 zyHP^#{D|cu14GXI!wQisFQ(l57!dYKlihOXeJw3jG@tdG5|?nKU{UF)+oZNRaL^#S zinl7ltA)y>kTVwX$r+}|^mrbJd0#@`@uyy<7cDnbuacKzs`M<5+u&k?_kP3GWKeFI z(xiDyl8aef4v8>6>>(Ao=oNl*-*Yg694oW3W}Y|D!znzh4}GZ-;vuGSL31NG(KeX0 zJDX!xzOP^-zo17A6TU!Gv1$T|lnr0_{p*I(W1x9a>f(-r`+2U@uOJLYJkReB?sM)SrYHt_JhP` r&0KgNtRZt+vYU3t^A?R=zemZ5yF0qh&)&y9U@looN<|kvdh~w)?1}%3 literal 8559 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 wO(0V=)K^V>!YVD8-cA2^kL@c;k-