NetworkX Interoperability Guide

This guide covers converting between Py3plex and NetworkX formats, preserving attributes, and integrating with external tools without losing layer context.

Why NetworkX Interoperability?

Py3plex uses NetworkX as its underlying graph backend, which provides:

  • Compatibility with hundreds of NetworkX algorithms

  • Easy export to other tools (Gephi, Cytoscape, graph-tool)

  • Integration with scientific Python ecosystem (NumPy, SciPy, pandas)

  • Standard formats (GraphML, GEXF, GML, etc.)

Throughout this guide, nodes are addressed by their canonical Py3plex form (node_id, layer_name). When serialized to text formats, this tuple is stringified using the configured label delimiter (default ---); adjust the delimiter if you need file-level compatibility with other tools.

Export to NetworkX

Basic Export

Convert a Py3plex multilayer network to a NetworkX graph. The returned object is the same MultiGraph or MultiDiGraph held inside Py3plex, so edits made via NetworkX are reflected in Py3plex and vice versa. Use network.core_network.copy() if you want an isolated copy:

from py3plex.core import multinet

# Load multilayer network
network = multinet.multi_layer_network()
network.load_network("data.csv", input_type="multiedgelist")

# Export to NetworkX MultiGraph
nx_graph = network.core_network  # Direct access to underlying MultiGraph/MultiDiGraph

# OR use conversion method (returns the same object)
nx_graph = network.to_nx_network()

print(f"NetworkX graph: {nx_graph.number_of_nodes()} nodes, "
      f"{nx_graph.number_of_edges()} edges")

What Gets Exported

When exporting to NetworkX, all attributes are preserved, and the graph type is retained (undirected → MultiGraph, directed → MultiDiGraph). Layer membership is always explicit on tuple nodes, so you do not need to parse stringified labels to recover it:

Node attributes (examples):

  • type - Layer name (identical to node[1] for tuple nodes)

  • Custom attributes added via add_nodes()

Edge attributes (examples):

  • weight - Edge weight (default 1.0)

  • type - Edge type (for multiplex coupling edges this is set to 'coupling')

  • Custom attributes added via add_edges()

Example:

# Access NetworkX graph
nx_graph = network.core_network

# Inspect node attributes
for node, attrs in list(nx_graph.nodes(data=True))[:3]:
    print(f"Node: {node}")
    print(f"  Attributes: {attrs}")
    print()

# Inspect edge attributes
for u, v, attrs in list(nx_graph.edges(data=True))[:3]:
    print(f"Edge: {u} -> {v}")
    print(f"  Attributes: {attrs}")
    print()

Attribute Preservation

Layer Information

Layer information is encoded in node tuples, making layer access explicit without parsing strings:

# Py3plex stores nodes as tuples: (node_id, layer_name)
#
# Example node: ('A', 'layer1')
# - node_id: 'A'
# - layer_name: 'layer1'

# Iterate over nodes and extract layer info
nx_graph = network.core_network

for node in nx_graph.nodes():
    node_id, layer = node  # Unpack tuple directly
    node_type = nx_graph.nodes[node]['type']  # Should equal layer

    print(f"Node {node_id} in layer {layer} (type={node_type})")

Weight Preservation

Edge weights are preserved as edge attributes:

nx_graph = network.core_network

# Get edge weights
for u, v, data in nx_graph.edges(data=True):
    weight = data.get('weight', 1.0)  # Default to 1.0
    print(f"{u} -> {v}: weight={weight}")

Custom Attributes

Any custom attributes you add are preserved:

from py3plex.core import multinet

# Create network
network = multinet.multi_layer_network()

# Add edges with custom attributes
network.add_edges([
    ['A', 'layer1', 'B', 'layer1', 1.0, {'color': 'red', 'importance': 0.8}],
    ['B', 'layer1', 'C', 'layer1', 1.0, {'color': 'blue', 'importance': 0.6}],
], input_type="list")

# Export to NetworkX
nx_graph = network.core_network

# Custom attributes are preserved
for u, v, data in nx_graph.edges(data=True):
    print(f"{u} -> {v}:")
    print(f"  color: {data.get('color')}")
    print(f"  importance: {data.get('importance')}")

Using NetworkX Algorithms

Most NetworkX algorithms that support MultiGraph/MultiDiGraph operate directly on Py3plex networks. Algorithms that expect simple graphs should be applied on a collapsed view, for example nx.Graph(network.core_network) to drop parallel edges:

Shortest Paths

import networkx as nx
from py3plex.core import multinet

# Load network
network = multinet.multi_layer_network()
network.load_network("network.csv", input_type="multiedgelist")

# Use NetworkX shortest path algorithm
nx_graph = network.core_network

try:
    # Find shortest path between two node-layer tuples
    source = ('A', 'layer1')
    target = ('C', 'layer2')

    path = nx.shortest_path(nx_graph, source=source, target=target)
    print(f"Shortest path: {' -> '.join(str(n) for n in path)}")

    # Path length
    length = nx.shortest_path_length(nx_graph, source=source, target=target)
    print(f"Path length: {length}")

except nx.NetworkXNoPath:
    print("No path exists between these nodes")

Centrality Measures

import networkx as nx

# Degree centrality
degree_cent = nx.degree_centrality(network.core_network)
top_nodes = sorted(degree_cent.items(), key=lambda x: x[1], reverse=True)[:5]

print("Top 5 nodes by degree centrality:")
for node, centrality in top_nodes:
    print(f"  {node}: {centrality:.3f}")

# Betweenness centrality (use weight='weight' for weighted paths)
between_cent = nx.betweenness_centrality(network.core_network, weight='weight')

# Closeness centrality
close_cent = nx.closeness_centrality(network.core_network, distance='weight')

# PageRank (edge weights respected by default)
pagerank = nx.pagerank(network.core_network, weight='weight')

Community Detection

import networkx as nx
from networkx.algorithms import community

# Greedy modularity communities
communities = community.greedy_modularity_communities(network.core_network)

print(f"Detected {len(communities)} communities")
for i, comm in enumerate(communities):
    print(f"Community {i}: {len(comm)} nodes")

Connectivity

import networkx as nx

# Collapse to a simple, undirected view for connectivity checks
G_simple = nx.Graph(network.core_network)

if nx.is_connected(G_simple):
    print("Network is connected")
else:
    print("Network has multiple components")

    # Find connected components
    components = list(nx.connected_components(G_simple))
    print(f"Number of components: {len(components)}")
    for i, comp in enumerate(components):
        print(f"  Component {i}: {len(comp)} nodes")

Integration with External Tools

Export to Gephi

Gephi is a popular network visualization tool. Export Py3plex networks to GEXF format:

import networkx as nx

# Export to GEXF (Gephi format)
nx_graph = network.core_network
# Tuple nodes are stringified (e.g., ('A', 'layer1') -> 'A---layer1') in the output file
nx.write_gexf(nx_graph, "network_for_gephi.gexf")

print("[OK] Exported to network_for_gephi.gexf")
print("  Open in Gephi: File → Open → network_for_gephi.gexf")

Export to Cytoscape

Cytoscape is a bioinformatics network analysis tool. Export to GraphML:

import networkx as nx

# Export to GraphML (Cytoscape format)
nx_graph = network.core_network
# Node identifiers are stringified when written to GraphML
nx.write_graphml(nx_graph, "network_for_cytoscape.graphml")

print("[OK] Exported to network_for_cytoscape.graphml")
print("  Open in Cytoscape: File → Import → Network from File")

Convert to igraph

igraph is a fast C-based network analysis library. When converting from a MultiGraph/MultiDiGraph, parallel edges are collapsed in the simple Graph created below; attach weights if you need to preserve multiplicity:

# Requires: pip install python-igraph
import igraph as ig
import networkx as nx

# Convert NetworkX to igraph
nx_graph = network.core_network

# Method 1: Via GraphML
nx.write_graphml(nx_graph, "temp.graphml")
ig_graph = ig.Graph.Read_GraphML("temp.graphml")

# Method 2: Via edge list (faster)
edges = list(nx_graph.edges())
nodes = list(nx_graph.nodes())

ig_graph = ig.Graph()
ig_graph.add_vertices(len(nodes))
ig_graph.vs["name"] = [str(n) for n in nodes]

# Map node names to indices
node_to_idx = {n: i for i, n in enumerate(nodes)}
ig_edges = [(node_to_idx[u], node_to_idx[v]) for u, v in edges]
ig_graph.add_edges(ig_edges)

print(f"[OK] Converted to igraph: {ig_graph.vcount()} vertices, {ig_graph.ecount()} edges")

# Use igraph algorithms
communities = ig_graph.community_multilevel()
print(f"  Detected {len(communities)} communities")

Py3plex → NetworkX → TensorLy

Complete Workflow

Convert a multilayer network to a dense tensor representation suitable for tensor decomposition workflows. Axes are ordered as (source_node_id, layer, target_node_id), so node identifiers are shared across layers and the layer axis carries context:

import numpy as np
import networkx as nx
from py3plex.core import multinet

# Step 1: Load multilayer network
network = multinet.multi_layer_network()
network.load_network("network.csv", input_type="multiedgelist")

# Step 2: Get NetworkX graph
nx_graph = network.core_network

# Step 3: Extract layer information
layers = sorted(set(node[1] for node in nx_graph.nodes()))
nodes = sorted(set(node[0] for node in nx_graph.nodes()))

n_nodes = len(nodes)
n_layers = len(layers)

print(f"Network: {n_nodes} nodes, {n_layers} layers")

# Step 4: Create 3D adjacency tensor (nodes × layers × nodes)
tensor = np.zeros((n_nodes, n_layers, n_nodes))

# Create mappings
node_to_idx = {node: i for i, node in enumerate(nodes)}
layer_to_idx = {layer: i for i, layer in enumerate(layers)}

# Fill tensor with intra-layer edges only; inter-layer edges are skipped here
for (u_id, u_layer), (v_id, v_layer), data in nx_graph.edges(data=True):
    if u_layer == v_layer:  # Intra-layer edge
        i = node_to_idx[u_id]
        j = node_to_idx[v_id]
        k = layer_to_idx[u_layer]
        weight = data.get('weight', 1.0)

        # Accumulate in case multiple edges exist between the same pair
        tensor[i, k, j] += weight
        if not nx_graph.is_directed():
            tensor[j, k, i] += weight  # Mirror for undirected graphs

print(f"Tensor shape: {tensor.shape}")
print(f"Non-zero entries: {np.count_nonzero(tensor)}")

# Step 5: Use TensorLy for decomposition
try:
    import tensorly as tl
    from tensorly.decomposition import tucker, parafac

    # Tucker decomposition
    core, factors = tucker(tl.tensor(tensor), rank=[5, 2, 5])
    print(f"\n[OK] Tucker decomposition complete")
    print(f"  Core tensor shape: {core.shape}")
    print(f"  Factor matrices: {[f.shape for f in factors]}")

    # PARAFAC/CP decomposition
    factors_cp = parafac(tl.tensor(tensor), rank=5)
    print(f"\n[OK] PARAFAC decomposition complete")
    print(f"  Rank: 5")

except ImportError:
    print("\n[X] TensorLy not installed")
    print("  Install: pip install tensorly")

Supra-Adjacency Matrix

Convert to supra-adjacency matrix for tensor analysis. The row/column ordering matches network.get_nodes(), so indices are aligned with tuple-based nodes:

import numpy as np
from scipy.sparse import lil_matrix

# Get supra-adjacency matrix (number of rows = node-layer pairs)
supra_adj = network.get_supra_adjacency_matrix(sparse=True)

print(f"Supra-adjacency matrix: {supra_adj.shape}")
print(f"Sparsity: {1 - supra_adj.nnz / (supra_adj.shape[0]**2):.2%}")

# Convert to dense (if small enough)
if supra_adj.shape[0] < 1000:
    dense_supra = supra_adj.toarray()

    # Use for analysis
    eigenvalues = np.linalg.eigvals(dense_supra)
    print(f"Largest eigenvalue: {max(eigenvalues):.3f}")
else:
    print("Matrix too large for safe dense conversion; keep sparse.")

Import from NetworkX

Create Py3plex Network from NetworkX

You can initialize a Py3plex network from an existing NetworkX graph. Nodes should already carry layer information (either as tuple (node, layer) or stringified with the configured delimiter), and any existing edge attributes are kept:

import networkx as nx
from py3plex.core import multinet

# Create NetworkX graph
G = nx.Graph()
G.add_edges_from([
    (('A', 'layer1'), ('B', 'layer1'), {'weight': 1.0}),
    (('B', 'layer1'), ('C', 'layer1'), {'weight': 0.8}),
    (('A', 'layer2'), ('C', 'layer2'), {'weight': 0.6}),
])

# Import to Py3plex
network = multinet.multi_layer_network()
network.load_network(G, input_type="nx")

print(f"[OK] Imported {network.core_network.number_of_nodes()} nodes")

Practical Examples

Example 1: Network Statistics Pipeline

import networkx as nx
from py3plex.core import multinet

# Load network
network = multinet.multi_layer_network()
network.load_network("network.csv", input_type="multiedgelist")

# Get NetworkX graph
G = network.core_network

# Collapse multiedges if your metrics assume a simple graph
G_simple = nx.Graph(G)

# Compute various statistics
print("=== Network Statistics ===")
print(f"Nodes: {G_simple.number_of_nodes()}")
print(f"Edges: {G_simple.number_of_edges()}")
print(f"Density: {nx.density(G_simple):.4f}")

# Degree statistics
degrees = dict(G_simple.degree())
print(f"Average degree: {sum(degrees.values()) / len(degrees):.2f}")
print(f"Max degree: {max(degrees.values())}")

# Clustering
clustering = nx.clustering(G_simple)
print(f"Average clustering: {sum(clustering.values()) / len(clustering):.4f}")

# Connected components
if not nx.is_directed(G_simple):
    components = list(nx.connected_components(G_simple))
    print(f"Connected components: {len(components)}")

Example 2: Multilayer PageRank

import networkx as nx

# Compute PageRank on multilayer network
G = network.core_network
pagerank = nx.pagerank(G, weight='weight')

# Group by layer
layers = {}
for (node_id, layer), score in pagerank.items():
    if layer not in layers:
        layers[layer] = []
    layers[layer].append((node_id, score))

# Print top nodes per layer
for layer, nodes in layers.items():
    top_nodes = sorted(nodes, key=lambda x: x[1], reverse=True)[:5]
    print(f"\nTop 5 nodes in {layer}:")
    for node_id, score in top_nodes:
        print(f"  {node_id}: {score:.4f}")

Example 3: Export for Gephi Visualization

import networkx as nx
from py3plex.core import multinet

# Load and process network
network = multinet.multi_layer_network()
network.load_network("network.csv", input_type="multiedgelist")

# Add community detection results
from py3plex.algorithms.community_detection import community_louvain
communities = community_louvain.best_partition(network.core_network)

# Add community as node attribute
for node, comm_id in communities.items():
    network.core_network.nodes[node]['community'] = comm_id

# Export to GEXF with communities
nx.write_gexf(network.core_network, "network_with_communities.gexf")

print("[OK] Exported to GEXF with community information")
print("  Open in Gephi and color by 'community' attribute")

Next Steps

For more examples, see the examples/ directory in the GitHub repository.