Zum Inhalt

Fortgeschrittenes Tutorial (knapp)

Dieses Tutorial ist für dich geeignet, wenn du dich bereits mit Python auskennst und nur schnell wissen möchtest, was die grundsätzliche Idee ist bzw. wie der Workflow aussieht. Wir werfen außerdem einen kurzen Blick in networkx.

1. Setup

  1. Installier networkx und dsstools (empfohlen über pip)
    pip install dsstools
    pip install networkx
    
  2. Wir nutzen folgende Aliase
    import networkx as nx
    import dsstools as dts
    

2. Networkx (knapp)

Falls du networkx noch nicht kennst, sieh dazu Abschnitt 1 im Einstiegstutorial und networkx Dokumentation.

Im Schnelldurchlauf/zur Erinnerung:

  • Es gibt immer einen Graph graph = nx.Graph()
  • Dieser hat Nodes graph.add_nodes_from(["A", "B", "C", "D"])
  • Die mit Edges verbunden sind graph.add_edges_from([("A", "B"), ("B", "C"), ("C", "D"), ("D", "A")])

Die Nodes und Edges können dann verschiedene Eigenschaften haben, die man dann visualisieren möchte. Hier kommt dsstools ins Spiel.

3. Verarbeitung: Positionen festlegen

Hier legen wir die Positionen der Nodes in der visuellen Darstellung des Graphen fest. Dafür gibt es zwei Möglichkeiten:

  1. NetworkX: Einfach und bereits installiert.
  2. Graphviz: Fortgeschritten und benötigt unter Windows eine komplizierte Installation. Sie liefert allerdings bessere Positionierungen.

In diesem Beispiel werden wird dementsprechend die Positionierung mit NetworkX verwenden. Das funktioniert wie folgt:

layouter = dts.Layouter()
positions = layouter.read_or_create_layout("positions.json", graph, seed=1234, k=1)
Das Layout ist hier quasi gleichzusetzen mit den Positionen der Nodes, wie sie nachher in der Abbildung sind. Die Methode zur Erzeugung eines Layouts (positions) nimmt vier Parameter entgegen:

  • Den Pfad, in den die Positionsdatei geschrieben wird,
  • den Graphen selbst und
  • einen sog. Seed. Der Seed bestimmt dabei die "Zufälligkeit" der Erzeugung.
  • k ist für das genutzte Layout relevant und bestimmt die optimale Distanz zwischen Knoten. Falls Knoten zu eng beieinander liegen, kann eine Erhöhung dieses Wertes zu besseren Abbildungen führen.

Zudem gibt die Methode ein Dictionary der gespeicherten Positionen zurück. Existiert am angegeben Pfad bereits eine Datei mit diesem Namen, wird sie entsprechend eingelesen und es wird keine neue erzeugt.

4. Erzeugung von Abbildungen

Das nun erstellte Layout nutzen wir für die Visualisierung. Dazu nutzen wir den dts.ImageGenerator:

image_generator = dts.ImageGenerator(graph)
image_generator.nodes.set_positions(positions)

Hinweis

Im Setzen der Positionen mittels image_generator.nodes.set_positions() kann entweder das Dict oder der Pfad zur Positionsdatei übergeben werden. Die Datei wird deshalb erstellt, damit innerhalb von Teams die Position festgelegt und über Git getrackt werden kann.

Nun können wir einzelne Einstellungen auswählen. Bspw. könnten wir alle Kanten grau einfärben:

image_generator.edges.set_colors("grey")

Für die Einfärbung von Knoten entlang eines vorhandenen, codierten Attributes durch qualitative mapping. Andere mappings wie filter, was eigene Expressions zulässt, wären z. B. auch möglich:

image_generator.nodes.set_colors(dts.qualitative("Attribute1", cmap="Set2"))

Woher kommen die Attribute?

Die Attribute entlang derer wir codieren, sind die, die man in networkx festlegen kann: graph.nodes["A"]["Attribute1"] = "Wert 1". Im Workflow mit dsstools kommen diese dann normalerweise aus den verschiedenen textsearch Funktionen.

Die Größe der Knoten möchten wir entlang des Degree skalieren (Degree ist dabei ein Stichwort, welches die Berechnung des Degrees intern anstößt). Die outrange definiert wie groß der kleinste und größte Knoten maximal sein können:

# Auch hier wären andere mappings möglich
image_generator.nodes.set_sizes(dts.sequential("degree", out_range=(100, 500)))

An dieser Stelle möchten wir dich gerne dazu ermutigen, einen tieferen Blick in die verschiedenen Mapping-Strategien zu werfen. dsstools bietet noch viele weitere komplexere Möglichkeiten zur Visualisierung von Graphen, die du hier erkunden kannst.

Hinweis

Es funktionieren ebenfalls indegree, outdegree, betweenness, closeness als automatisch zu berechnende Stichwörter.

Zuletzt wird die Abbildung gezeichnet und dann in eine Datei geschrieben:

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

Hinweis

Das Gleiche lässt sich auch in einem Aufwasch schreiben:

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

5. Anwendungsbeispiel: Ein Workflow

Hier einmal ein Beispiel, was dsstools bringen kann bei einem großen Graphen, wie du ihn bestimmt selber haben wirst.

Schritt 1: Einlesen eines bestehenden Graphen

Für das Einlesen bieten sich mehrere Optionen an, sowohl in dssTools als auch in NetworkX. Da es sich hier um ein einfaches Beispiel handelt, wollen wir ein vorgefertigtes Netzwerk nutzen. Lade dafür diese Datei herunter und speichere sie entsprechend.

Schritt 2: Erstellung des Layouts und setzen der Positionen

import networkx as nx
import dsstools as dts

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

# Erzeuge ein ImageGenerator-Objekt. Ein ImageGenerator erzeugt mit einem Graph und
# verschieden Einstellungen eine (!) Abbildung.
image_generator = dts.ImageGenerator(graph)

# Setze die Positionen im ImageGenerator.
image_generator.nodes.set_positions(positions)

Schritt 3: Farben und Größen setzen

# Lege die Farben für die Kanten fest.
image_generator.edges.set_colors("grey")

# Lege die Farben für die Knoten fest. Wir nutzen ein qualitatives Mapping. Das
# bedeutet, dass mehrere Kategorien unterschiedliche eingefärbt werden.
image_generator.nodes.set_colors(dts.qualitative("stage_of_sf", cmap="Set2"))

# Lege die Knotengröße fest. Wir nutzen ein sequentielles Mapping. Das bedeutet, dass
# kontinuierliche Werte auf eine von uns gewählte Skala übertragen werden (`out_range`).
# In diesem Fall würde also das geringste Degree den Knotenradius 5, das höchste Degree
# im Netzwerk den Knotenradius 500 bedeuten.
image_generator.nodes.set_sizes(dts.sequential("degree", out_range=(5, 500)))

Schritt 4: Abbildung erstellen und speichern

# Wir zeichnen und schreiben die Datei. Zeichnen und Schreiben finden getrennt statt, da
# es möglich ist, eine Abbildung in mehreren Ebenen zu komponieren.
image_generator.draw().write_file("./abbildung.svg")

Die ganze mögliche Lösung kannst du hier herunterladen. Probier dich gerne noch weiter aus.