Skip to content

Entry-level Tutorial (detailed)

In this example we will get to know the basic functionalities of net-bench and to try them out.

The tutorial is structured into four steps:

  • Preparation (Installation and tips)
  • Creation (Creation of your own graph)
  • Processing (Layouting)
  • Product (Generating different images)

Throughout all the steps we work with an nx.Graph (or nx.DiGraph, if directed). Changes are written and read by using the graph, node and edge attributes.

Note

If that sounds complicated to you: That is ok, you can use net-bench without understanding all of these concepts.

Let's start with a clean slate and prepare everything:

0. Preparation

First, you need to install net-bench and NetworkX. For the organisation of packages you can use pip. For an explanation for the installation via pip click_here.

It works through your IDE (probably PyCharm) or your Terminal:

pip install dsslab-net-bench
pip install networkx

We suggest creating a folder tutorial-intro for the project, which we then want to fill with files and other folders. This folder will be called root folder from now on.

Note

It may be desirable to track your progress in a Git-Repository. If you want to do that, now is the time to do so.

If everything worked as intended, you can now create a python file in your root folder and call it main.py. Add the following at the start of the file:

import networkx as nx
import dsslab.net_bench as dnb

We import networkx as well as net-bench for the file to be able to use them here. Both packages get a so-called alias. The package networkx will always be called networkx when importing in python and will usually get the alias nx to shorten it (similarily for net-bench). For example: To access the class Graph from networkx, you can now write nx.Graph instead of networkx.Graph. Aliases can make code shorter to write in this way.

Execute this file. There should be no errors. If there are any errors, like ImportError, please check the installation of the individual packages.

If there are no problems, you can proceed to the next step.

1. Creation: Creation of your own graph

  • Creating an easy graph yourself
  • This should also include attributes which we can use later to draw the graph

To be able to understand the next steps, we want to start with explaining how a networkx-Graph is created. To do that, we will fill main.py step by step with the following pieces of code.

We start with the Graph object. With the following code you create an 'empty' graph:

graph = nx.Graph()

Now we will fill the graph with a few nodes:

graph.add_node("A")
graph.add_node("B")
graph.add_node("C")
graph.add_node("D")

Note

This code is helpful for understanding, but it isn't really best practice. Usually we would call the method graph.add_nodes_from(["A", "B", "C", "D"]) with a list of node names.

Next, we create edges between the nodes:

graph.add_edge("A", "B")
graph.add_edge("B", "C")
graph.add_edge("C", "D")
graph.add_edge("D", "A")

Note

There is also a best practice way of doing this: adding Nodes and Edges directly with graph.add_edges_from([("A", "B"), ("B", "C"), ("C", "D"), ("D", "A")]), but we want to show what to do step by step at this point.

So far we have nodes which are connected by the edges we just defined. In a real network, nodes often have attributes, which are important for the analysis. For example, in the case of semantic network analysis, every word could be a node and there could be the attribute organisation, which makes clear, which organisation is connected with that word.

One way to add attributes looks like this:

graph.nodes["A"]["Our Attribute"] = "Value 1"
graph.nodes["B"]["Our Attribute"] = "Value 2"
graph.nodes["C"]["Our Attribute"] = "Value 3"
graph.nodes["D"]["Our Attribute"] = "Value 1"  # On purpose the same as node A (see below)

Note

In most actual examples many nodes have the same attributes to make the more comparable. In the example above, every word would have the attribute organisation, just because every one of them would be connected to an organisation. The value could be different for every word, but wouldn't have to be.

Now we have created our first very simple graph. Using the print method we can find some things out about our graph. That should look like this:

print(graph)  # --> "Graph with 4 nodes and 4 edges"
print(graph.nodes)  # --> ['A', 'B', 'C', 'D']
print(graph.edges)  # --> [('A', 'B'), ('A', 'D'), ('B', 'C'), ('C', 'D')]

With this graph object we can now begin our next steps.

2. Processing: Layouting

Here we decide the positions of the nodes in our graph. There are two ways of doing that:

  1. NetworkX: Easy and already installed.
  2. Graphviz: Advanced and need a complicated installation when using windows. Makes better layouting possible.

In this example we will use NetworkX. This works as follows:

Note

In the example code you can find the name position.json. .json is a file ending like .pdf or .docx. Our json file will contain information about our graph which will be structured in a specific way (instead of just text like the other two examples). This format is similar to a dictionary in python and is used often because of that.

layouter = dnb.Layouter()
positions = layouter.read_or_create_layout("positions.json", graph, seed=1234, k=1)

At first, we create a Layouter object and call it layouter. The Layouter determines the positions of the nodes in the graph and provides settings. In the next line, a layout is created with the object method layouter.read_or_create_layout(). (Layout means the positioning) This method takes four arguments:

  • The path to which the position file is written
  • the grph itself
  • a so-called seed. The seed determines the randomness of the creation
  • k is relevant for the used_layout and determines the optimal distance between nodes. If nodes are to close to each other, you can try increasing this value to achieve better images.

layouter.read_or_create_layout() furthermore returns a dictionary, which we assign to the variable positions. After executing the code, there should be positions.json next to main.py in your directory:

tutorial-intro
├── main.py
└── positions.json

Warning

read_or_create_layout() reads an already exising layout, if there is already a file under the given path. Otherwise it writes a new file. If you want to create new images which overwrite the new ones, pass the argument overwrite=True:

positions = layouter.read_or_create_layout(f"positions.json", graph, seed=i, overwrite=True)

Note

To compare different layouts, iterate over the seed with a loop. This could look like this, if you want to create 20 different images:

position_list = []
for i in range(0,20):
    positions = layouter.read_or_create_layout(f"positions_{i}.json", graph, seed=i)
    position_list.append(positions)

3. Product: Generating different images

Now we arrive at drawing. We first create an ImageGenerator. This is a net-bench object, through which all settings for the correspondig image can be made. First, it is initialised via the graph:

image_generator = dnb.ImageGenerator(graph)

First, we have to set the positions:

image_generator.nodes.set_positions(positions)

Note

While setting positions with image_generator.nodes.set_positions(), you can either pass the dict or the path to the positions file. The file is created so that teams can set positions and track them through Git.

Now we can choose specific settings. For example: We can colour all edges grey:

image_generator.edges.set_colors("grey")

image_generator.edges.set_colors() receives an argument in the form of "grey". This means, that for all edges the value "grey" is used as a color.

For colouring the nodes according to an existing, codified attribute:

image_generator.nodes.set_colors(dnb.qualitative("Our Attribute", cmap="Set2"))

Note

We use the attribute of the node which we created earlier. Of course you can also create your own attributes and attach them to the network elements.

We want the size of the nodes to be scaled along their degree (Degree is the keyword, which triggers the internal calculation of the degree). outrange defines the maximal and minimal sizes of the nodes.

image_generator.nodes.set_sizes(dnb.sequential("degree", out_range=(100, 500)))

Note

Other automatically calculated keywords are: indegree, outdegree, betweenness, closeness.

Finally, our image is drawn and written into a file:

image_generator.draw().write_file("./image.svg")

Note

We can write the same code all at once: To write code over multiple lines in python, you have to bracket the lines.

image_generator = dnb.ImageGenerator(graph)
(
    image_generator.nodes.set_positions(positions)
    .set_colors(dnb.qualitative("Unser Attribute", cmap="Set2"))
    .set_sizes(dnb.sequential("degree", out_range=(100, 500)))
)
image_generator.edges.set_colors("grey")
image_generator.draw().write_file("./abbildung.svg")

Now there should be a new file called image.svg in your root folder, which holds the network as an image. The full python-file can be downloaded here.

The root folder looks like this now:

tutorial-intro
├── image.svg
├── main.py
└── positions.json

4. A Usage example: The Workflow

Often you don't create your own graph, but instead use already existing data. To understand how that works, create a new folder tutorial-import and within tht a new main.py with the same imports as earlier:

tutorial-import
└── main.py

Reading in of an existing graph

To get an existing graph there are several options in net-bench as well as in NetworkX. Because this is an easy example, we want to use an already existing network. Download this_file and save it to your root folder.

The root folder tutorial-import should look like this now (with your script and the new file):

tutorial-import
├── example_graph.gexf
└── main.py

Now you can read the file into python. Because we don't need any special functions for now, we can use the standard_function in NetworkX for that:

graph = nx.read_gexf("./example_graph.gexf")

Further processing

Now we need to create a positions file like we did above. You can do this the same way as earlier. One way is shown in the following box. Try to find a way yourself first if you want to!

Possible Solution
import networkx as nx
import dsslab.net_bench as dnb

layouter = dnb.Layouter()
positions = layouter.read_or_create_layout("positions.json", graph, seed=1234, k=1)

# Create an ImageGenerator object. An ImageGenerator creates one (!) image
# with a graph and different settings.

image_generator = dnb.ImageGenerator(graph)

# Set positions in the ImageGenerator
image_generator.nodes.set_positions(positions)

Now we want to set colours and sizes. Think of a combination and set it.

Possible Solution
# Set the colors for the edges
image_generator.edges.set_colors("grey")

# Set the colors for the nodes. We use qualitative mappig. This means, that multiple
# categories can be coloured differently.

image_generator.nodes.set_colors(dnb.qualitative("stage_of_sf", cmap="Set2"))

# Set the size of the nodes. We use a sequential mapping. This means, that continoous
# values are transferred onto a scaloe of our choosing (`out_range`).
# In this case the smallest degree would mean a radius of 5 for the node, the highest
# degree would mean a radius of 500.

image_generator.nodes.set_sizes(dnb.sequential("degree", out_range=(5, 500)))

Finally, we want to write the file to a location of our choosing. Think about how to do that or look above to find out how it works!

Possible Solution
# We draw and write a file. Drawing and writing takes place separately, because it is possible
# to create an image on different layers.

image_generator.draw().write_file("./image.svg")

The whole solution can be downloaded here.