Skip to content

Network

The Network class is the central object in NeKo. It holds a directed graph of biological nodes (genes, proteins, complexes) and edges (interactions), and exposes methods for expanding, connecting, querying, and exporting those graphs.

Import

from neko.core.network import Network

Quick example

from neko.core.network import Network
from neko.inputs import Universe

resources = Universe("omnipath")

net = Network(["EGFR", "KRAS", "MYC"], resources=resources.interactions)
net.connect_nodes()
print(net.nodes)
print(net.edges)

Complete a seed network

complete_connection attempts both directed orientations for every original seed pair. Path selection and reuse are explicit:

net.complete_connection(
    maxlen=2,
    path_policy="all_shortest",
    reuse_policy="induced_subgraph",
    only_signed=True,
    consensus=False,
)

Path policies are one_shortest, all_shortest, and all_bounded. Reuse policies are none, discovered_paths, and induced_subgraph. A finite positive maxlen is mandatory. The legacy algorithm, minimal, and connect_with_bias parameters remain temporarily available and emit a migration warning with the equivalent explicit call.

If neither old nor new selectors are supplied, the transition release keeps the former effective default: all_bounded + discovered_paths. An explicitly disabled minimal flag maps to none unless bias is enabled; either biased legacy combination maps to induced_subgraph.

See Choosing a connection strategy for the full policy matrix, fictitious topology diagrams, and the biological implications of all public connection strategies.

Connect to a GO term

net.connect_genes_to_phenotype(
    id_accession="GO:0062043",
    only_signed=True,
    compress=True,
    maxlen=1,
)

The accession is sufficient: NeKo obtains the canonical term label from GO. Exact-term human annotations are used by default. Use include_descendants=True to include genes annotated to more specific GO terms, or change taxon_id for another organism.

With compress=True, connected GO-associated genes are replaced by one node named from the canonical GO term label. If collapsing those genes produces both an activating and an inhibiting interaction between the same two nodes, NeKo retains the conflicting evidence as one bimodal interaction. References and interaction types from the contributing edges are preserved.

Parallel regulatory edges elsewhere in a network follow the same rule: an A stimulation B edge together with an A inhibition B edge is represented as A bimodal B. Complex formation remains separate because it does not encode a regulatory sign.


Class reference

Network

Network(initial_nodes: list[str] = None, sif_file=None, resources=None)

A molecular interaction network.

The `Network` object is the central organizing component of the `neko`
module. It is the subject of all operations implemented here, including
topological algorithms, graph analysis, network visualization and
integration of database knowledge.

Args: initial_nodes: A list of initial nodes to be added to the network. sif_file: A SIF (Simple Interaction Format) file to load the network from. resources: A pandas DataFrame containing the resources database.

Methods:

Source code in neko/core/network.py
def __init__(
        self,
        initial_nodes: list[str] = None,
        sif_file=None,
        resources=None,
    ):

    self._init_args = locals()
    del self._init_args['self']
    self.nodes = pd.DataFrame(columns=["Genesymbol", "Uniprot", "Type"])
    self.edges = pd.DataFrame(columns=["source", "target", "Type", "Effect", "References"])
    # Internal object-based storage
    self._node_objs = set()  # Set of Node objects
    self._edge_objs = set()  # Set of Edge objects
    self.initial_nodes = initial_nodes
    self._ontology = Ontology()
    # --- NetworkState history tracking ---
    self._states: dict[int, NetworkState] = {}
    self._state_metadata: dict[int, dict] = {}
    self._state_log: list[int] = []
    self._state_counter: int = 0
    self._current_state_id: Optional[int] = None
    self._root_state_id: Optional[int] = None
    self._auto_state_depth: int = 0
    self._history_enabled: bool = True
    self._max_history: Optional[int] = None
    self._is_initializing = True
    self._populate()
    self._is_initializing = False

Methods:

add_node
add_node(node: str, from_sif: bool = False) -> bool

Adds a node to the network. The node is added to the nodes DataFrame of the network. The function checks the syntax for the genesymbol to ensure it is correct. If the node is a complex, it is added with the 'Genesymbol' as the complex string and 'Uniprot' as the node. Otherwise, it is added with the 'Genesymbol' as the genesymbol and 'Uniprot' as the uniprot. The 'Type' is set as 'NaN' for all new nodes.

Args: - node: A string representing the node to be added. The node can be represented by either its Genesymbol or Uniprot identifier.

Returns: - None.

Source code in neko/core/network.py
@_record_state_operation
def add_node(self, node: str, from_sif: bool = False) -> bool:
    """
    Adds a node to the network. The node is added to the nodes DataFrame of the network. The function checks the
    syntax for the genesymbol to ensure it is correct. If the node is a complex, it is added with the
    'Genesymbol' as the complex string and 'Uniprot' as the node. Otherwise, it is added with the 'Genesymbol' as
    the genesymbol and 'Uniprot' as the uniprot. The 'Type' is set as 'NaN' for all new nodes.

    Args:
        - node: A string representing the node to be added. The node can be represented by either its
                Genesymbol or Uniprot identifier.

    Returns:
        - None.
    """

    if from_sif:
        # check that the new entry node can be translated using the function mapping node identifier (all the
        # output of the function should be None) if it cannot be translated, print an error message but add the
        # node to the network anyway

        complex_string, genesymbol, uniprot = mapping_node_identifier(node)
        if not complex_string and not genesymbol and not uniprot:
            print("Error: node %s could not be automatically translated" % node)
            new_entry = {"Genesymbol": node, "Uniprot": node, "Type": "NaN"}
            self.nodes.loc[len(self.nodes)] = new_entry
            self.nodes = self.nodes.drop_duplicates().reset_index(drop=True)
            self._add_node_obj(node, node, "NaN")
            return True
        new_entry = {"Genesymbol": genesymbol, "Uniprot": uniprot, "Type": "NaN"}
        self.nodes.loc[len(self.nodes)] = new_entry
        self.nodes = self.nodes.drop_duplicates().reset_index(drop=True)
        self._add_node_obj(genesymbol, uniprot, "NaN")
        self.initial_nodes.append(new_entry["Genesymbol"])
        self.initial_nodes = list(set(self.initial_nodes))
        return True
    complex_string, genesymbol, uniprot = mapping_node_identifier(node)

    # The identifier stored in ``Uniprot`` is also the identifier used by
    # resource edges. Prefer the caller's exact identifier when the
    # resource uses it (e.g. PhosphoSitePlus gene symbols and sites), and
    # otherwise use its translated UniProt or display identifier.
    resource_identifier = next(
        (
            identifier
            for identifier in (node, uniprot, genesymbol)
            if identifier is not None and self.check_node(identifier)
        ),
        None,
    )

    if resource_identifier is None:
        print("Error: node %s is not present in the resources database" % node)
        return False

    new_entry = {
        "Genesymbol": complex_string or genesymbol or node,
        "Uniprot": resource_identifier,
        "Type": "NaN",
    }
    self.nodes.loc[len(self.nodes)] = new_entry
    self.nodes = self.nodes.drop_duplicates().reset_index(drop=True)
    self._add_node_obj(new_entry["Genesymbol"], new_entry["Uniprot"], new_entry["Type"])
    return True
add_edge
add_edge(edge: DataFrame) -> None

This method adds an interaction to the list of interactions while converting it to the NeKo-network format. It checks if the edge represents inhibition or stimulation and sets the effect accordingly. It also checks if the nodes involved in the interaction are already present in the network, if not, it adds them.

Args: - edge: A pandas DataFrame representing the interaction. The DataFrame should contain columns for 'source', 'target', 'type', and 'references'. The 'source' and 'target' columns represent the nodes involved in the interaction. The 'type' column represents the type of interaction. The 'references' column contains the references for the interaction.

Returns: - None

Source code in neko/core/network.py
@_record_state_operation
def add_edge(self, edge: pd.DataFrame) -> None:
    """
    This method adds an interaction to the list of interactions while converting it to the NeKo-network format.
    It checks if the edge represents inhibition or stimulation and sets the effect accordingly. It also checks if the
    nodes involved in the interaction are already present in the network, if not, it adds them.

    Args:
        - edge: A pandas DataFrame representing the interaction. The DataFrame should contain columns for
        'source', 'target', 'type', and 'references'. The 'source' and 'target' columns represent the nodes involved
        in the interaction. The 'type' column represents the type of interaction. The 'references' column contains
        the references for the interaction.

    Returns:
        - None
    """

    # Check if the edge represents inhibition or stimulation and set the effect accordingly
    effect = check_sign(edge)
    references = edge["references"].values[0] if "references" in edge.columns else None
    edge_type = edge["type"].values[0] if "type" in edge.columns else None
    df_edge = pd.DataFrame({
        "source": edge["source"],
        "target": edge["target"],
        "Type": edge_type,
        "Effect": effect,
        "References": references
    })

    # Convert the "Uniprot" column to a set for efficient membership test
    uniprot_nodes = set(self.nodes["Uniprot"].unique())

    # add the new nodes to the nodes dataframe
    if edge["source"].values[0] not in uniprot_nodes:
        self.add_node(edge["source"].values[0])
    if edge["target"].values[0] not in uniprot_nodes:
        self.add_node(edge["target"].values[0])

    self.edges = pd.concat([self.edges, df_edge], ignore_index=True)
    self.edges = consolidate_edges(self.edges)
    self.sync_edges_from_df()
    return
remove_node
remove_node(node: str) -> None

Removes a node from the network. The node is removed from both the list of nodes and the list of edges.

Args: - node: A string representing the node to be removed. The node can be represented by either its Genesymbol or Uniprot identifier.

Returns: - None

Source code in neko/core/network.py
@_record_state_operation
def remove_node(self, node: str) -> None:
    """
    Removes a node from the network. The node is removed from both the list of nodes and the list of edges.

    Args:
        - node: A string representing the node to be removed. The node can be represented by either its
                Genesymbol or Uniprot identifier.

    Returns:
        - None
    """
    if node is None or (not isinstance(node, str) and pd.isna(node)):
        return

    matching_nodes = self.nodes[
        (self.nodes["Genesymbol"] == node)
        | (self.nodes["Uniprot"] == node)
    ]
    identifiers = {node}
    identifiers.update(
        value
        for value in matching_nodes[["Genesymbol", "Uniprot"]].stack()
        if pd.notna(value)
    )

    if matching_nodes.empty:
        translated = mapping_node_identifier(node)
        identifiers.update(value for value in translated if value is not None)

    self.nodes = self.nodes[
        ~self.nodes[["Genesymbol", "Uniprot"]]
        .isin(identifiers)
        .any(axis=1)
    ]
    self.edges = self.edges[
        ~self.edges[["source", "target"]]
        .isin(identifiers)
        .any(axis=1)
    ]

    return
connect_nodes
connect_nodes(only_signed: bool = False, consensus_only: bool = False) -> None

Delegates to strategies.connect_nodes.

Source code in neko/core/network.py
@_record_state_operation
def connect_nodes(self, only_signed: bool = False, consensus_only: bool = False) -> None:
    """
    Delegates to strategies.connect_nodes.
    """
    from .strategies import connect_nodes
    return connect_nodes(self, only_signed=only_signed, consensus_only=consensus_only)
connect_subgroup
connect_subgroup(group, maxlen: int = 1, only_signed: bool = False, consensus: bool = False) -> None

Delegates to strategies.connect_subgroup.

Source code in neko/core/network.py
@_record_state_operation
def connect_subgroup(self, group, maxlen: int = 1, only_signed: bool = False, consensus: bool = False) -> None:
    """
    Delegates to strategies.connect_subgroup.
    """
    from .strategies import connect_subgroup
    return connect_subgroup(self, group, maxlen=maxlen, only_signed=only_signed, consensus=consensus)
connect_component
connect_component(comp_A, comp_B, maxlen: int = 2, mode: Literal['OUT', 'IN', 'ALL'] = 'OUT', only_signed: bool = False, consensus: bool = False) -> None

Delegates to strategies.connect_component.

Source code in neko/core/network.py
@_record_state_operation
def connect_component(self, comp_A, comp_B, maxlen: int = 2, mode: Literal['OUT', 'IN', 'ALL'] = 'OUT', only_signed: bool = False, consensus: bool = False) -> None:
    """
    Delegates to strategies.connect_component.
    """
    from .strategies import connect_component
    return connect_component(self, comp_A, comp_B, maxlen=maxlen, mode=mode, only_signed=only_signed, consensus=consensus)
connect_to_upstream_nodes
connect_to_upstream_nodes(nodes_to_connect=None, depth: int = 1, rank: int = 1, only_signed: bool = True, consensus: bool = False) -> None

Delegates to strategies.connect_to_upstream_nodes.

Source code in neko/core/network.py
@_record_state_operation
def connect_to_upstream_nodes(self, nodes_to_connect=None, depth: int = 1, rank: int = 1, only_signed: bool = True, consensus: bool = False) -> None:
    """
    Delegates to strategies.connect_to_upstream_nodes.
    """
    from .strategies import connect_to_upstream_nodes
    return connect_to_upstream_nodes(self, nodes_to_connect=nodes_to_connect, depth=depth, rank=rank, only_signed=only_signed, consensus=consensus)
connect_genes_to_phenotype
connect_genes_to_phenotype(phenotype: str = None, id_accession: str = None, sub_genes: list = None, maxlen: int = 2, only_signed: bool = False, compress: bool = False, taxon_id=9606, include_descendants: bool = False, exclude_automatic_assertions: bool = False) -> None

Connect this network to genes associated with a GO term.

The GO accession is authoritative. Exact human annotations are used by default; include_descendants enables annotations propagated from more specific terms. When compress is true, connected GO genes are replaced by a node carrying the canonical GO term label.

Args: phenotype: Optional backward-compatible phenotype alias. id_accession: GO accession such as GO:0062043. sub_genes: Optional subset of network genes to connect from. maxlen: Maximum path length in the interaction resource. only_signed: Restrict paths to signed interactions. compress: Collapse connected GO genes into one phenotype node. taxon_id: NCBI taxonomy ID or NCBITaxon: CURIE. include_descendants: Include associations to descendant GO terms. exclude_automatic_assertions: Exclude ECO:0000501 records.

Source code in neko/core/network.py
@_record_state_operation
def connect_genes_to_phenotype(
        self,
        phenotype: str = None,
        id_accession: str = None,
        sub_genes: list = None,
        maxlen: int = 2,
        only_signed: bool = False,
        compress: bool = False,
        taxon_id=9606,
        include_descendants: bool = False,
        exclude_automatic_assertions: bool = False,
    ) -> None:
    """
    Connect this network to genes associated with a GO term.

    The GO accession is authoritative. Exact human annotations are used
    by default; ``include_descendants`` enables annotations propagated
    from more specific terms. When ``compress`` is true, connected GO
    genes are replaced by a node carrying the canonical GO term label.

    Args:
        phenotype: Optional backward-compatible phenotype alias.
        id_accession: GO accession such as ``GO:0062043``.
        sub_genes: Optional subset of network genes to connect from.
        maxlen: Maximum path length in the interaction resource.
        only_signed: Restrict paths to signed interactions.
        compress: Collapse connected GO genes into one phenotype node.
        taxon_id: NCBI taxonomy ID or ``NCBITaxon:`` CURIE.
        include_descendants: Include associations to descendant GO terms.
        exclude_automatic_assertions: Exclude ``ECO:0000501`` records.
    """
    from .strategies import connect_genes_to_phenotype
    return connect_genes_to_phenotype(
        self,
        phenotype=phenotype,
        id_accession=id_accession,
        sub_genes=sub_genes,
        maxlen=maxlen,
        only_signed=only_signed,
        compress=compress,
        taxon_id=taxon_id,
        include_descendants=include_descendants,
        exclude_automatic_assertions=exclude_automatic_assertions,
    )
complete_connection
complete_connection(maxlen: Optional[int] = 2, algorithm=UNSET, minimal=UNSET, only_signed: bool = False, consensus: bool = False, connect_with_bias=UNSET, *, path_policy: Optional[PathPolicy] = None, reuse_policy: Optional[ReusePolicy] = None) -> None

Delegates to strategies.complete_connection.

Source code in neko/core/network.py
@_record_state_operation
def complete_connection(self,
                    maxlen: Optional[int] = 2,
                    algorithm=UNSET,
                    minimal=UNSET,
                    only_signed: bool = False,
                    consensus: bool = False,
                    connect_with_bias=UNSET,
                    *,
                    path_policy: Optional[PathPolicy] = None,
                    reuse_policy: Optional[ReusePolicy] = None,
                        ) -> None:
    """
    Delegates to strategies.complete_connection.
    """
    from .strategies import complete_connection
    return complete_connection(
        self,
        maxlen=maxlen,
        algorithm=algorithm,
        minimal=minimal,
        only_signed=only_signed,
        consensus=consensus,
        connect_with_bias=connect_with_bias,
        path_policy=path_policy,
        reuse_policy=reuse_policy,
        _warning_stacklevel=5,
    )
convert_edgelist_into_genesymbol
convert_edgelist_into_genesymbol() -> pd.DataFrame

This function generates a new edges dataframe with the source and target identifiers translated (if possible) in Genesymbol format.

The network's node table is the primary mapping source so custom nodes such as phenotypes are retained. Identifiers unknown to both the node table and the biological mapper are preserved rather than replaced by null values.

Args: - None

Returns: - A pandas DataFrame containing the edges with the source and target identifiers translated into Genesymbol format.

Source code in neko/core/network.py
def convert_edgelist_into_genesymbol(self) -> pd.DataFrame:
    """
    This function generates a new edges dataframe with the source and target identifiers translated (if possible)
    in Genesymbol format.

    The network's node table is the primary mapping source so custom nodes
    such as phenotypes are retained. Identifiers unknown to both the node
    table and the biological mapper are preserved rather than replaced by
    null values.

    Args:
         - None

    Returns:
        - A pandas DataFrame containing the edges with the source and target identifiers translated into Genesymbol
            format.
    """

    uniprot_to_genesymbol = {}
    genesymbols = set()

    for _, node in self.nodes.iterrows():
        genesymbol = node.get("Genesymbol")
        uniprot = node.get("Uniprot")

        if pd.notna(genesymbol):
            genesymbols.add(genesymbol)

        if pd.isna(uniprot) or pd.isna(genesymbol):
            continue

        existing = uniprot_to_genesymbol.get(uniprot)
        if existing is not None and existing != genesymbol:
            raise ValueError(
                f"Network identifier {uniprot!r} has multiple gene-symbol "
                f"labels: {existing!r} and {genesymbol!r}."
            )
        uniprot_to_genesymbol[uniprot] = genesymbol

    ambiguous = {
        identifier: label
        for identifier, label in uniprot_to_genesymbol.items()
        if identifier in genesymbols and identifier != label
    }
    if ambiguous:
        detail = ", ".join(
            f"{identifier!r} -> {label!r}"
            for identifier, label in ambiguous.items()
        )
        raise ValueError(
            "Network identifiers are ambiguous between the Uniprot and "
            f"Genesymbol columns ({detail})."
        )

    def convert_identifier(identifier):
        if pd.isna(identifier):
            return identifier
        if identifier in uniprot_to_genesymbol:
            return uniprot_to_genesymbol[identifier]
        if identifier in genesymbols:
            return identifier

        identifiers = mapping_node_identifier(identifier)
        return identifiers[0] or identifiers[1] or identifier

    gs_edges = self.edges.copy()

    gs_edges["source"] = gs_edges["source"].apply(convert_identifier)
    gs_edges["target"] = gs_edges["target"].apply(convert_identifier)

    return gs_edges