教程

本页是关于 igraph Python 功能的详细教程。要快速了解 igraph 的功能,请查看快速入门。如果您尚未安装 igraph,请按照安装 igraph 部分的说明进行操作。

注意

对于不耐烦的读者,请参阅示例图库页面,其中包含简短、独立的示例。

启动 igraph

使用 igraph 最常见的方式是在 Python 环境中作为命名导入(例如,裸 Python shell、IPython shell、Jupyter notebook 或 JupyterLab 实例、Google ColabIDE

$ python
Python 3.9.6 (default, Jun 29 2021, 05:25:02)
[Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import igraph as ig

要调用函数,您需要以 ig(或您选择的任何名称)作为前缀

>>> import igraph as ig
>>> print(ig.__version__)
0.9.8

注意

可以使用 igraph 的 *星形导入*(star imports)

>>> from igraph import *

通常不建议这样做

还有第二种启动 igraph 的方式,即从终端调用脚本 igraph

$ igraph
No configuration file, using defaults
igraph 0.9.6 running inside Python 3.9.6 (default, Jun 29 2021, 05:25:02)
Type "copyright", "credits" or "license" for more information.
>>>

注意

Windows 用户会在 Python 的 scripts 子目录中找到该脚本,并且可能需要手动将其添加到路径中。

该脚本会启动一个合适的 shell(如果找到则为 IPythonIDLE,否则为纯 Python shell),并使用星形导入(参见上文)。这有时便于使用 *igraph* 的函数进行操作。

注意

您可以通过 *igraph* 的配置文件指定此脚本应使用的 shell。

本教程假定您已使用命名空间 ig 导入 igraph。

创建图

创建图的最简单方法是使用 Graph 构造函数。要创建一个空图

>>> g = ig.Graph()

要创建一个包含 10 个节点(编号从 09)以及连接节点 0-10-5 的两条边的图

>>> g = ig.Graph(n=10, edges=[[0, 1], [0, 5]])

我们可以打印图以获取其节点和边的摘要

>>> print(g)
IGRAPH U--- 10 2 --
+ edges:
0--1 0--5

这意味着:具有 10 个顶点和 2 条边的**无**向图,并列出了具体的边。如果图具有 name 属性,它也会被打印出来。

注意

igraph 还具有一个 igraph.summary() 函数,它类似于 print() 但不列出边。这对于具有数百万条边的大图来说很方便

>>> ig.summary(g)
IGRAPH U--- 10 2 --

添加/删除顶点和边

让我们再次从空图开始。要向现有图添加顶点,请使用 Graph.add_vertices()

>>> g = ig.Graph()
>>> g.add_vertices(3)

igraph 中,顶点总是从零开始编号。顶点的编号称为 *顶点 ID*。顶点可能有名或没有名。

类似地,要添加边,请使用 Graph.add_edges()

>>> g.add_edges([(0, 1), (1, 2)])

通过为每条边指定源顶点和目标顶点来添加边。此调用添加了两条边,一条连接顶点 01,另一条连接顶点 12。边也从零开始编号(*边 ID*),并且可以有一个可选名称。

警告

如这里所示,创建一个空图并逐个添加顶点和边,可能比之前演示的创建包含其顶点和边的图要慢得多。如果关注速度,您应尤其避免*一次添加一个*顶点和边。如果您仍需要这样做,可以使用 Graph.add_vertex()Graph.add_edge()

如果您尝试向具有无效 ID 的顶点添加边(即,当图只有三个顶点时,您尝试向顶点 5 添加边),您将收到 igraph.InternalError 异常

>>> g.add_edges([(5, 4)])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.10/site-packages/igraph/__init__.py", line 376, in add_edges
    res = GraphBase.add_edges(self, es)
igraph._igraph.InternalError: Error at src/graph/type_indexededgelist.c:270: cannot add edges. -- Invalid vertex id

消息会尝试解释出了什么问题(cannot add edges. -- Invalid vertex id),并附带了源代码中发生错误的相应行。

注意

完整的追溯信息,包括源代码信息,在我们的 GitHub 问题页面报告错误时非常有用。如果您创建新问题,请务必包含它!

让我们向我们的图添加更多的顶点和边

>>> g.add_edges([(2, 0)])
>>> g.add_vertices(3)
>>> g.add_edges([(2, 3), (3, 4), (4, 5), (5, 3)])
>>> print(g)
IGRAPH U---- 6 7 --
+ edges:
0--1 1--2 0--2 2--3 3--4 4--5 3--5

我们现在有一个包含 6 个顶点和 7 条边的无向图。顶点和边的 ID 始终是*连续的*,因此如果您删除一个顶点,所有后续顶点都将被重新编号。当顶点被重新编号时,边**不会**被重新编号,但它们的源和目标顶点会。使用 Graph.delete_vertices()Graph.delete_edges() 来执行这些操作。例如,要删除连接顶点 2-3 的边,请获取其 ID 然后删除它

>>> g.get_eid(2, 3)
3
>>> g.delete_edges(3)

生成图

igraph 包含确定性图生成器和随机图生成器(参见图生成)。*确定性*生成器每次调用函数都会生成相同的图,例如

>>> g = ig.Graph.Tree(127, 2)
>>> summary(g)
IGRAPH U--- 127 126 --

使用 Graph.Tree() 生成一个具有 127 个顶点的正则树图,每个顶点有两个子节点(当然还有一个父节点)。无论您调用 Graph.Tree() 多少次,如果使用相同的参数,生成的图将始终相同

>>> g2 = ig.Graph.Tree(127, 2)
>>> g2.get_edgelist() == g.get_edgelist()
True

上面的代码片段还显示了 get_edgelist() 方法,该方法返回所有边的源顶点和目标顶点的列表,按边 ID 排序。如果您打印前 10 个元素,您将得到

>>> g2.get_edgelist()[:10]
[(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6), (3, 7), (3, 8), (4, 9), (4, 10)]

随机生成器每次生成不同的图,例如 Graph.GRG()

>>> g = ig.Graph.GRG(100, 0.2)
>>> summary(g)
IGRAPH U---- 100 516 --
+ attr: x (v), y (v)

注意

+ attr 显示顶点 (v) 和边 (e) 的属性,在此示例中为两个顶点属性,没有边属性。

这生成了一个几何随机图:在单位正方形内随机均匀地选择 *n* 个点,然后将彼此距离小于预定义距离 *d* 的点对用边连接起来。如果使用相同的参数生成 GRG,它们将是不同的

>>> g2 = ig.Graph.GRG(100, 0.2)
>>> g.get_edgelist() == g2.get_edgelist()
False

检查图是否等价的一种稍微宽松的方法是通过 isomorphic()

>>> g.isomorphic(g2)
False

对于大型图,检查同构性可能需要一段时间(在这种情况下,通过检查两个图的度分布可以快速得出答案)。

设置和检索属性

如上所述,igraph 中图的顶点和边具有从 0 开始的数字 ID。因此,删除顶点或边可能会导致顶点和/或边 ID 的重新分配。除了 ID 之外,顶点和边还可以具有*属性*,例如名称、绘图坐标、元数据和权重。图本身也可以具有此类属性(例如,一个名称,它将显示在 printGraph.summary() 中)。从某种意义上说,每个 Graph、顶点和边都可以用作 Python 字典来存储和检索这些属性。

为了演示属性的使用,让我们创建一个简单的社交网络

>>> g = ig.Graph([(0,1), (0,2), (2,3), (3,4), (4,2), (2,5), (5,0), (6,3), (5,6)])

每个顶点代表一个人,因此我们希望存储姓名、年龄和性别

>>> g.vs["name"] = ["Alice", "Bob", "Claire", "Dennis", "Esther", "Frank", "George"]
>>> g.vs["age"] = [25, 31, 18, 47, 22, 23, 50]
>>> g.vs["gender"] = ["f", "m", "f", "m", "f", "m", "m"]
>>> g.es["is_formal"] = [False, False, True, True, True, False, True, False, False]

Graph.vsGraph.es 分别是获取所有顶点和边序列的标准方式。就像 Python 字典一样,我们可以使用方括号设置每个属性。该值必须是一个与顶点(对于 Graph.vs)或边(对于 Graph.es)长度相同的列表。这会一次性将属性分配给*所有*顶点/边。

要为单个顶点/边分配或修改属性,可以使用索引

>>> g.es[0]["is_formal"] = True

事实上,单个顶点通过类 Vertex 表示,单个边通过 Edge 表示。它们以及 Graph 都可以像字典一样使用键来设置属性,例如向图中添加日期

>>> g["date"] = "2009-01-10"
>>> print(g["date"])
2009-01-10

要检索属性字典,可以使用 Graph.attributes()Vertex.attributes()Edge.attributes()

此外,每个 Vertex 都有一个特殊属性 Vertex.index,用于查找顶点的 ID。每个 Edge 都有 Edge.index 以及另外两个属性 Edge.sourceEdge.target,用于查找此边连接的顶点的 ID。要一次性以元组形式获取两者,可以使用 Edge.tuple

要将属性分配给顶点或边的子集,可以使用切片

>>> g.es[:1]["is_formal"] = True

g.es[:1] 的输出是 EdgeSeq 的实例,而 VertexSeq 是表示顶点子集的等效类。

要删除属性,请使用 Python 关键字 del,例如

>>> g.vs[3]["foo"] = "bar"
>>> g.vs["foo"]
[None, None, None, 'bar', None, None, None]
>>> del g.vs["foo"]
>>> g.vs["foo"]
Traceback (most recent call last):
  File "<stdin>", line 25, in <module>
KeyError: 'Attribute does not exist'

警告

属性可以是任意 Python 对象,但如果您将图保存到文件,则只会保留字符串和数字属性。如果您正在寻找保存其他属性类型的方法,请参阅标准 Python 库中的 pickle 模块。您可以单独序列化(pickle)您的属性,将它们存储为字符串并保存,或者如果您知道只想将图重新加载回 Python,则可以序列化整个 Graph

图的结构属性

除了上面描述的简单图和属性操作例程之外,igraph 还提供了一大套方法来计算图的各种结构属性。本文档的范围超出了本教程的范围,因此本节将仅介绍其中一些以作说明之用。我们将使用上一节中构建的小型社交网络。

可能最简单的属性是顶点度。顶点的度等于与其相邻的边数。在有向网络的情况下,我们还可以定义入度(指向该顶点的边数)和出度(从该顶点发出的边数)。igraph 能够使用简单的语法计算所有这些属性

>>> g.degree()
[3, 1, 4, 3, 2, 3, 2]

如果图是有向的,我们可以使用 g.degree(mode="in")g.degree(mode="out") 分别计算入度和出度。如果您只想计算顶点子集的度数,也可以将单个顶点 ID 或顶点 ID 列表传递给 degree()

>>> g.degree(6)
2
>>> g.degree([2,3,4])
[4, 3, 2]

此调用约定适用于 igraph 可以计算的大多数结构属性。对于顶点属性,方法接受一个顶点 ID 或一个顶点 ID 列表(如果省略,则默认是所有顶点的集合)。对于边属性,方法接受单个边 ID 或边 ID 列表。除了 ID 列表,您还可以适当地提供 VertexSeqEdgeSeq 实例。在下一章中,您将学习如何将它们精确限制为您想要的顶点或边。

注意

对于某些度量,仅计算少数顶点或边而不是整个图是没有意义的,因为无论如何都会花费相同的时间。在这种情况下,方法将不接受顶点或边 ID,但您仍然可以使用标准列表索引和切片操作符在以后限制结果列表。一个这样的例子是特征向量中心性(Graph.evcent())。

除了度数,igraph 还包括内置的例程来计算许多其他中心性属性,包括顶点和边介数(Graph.betweennessGraph.edge_betweenness)或 Google 的 PageRank(Graph.pagerank())等等。这里我们只演示边介数

>>> g.edge_betweenness()
[6.0, 6.0, 4.0, 2.0, 4.0, 3.0, 4.0, 3.0. 4.0]

现在我们还可以通过一些 Python 技巧找出哪些连接具有最高的介数中心性

>>> ebs = g.edge_betweenness()
>>> max_eb = max(ebs)
>>> [g.es[idx].tuple for idx, eb in enumerate(ebs) if eb == max_eb]
[(0, 1), (0, 2)]

大多数结构属性也可以通过在感兴趣的 VertexSeqEdgeSeqVertexEdge 对象上调用相应的方法来检索其子集顶点或边,或单个顶点或边。

>>> g.vs.degree()
[3, 1, 4, 3, 2, 3, 2]
>>> g.es.edge_betweenness()
[6.0, 6.0, 4.0, 2.0, 4.0, 3.0, 4.0, 3.0. 4.0]
>>> g.vs[2].degree()
4

根据属性查询顶点和边

选择顶点和边

想象在一个给定的社交网络中,您想找出谁拥有最大的度数或介数中心性。您可以使用迄今为止介绍的工具和一些基本的 Python 知识来做到这一点,但由于根据属性或结构特性选择顶点和边是一项常见任务,igraph 为您提供了更简单的方法来实现这一目标

>>> g.vs.select(_degree=g.maxdegree())["name"]
['Claire']

这种语法乍一看可能有些别扭,所以让我们一步步尝试解释它。select()VertexSeq 的一个方法,其唯一目的是根据单个顶点的属性过滤 VertexSeq。它过滤顶点的方式取决于其位置参数和关键字参数。位置参数(例如上面没有显式名称的 _degree)总是先于关键字参数处理,具体如下

  • 如果第一个位置参数是 None,则返回一个空序列(不包含任何顶点)

    >>> seq = g.vs.select(None)
    >>> len(seq)
    0
    
  • 如果第一个位置参数是可调用对象(即函数、绑定方法或任何行为像函数的东西),则将为序列中当前存在的每个顶点调用该对象。如果函数返回 True,则包含该顶点,否则将其排除

    >>> graph = ig.Graph.Full(10)
    >>> only_odd_vertices = graph.vs.select(lambda vertex: vertex.index % 2 == 1)
    >>> len(only_odd_vertices)
    5
    
  • 如果第一个位置参数是可迭代对象(即列表、生成器或任何可迭代的对象),它*必须*返回整数,并且这些整数将被视为当前顶点集(不一定是整个图)的索引。只有与给定索引匹配的那些顶点才会被包含在过滤后的顶点集中。浮点数、字符串、无效顶点 ID 将被静默忽略

    >>> seq = graph.vs.select([2, 3, 7])
    >>> len(seq)
    3
    >>> [v.index for v in seq]
    [2, 3, 7]
    >>> seq = seq.select([0, 2])         # filtering an existing vertex set
    >>> [v.index for v in seq]
    [2, 7]
    >>> seq = graph.vs.select([2, 3, 7, "foo", 3.5])
    >>> len(seq)
    3
    
  • 如果第一个位置参数是整数,则所有剩余参数也应为整数,并且它们被解释为当前顶点集的索引。这只是语法糖,您可以通过将列表作为第一个位置参数来达到相同的效果,但这种方式您可以省略方括号

    >>> seq = graph.vs.select(2, 3, 7)
    >>> len(seq)
    3
    

关键字参数可用于根据顶点的属性或结构特性来过滤顶点。每个关键字参数的名称应最多由两部分组成:属性或结构特性的名称以及过滤运算符。运算符可以省略;在这种情况下,我们自动假定为相等运算符。可能性如下(其中 *name* 表示属性或特性的名称)

关键字参数

含义

name_eq

属性/特性值必须*等于*关键字参数的值

name_ne

属性/特性值必须*不等于*关键字参数的值

name_lt

属性/特性值必须*小于*关键字参数的值

name_le

属性/特性值必须*小于或等于*关键字参数的值

name_gt

属性/特性值必须*大于*关键字参数的值

name_ge

属性/特性值必须*大于或等于*关键字参数的值

name_in

属性/特性值必须*包含在*关键字参数的值中,在这种情况下,该值必须是一个序列

name_notin

属性/特性值必须*不包含在*关键字参数的值中,在这种情况下,该值必须是一个序列

例如,以下命令在我们的假想社交网络中找出年龄小于 30 岁的人

>>> g.vs.select(age_lt=30)

注意

由于 Python 的语法限制,您不能使用虽然更简单的语法 g.vs.select(age < 30),因为 Python 中只允许相等运算符出现在参数列表中。

为了省去一些输入,如果您愿意,甚至可以省略 select() 方法

>>> g.vs(age_lt=30)

理论上,可能存在同名的属性和结构特性(例如,您可能有一个名为 degree 的顶点属性)。在这种情况下,我们将无法判断用户是指 degree 作为结构特性还是作为顶点属性。为了解决这种歧义,在过滤时,结构特性名称*必须*始终以单个下划线(_)开头。例如,要查找度数大于 2 的顶点

>>> g.vs(_degree_gt=2)

还有一些用于选择边的特殊结构属性

  • EdgeSeq.select() 的关键字参数列表中使用 _source_from 可以根据边的源顶点进行过滤。例如,要选择所有源自 Claire(顶点索引为 2)的边

    >>> g.es.select(_source=2)
    
  • 使用 _target_to 根据目标顶点进行过滤。如果图是有向的,这与 _source_from 不同。

  • _within 接受一个 VertexSeq 对象或一个顶点索引列表或集合,并选择在给定顶点集中起源和终止的所有边。例如,以下表达式选择 Claire(顶点索引 2)、Dennis(顶点索引 3)和 Esther(顶点索引 4)之间的所有边

    >>> g.es.select(_within=[2,3,4])
    

    我们也可以使用 VertexSeq 对象

    >>> g.es.select(_within=g.vs[2:5])
    
  • _between 接受一个元组,该元组由两个 VertexSeq 对象或包含顶点索引或 Vertex 对象的列表组成,并选择所有从其中一个集合起源并在另一个集合终止的边。例如,要选择所有连接男性和女性的边

    >>> men = g.vs.select(gender="m")
    >>> women = g.vs.select(gender="f")
    >>> g.es.select(_between=(men, women))
    

查找具有特定属性的单个顶点或边

在许多情况下,我们正在寻找图中具有某些属性的单个顶点或边,如果存在多个匹配项,我们要么不关心返回哪个匹配项,要么我们事先知道只会有一个匹配项。一个典型的例子是根据其 name 属性查找顶点。VertexSeqEdgeSeq 对象提供了 find() 方法来处理此类用例。find() 的工作方式类似于 select(),但如果存在多个匹配项,它只返回第一个匹配项,如果未找到匹配项则抛出异常。例如,要查找与 Claire 对应的顶点,可以这样做

>>> claire = g.vs.find(name="Claire")
>>> type(claire)
igraph.Vertex
>>> claire.index
2

查找未知名称将引发异常

>>> g.vs.find(name="Joe")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: no such vertex

按名称查找顶点

按名称查找顶点是一种非常常见的操作,通常记住图中顶点的名称比记住它们的 ID 容易得多。为此,igraph 对顶点的 name 属性进行了特殊处理;它们被索引,以便可以在均摊常数时间内通过名称查找顶点。为了使事情更简单,igraph 在需要顶点 ID 的任何地方(几乎)都接受顶点名称,并且在需要顶点 ID 列表或 VertexSeq 实例的任何地方也接受顶点名称的集合(列表、元组等)。例如,您可以简单地按如下方式查找 Dennis 的度数(连接数)

>>> g.degree("Dennis")
3

或者,您可以

>>> g.vs.find("Dennis").degree()
3

顶点名称和 ID 之间的映射由 igraph 在后台透明地维护;无论何时图发生变化,igraph 也会更新内部映射。但是,*igraph* **不**强制执行顶点名称的唯一性;您可以轻松创建一个两个顶点同名的图,但是当您按名称查找它们时,igraph 只会返回其中一个,另一个只能通过其索引访问。

将图视为邻接矩阵

邻接矩阵是形成图的另一种方式。在邻接矩阵中,行和列由图的顶点标记:矩阵的元素表示顶点 *i* 和 *j* 是否有一条公共边 (*i, j*)。示例图的邻接矩阵是

>>> g.get_adjacency()
Matrix([
  [0, 1, 1, 0, 0, 1, 0],
  [1, 0, 0, 0, 0, 0, 0],
  [1, 0, 0, 1, 1, 1, 0],
  [0, 0, 1, 0, 1, 0, 1],
  [0, 0, 1, 1, 0, 0, 0],
  [1, 0, 1, 0, 0, 0, 1],
  [0, 0, 0, 1, 0, 1, 0]
])

例如,Claire([1, 0, 0, 1, 1, 1, 0])直接连接到 Alice(顶点索引为 0)、Dennis(索引 3)、Esther(索引 4)和 Frank(索引 5),但不连接到 Bob(索引 1)或 George(索引 6)。

布局和绘图

图是一个抽象的数学对象,在 2D 或 3D 空间中没有特定的表示。这意味着每当我们想要可视化一个图时,我们首先必须找到一个从顶点到二维或三维空间中坐标的映射,最好以一种令人愉悦的方式。图论的一个独立分支,即图绘制,试图通过几种图布局算法来解决这个问题。igraph 实现了相当多的布局算法,并且能够使用 Cairo 库将它们绘制到屏幕或 PDF、PNG 或 SVG 文件中。

重要提示

要学习本小节的示例,您需要 Cairo 库的 Python 绑定或 matplotlib(取决于选择的后端)。前一章(安装 igraph)会告诉您更多关于如何安装 Cairo 的 Python 绑定的信息。

布局算法

igraph 中的布局方法位于 Graph 对象中,并且总是以 layout_ 开头。下表总结了它们

方法名称

短名称

算法描述

layout_circle

circle, circular

将顶点放置在圆上的确定性布局

layout_davidson_harel

dh

Davidson-Harel 模拟退火算法

layout_drl

drl

用于大型图的分布式递归布局算法

layout_fruchterman_reingold

fr

Fruchterman-Reingold 强制引导算法

layout_fruchterman_reingold_3d

fr3d, fr_3d

Fruchterman-Reingold 三维强制引导算法

layout_graphopt

graphopt

用于大型图的 GraphOpt 算法

layout_grid

grid

规则网格布局

layout_kamada_kawai

kk

Kamada-Kawai 强制引导算法

layout_kamada_kawai_3d

kk3d, kk_3d

Kamada-Kawai 三维强制引导算法

layout_lgl

large, lgl, large_graph

用于大型图的大型图布局算法

layout_mds

mds

多维尺度布局

layout_random

random

完全随机放置顶点

layout_random_3d

random_3d

在 3D 空间中完全随机放置顶点

layout_reingold_tilford

rt, tree

Reingold-Tilford 树布局,适用于(几乎)树状图

layout_reingold_tilford_circular

rt_circular

Reingold-Tilford 树布局,带极坐标后变换,适用于(几乎)树状图

layout_sphere

sphere, spherical, circular_3d

将顶点均匀放置在球体表面的确定性布局

布局算法可以直接调用,也可以使用名为 layout() 的通用布局方法调用

>>> layout = g.layout_kamada_kawai()
>>> layout = g.layout("kamada_kawai")

layout() 方法的第一个参数必须是布局算法的短名称(参见上表)。所有剩余的位置参数和关键字参数将完整地传递给所选的布局方法。例如,以下两个调用是完全等效的

>>> layout = g.layout_reingold_tilford(root=[2])
>>> layout = g.layout("rt", root=[2])

布局方法返回一个 Layout 对象,其行为大部分类似于列表的列表。 Layout 对象中的每个列表条目对应于原始图中的一个顶点,并包含该顶点在 2D 或 3D 空间中的坐标。Layout 对象还包含一些有用的方法,可以批量平移、缩放或旋转坐标。然而,Layout 对象的主要用途是,您可以将它们与图一起传递给 plot() 函数以获得 2D 绘图。

使用布局绘制图

例如,我们可以使用 Kamada-Kawai 布局算法绘制我们的假想社交网络,如下所示

>>> layout = g.layout("kk")
>>> ig.plot(g, layout=layout)

这应该会打开一个外部图像查看器,显示网络的视觉表示,类似于下图所示(尽管由于布局不是确定性的,节点的确切位置在您的机器上可能不同)

The visual representation of our social network (Cairo backend)

使用 Kamada-Kawai 布局算法的社交网络

如果您喜欢使用 matplotlib 作为绘图引擎,请创建一个轴并使用 target 参数

>>> import matplotlib.pyplot as plt
>>> fig, ax = plt.subplots()
>>> ig.plot(g, layout=layout, target=ax)
The visual representation of our social network (matplotlib backend)

注意

绘制有根树时,Cairo 会自动将根放置在图像顶部,叶子放置在底部。对于 matplotlib,根通常位于底部。您可以通过调用 ax.invert_yaxis() 轻松地将根放置在顶部。

嗯,到目前为止这还不太漂亮。一个简单的改进是使用名称作为顶点标签,并根据性别为顶点着色。顶点标签默认取自 label 属性,顶点颜色由 color 属性决定,因此我们可以简单地创建这些属性并重新绘制图

>>> g.vs["label"] = g.vs["name"]
>>> color_dict = {"m": "blue", "f": "pink"}
>>> g.vs["color"] = [color_dict[gender] for gender in g.vs["gender"]]
>>> ig.plot(g, layout=layout, bbox=(300, 300), margin=20)  # Cairo backend
>>> ig.plot(g, layout=layout, target=ax)  # matplotlib backend

请注意,我们在这里只是重用了之前的布局对象,但对于 Cairo 后端,我们还指定需要一个更小的绘图(300 x 300 像素)和图周围更大的边距以适应标签(20 像素)。这些设置对于 Matplotlib 后端将被忽略。结果是

The visual representation of our social network - with names and genders

我们的社交网络——以名称为标签,以性别为颜色

对于 matplotlib

The visual representation of our social network - with names and genders

我们的社交网络——以名称为标签,以性别为颜色

除了将视觉属性指定为顶点和边属性外,您还可以将它们作为关键字参数传递给 plot()

>>> color_dict = {"m": "blue", "f": "pink"}
>>> ig.plot(g, layout=layout, vertex_color=[color_dict[gender] for gender in g.vs["gender"]])

如果您希望将图的可视化表示属性与图本身分开,则首选后一种方法。您可以简单地设置一个包含您要传递给 plot() 的关键字参数的 Python 字典,然后使用双星号(**)运算符将您的特定样式属性传递给 plot()

>>> visual_style = {}
>>> visual_style["vertex_size"] = 20
>>> visual_style["vertex_color"] = [color_dict[gender] for gender in g.vs["gender"]]
>>> visual_style["vertex_label"] = g.vs["name"]
>>> visual_style["edge_width"] = [1 + 2 * int(is_formal) for is_formal in g.es["is_formal"]]
>>> visual_style["layout"] = layout
>>> visual_style["bbox"] = (300, 300)
>>> visual_style["margin"] = 20
>>> ig.plot(g, **visual_style)

最终的绘图显示正式关系用粗线表示,非正式关系用细线表示

The visual representation of our social network - with names, genders and formal ties

我们的社交网络——也显示了哪些关系是正式的

总结一下:存在与图的可视化表示相对应的特殊顶点和边属性。这些属性会覆盖 igraph 的默认设置(有关覆盖系统范围默认设置的信息,请参见配置)。此外,提供给 plot() 的适当关键字参数会覆盖顶点和边属性提供的视觉属性。以下两个表分别总结了最常用的顶点和边的视觉属性

控制图绘制的顶点属性

属性名称

关键字参数

用途

color

vertex_color

顶点的颜色

font

vertex_font

顶点的字体家族

label

vertex_label

顶点的标签

label_angle

vertex_label_angle

顶点标签在围绕顶点的圆上的位置。这是一个以弧度表示的角度,零度位于顶点的右侧。

label_color

vertex_label_color

顶点标签的颜色

label_dist

vertex_label_dist

顶点标签与顶点本身的距离,相对于顶点大小

label_size

vertex_label_size

顶点标签的字体大小

order

vertex_order

顶点的绘制顺序。具有较小顺序参数的顶点将首先绘制。

shape

vertex_shape

顶点的形状。已知形状有:rectanglecirclediamondhiddentriangle-uptriangle-down。还接受多种别名,参见 drawing.known_shapes

size

vertex_size

顶点的像素大小

控制图绘制的边属性

属性名称

关键字参数

用途

color

edge_color

边的颜色

curved

edge_curved

边的弯曲度。正值对应逆时针方向弯曲的边,负值对应顺时针方向弯曲的边。零表示直线边。True 被解释为 0.5,False 被解释为零。这对于使多条边可见很有用。另请参见 plot()autocurve 关键字参数。

font

edge_font

边的字体家族

arrow_size

edge_arrow_size

如果图是有向的,箭头的尺寸(长度),相对于 15 像素。

arrow_width

edge_arrow_width

如果图是有向的,箭头的宽度,相对于 10 像素。

loop_size

edge_loop_size

自环的大小。可以设置为负数,在这种情况下,它会根据相应顶点的大小进行缩放(例如,-1.0 表示循环与顶点大小相同)。此属性会被非自环的边忽略。此属性仅在 matplotlib 后端可用。

width

edge_width

边的像素宽度。

label

edge_label

如果指定,它会为边添加一个标签。

background

edge_background

如果指定,它会在边标签周围添加一个指定颜色的矩形框(仅限 matplotlib)。

align_label

edge_align_label

如果为 True,则旋转边标签使其与边方向对齐。倒置的标签会被翻转(仅限 matplotlib)。

plot() 的通用关键字参数

这些设置可以作为关键字参数传递给 plot() 函数,以控制绘图的整体外观。

关键字参数

用途

autocurve

是否自动确定具有多条边的图的边的曲率。对于边数少于 10,000 的图,默认值为 True,否则为 False

bbox

绘图的边界框。这必须是一个包含所需绘图宽度和高度的元组。默认绘图为 600 像素宽和 600 像素高。对于 Matplotlib 后端,此项将被忽略。

layout

要使用的布局。它可以是 Layout 的实例,一个包含 X-Y 坐标的元组列表,或布局算法的名称。默认值为 auto,它根据图的大小和连通性自动选择布局算法。

margin

绘图的顶部、右侧、底部和左侧边距,以像素为单位。此参数必须是列表或元组,如果您指定了少于四个元素的列表或元组,其元素将被重复使用。对于 Matplotlib 后端,此项将被忽略。

在绘图中指定颜色

igraph 在需要颜色(例如,相应属性中的边、顶点或标签颜色)的任何地方都支持以下颜色规范

X11 颜色名称

有关完整列表,请参阅维基百科中X11 颜色名称的列表。或者,您可以查看 igraph.drawing.colors.known_colors 字典的键。igraph 中的颜色名称不区分大小写,因此 "DarkBlue" 也可以写成 "darkblue"

CSS 语法中的颜色规范

这是一个符合以下格式之一的字符串(其中 *R*、*G* 和 *B* 分别表示红色、绿色和蓝色分量)

  • #RRGGBB,分量范围从 0 到 255(十六进制格式)。示例:"#0088ff"

  • #RGB,分量范围从 0 到 15(十六进制格式)。示例:"#08f"

  • rgb(R, G, B),分量范围从 0 到 255 或从 0% 到 100%。示例:"rgb(0, 127, 255)""rgb(0%, 50%, 100%)"

0-1 范围内的 RGB 值列表或元组

示例:(1.0, 0.5, 0)[1.0, 0.5, 0]

请注意,当为所有顶点或边指定相同颜色时,您可以使用字符串形式,但不能使用元组或列表语法,因为元组或列表将被解释为元组中的*项*是针对单个顶点或边的。因此,这将有效

>>> ig.plot(g, vertex_color="green")

但这将无效,因为它会将元组中的项视为第一个、第二个和第三个顶点的调色板索引

>>> ig.plot(g, vertex_color=(1, 0, 0))

在后一种情况下,您需要为每个顶点显式地扩展颜色规范

>>> ig.plot(g, vertex_color=[(1, 0, 0)] * g.vcount())

保存绘图

igraph 可以用于创建出版质量的绘图,只需请求 plot() 函数将绘图保存到文件而不是显示在屏幕上即可。这可以通过在图本身之后将目标文件名作为附加参数传递来实现。首选格式将从文件扩展名推断。igraph 可以保存为 Cairo 支持的任何格式,包括 SVG、PDF 和 PNG 文件。如果您喜欢,SVG 或 PDF 文件稍后可以转换为 PostScript(.ps)或 Encapsulated PostScript(.eps)格式,而 PNG 文件可以转换为 TIF(.tif)格式。

>>> ig.plot(g, "social_network.pdf", **visual_style)

如果您使用的是 matplotlib 后端,您可以像往常一样保存您的绘图

>>> fig, ax = plt.subplots()
>>> ig.plot(g, **visual_style)
>>> fig.savefig("social_network.pdf")

matplotlib 支持多种文件格式。

igraph 与外部世界

任何图模块都离不开某种导入/导出功能,以使包能够与外部程序和工具包进行通信。igraph 也不例外:它提供了读取最常见图格式的函数,以及将 Graph 对象保存到符合这些格式规范的文件中的函数。下表总结了 igraph 可以读取或写入的格式

格式

短名称

读取方法

写入方法

邻接列表

lgl

Graph.Read_Lgl()

Graph.write_lgl()

(又称 LGL

邻接矩阵

adjacency

Graph.Read_Adjacency()

Graph.write_adjacency()

DIMACS

dimacs

Graph.Read_DIMACS()

Graph.write_dimacs()

DL

dl

Graph.Read_DL()

暂不支持

边列表

edgelist, edges, edge

Graph.Read_Edgelist()

Graph.write_edgelist()

GraphViz

graphviz, dot

暂不支持

Graph.write_dot()

GML

gml

Graph.Read_GML()

Graph.write_gml()

GraphML

graphml

Graph.Read_GraphML()

Graph.write_graphml()

Gzip 压缩的 GraphML

graphmlz

Graph.Read_GraphMLz()

Graph.write_graphmlz()

LEDA

leda

暂不支持

Graph.write_leda()

带标签的边列表

ncol

Graph.Read_Ncol()

Graph.write_ncol()

(又称 NCOL

Pajek 格式

pajek, net

Graph.Read_Pajek()

Graph.write_pajek()

序列化图

pickle

Graph.Read_Pickle()

Graph.write_pickle()

作为练习,请从此文件下载著名的扎卡里空手道俱乐部研究的图表示,解压后尝试将其加载到 *igraph* 中。由于它是 GraphML 文件,您必须使用上表中的 GraphML 读取方法(请确保使用下载文件的正确路径)

>>> karate = ig.Graph.Read_GraphML("zachary.graphml")
>>> ig.summary(karate)
IGRAPH UNW- 34 78 -- Zachary's karate club network

如果您想将同一个图转换为 Pajek 格式,可以使用上表中的 Pajek 写入方法

>>> karate.write_pajek("zachary.net")

注意

大多数格式都有其自身的限制;例如,并非所有格式都能存储属性。如果您想将 *igraph* 图保存为可以从外部包读取的格式,并且希望保留数字和字符串属性,那么 GraphML 或 GML 可能是您最好的选择。如果您没有属性,边列表和 NCOL 也可以(尽管 NCOL 支持顶点名称和边权重)。如果您不想在 *igraph* 之外使用您的图,但又想将它们存储以备将来会话使用,那么序列化图(pickled graph)格式可确保您获得完全相同的图。序列化图格式使用 Python 的 pickle 模块来存储和读取图。

还有两个辅助方法:read() 是读取方法的通用入口点,它尝试从文件扩展名推断合适的格式。Graph.write()read() 相反:它允许您保存图,其中首选格式再次从扩展名推断。 read()Graph.write() 的格式检测可以通过 format 关键字参数覆盖,该参数接受上表中格式的短名称

>>> karate = ig.load("zachary.graphml")
>>> karate.write("zachary.net")
>>> karate.write("zachary.my_extension", format="gml")

下一步

本教程只是触及了 igraph 功能的皮毛。我的长期计划是在接下来的章节中将本教程扩展为 igraph 的正式手册式文档。与此同时,请查阅API 参考,其中应提供关于几乎每个 igraph 类、函数或方法的信息。一个好的起点是 Graph 类的文档。如果您遇到困难,请首先尝试在我们的论坛组中提问——也许有人可以立即帮助您。