python-igraph manual

For using igraph from Python

   Home       Trees       Indices       Help   
Package igraph :: Module summary'
[hide private]

Source Code for Module igraph.summary'

  1  # vim:ts=4:sw=4:sts=4:et 
  2  # -*- coding: utf-8 -*- 
  3  """Summary representation of a graph. 
  4   
  5  @undocumented: _get_wrapper_for_width, FakeWrapper 
  6  """ 
  7   
  8  from igraph.vendor import vendor_import 
  9  from igraph.statistics import median 
 10  from itertools import islice 
 11  from math import ceil 
 12  from textwrap import TextWrapper 
 13   
 14  __all__ = ["GraphSummary"] 
 15   
 16  __license__ = u"""\ 
 17  Copyright (C) 2006-2012  Tamás Nepusz <ntamas@gmail.com> 
 18  Pázmány Péter sétány 1/a, 1117 Budapest, Hungary 
 19   
 20  This program is free software; you can redistribute it and/or modify 
 21  it under the terms of the GNU General Public License as published by 
 22  the Free Software Foundation; either version 2 of the License, or 
 23  (at your option) any later version. 
 24   
 25  This program is distributed in the hope that it will be useful, 
 26  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 27  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 28  GNU General Public License for more details. 
 29   
 30  You should have received a copy of the GNU General Public License 
 31  along with this program; if not, write to the Free Software 
 32  Foundation, Inc.,  51 Franklin Street, Fifth Floor, Boston, MA 
 33  02110-1301 USA 
 34  """ 
 35   
 36  texttable = vendor_import("texttable") 
 37   
38 -class FakeWrapper(object):
39 """Object whose interface is compatible with C{textwrap.TextWrapper} 40 but does no wrapping.""" 41
42 - def __init__(self, *args, **kwds):
43 pass 44
45 - def fill(self, text):
46 return [text] 47
48 - def wrap(self, text):
49 return [text]
50
51 -def _get_wrapper_for_width(width, *args, **kwds):
52 """Returns a text wrapper that wraps text for the given width. 53 54 @param width: the maximal width of each line that the text wrapper 55 produces. C{None} means that no wrapping will be performed. 56 """ 57 if width is None: 58 return FakeWrapper(*args, **kwds) 59 return TextWrapper(width=width, *args, **kwds) 60
61 -class GraphSummary(object):
62 """Summary representation of a graph. 63 64 The summary representation includes a header line and the list of 65 edges. The header line consists of C{IGRAPH}, followed by a 66 four-character long code, the number of vertices, the number of 67 edges, two dashes (C{--}) and the name of the graph (i.e. 68 the contents of the C{name} attribute, if any). For instance, 69 a header line may look like this:: 70 71 IGRAPH U--- 4 5 -- 72 73 The four-character code describes some basic properties of the 74 graph. The first character is C{U} if the graph is undirected, 75 C{D} if it is directed. The second letter is C{N} if the graph 76 has a vertex attribute called C{name}, or a dash otherwise. The 77 third letter is C{W} if the graph is weighted (i.e. it has an 78 edge attribute called C{weight}), or a dash otherwise. The 79 fourth letter is C{B} if the graph has a vertex attribute called 80 C{type}; this is usually used for bipartite graphs. 81 82 Edges may be presented as an ordinary edge list or an adjacency 83 list. By default, this depends on the number of edges; however, 84 you can control it with the appropriate constructor arguments. 85 86 @undocumented: _construct_edgelist_adjlist, _construct_edgelist_compressed, 87 _construct_edgelist_edgelist, _construct_graph_attributes, 88 _construct_vertex_attributes, _construct_header, _edge_attribute_iterator, 89 _infer_column_alignment, _new_table, _vertex_attribute_iterator 90 """ 91 92
93 - def __init__(self, graph, verbosity=0, width=78, 94 edge_list_format="auto", 95 max_rows=99999, 96 print_graph_attributes=False, 97 print_vertex_attributes=False, 98 print_edge_attributes=False, 99 full=False):
100 """Constructs a summary representation of a graph. 101 102 @param verbosity: the verbosity of the summary. If zero, only 103 the header line will be returned. If one, the header line 104 and the list of edges will both be returned. 105 @param width: the maximal width of each line in the summary. 106 C{None} means that no limit will be enforced. 107 @param max_rows: the maximal number of rows to print in a single 108 table (e.g., vertex attribute table or edge attribute table) 109 @param edge_list_format: format of the edge list in the summary. 110 Supported formats are: C{compressed}, C{adjlist}, C{edgelist}, 111 C{auto}, which selects automatically from the other three based 112 on some simple criteria. 113 @param print_graph_attributes: whether to print graph attributes 114 if there are any. 115 @param print_vertex_attributes: whether to print vertex attributes 116 if there are any. 117 @param print_edge_attributes: whether to print edge attributes 118 if there are any. 119 @param full: False has no effect; True turns on the attribute 120 printing for graph, vertex and edge attributes with verbosity 1. 121 """ 122 if full: 123 print_graph_attributes = True 124 print_vertex_attributes = True 125 print_edge_attributes = True 126 verbosity = max(verbosity, 1) 127 128 self._graph = graph 129 self.edge_list_format = edge_list_format.lower() 130 self.max_rows = int(max_rows) 131 self.print_graph_attributes = print_graph_attributes 132 self.print_vertex_attributes = print_vertex_attributes 133 self.print_edge_attributes = print_edge_attributes 134 self.verbosity = verbosity 135 self.width = width 136 self.wrapper = _get_wrapper_for_width(self.width, 137 break_on_hyphens=False) 138 139 if self._graph.is_named(): 140 self._edges_header = "+ edges (vertex names):" 141 else: 142 self._edges_header = "+ edges:" 143 self._arrow = ["--", "->"][self._graph.is_directed()] 144 self._arrow_format = "%%s%s%%s" % self._arrow 145
147 """Constructs the part in the summary that prints the edge list in an 148 adjacency list format.""" 149 result = [self._edges_header] 150 arrow = self._arrow_format 151 152 if self._graph.vcount() == 0: 153 return 154 155 if self._graph.is_named(): 156 names = self._graph.vs["name"] 157 maxlen = max(len(name) for name in names) 158 format_str = "%%%ds %s %%s" % (maxlen, self._arrow) 159 for v1, name in enumerate(names): 160 neis = self._graph.successors(v1) 161 neis = ", ".join(str(names[v2]) for v2 in neis) 162 result.append(format_str % (name, neis)) 163 else: 164 maxlen = len(str(self._graph.vcount())) 165 num_format = "%%%dd" % maxlen 166 format_str = "%s %s %%s" % (num_format, self._arrow) 167 for v1 in xrange(self._graph.vcount()): 168 neis = self._graph.successors(v1) 169 neis = " ".join(num_format % v2 for v2 in neis) 170 result.append(format_str % (v1, neis)) 171 172 # Try to wrap into multiple columns if that works with the given width 173 if self.width is not None: 174 maxlen = max(len(line) for line in result[1:]) 175 colcount = int(self.width + 3) / int(maxlen + 3) 176 if colcount > 1: 177 # Rewrap to multiple columns 178 nrows = len(result) - 1 179 colheight = int(ceil(nrows / float(colcount))) 180 newrows = [[] for _ in xrange(colheight)] 181 for i, row in enumerate(result[1:]): 182 newrows[i % colheight].append(row.ljust(maxlen)) 183 result[1:] = [" ".join(row) for row in newrows] 184 185 return result 186
188 """Constructs the part in the summary that prints the edge list in a 189 compressed format suitable for graphs with mostly small degrees.""" 190 result = [self._edges_header] 191 arrow = self._arrow_format 192 193 if self._graph.is_named(): 194 names = self._graph.vs["name"] 195 edges = ", ".join(arrow % (names[edge.source], names[edge.target]) 196 for edge in self._graph.es) 197 else: 198 edges = " ".join(arrow % edge.tuple for edge in self._graph.es) 199 200 result.append(edges) 201 return result 202
204 """Constructs the part in the summary that prints the edge list in a 205 full edge list format.""" 206 attrs = sorted(self._graph.edge_attributes()) 207 208 table = self._new_table(headers=["", "edge"] + attrs) 209 table.add_rows(islice(self._edge_attribute_iterator(attrs), 0, self.max_rows), 210 header=False) 211 table.set_cols_align(["l", "l"] + self._infer_column_alignment(edge_attrs=attrs)) 212 213 result = [self._edges_header] 214 result.extend(table.draw().split("\n")) 215 216 return result 217
219 """Constructs the part in the summary that lists the graph attributes.""" 220 attrs = self._graph.attributes() 221 if not attrs: 222 return [] 223 224 result = ["+ graph attributes:"] 225 attrs.sort() 226 for attr in attrs: 227 result.append("[[%s]]" % (attr, )) 228 result.append(str(self._graph[attr])) 229 return result 230
232 """Constructs the part in the summary that lists the vertex attributes.""" 233 attrs = sorted(self._graph.vertex_attributes()) 234 if not attrs or (len(attrs) == 1 and "name" in attrs): 235 return [] 236 237 table = self._new_table(headers=[""] + attrs) 238 table.add_rows(islice(self._vertex_attribute_iterator(attrs), 0, self.max_rows), 239 header=False) 240 table.set_cols_align(["l"] + self._infer_column_alignment(vertex_attrs=attrs)) 241 242 result = ["+ vertex attributes:"] 243 result.extend(table.draw().split("\n")) 244 245 return result 246
247 - def _construct_header(self):
248 """Constructs the header part of the summary.""" 249 graph = self._graph 250 params = dict( 251 directed="UD"[graph.is_directed()], 252 named="-N"[graph.is_named()], 253 weighted="-W"[graph.is_weighted()], 254 typed="-T"["type" in graph.vertex_attributes()], 255 vcount=graph.vcount(), 256 ecount=graph.ecount(), 257 ) 258 if "name" in graph.attributes(): 259 params["name"] = graph["name"] 260 else: 261 params["name"] = "" 262 result = ["IGRAPH %(directed)s%(named)s%(weighted)s%(typed)s "\ 263 "%(vcount)d %(ecount)d -- %(name)s" % params] 264 265 attrs = ["%s (g)" % (name, ) for name in sorted(graph.attributes())] 266 attrs.extend("%s (v)" % (name, ) for name in sorted(graph.vertex_attributes())) 267 attrs.extend("%s (e)" % (name, ) for name in sorted(graph.edge_attributes())) 268 if attrs: 269 result.append("+ attr: %s" % ", ".join(attrs)) 270 if self.wrapper is not None: 271 self.wrapper.subsequent_indent = ' ' 272 result[-1:] = self.wrapper.wrap(result[-1]) 273 self.wrapper.subsequent_indent = '' 274 275 return result 276
277 - def _edge_attribute_iterator(self, attribute_order):
278 """Returns an iterator that yields the rows of the edge attribute table 279 in the summary. `attribute_order` must be a list containing the names of 280 the attributes to be presented in this table.""" 281 arrow = self._arrow_format 282 283 if self._graph.is_named(): 284 names = self._graph.vs["name"] 285 for edge in self._graph.es: 286 formatted_edge = arrow % (names[edge.source], names[edge.target]) 287 yield ["[%d]" % edge.index, formatted_edge] + \ 288 [edge[attr] for attr in attribute_order] 289 else: 290 for edge in self._graph.es: 291 formatted_edge = arrow % edge.tuple 292 yield ["[%d]" % edge.index, formatted_edge] + \ 293 [edge[attr] for attr in attribute_order] 294
295 - def _infer_column_alignment(self, vertex_attrs=None, edge_attrs=None):
296 """Infers the preferred alignment for the given vertex and edge attributes 297 in the tables by peeking into the attribute values of the first 100 vertices 298 or edges. Numeric attributes will be aligned right, everything else will be 299 aligned left.""" 300 values = [] 301 if vertex_attrs is not None: 302 vs = self._graph.vs[:100] 303 values.extend(vs[attr] for attr in vertex_attrs) 304 if edge_attrs is not None: 305 es = self._graph.es[:100] 306 values.extend(es[attr] for attr in edge_attrs) 307 308 result = [] 309 for vs in values: 310 is_numeric = True 311 try: 312 [float(x) for x in vs] 313 except ValueError: 314 is_numeric = False 315 if is_numeric: 316 result.append("r") 317 else: 318 result.append("l") 319 320 return result 321
322 - def _new_table(self, headers=None):
323 """Constructs a new table to pretty-print vertex and edge attributes""" 324 table = texttable.Texttable(max_width=0) 325 table.set_deco(0) 326 if headers is not None: 327 table.header(headers) 328 return table 329
330 - def _vertex_attribute_iterator(self, attribute_order):
331 """Returns an iterator that yields the rows of the vertex attribute table 332 in the summary. `attribute_order` must be a list containing the names of 333 the attributes to be presented in this table.""" 334 for vertex in self._graph.vs: 335 yield ["[%d]" % vertex.index] + [vertex[attr] for attr in attribute_order] 336
337 - def __str__(self):
338 """Returns the summary representation as a string.""" 339 output = self._construct_header() 340 341 if self.print_graph_attributes: 342 output.extend(self._construct_graph_attributes()) 343 if self.print_vertex_attributes: 344 output.extend(self._construct_vertex_attributes()) 345 346 if self.verbosity <= 0: 347 return "\n".join(output) 348 349 if self._graph.ecount() > 0: 350 # Add the edge list 351 if self.edge_list_format == "auto": 352 if (self.print_edge_attributes and self._graph.edge_attributes()): 353 format = "edgelist" 354 elif median(self._graph.degree(mode="out")) < 3: 355 format = "compressed" 356 else: 357 format = "adjlist" 358 else: 359 format = self.edge_list_format 360 361 method_name = "_construct_edgelist_%s" % format 362 if hasattr(self, method_name): 363 output.extend(getattr(self, method_name)()) 364 365 if self.wrapper is not None: 366 return "\n".join("\n".join(self.wrapper.wrap(line)) for line in output) 367 368 return "\n".join(output)
369

   Home       Trees       Indices       Help