py3plex - documentation¶
Found at: https://github.com/SkBlaz/Py3Plex
Welcome to the py3plex library’s documentation! Here, user can learn more about how py3plex can be used to solve problems related to complex networks!
The aim of this library is to:
- Provide primitives for working with multilayer (and multiplex) complex networks
- Provide a core set of algorithm for statistical analysis of such networks
- Provide extensive collection of network decomposition algorithms
- Provide python wrappers for highly efficient algorithm implementations
pip install py3plex
To test whether the core library functionality works well, you can run the test suite from the ./tests folder as:
python3 -m pytest test_core_functionality.py
That’s almost it. For full functionality, one needs node2vec and InfoMap binary files, which need to be put into the ./bin folder. This project offers pre-compiled versions, however was tested only on Ubuntu linux > 15.
A quick overview is discussed next:
Core idea and principles¶
The key idea behind py3plex is simplicity. The purpose of this library is to offer off-the-shelf functionality not supported elsewhere, with minimal user effort. The purpose of this project is to offer:
- Simple API-like interface to modular analysis of multilayer and multiplex networks
- State-of-the-art visualization of multilayer networks
The latest paper discussin py3plex and its functionality is available at: https://link.springer.com/article/10.1007/s41109-019-0203-7
py3plex - key principles¶
py3plex is a general purpose multilayer analysis toolkit. Built on top of NetworkX, a widely usedy Python3 graph analysis library, it offers intuitive and efficient exploration of multilayer networks. Written in python3, it can be installed as simply as:
pip install py3plex
or
pip install git+https://github.com/skblaz/py3plex
The basic usage is discussed next. More or less all functionality revolves around the multinet class, which is imported as:
from py3plex.core import multinet
For the remainder of this documentation, we assume the datasets (from the home py3plex repo directory) is present along the code.
A 3min introduction¶

A network can be loaded by either using one of the many available parsers (below), or constructed using our functional API. Examples of loading the network: (See example files in the examples/ folder!) First, simple edgelists:
n1 n2
n3 n1
Such simple networks are the building block for multilayer structures, and can be loaded as:
multilayer_network = multinet.multi_layer_network().load_network("./datasets/test.edgelist",directed=False, input_type="edgelist")
However, this is normally not enough, and is not the main purpose of py3plex. Extension can be done by adding the layers and the weights, a single edge looks like (node layer node layer weight):
n1 l1 n2 l2 0.4
n1 l3 n3 l1 1
Note that the node and layer names are not necessarily int-encoded, although py3plex can also do that. Taking care of proper mappings is done under the hood, and as such abstracted away from the user.
The key object around everything evolves is the multilayer_network, initiated as follows (from some multiedgelist as discussed above):
multilayer_network = multinet.multi_layer_network().load_network("./datasets/multiedgelist.txt",directed=False, input_type="multiedgelist")
And that’s it! You’ve just learned to parse one of the most basic input types. Now, what can be done with this object?
One common step is to summarize what you are dealing with.:
multilayer_network.basic_stats()
See other chapters for more detailed functionality.
What about multiplex networks?¶

Compared to multilayers, multiplex networks can be interpreted as the same set of nodes, projected across different contexts (one type of node, multiple possible, different edges between them). py3plex also supports parsing of multiplex networks, such as seen in the example below. Here, the input is expected to be in the form:
l n1 n2 w
For example:
multiplex_network = multinet.multi_layer_network(network_type = "multiplex").load_network("./datasets/simple_multiplex.edgelist ",directed=False, input_type="multiplex_edges")
Note the network_type argument? This can be set either at “multiplex” or “multilayer”. If multilayer is considered, no additional couplings between same-named nodes across layers will be added. However, in the multiplex case, such couplings are properly added.
More involved input schemes?¶
Sometimes having node-layer tuples is not enough, or not elegant enough. py3plex offers addition of arbitrary attributes to the constructed multilayer (plex) objects as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | from py3plex.core import multinet from py3plex.core import random_generators ## An example general multilayer network A = multinet.multi_layer_network() ## add a single node with type simple_node = {"source" : "node1","type":"t1"} A.add_nodes(simple_node) A.monitor("Printing a single node.") print(list(A.get_nodes(data=True))) ## add a single edge with type simple_edge = {"source":"node1", "target":"node2", "type":"mention", "source_type":"t1", "weight" : 2, ## add arbitrary attributes! "sunrise_tomorrow" : True, "target_type":"t2"} A.add_edges(simple_edge) A.monitor("Printing a single edge.") print(list(A.get_edges(data=True))) ## multiple edges are added by simply packing existing edges into a list. simple_attributed_edges = [{"source":"node1","target":"node6","type":"mention","source_type":"t1","target_type":"t5"},{"source":"node3","target":"node2","type":"mention","source_type":"t1","target_type":"t3"}] A.add_edges(simple_attributed_edges) A.monitor("Printing multiple edges") print(list(A.get_edges(data=True))) ## Edges can also be added as lists: [n1,l1,n2,l2,w] example_list_edge = [["node3","t2","node2","t6",1],["node3","t2","node2","t6",1]] ## specify that input is list, all else is recognized by Py3plex! A.add_edges(example_list_edge,input_type="list") print(list(A.get_edges())) |
Hence, arbitrary data (structures) can be added to individual node-layer tuplets, offering additional flexibility. For example, adding temporal component is one of possibilities.
Analysis of multilayers¶
Having discussed how the multilayer networks can be constructed, the next logical step is to discuss what analytics is offered by py3plex. In this chapter, we discuss the following functionality:
- Looping constructs and iteration (multilayer networks)
- Traversal (multilayer networks).
- Bindings to NetworkX for arbitrary monolayer functinality.
Some useful constructs¶
Analysis of multilayer networks can be often tricky, as these structures need to be first simplified to more algorithm-friendly inputs. We next discuss some functionality supported by py3plex that potentially facilitates such endeavours. The multinet object is the core class around everything more or less evolves.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | from py3plex.core import multinet ## a multilayer object A = multinet.multi_layer_network().load_network("../datasets/multiedgelist.txt",input_type="multiedgelist",directed=False) A.basic_stats() ## this is nicer printing. A.monitor("Edge looping:") ## looping through edges: for edge in A.get_edges(data = True): print(edge) A.monitor("Node looping:") ## what about nodes? for node in A.get_nodes(data = True): print(node) C1 = A.subnetwork(['1'],subset_by="layers") A.monitor(list(C1.get_nodes())) C2 = A.subnetwork(['1'],subset_by="node_names") A.monitor(list(C2.get_nodes())) C3 = A.subnetwork([('1','1'),('2','1')],subset_by="node_layer_names") A.monitor(list(C3.get_nodes())) |
Network traversal¶
One of the simplest examples that leads to understanding what one can do with a given multilayer entwork is the notion of network traversal. We next present an example where a multilayer network is first generated and next traversed, where the locations of the random walkers traversing across intra- as well as inter-layer edges are considered.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | from py3plex.core import multinet from py3plex.core import random_generators import numpy as np import queue import matplotlib.pyplot as plt import seaborn as sns ## some random graph ER_multilayer = random_generators.random_multilayer_ER(3000,10,0.05,directed=False) ## seed node all_nodes = list(ER_multilayer.get_nodes()) all_nodes_indexed = {x:en for en,x in enumerate(all_nodes)} ## spread from a random node random_init = np.random.randint(len(all_nodes)) random_node = all_nodes[random_init] spread_vector = np.zeros(len(ER_multilayer.core_network)) Q = queue.Queue(maxsize=3000) Q.put(random_node) layer_visit_sequence = [] node_visit_sequence = [] iterations = 0 while True: if not Q.empty(): candidate = Q.get() iterations+=1 if iterations % 100 == 0: print("Iterations: {}".format(iterations)) for neighbor in ER_multilayer.get_neighbors(candidate[0],candidate[1]): idx = all_nodes_indexed[neighbor] if spread_vector[idx] != 1: layer_visit_sequence.append(candidate[1]) node_visit_sequence.append((neighbor,iterations)) Q.put(neighbor) spread_vector[idx] = 1 else: break sns.distplot(layer_visit_sequence) plt.xlabel("Layer") plt.ylabel("Visit density") plt.show() |

Extending functionality with networkX?¶
As, under the hood, most of the py3plex objects are some form of multigraphs, with some simplification, many ad hoc functionality is readily available! Assuming you still have the C1 network from the first example, simply call the monoplex_nx_wrapper method with corresponding function name:
1 2 | centralities = C1.monoplex_nx_wrapper("degree_centrality") A.monitor(centralities) |
A technical note¶
If you have a network without layer information, however would like to start from there, the:
A.add_dummy_layers()
Will equip each node with a dummy layer (hence all nodes are in the same layer).
Analysis of multiplex networks¶
Multiplex networks are more convenient for analysis, hence many existing approaches can be considered, and were implemented as a part of py3plex. The main ones are discussed next:
Aggregations¶
One of the most common way to approach multiplex network analysis is by aggregating information across layers. Let that be the information bound to nodes or edges, both can be aggregated into a single homogeneous network that can readily be analysed. An example of aggregation is given below, on a random multiplex ER (multiple ERs across same node set).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ### aggregate a multiplex network import networkx as nx from py3plex.core import multinet from py3plex.core import random_generators ## initiate an instance of a random graph ER_multilayer = random_generators.random_multiplex_ER(500,8,0.0005,directed=False) ER_multilayer.basic_stats() ## simple networkx object aggregated_network1 = ER_multilayer.aggregate_edges(metric="count",normalize_by="degree") print(nx.info(aggregated_network1)) ## unnormalized counts for edge weights aggregated_network2 = ER_multilayer.aggregate_edges(metric="count",normalize_by="raw") print(nx.info(aggregated_network2)) ## The two networks have the same number of links (all) ## However, the weights differ! for e in aggregated_network2.edges(data=True): print(e) for e in aggregated_network1.edges(data=True): print(e) |
The first network divides the contribution of an individual edge with the average node degree in a given layer, and the second one simply sums them.
Subsetting¶
Subsetting operates in the same manner than for multilayers, hence:
1 2 3 4 5 6 7 8 9 10 11 12 | B = multinet.multi_layer_network(network_type="multiplex") B.add_edges([[1,1,2,1,1],[1,2,3,2,1],[1,2,3,1,1],[2,1,3,2,1]],input_type="list") ## subset the network by layers C = B.subnetwork([2],subset_by="layers") print(list(C.get_nodes())) C = B.subnetwork([1],subset_by="node_names") print(list(C.get_nodes())) C = B.subnetwork([(1,1),(1,2)],subset_by="node_layer_names") print(list(C.get_nodes())) |
Supra adjacency matrices¶
Multiplex (layer) networks can also be represented as supra-adjacency matrices as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ### simple supra adjacency matrix manipulation ## tensor-based operations examples from py3plex.core import multinet from py3plex.core import random_generators ## initiate an instance of a random graph ER_multilayer = random_generators.random_multilayer_ER(500,8,0.05,directed=False) mtx = ER_multilayer.get_supra_adjacency_matrix() comNet = multinet.multi_layer_network(network_type="multiplex",coupling_weight=1).load_network('../datasets/simple_multiplex.edgelist',directed=False,input_type='multiplex_edges') comNet.basic_stats() comNet.load_layer_name_mapping('../datasets/simple_multiplex.txt') mat = comNet.get_supra_adjacency_matrix() print(mat.shape) kwargs = {"display":True} comNet.visualize_matrix(kwargs) ## how are nodes ordered? for edge in comNet.get_edges(data=True): print(edge) print (comNet.node_order_in_matrix) |

Some additional tensor-like indexing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ## tensor-based operations examples from py3plex.core import multinet from py3plex.core import random_generators ## initiate an instance of a random graph ER_multilayer = random_generators.random_multilayer_ER(500,8,0.05,directed=False) ## some simple visualization visualization_params = {"display":True} ER_multilayer.visualize_matrix(visualization_params) some_nodes = [node for node in ER_multilayer.get_nodes()][0:5] some_edges = [node for node in ER_multilayer.get_edges()][0:5] ## random node is accessed as follows print(ER_multilayer[some_nodes[0]]) ## and random edge as print(ER_multilayer[some_edges[0][0]][some_edges[0][1]]) |
Network visualization¶
This section includes basic examples on network visualization. (datasets are available in the ./datasets folder on the repo home page!)
From hairball to multilayer plots¶
The following example shows minimal usecase for obtaining both types of visualization. For more detailed examples, visit the ./examples folder in the main repo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ## visualization of a simple heterogeneous network from py3plex.visualization.multilayer import * from py3plex.visualization.colors import all_color_names,colors_default from py3plex.core import multinet ## you can try the default visualization options --- this is the simplest option/ ## multilayer multilayer_network = multinet.multi_layer_network().load_network("../datasets/goslim_mirna.gpickle",directed=False, input_type="gpickle_biomine") multilayer_network.basic_stats() ## check core imports ## a simple hairball plot multilayer_network.visualize_network(style="hairball") plt.show() |
Yields the harball plot:

1 2 3 | ## going full py3plex (default 100 iterations, layout_parameters can carry additional parameters) multilayer_network.visualize_network(style="diagonal") plt.show() |
And the diagonal multilayer layout:

For more custom visualizations, please consider ./examples/example_multilayer_visualization.py!
Acknowledgements
ForceAtlas2 cython implementation is based on the one provided at https://github.com/bhargavchippada/forceatlas2, developed by Bhargav Chippada. The code is included by the author’s permission. We also thank Thomas Aynaud for the permission to include the initial version of the Louvain algorithm. Other contributors to this project are Jan Kralj and Nika Erzen.
Community detection (multiplex)¶
Community detection is considered when a given network’s topology is considered at meso-scales. py3plex supports both the widely used InfoMap, for which it offers a wrapper:
1 2 3 4 5 6 | from py3plex.algorithms.community_detection import community_wrapper as cw from py3plex.core import multinet network = multinet.multi_layer_network(network_type = "multiplex").load_network(input_file="../datasets/simple_multiplex.edgelist",directed=False,input_type="multiplex_edges") partition = cw.infomap_communities(network, binary="../bin/Infomap", multiplex=True, verbose=True) print(partition) |
But also the multiplex Louvain (pip install louvain):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | ## multiplex community detection! from py3plex.algorithms.community_detection import community_wrapper as cw from py3plex.core import multinet network = multinet.multi_layer_network(network_type = "multiplex").load_network(input_file="../datasets/multiplex_example.edgelist",directed=True,input_type="multiplex_edges") partition = cw.infomap_communities(network, binary="../bin/Infomap", multiplex=True, verbose=True) print(partition) ## get communities with multiplex louvain import igraph as ig import louvain #optimiser = louvain.Optimiser() network.split_to_layers(style = "none") network_list = [] ## cast this to igraph unique_node_id_counter = 0 node_hash = {} for layer in network.separate_layers: g = ig.Graph() edges_all = [] for edge in layer.edges(): first_node = int(edge[0][0]) second_node = int(edge[1][0]) g.add_vertex(first_node) g.add_vertex(second_node) edges_all.append((first_node,second_node)) print(edges_all) g.add_edges(edges_all) network_list.append(g) membership, improv = louvain.find_partition_multiplex(network_list, louvain.ModularityVertexPartition) ## for each node we get community assignment. network.monitor(membership) network.monitor(improv) |
Simple, homogeneous community detection is also possible!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | network = multinet.multi_layer_network().load_network(input_file="../datasets/cora.mat", directed=False, input_type="sparse") partition = cw.louvain_communities(network) #print(partition) # select top n communities by size top_n = 10 partition_counts = dict(Counter(partition.values())) top_n_communities = list(partition_counts.keys())[0:top_n] # assign node colors color_mappings = dict(zip(top_n_communities,[x for x in colors_default if x != "black"][0:top_n])) network_colors = [color_mappings[partition[x]] if partition[x] in top_n_communities else "black" for x in network.get_nodes()] # visualize the network's communities! hairball_plot(network.core_network, color_list=network_colors, layout_parameters={"iterations": args.iterations}, scale_by_size=True, layout_algorithm="force", legend=False) plt.show() |

Random networks¶
Multi-layered structures can also be generated (ER-based)
1 2 3 4 5 | from py3plex.core import multinet from py3plex.core import random_generators ER_multilayer = random_generators.random_multilayer_ER(200,6,0.09,directed=True) ER_multilayer.visualize_network(show=True, no_labels = True) |

Learning - label propagation¶
Learning propagation is one of the simplest learning processes one can conduct on labeled networks. Py3plex offers off-the-shelf validation procedures for evaluating multiple variants of LP!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | from py3plex.core import multinet from py3plex.algorithms.network_classification import * from py3plex.visualization.benchmark_visualizations import * import scipy import pandas as pd multilayer_network = multinet.multi_layer_network().load_network("../datasets/cora.mat",directed=False, input_type="sparse") ## WARNING: sparse matrices are meant for efficiency. Many operations with standard px objects are hence not possible, e.g., basic_stats()... ## different heuristic-based target weights.. normalization_schemes = ["freq","basic","freq_amplify","exp"] result_frames = [] for scheme in normalization_schemes: result_frames.append(validate_label_propagation(multilayer_network.core_network,multilayer_network.labels,dataset_name="cora_classic",repetitions=5,normalization_scheme=scheme)) ## results frame validation_results = pd.DataFrame() ## construct a single dataframe for x in result_frames: validation_results = validation_results.append(x,ignore_index=True) validation_results.reset_index() ## plot results plot_core_macro(validation_results) |

Learning - Node embeddings¶
Node embeddings are real-valued representations of nodes that capture the node’s neighborhood (and beyond).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from py3plex.core import multinet from py3plex.wrappers import train_node2vec_embedding from py3plex.visualization.embedding_visualization import embedding_visualization from py3plex.visualization.embedding_visualization import embedding_tools import json ## load network in GML multilayer_network = multinet.multi_layer_network().load_network("../datasets/imdb_gml.gml",directed=True,input_type="gml") # save this network as edgelist for node2vec multilayer_network.save_network("../datasets/test.edgelist") ## call a specific embedding binary --- this is not limited to n2v train_node2vec_embedding.call_node2vec_binary("../datasets/test.edgelist","../datasets/test_embedding.emb",binary="../bin/node2vec",weighted=False) ## preprocess and check embedding multilayer_network.load_embedding("../datasets/test_embedding.emb") ## visualize embedding embedding_visualization.visualize_embedding(multilayer_network) ## output embedded coordinates as JSON output_json = embedding_tools.get_2d_coordinates_tsne(multilayer_network,output_format="json") with open('../datasets/embedding_coordinates.json', 'w') as outfile: json.dump(output_json, outfile) |

py3plex¶
py3plex package¶
Subpackages¶
py3plex.algorithms package¶
Subpackages¶
Module contents¶
py3plex.core package¶
Subpackages¶
Submodules¶
py3plex.core.converters module¶
py3plex.core.multinet module¶
py3plex.core.parsers module¶
py3plex.core.random_generators module¶
py3plex.core.supporting module¶
Module contents¶
py3plex.visualization package¶
Subpackages¶
Submodules¶
py3plex.visualization.benchmark_visualizations module¶
py3plex.visualization.bezier module¶
py3plex.visualization.colors module¶
py3plex.visualization.drawing_machinery module¶
py3plex.visualization.layout_algorithms module¶
py3plex.visualization.misc_tools module¶
py3plex.visualization.multilayer module¶
py3plex.visualization.polyfit module¶
Module contents¶
Module contents¶
All examples and tutorials are accessible in https://github.com/SkBlaz/Py3Plex/tree/master/examples
In progress: We are adding more involved examples, which are for now found in ./examples folder!
The documentation of all methods is given below: