Graph-tool Filter Vertex to Draw
Quick set out victimization graph-tool
exported as notebook from here past @eliaswalyba
The graph_tool module provides a Graph assort and several algorithms that engage thereon. The internals of this class, and of most algorithms, are written in C++ for performance, using the Boost Graph Library.
The module must be naturally foreign before information technology can be used. The package is subdivided into several sub-modules. To spell everything from all of them, one can do:
from graph_tool.all import * In the following, information technology will e'er be assumed that the previous line was run.
Creating and manipulating graphs
An glazed graph can be created by instantiating a Graph class:
Away default, newly created graphs are e'er directed. To construct undirected graphs, cardinal mustiness passing play a value to the directed parameter:
ug = Graph(manageable = False) A graph can always comprise switched on-the-fly from directed to directionless (and vice versa), with the set_directed() method. The "directedness" of the graph can be queried with the is_directed() method acting,
ug = Graph() ug.set_directed(Imitative) print(ug.is_directed() == Mistaken) A chart can also be created by providing another graph, in which case the stallion graph (and its internal property maps, examine Property maps) is copied:
g1 = Graph() g2 = Graph(g1) Above, g2 is a "deep" copy of g1, i.e. any modification of g2 bequeath not involve g1.
Once a graph is created, information technology can be inhabited with vertices and edges. A vertex can be added with the add_vertex() method, which returns an instance of a Vertex form, likewise called a vertex descriptor. For illustration, the following code creates two vertices, and returns vertex descriptors stored in the variables v1 and v2.
v1 = g.add_vertex() v2 = g.add_vertex() Edges keister be added in an analogous manner, away calling the add_edge() method, which returns an edge descriptor (an instance of the Edge social class):
The above code creates a directed edge from v1 to v2. We can visualize the graph we created until now with the graph_draw() function.
graph_draw(g, vertex_text = g.vertex_index, output = "assets/graph.png") <VertexPropertyMap object with value type 'vector<double>', for Graph 0x7f18ed2835e0, at 0x7f1920eaca90>
A simplex directed graph with two vertices and one edge, created by the commands above.
With vertex and edge descriptors, one can examine and rig the graph in an impulsive way. For instance, in order to obtain the out-degree of a vertex, we can simply call the out_degree() method:
Analogously, we could have utilized the in_degree() method acting to query the in-degree.
Note: For undirected graphs, the "out-degree" is equivalent word for degree, and in this case the in-degree of a apex is forever zero.
Edge descriptors have two useful methods, source() and target(), which generate the source and target vertex of an edge, respectively.
print(e.source(), e.target()) The add_vertex() method acting also accepts an optional parameter which specifies the number of vertices to create. If this treasure is greater than 1, it returns an iterator connected the added apex descriptors:
vlist = g.add_vertex(10) print(len(list(vlist))) All vertex in a graph has an specific index, which is always between 0 and N−1, where N is the number of vertices. This index can constitute obtained away using the vertex_index dimension of the graph (which is a property map, imag Dimension maps), or by converting the vertex descriptor to an int.
v = g.add_vertex() impress(g.vertex_index[v]) print(int(v)) Edges and vertices can also follow removed at any time with the remove_vertex() and remove_edge() methods
g.remove_edge(e) # e no more exists g.remove_vertex(v2) # the minute vertex is also gone Note: Removing a vertex is typically an O(N) operation. The vertices are internally stored in a STL transmitter, so removing an element somewhere in the middle of the list requires the unsteady of the rest of the list. Thus, instant O(1) removals are only possible either if one can guarantee that exclusive vertices in the end of the lean are abstracted (the ones lastly added to the graph), or if the relative peak ordering is invalidated. The latter behavior can be achieved by exit the option fast == True, to remove_vertex(), which causes the acme being deleted to be 'swapped' with the last peak (i.e. with the largest index), which will successively inherit the index of the vertex being deleted.
Warning: Because of the above, removing a vertex with an forefinger smaller than N−1 will invalidate either the last (fast = True) or all (fast-paced = False) descriptors pointing to vertices with higher indicant.
American Samoa a consequence, if more than one vertex is to be removed at a given time, they should always be removed in decreasing index order:
# 'del_list' is a list of vertex descriptors for v in reversed(sorted(del_list)): g.remove_vertex(v) As an alternative (and preferably), a list (or any iterable) may equal passed straight as the vertex parameter of the remove_vertex() function, and the above is performed internally (in C++).
Note that property correspondenc values (see Property maps) are unaffected away the forefinger changes due to vertex removal, as they are modified accordingly by the library.
Preeminence: Removing an butt is an O(Kansas+kt) operation, where ks is the out-level of the source apex, and karat is the in-stage of the target vertex. This hind end be made faster by background set_fast_edge_removal() to True, in which case it becomes O(1), at the expense of additional data of size O(E). No boundary descriptors are ever invalidated after edge remotion, with the exception of the edge beingness far.
Since vertices are uniquely identifiable by their indexes, there is no need to keep the apex descriptor prevarication around to access them at a subsequent repoint. If we know its index, we can obtain the descriptor of a vertex with a conferred power using the vertex() method,
which takes an index, and returns a acme descriptor. Edges cannot be directly obtained by its indicant, but if the root and target vertices of a given edge are known, it can equal retrieved with the edge() method
g.add_edge(g.vertex(2), g.vertex(3)) e = g.edge(2, 3) Other way to obtain edge surgery vertex descriptors is to ingeminate through them, as described in section Iterating over vertices and edges. This is in fact the most recyclable way of obtaining peak and edge descriptors.
Like vertices, edges too have unique indexes, which are tending by the edge_index property:
e = g.add_edge(g.peak(0), g.peak(1)) print(g.edge_index[e]) Differently from vertices, edge indexes do not necessarily meet any specific lay out. If no edges are ever so removed, the indexes will be in the range [0,E−1], where E is the telephone number of edges, and edges added early have lower indexes. However if an edge is removed, its index testament exist "vacant", and the odd indexes will cost left unmodified, and thus will not every dwell the wander [0,E−1]. If a new edge is added, it will reuse old indexes, in an increasing plac.
Iterating over vertices and edges
Algorithms must often iterate through vertices, edges, out-edges of a vertex, etc. The Graph and Vertex classes provide other types of iterators for doing soh. The iterators always point to edge operating room vertex descriptors.
Iterating terminated all vertices or edges
Systematic to iterate through wholly the vertices or edges of a graph, the vertices() and edges() methods should equal used:
for v in g.vertices(): print(v) for e in g.edges(): photographic print(e) 0 1 2 3 4 5 6 7 8 9 10 11 (0, 1) (2, 3) The code above will print the vertices and edges of the graph in the order they are found.
Iterating over the neighborhood of a vertex
The out- and in-edges of a acme, as well as the out- and in-neighbors can be iterated done the out_edges(), in_edges(), out_neighbors() and in_neighbors() methods, respectively.
for v in g.vertices(): for e in v.out_edges(): print(e) for w in v.out_neighbors(): print(w) # the edge and neighbors parliamentary law always match for e, w in zip(v.out_edges(), v.out_neighbors()): assert e.target() == w The codification above will print the out-edges and out-neighbors of altogether vertices in the chart.
Warning: You should never remove vertex or edge descriptors when iterating over them, since this invalidates the iterators. If you plan to absent vertices or edges during iteration, you moldiness first store them somewhere (much as in a list) and remove them only after no iterator is beingness used. Removal during iteration will cause bad things to happen.
Fast iteration over vertices and edges
While convenient, looping over the graph as described in the previous incision is non the most efficient approach. This is because the loops are performed in pure Python, and hence it undermines the main feature of the library, which is the offloading of loops from Python to C++. Following the numpy philosophy, graph_tool likewise provides an align-based interface that avoids loops in Python. This is through with the get_vertices(), get_edges(), get_out_edges(), get_in_edges(), get_all_edges(), get_out_neighbors(), get_in_neighbors(), get_all_neighbors(), get_out_degrees(), get_in_degrees() and get_total_degrees() methods, which return numpy.ndarray instances instead of iterators.
For exercise, using this interface we can stick the out-degree of each node via:
print(g.get_out_degrees(g.get_vertices())) [1 0 1 0 0 0 0 0 0 0 0 0] or the sum of the product of the in and out-degrees of the endpoints of each edge with:
edges = g.get_edges() in_degs = g.get_in_degrees(g.get_vertices()) out_degs = g.get_out_degrees(g.get_vertices()) print((out_degs[edges[:,0]] * in_degs[edges[:,1]]).kernel()) Property maps
Property maps are a way of associating additional information to the vertices, edges OR to the graph itself. There are thus three types of property maps: vertex, edge and graph. They are handled by the classes VertexPropertyMap, EdgePropertyMap, and GraphPropertyMap. To each one created property correspondenc has an joint value type, which mustiness be chosen from the predefined set:
| Type name | False name |
|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
New property maps can be created for a given graph by vocation one of the methods new_vertex_property() (alias new_vp()), new_edge_property() (a.k.a. new_ep()), or new_graph_property() (alias new_gp()), for each map case. The values are then accessed by vertex or edge descriptors, operating theatre the graph itself, as so much:
from numpy.random import randint g = Graph() g.add_vertex(100) # insert some random links for s,t in goose egg(randint(0, 100, 100), randint(0, 100, 100)): g.add_edge(g.apex(s), g.acme(t)) vprop_double = g.new_vertex_property("double") # Double-precision floating point v = g.vertex(10) vprop_double[v] = 3.1416 vprop_vint = g.new_vertex_property("vector<int>") # Transmitter of ints v = g.acme(40) vprop_vint[v] = [1, 3, 42, 54] eprop_dict = g.new_edge_property("objective") # Capricious Python objective. e = g.edges().next() eprop_dict[e] = {"foo": "bar", "gnu": 42} # In this case, a dict. gprop_bool = g.new_graph_property("bool") # Boolean gprop_bool[g] = True Property maps with scalar value types can as wel be accessed as a numpy.ndarray, with the get_array() method, or the a attribute, e.g.,
from numpy.unselected import random # this assigns random values to the vertex properties vprop_double.get_array()[:] = random(g.num_vertices()) # or more handily (this is equivalent to the above) vprop_double.a = random(g.num_vertices()) Internal property maps
Whatever created property map can follow made "internal" to the same chart. This means that it will be copied and saved to a file unneurotic with the graph. Properties are internalized past including them in the graph's dictionary-like attributes vertex_properties, edge_properties or graph_properties (or their aliases, vp, ep or gp, respectively). When inserted in the graph, the property maps must have an unique name (between those of the same type):
eprop = g.new_edge_property("bowed stringed instrument") g.edge_properties["some name"] = eprop g.list_properties() whatever advert (butt against) (character: string) Inner graph property maps behave slightly otherwise. Instead of returning the property map object, the value itself is returned from the dictionaries:
gprop = g.new_graph_property("int") g.graph_properties["foo"] = gprop # this sets the de facto belongings map g.graph_properties["foo"] = 42 # this sets its value print(g.graph_properties["foo"]) del g.graph_properties["foo"] # the property map accounting entry is deleted from the dictionary For convenience, the internal property maps can also Be accessed via attributes:
vprop = g.new_vertex_property("double") g.vp.foo = vprop # equivalent to g.vertex_properties["foo"] = vprop v = g.vertex(0) g.vp.foo[v] = 3.14 print(g.vp.foo[v]) Graph I/O
Graphs toilet be saved and loaded in four formats: graphml, dot, gml and a custom binary format gt (see The gt file format).
Monitory: The binary format gt and the text-settled graphml are the preferred formats, since they are far and away the most complete. Both these formats are equally fleshed out, but the gt format is quicker and requires fewer storage. The dot and gml formats are fully supported, but since they contain no precise type information, all properties are read as string section (or also as double, in the event of gml), and must be converted by hand to the in demand type. Therefore you should ever use either gt or graphml, since they implement an exact bit-for-act agency of complete hanging down Belongings maps types, leave off when interfacing with other software, or present data, which uses DoT operating room gml.
A graph can be protected or loaded to a charge with the save and lading methods, which take either a filename or a sharp object. A graph can also be loaded from disc with the load_graph() function, arsenic such:
g = Graph() # ... meet the chart ... g.save("assets/my_graph.xml.gz") g2 = load_graph("assets/my_graph.xml.gz") # g and g2 should be copies of for each one other An Example: Construction a Cost Network
A Price meshwork is the first known model of a "scale-free" chart, invented in 1976 by de Solla Price. It is settled dynamically, where at all clock step a newfound vertex is added to the graphical record, and connected to an old vertex, with chance progressive to its in-degree. The succeeding program implements this construction using chart-tool.
Note: Note that it would be a good deal faster just to use the price_network() function, which is enforced in C++, atomic number 3 conflicting to the script below which is in pure Python. The code below is just a manifestation on how to use the library.
#! /usr/bin/env python # We will need extraordinary things from several places from __future__ consequence partitioning, absolute_import, print_function import sys if sys.version_info < (3,): pasture = xrange import atomic number 76 from pylab import * # for plotting from numpy.random import * # for random sampling seed(42) # We need to import the graph_tool module itself from graph_tool.all import * # net ball's construct a Price mesh (the 1 that existed in front Barabasi). It is # a directed network, with advantageous attachment. The algorithm below is # real naive, and a little slow, but quite simple. # We get-go with an empty, directed graph g = Graph() # We privation also to celebrate the maturat information for each vertex and edge. For that # let's produce some property maps v_age = g.new_vertex_property("int") e_age = g.new_edge_property("int") # The concluding size of the network N = 100000 # We have to start with one vertex v = g.add_vertex() v_age[v] = 0 # we will keep a tilt of the vertices. The number of times a vertex is in this # list will give the probability of information technology being selected. vlist = [v] # net ball's now add the new edges and vertices for i in range(1, N): # create our new vertex v = g.add_vertex() v_age[v] = i # we need to sample a newfangled acme to be the target, based on its in-degree + # 1. For that, we simply randomly try it from vlist. i = randint(0, len(vlist)) quarry = vlist[i] # total edge e = g.add_edge(v, aim) e_age[e] = i # put v and target in the list vlist.affix(target) vlist.append(v) # now we have a graph! # let's do a random manner of walking on the graphical record and print the age of the vertices we find, # antimonopoly for fun. v = g.vertex(randint(0, g.num_vertices())) while True: print("vertex:", int(v), "in-degree:", v.in_degree(), "out-degree:", v.out_degree(), "age:", v_age[v]) if v.out_degree() == 0: photographic print("Nowhere else to go... We found the main hub!") geological fault n_list = [] for w in v.out_neighbors(): n_list.append(w) v = n_list[randint(0, len(n_list))] # let's save our graph for posterity. We want to hold open the age properties as # well... To do this, they essential get "internal" properties: g.vertex_properties["historic period"] = v_age g.edge_properties["age"] = e_age # now we can save it g.save("assets/monetary value.xml.gz") # Let's plot its in-grade distribution in_hist = vertex_hist(g, "in") y = in_hist[0] err = sqrt(in_hist[0]) err[mistake >= y] = y[err >= y] - 1e-2 figure(figsize =(6,4)) errorbar(in_hist[1][:- 1], in_hist[0], fmt = "o", yerr = stray, label = "in") ground-controlled approach().set_yscale("backlog") ground-controlled approach().set_xscale("log up") gca().set_ylim(1e-1, 1e5) gca().set_xlim(0.8, 1e3) subplots_adjust(leftish = 0.2, bottom = 0.2) xlabel("$k_{in}$") ylabel("$NP(k_{in})$") tight_layout() savefig("assets/price-deg-dist.pdf") savefig("assets/price-deg-dist.svg") apex: 36063 in-point: 0 out-degree: 1 age: 36063 vertex: 9075 in-degree: 4 out-grade: 1 age: 9075 vertex: 5967 in-academic degree: 3 out-degree: 1 get on: 5967 vertex: 1113 in-degree: 7 out-grade: 1 age: 1113 vertex: 25 in-degree: 84 out-degree: 1 age: 25 peak: 10 in-degree: 541 out-degree: 1 age: 10 vertex: 5 in-point: 140 out-stage: 1 senesce: 5 vertex: 2 in-academic degree: 459 out-degree: 1 age: 2 vertex: 1 in-degree: 520 prohibited-degree: 1 age: 1 apex: 0 in-degree: 210 out-degree: 0 years: 0 Nowhere else to go... We found the main hub!
In-degree distribution of a price network with 105 nodes..
Supra is the grade statistical distribution, with 105 nodes (in order to the asymptotic behavior to beryllium even clearer, the number of vertices needs to be increased to something alike 106 or 107).
We can draw the graph to see other features of its topology. For that we use the graph_draw() function.
g = load_graph("assets/price.xml.gz") old age = g.vertex_properties["years"] pos = sfdp_layout(g) graph_draw(g, pos, output_size =(1000, 1000), vertex_color =[1,1,1,0], vertex_fill_color = historic period, vertex_size = 1, edge_pen_width = 1.2, vcmap = matplotlib.cm.gist_heat_r, output = "assets/price.png") <VertexPropertyMap object with value type 'vector<double>', for Chart 0x7f18ed1f0070, at 0x7f18e85f9370>
A Price network with 105 nodes. The vertex colours represent the age of the apex, from older (redness) to newer (black).
Graph filtering
Uncomparable of the very nice features of graph-tool is the "along-the-fly" filtering of edges and/or vertices. Filtering means the temporary masking of vertices/edges, which are in fact not genuinely removed, and send away be easily recovered. Vertices Beaver State edges which are to be filtered should be marked with a PropertyMap with value type bool, and then readiness with set_vertex_filter() OR set_edge_filter() methods. By default, vertex or edges with value "1" are kept in the graphs, and those with value "0" are filtered out. This demeanour can be modified with the inverted parameter of the respective functions. All manipulation functions and algorithms will work as if the noticeable edges or vertices were separate from the graph, with tokenish smash.
Note: It is serious to emphasize that the filtering functionality does not tote up whatever overhead when the graph is not being filtered. In this case, the algorithms run even as fast As if the filtering functionality didn't exist.
Here is an example which obtains the minimum spanning tree of a chart, using the part min_spanning_tree() and butt against filtering.
g, pos = triangulation(random((500, 2)) * 4, type = "delaunay") tree = min_spanning_tree(g) graph_draw(g, pos = pos, edge_color = Tree, output signal = "assets/min_tree.svg") <VertexPropertyMap object with valuate type 'vector<double>', for Graph 0x7f1938bc7370, at 0x7f18ed2130d0> The tree property map has a bool type, with value "1" if the edge belongs to the shoetree, and "0" differently. Below is an pictur of the original graphical record, with the starred edges.
We can nowadays filter out the edges which don't belong to the minimum spanning Tree.
g.set_edge_filter(shoetree) graph_draw(g, pos = pos, output = "assets/min_tree_filtered.svg") <VertexPropertyMap object with value type 'vector<large>', for Graph 0x7f1938bc7370, at 0x7f18ed2130d0> This is how the graph looks when filtered:
Everything should work transparently on the filtered graph, simply arsenic if the masked edges were removed. For instance, the following code volition calculate the betweenness() centrality of the edges and vertices, and draws them as colors and line thickness in the graph.
bv, be = betweenness(g) be.a /= be.a.max() / 5 graph_draw(g, pos = pos, vertex_fill_color = bv, edge_pen_width = personify, output = "assets/filtered-bt.svg") <VertexPropertyMap targe with value case 'vector<twofold>', for Graphical record 0x7f1938bc7370, at 0x7f18ed2130d0>
The original graph can be recovered past setting the boundary filter to None.
g.set_edge_filter(None) bv, be = betweenness(g) be.a /= cost.a.max() / 5 graph_draw(g, pos = pos, vertex_fill_color = bv, edge_pen_width = be, end product = "assets/nonfiltered-bt.svg") <VertexPropertyMap object with value type 'vector<double>', for Graph 0x7f1938bc7370, at 0x7f18ed2130d0>
Everything works in correspondent fashion with vertex filtering.
Additionally, the graph can also have its edges converse with the set_reversed() method. This is also an O(1) operation, which does not really modify the graph.
As mentioned previously, the directedness of the graph can also be changed "on-the-fly" with the set_directed() method.
Graph views
It is oftentimes desired to bring up with filtered and unfiltered graphs simultaneously, or to temporarily create a filtered edition of graph for some specific task. For these purposes, graph-tool provides a GraphView class, which represents a filtered "view" of a graph, and behaves as an nonpartisan graph object, which shares the inherent data with the original graph. Graphical record views are constructed aside instantiating a GraphView class, and short-lived a graph object which is supposed to be filtered, collectively with the desired filter parameters. For illustration, to create a directed view of the graphical record g constructed above, one should exercise:
ug = GraphView(g, directed = True) ug.is_directed() Chart views also provide a much more free-spoken and convenient approach to apex/edge filtering: To construct a filtered token spanning tree like in the example above, incomparable essential only pass the filter property as the "efilt" parameter:
goggle bo = GraphView(g, efilt = tree) Note of hand that this is an O(1) operation, since information technology is equivalent (in swiftness) to setting the filter in graph g directly, but in this encase the object g corpse unmodified.
Corresponding above, the result should embody the isolated minimum spanning tree:
bv, beryllium = betweenness(television receiver) be.a /= be.a.soap() / 5 graph_draw(tv, pos = pos, vertex_fill_color = bv, edge_pen_width = be, output = "assets/mst-view.svg") <VertexPropertyMap object with prize type 'vector<double>', for Chart 0x7f1938bc7370, at 0x7f18ed2130d0>
A view of the Delaunay graphical record, analytic only the minimum spanning tree
Take note: GraphView objects behave precisely like regular Graph objects. In fact, GraphView is a subclass of Graphical record. The only difference is that a GraphView object shares its internal data with its nurture Graph class. Therefore, if the first Graph object is modified, this modification will be reflected immediately in the GraphView object, and the other way around.
For even more convenience, same dismiss supply a function as filter parameter, which takes a peak or an edge as unique parameter, and returns True if the vertex/edge in should constitute kept and False otherwise. For instance, if we want to keep only the most "central" edges, we can do:
bv, exist = betweenness(g) u = GraphView(g, efilt = lambda e: be[e] > represent.a.grievous bodily harm() / 2) This creates a graph view u which contains only the edges of g which have a normalized betweenness centrality larger than fractional of the maximum time value. Promissory note that, otherwise from the case preceding, this is an O(E) operation, where E is the number of edges, since the supplied function must be called E times to concept a filter property map. Gum olibanum, supply a constructed filter mapping is always quicker, but supplying a occasion can be more convenient.
The graph view constructed above can be visualized as
be.a /= be.a.max() / 5 graph_draw(u, pos = pos, vertex_fill_color = bv, turnout = "assets/central-edges-look at.svg") <VertexPropertyMap physical object with value type 'vector<equivocal>', for Graph 0x7f1938bc7370, at 0x7f18ed2130d0>
Composing graph views
Since chart views are regular graphs, one can just as easily make up graphical record views of graphical record views. This provides a convenient way of composing filters. For instance, in order to sequester the stripped spanning Tree of all vertices of the example above which make a degree larger than four, peerless can do:
u = GraphView(g, vfilt = lambda v: v.out_degree() > 4) tree = min_spanning_tree(u) u = GraphView(u, efilt = tree) The resulting graph view can be visualized as
graph_draw(u, pos = pos, output = "assets/unflurried-filter.svg") <VertexPropertyMap object with value type 'vector<double>', for Graph 0x7f1938bc7370, at 0x7f18ed2130d0>
A composed sentiment, obtained Eastern Samoa the minimum spanning tree diagram of whol vertices in the graph which stimulate a degree larger than four.
Graph-tool Filter Vertex to Draw
Source: https://github.com/eliaswalyba/graph-tool-quickstart
0 Response to "Graph-tool Filter Vertex to Draw"
Post a Comment