From c3e2af35535f8e358842be64fe872f55dbc06cdb Mon Sep 17 00:00:00 2001 From: veronicavenafra <77729333+veronicavenafra@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:10:53 +0100 Subject: [PATCH 1/5] signalingprofiler implementation --- docs/src/_static/nc_signalingprofiler.svg | 802 +++++++++++++++++++ docs/src/api.rst | 15 + docs/src/methods.rst | 31 +- networkcommons/methods/_signalingprofiler.py | 221 +++++ 4 files changed, 1068 insertions(+), 1 deletion(-) create mode 100644 docs/src/_static/nc_signalingprofiler.svg create mode 100644 networkcommons/methods/_signalingprofiler.py diff --git a/docs/src/_static/nc_signalingprofiler.svg b/docs/src/_static/nc_signalingprofiler.svg new file mode 100644 index 0000000..e98808b --- /dev/null +++ b/docs/src/_static/nc_signalingprofiler.svg @@ -0,0 +1,802 @@ + + + + +Naive network generation + CARNIVAL optimization + + + One-layer + + + + + + + + + + + + + + T + + + + + K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PP + + + + O + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Two-layers + + + + + + O + + + + K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PP + + + O + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Firstlayer + Secondlayer + Three-layers + + + + + + + K + + + + K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PP + + + + K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + O + + + + K + O + + + O + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thirdlayer + + + + + + + + + + diff --git a/docs/src/api.rst b/docs/src/api.rst index 9be4608..d93a00f 100644 --- a/docs/src/api.rst +++ b/docs/src/api.rst @@ -79,6 +79,21 @@ CORNETO methods.run_corneto_carnival + +.. _api-signalingprofiler: + +SignalingProfiler +================= +.. module::networkcommons.methods +.. currentmodule:: networkcommons + +.. autosummary:: + :toctree: api + :recursive: + + methods.mf_classifier + methods.run_signalingprofiler + .. _api-pk: Prior Knowledge diff --git a/docs/src/methods.rst b/docs/src/methods.rst index 9e8067c..2b55094 100644 --- a/docs/src/methods.rst +++ b/docs/src/methods.rst @@ -178,4 +178,33 @@ CORNETO (Constraint-based Optimization for the Reconstruction of NETworks from O **Edge weights:** w(e) ∈ {1, −1} -**Functions:** See API documentation for :ref:`CORNETO `. \ No newline at end of file +**Functions:** See API documentation for :ref:`CORNETO `. + +SignalingProfiler +------------------ + +SignalingProfiler (https://doi.org/10.1038/s41540-024-00417-6) Python implementation is a two-steps pipeline. +In the first step, SignalingProfiler generates the Naïve Network, a hierarchical and multi-layered network between source and target nodes using networkcommons "All paths". +Three different layouts can be chosen, defined as one-, two-, or three-layers networks, with an increasing level of deepness. + +Each layer is defined by a different set of molecular functions. +The molecular function for each protein is obtained by parsing the UNIPROT database GO Molecular Function annotation according to relative GO Ancestor terms. +This molecular function annotation refers to signal trasduction context: K, kinase; PP, phosphatases; T, transcription factor; O, all the other molecular functions. + +In the one-layer network, the perturbed node is connected to every target and is molecular function agnostic. +The two-layers network connects the perturbed node to kinases/phosphatases/others (first layer) and then connect the latters to transcription factors (second layer). +The three-layers network adds another layer between kinases/phosphatases and other signaling proteins. + +In the second step, SignalingProfiler calls "CORNETO - CARNIVAL" to retrieve only sign-consistent edges from the naïve network (removing grey dashed edges). + +.. raw:: html + + + +**Input:** Set of weighted target and source nodes, network graph + +**Node weights:** w(v) ∈ ℝ + +**Edge weights:** w(e) ∈ {1, −1} + +**Functions:** See API documentation for :ref:`SignalingProfiler `. \ No newline at end of file diff --git a/networkcommons/methods/_signalingprofiler.py b/networkcommons/methods/_signalingprofiler.py new file mode 100644 index 0000000..438acd6 --- /dev/null +++ b/networkcommons/methods/_signalingprofiler.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python + +# +# This file is part of the `networkcommons` Python module +# +# Copyright 2024 +# Heidelberg University Hospital +# +# File author(s): Perfetto Lab (livia.perfetto@gmail.com) +# +# Distributed under the GPLv3 license +# See the file `LICENSE` or read a copy at +# https://www.gnu.org/licenses/gpl-3.0.txt +# + +""" +SignalingProfiler: a multi-step pipeline integrating topological and causal inference methods to derive context-specific signaling networks +""" + +__all__ = [ + 'run_phosphoscore', + 'run_signalingprofiler', +] + +import networkcommons as nc +import pandas as pd +import networkx as nx +from typing import Union, List +from networkcommons._session import _log + + +def mf_classifier(proteins, with_exp=False, comp_list=None): + """ + Classify proteins in four broad molecular functions (MF) according to Gene Ontology: kinases (kin), phosphatases (phos), transcription factors (tf), and all other types (other). + + Args: + - proteins (dict): A dictionary of protein names and exp values to classify. + - with_exp (bool): If True, return a dictionary with exp value -1 or 1. If False, return a dictionary with sets. + - comp_list (list): A list of protein names to use to subset proteins + + Returns: + - dict: A dictionary where keys are MF categories and values are sets or dictionaries with experimental values. + """ + + if comp_list: + proteins = {k: v for k, v in proteins.items() if k in comp_list} + + # Read the GO molecular function (MF) data + GO_mf_df = pd.read_csv('https://filedn.eu/ld7S7VEWtgOf5uN0V7fbp84/gomf_annotation_networkcommons.tsv', sep='\t') + mf_dict = GO_mf_df.groupby('mf')['gene_name'].apply(set).to_dict() + + proteins_dict = { + mf: {gene: proteins[gene] if with_exp else '' for gene in genes if gene in proteins} + for mf, genes in mf_dict.items() + } + + # Identify unclassified proteins + classified_proteins = {gene for genes in proteins_dict.values() for gene in (genes if isinstance(genes, set) else genes.keys())} + unclassified_proteins = set(proteins) - classified_proteins + + if unclassified_proteins: + proteins_dict['other'] = ( + {protein: proteins[protein] for protein in unclassified_proteins} if with_exp else '' + ) + + return proteins_dict + +def validate_inputs(sources, measurements, graph, layers, max_length): + if not isinstance(graph, nx.Graph): + raise TypeError("The 'graph' parameter must be an instance of networkx.Graph.") + + if not isinstance(sources, dict) or not sources: + raise ValueError("The 'sources' parameter must be a non-empty list or dictionary.") + + if not isinstance(measurements, dict) or not measurements: + raise ValueError("The 'measurements' parameter must be a non-empty list or dictionary.") + + if layers not in ['one', 'two', 'three']: + raise ValueError("The 'layers' parameter must be 'one', 'two', or 'three'.") + + if layers == 'one' and not isinstance(max_length, int): + raise TypeError("For 'one' layer, 'max_length' must be an integer.") + + if layers in ['two', 'three'] and (not isinstance(max_length, list) or len(max_length) != 2): + raise ValueError("For 'two' or 'three' layers, 'max_length' must be a list of two integers.") + + if isinstance(max_length, list) and any(not isinstance(x, int) or x <= 0 for x in max_length): + raise ValueError("'max_length' values must be positive integers.") + + if isinstance(max_length, int) and max_length <= 0: + raise ValueError("'max_length' must be a positive integer.") + + +def generate_naive_network(source_dict, measurements, graph, layers, max_length: Union[int, List[int]]): + + """ + Generates a hierarchical (multi)layered network from source nodes defining layers by distinguishing measured nodes by molecular function. + + Args: + - source_dict (dict): A dictionary containing the sources and sign of perturbation. + - measurements (dict): A dictionary containing the targets and sign of measurements. + - graph (nx.Graph): The network. + - layers (str): specifies the number of layers to generate ('one', 'two', or 'three'). + - max_length (int or list of int): The depth cutoff for finding paths. If `layers` is 'one', this should be an int. For 'two' or 'three', it should be a list of ints. + + Returns: + - naive_network (nx.Graph): The constructed multi-layered network. + """ + + _log('SignalingProfiler naive network building via all paths algorithm...') + + validate_inputs(source_dict, measurements, graph, layers, max_length) + + # Define targets with molecular function classification + targets = mf_classifier(measurements, with_exp=True) + + if layers == 'one': + # Generate one-layered network + naive_network, _ = nc.methods.run_all_paths(graph, source_dict, targets, depth_cutoff=max_length) + + + elif layers == 'two': + # Generate the first layer + all_paths_network1, _ = nc.methods.run_all_paths( + graph, + source_dict, + {**targets.get('kin'), **targets.get('phos'), **targets.get('other')}, + depth_cutoff=max_length[0] + ) + + # Prepare new sources for the second layer + gene_nodes = list(all_paths_network1.nodes()) + sources_new = mf_classifier(measurements, with_exp=True, comp_list=gene_nodes) + + # Generate the second layer + all_paths_network2, _ = nc.methods.run_all_paths( + graph, + {**sources_new.get('kin'), **sources_new.get('phos'), **sources_new.get('other')}, + targets.get('tf'), + depth_cutoff=max_length[1] + ) + + # Combine the first and second layer + naive_network = nx.compose(all_paths_network1, all_paths_network2) + + elif layers == 'three': + # Generate the first layer + all_paths_network1, _ = nc.methods.run_all_paths( + graph, + source_dict, + {**targets.get('kin'), **targets.get('phos')}, + depth_cutoff=max_length[0] + ) + + # Prepare new sources for the second layer + gene_nodes = list(all_paths_network1.nodes()) + sources_new = mf_classifier(measurements, with_exp=True,comp_list=gene_nodes) + + # Generate the second layer + all_paths_network2, _ = nc.methods.run_all_paths( + graph, + {**sources_new.get('kin'), **sources_new.get('phos')}, + {**targets.get('kin'), **targets.get('phos'), **targets.get('other')}, + depth_cutoff=1 + ) + + # Combine the first and second layers + combined_network = nx.compose(all_paths_network1, all_paths_network2) + + # Prepare new sources for the thirs layer + gene_nodes2 = list(combined_network.nodes()) + sources_new2 = mf_classifier(measurements, with_exp=True, comp_list=gene_nodes2) + + ### Third layer + all_paths_network3, _ = nc.methods.run_all_paths( + graph, + {**sources_new2.get('kin'), **sources_new2.get('phos'), **sources_new2.get('other')}, + targets.get('tf'), + depth_cutoff=max_length[1] + ) + + naive_network = nx.compose(combined_network, all_paths_network3) + + else: + raise ValueError("Invalid value for 'layers'. Choose 'one', 'two', or 'three'.") + + return(naive_network) + +def run_signalingprofiler(source_dict, measurements, graph, layers, max_length, betaWeight=0.2, solver=None, verbose=False): + """ + Generates a hierarchical (multi)layered network from source nodes defining layers by distinguishing measured nodes by molecular function and run the Vanilla Carnival algorithm via CORNETO to retrieve sign-coherent edges with nodes measured activity. + Args: + - source_dict (dict): A dictionary containing the sources and sign of perturbation. + - measurements (dict): A dictionary containing the targets and sign of measurements. + - graph (nx.Graph): The network. + - layers (str): specifies the number of layers to generate ('one', 'two', or 'three'). + - max_length (int or list of int): The depth cutoff for finding paths. If `layers` is 'one', this should be an int. For 'two' or 'three', it should be a list of ints. + + Returns: + - naive_network (nx.Graph): The constructed multi-layered network. + """ + # Generate naive_network + naive_network = generate_naive_network( + source_dict=source_dict, + measurements=measurements, + graph=graph, + layers=layers, + max_length=max_length + ) + + # Optimize network using CORNETO + opt_net = nc.methods.run_corneto_carnival( + naive_network, + source_dict, + measurements, + betaWeight=betaWeight, + solver=solver, + verbose=verbose + ) + + return(opt_net) \ No newline at end of file From b3030a9dab3fe93ea42e7bda3c77bb087f27368e Mon Sep 17 00:00:00 2001 From: veronicavenafra Date: Tue, 19 Nov 2024 16:37:39 +0100 Subject: [PATCH 2/5] signalingprofiler implementation --- networkcommons/methods/_signalingprofiler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/networkcommons/methods/_signalingprofiler.py b/networkcommons/methods/_signalingprofiler.py index 438acd6..dbc8ada 100644 --- a/networkcommons/methods/_signalingprofiler.py +++ b/networkcommons/methods/_signalingprofiler.py @@ -18,7 +18,6 @@ """ __all__ = [ - 'run_phosphoscore', 'run_signalingprofiler', ] From b8d8ae8067b2142cdfa843d00e42a6039a7c1a80 Mon Sep 17 00:00:00 2001 From: deeenes Date: Thu, 21 Nov 2024 23:17:31 +0100 Subject: [PATCH 3/5] `signalingprofiler`: first refactoring --- networkcommons/methods/_signalingprofiler.py | 352 ++++++++++++------- 1 file changed, 223 insertions(+), 129 deletions(-) diff --git a/networkcommons/methods/_signalingprofiler.py b/networkcommons/methods/_signalingprofiler.py index dbc8ada..4d42669 100644 --- a/networkcommons/methods/_signalingprofiler.py +++ b/networkcommons/methods/_signalingprofiler.py @@ -14,207 +14,301 @@ # """ -SignalingProfiler: a multi-step pipeline integrating topological and causal inference methods to derive context-specific signaling networks +SignalingProfiler: a multi-step pipeline integrating topological and causal +inference methods to derive context-specific signaling networks """ +from __future__ import annotations + __all__ = [ 'run_signalingprofiler', ] -import networkcommons as nc +from typing import Literal +from collections.abc import Collection +from collections import ChainMap +import functools as ft + import pandas as pd import networkx as nx -from typing import Union, List +from pypath_common import _misc + from networkcommons._session import _log +from networkcommons.methods import _causal, _graph +from networkcommons.data.omics import _common as _downloader -def mf_classifier(proteins, with_exp=False, comp_list=None): +def _mf_classifier( + proteins: dict[str, float], + with_exp: bool = False, + only_proteins: Collection | None = None, + ): """ - Classify proteins in four broad molecular functions (MF) according to Gene Ontology: kinases (kin), phosphatases (phos), transcription factors (tf), and all other types (other). + Classify proteins in four broad molecular functions (MF) according to Gene + Ontology: kinases (kin), phosphatases (phos), transcription factors (tf), + and all other types (other). Args: - - proteins (dict): A dictionary of protein names and exp values to classify. - - with_exp (bool): If True, return a dictionary with exp value -1 or 1. If False, return a dictionary with sets. - - comp_list (list): A list of protein names to use to subset proteins + proteins: A dictionary of protein names and exp values to classify. + with_exp: If True, return a dictionary with exp value -1 or 1. If + False, return a dictionary with sets. + only_proteins: A list of protein + names to use to subset proteins Returns: - - dict: A dictionary where keys are MF categories and values are sets or dictionaries with experimental values. + A dictionary where keys are MF categories and values are sets or + dictionaries with experimental values. """ - if comp_list: - proteins = {k: v for k, v in proteins.items() if k in comp_list} + if only_proteins: + + proteins = {k: v for k, v in proteins.items() if k in only_proteins} # Read the GO molecular function (MF) data - GO_mf_df = pd.read_csv('https://filedn.eu/ld7S7VEWtgOf5uN0V7fbp84/gomf_annotation_networkcommons.tsv', sep='\t') + GO_mf_df = _downloader._open( + ( + 'https://filedn.eu/ld7S7VEWtgOf5uN0V7fbp84/' + 'gomf_annotation_networkcommons.tsv' + ), + ftype = 'tsv', + ) + mf_dict = GO_mf_df.groupby('mf')['gene_name'].apply(set).to_dict() proteins_dict = { - mf: {gene: proteins[gene] if with_exp else '' for gene in genes if gene in proteins} + mf: { + gene: proteins[gene] if with_exp else '' + for gene in genes + if gene in proteins + } for mf, genes in mf_dict.items() } # Identify unclassified proteins - classified_proteins = {gene for genes in proteins_dict.values() for gene in (genes if isinstance(genes, set) else genes.keys())} + classified_proteins = { + gene + for genes in proteins_dict.values() + for gene in set(genes) + } unclassified_proteins = set(proteins) - classified_proteins if unclassified_proteins: + proteins_dict['other'] = ( - {protein: proteins[protein] for protein in unclassified_proteins} if with_exp else '' + { + protein: proteins[protein] + for protein in unclassified_proteins + } + if with_exp else {} ) return proteins_dict -def validate_inputs(sources, measurements, graph, layers, max_length): + +def _validate_inputs( + sources: dict, + measurements: dict, + graph: nx.Graph, + layers: int, + max_length: int | list[int], + ) -> None: + + err = [] + if not isinstance(graph, nx.Graph): - raise TypeError("The 'graph' parameter must be an instance of networkx.Graph.") - + err.append( + "The 'graph' parameter must be an instance of networkx.Graph." + ) + if not isinstance(sources, dict) or not sources: - raise ValueError("The 'sources' parameter must be a non-empty list or dictionary.") - + err.append( + "The 'sources' parameter must be a non-empty list or dictionary." + ) + if not isinstance(measurements, dict) or not measurements: - raise ValueError("The 'measurements' parameter must be a non-empty list or dictionary.") - - if layers not in ['one', 'two', 'three']: - raise ValueError("The 'layers' parameter must be 'one', 'two', or 'three'.") - - if layers == 'one' and not isinstance(max_length, int): - raise TypeError("For 'one' layer, 'max_length' must be an integer.") - - if layers in ['two', 'three'] and (not isinstance(max_length, list) or len(max_length) != 2): - raise ValueError("For 'two' or 'three' layers, 'max_length' must be a list of two integers.") - - if isinstance(max_length, list) and any(not isinstance(x, int) or x <= 0 for x in max_length): - raise ValueError("'max_length' values must be positive integers.") - + err.append( + "The 'measurements' parameter must be " + "a non-empty list or dictionary." + ) + + if not (isinstance(layers, int) and 0 < layers < 4): + err.append("The 'layers' parameter must be 1, 2, or 3.") + + if layers == 1 and not isinstance(max_length, int): + err.append("For 1 layers, 'max_length' must be an integer.") + + if ( + layers in [2, 3] and ( + not isinstance(max_length, list) or + len(max_length) != 2 + ) + ): + err.append( + "For 2 or 3 layers, 'max_length' " + "must be a list of two integers." + ) + + if ( + isinstance(max_length, list) and + any( + not isinstance(x, int) or + x <= 0 for x in max_length + ) + ): + err.append("'max_length' values must be positive integers.") + if isinstance(max_length, int) and max_length <= 0: - raise ValueError("'max_length' must be a positive integer.") + err.append("'max_length' must be a positive integer.") + + if err: + + msg = 'Problem(s) with SignalingProfiler inputs: ' + + for e in err: + + _log(f'{msg}{e}') + raise ValueError(f'{msg}{"; ".join(err)}') -def generate_naive_network(source_dict, measurements, graph, layers, max_length: Union[int, List[int]]): + +def _generate_naive_network( + sources: dict, + measurements: dict, + graph: nx.Graph, + layers: int, + max_length: int | list[int], + ) -> nx.Graph: """ - Generates a hierarchical (multi)layered network from source nodes defining layers by distinguishing measured nodes by molecular function. + Generates a hierarchical (multi)layersed network from source nodes defining + layers by distinguishing measured nodes by molecular function. Args: - - source_dict (dict): A dictionary containing the sources and sign of perturbation. - - measurements (dict): A dictionary containing the targets and sign of measurements. - - graph (nx.Graph): The network. - - layers (str): specifies the number of layers to generate ('one', 'two', or 'three'). - - max_length (int or list of int): The depth cutoff for finding paths. If `layers` is 'one', this should be an int. For 'two' or 'three', it should be a list of ints. + sources: A dictionary containing the sources and sign of + perturbation. + measurements: A dictionary containing the targets and sign of + measurements. + graph: The network. + layers: specifies the number of layers to generate. + Must be > 0 and < 4. + max_length: The depth cutoff for finding paths. + If `layers` is 1, this should be an int. For 2 or 3, + it should be a list of ints. Returns: - - naive_network (nx.Graph): The constructed multi-layered network. + The constructed multi-layersed network. """ _log('SignalingProfiler naive network building via all paths algorithm...') - validate_inputs(source_dict, measurements, graph, layers, max_length) + _validate_inputs(sources, measurements, graph, layers, max_length) + + + def _by_func( + classes: dict, + funcs: Collection[Literal['kin', 'phos', 'other', 'tf']] | None = None, + ) -> dict: + + return ( + classes + if funcs is None else + ChainMap(*(classes.get(func, {}) for func in _misc.to_set(funcs))) + ) + # Define targets with molecular function classification - targets = mf_classifier(measurements, with_exp=True) + targets = _mf_classifier(measurements, with_exp=True) + max_length = _misc.to_list(max_length) + + stages = ( + None, + ('kin', 'phos'), + ('kin', 'phos', 'other'), + None if layers == 0 else 'tf', + ) + stages = (stages[0],) + stages[-layers:] + networks = [] - if layers == 'one': - # Generate one-layered network - naive_network, _ = nc.methods.run_all_paths(graph, source_dict, targets, depth_cutoff=max_length) + for i, (src_funcs, tgt_funcs) in enumerate(zip(stages[:-1], stages[1:])): + _log(f'SignalingProfiler naive network: stage {i + 1}') - elif layers == 'two': - # Generate the first layer - all_paths_network1, _ = nc.methods.run_all_paths( - graph, - source_dict, - {**targets.get('kin'), **targets.get('phos'), **targets.get('other')}, - depth_cutoff=max_length[0] + networks.append( + _graph.run_all_paths( + graph, + _by_func(sources, src_funcs), + _by_func(targets, tgt_funcs), + depth_cutoff = max_length[i], + ) ) - # Prepare new sources for the second layer - gene_nodes = list(all_paths_network1.nodes()) - sources_new = mf_classifier(measurements, with_exp=True, comp_list=gene_nodes) + if i == layers - 1: - # Generate the second layer - all_paths_network2, _ = nc.methods.run_all_paths( - graph, - {**sources_new.get('kin'), **sources_new.get('phos'), **sources_new.get('other')}, - targets.get('tf'), - depth_cutoff=max_length[1] - ) + break - # Combine the first and second layer - naive_network = nx.compose(all_paths_network1, all_paths_network2) - - elif layers == 'three': - # Generate the first layer - all_paths_network1, _ = nc.methods.run_all_paths( - graph, - source_dict, - {**targets.get('kin'), **targets.get('phos')}, - depth_cutoff=max_length[0] + sources = _mf_classifier( + measurements, + with_exp = True, + only_proteins = networks[-1].nodes(), ) - # Prepare new sources for the second layer - gene_nodes = list(all_paths_network1.nodes()) - sources_new = mf_classifier(measurements, with_exp=True,comp_list=gene_nodes) + naive_network = ft.reduce(nx.compose, networks) - # Generate the second layer - all_paths_network2, _ = nc.methods.run_all_paths( - graph, - {**sources_new.get('kin'), **sources_new.get('phos')}, - {**targets.get('kin'), **targets.get('phos'), **targets.get('other')}, - depth_cutoff=1 - ) - - # Combine the first and second layers - combined_network = nx.compose(all_paths_network1, all_paths_network2) - - # Prepare new sources for the thirs layer - gene_nodes2 = list(combined_network.nodes()) - sources_new2 = mf_classifier(measurements, with_exp=True, comp_list=gene_nodes2) - - ### Third layer - all_paths_network3, _ = nc.methods.run_all_paths( - graph, - {**sources_new2.get('kin'), **sources_new2.get('phos'), **sources_new2.get('other')}, - targets.get('tf'), - depth_cutoff=max_length[1] - ) + _log('SignalingProfiler naive network building ready.') - naive_network = nx.compose(combined_network, all_paths_network3) - - else: - raise ValueError("Invalid value for 'layers'. Choose 'one', 'two', or 'three'.") - - return(naive_network) + return naive_network -def run_signalingprofiler(source_dict, measurements, graph, layers, max_length, betaWeight=0.2, solver=None, verbose=False): + +def run_signalingprofiler( + sources: dict, + measurements: dict, + graph: nx.Graph, + layers: int, + max_length: int | list[int], + betaWeight: float = 0.2, + solver: str | None = None, + verbose: bool = False, + ) -> nx.Graph: """ - Generates a hierarchical (multi)layered network from source nodes defining layers by distinguishing measured nodes by molecular function and run the Vanilla Carnival algorithm via CORNETO to retrieve sign-coherent edges with nodes measured activity. + Contextualize networks by the SignalingProfiler algorithm. + + Generates a hierarchical (multi)layersed network from source nodes defining + layers by distinguishing measured nodes by molecular function and run the + Vanilla Carnival algorithm via CORNETO to retrieve sign-coherent edges with + nodes measured activity. + Args: - - source_dict (dict): A dictionary containing the sources and sign of perturbation. - - measurements (dict): A dictionary containing the targets and sign of measurements. - - graph (nx.Graph): The network. - - layers (str): specifies the number of layers to generate ('one', 'two', or 'three'). - - max_length (int or list of int): The depth cutoff for finding paths. If `layers` is 'one', this should be an int. For 'two' or 'three', it should be a list of ints. + sources: A dictionary containing the sources and sign of perturbation. + measurements: A dictionary containing the targets and sign of + measurements. + graph: The network. + layers: specifies the number of layers to generate. + Must be > 0 and < 4. + max_length: The depth cutoff for finding paths. If `layers` is 1, + this should be an int. For 2 or 3, it should be a list of + ints. Returns: - - naive_network (nx.Graph): The constructed multi-layered network. + The constructed multi-layersed network. """ + # Generate naive_network - naive_network = generate_naive_network( - source_dict=source_dict, - measurements=measurements, - graph=graph, - layers=layers, - max_length=max_length + naive_network = _generate_naive_network( + sources = sources, + measurements = measurements, + graph = graph, + layers = layers, + max_length = max_length ) # Optimize network using CORNETO - opt_net = nc.methods.run_corneto_carnival( - naive_network, - source_dict, - measurements, - betaWeight=betaWeight, - solver=solver, - verbose=verbose + opt_net = _causal.run_corneto_carnival( + naive_network, + sources, + measurements, + betaWeight = betaWeight, + solver = solver, + verbose = verbose ) - - return(opt_net) \ No newline at end of file + + return opt_net From f253263e89cd5164e1240f13df8a4c61e49093ab Mon Sep 17 00:00:00 2001 From: deeenes Date: Thu, 21 Nov 2024 23:38:09 +0100 Subject: [PATCH 4/5] `signalingprofiler`: exports --- networkcommons/methods/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/networkcommons/methods/__init__.py b/networkcommons/methods/__init__.py index 4497219..a76615a 100644 --- a/networkcommons/methods/__init__.py +++ b/networkcommons/methods/__init__.py @@ -20,3 +20,4 @@ from ._graph import * from ._causal import * from ._moon import * +from ._signalingprofiler import * From e4708d519826621fc3cf459e4fd5c08438795807 Mon Sep 17 00:00:00 2001 From: deeenes Date: Thu, 21 Nov 2024 23:45:26 +0100 Subject: [PATCH 5/5] `signalingprofiler`: docs: `mf_classifier` is not exported --- docs/src/api.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/api.rst b/docs/src/api.rst index d93a00f..c4637ff 100644 --- a/docs/src/api.rst +++ b/docs/src/api.rst @@ -91,7 +91,6 @@ SignalingProfiler :toctree: api :recursive: - methods.mf_classifier methods.run_signalingprofiler .. _api-pk: @@ -236,7 +235,7 @@ Evaluation and description eval.get_phosphorylation_status eval.perform_random_controls eval.shuffle_dict_keys - + .. _api-vis: Visualization @@ -282,7 +281,7 @@ Utilities :toctree: api :recursive: - + utils.to_cornetograph utils.to_networkx utils.read_network_from_file