教程
本页是关于 igraph Python 功能的详细教程。要快速了解 igraph 的功能,请查看快速入门。如果您尚未安装 igraph,请按照安装 igraph 部分的说明进行操作。
注意
对于不耐烦的读者,请参阅示例图库页面,其中包含简短、独立的示例。
启动 igraph
使用 igraph 最常见的方式是在 Python 环境中作为命名导入(例如,裸 Python shell、IPython shell、Jupyter notebook 或 JupyterLab 实例、Google Colab 或 IDE)
$ 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 的方式,即从终端调用脚本 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(如果找到则为 IPython 或 IDLE,否则为纯 Python shell),并使用星形导入(参见上文)。这有时便于使用 *igraph* 的函数进行操作。
注意
您可以通过 *igraph* 的配置文件指定此脚本应使用的 shell。
本教程假定您已使用命名空间 ig
导入 igraph。
创建图
创建图的最简单方法是使用 Graph
构造函数。要创建一个空图
>>> g = ig.Graph()
要创建一个包含 10 个节点(编号从 0
到 9
)以及连接节点 0-1
和 0-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)])
通过为每条边指定源顶点和目标顶点来添加边。此调用添加了两条边,一条连接顶点 0
和 1
,另一条连接顶点 1
和 2
。边也从零开始编号(*边 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 之外,顶点和边还可以具有*属性*,例如名称、绘图坐标、元数据和权重。图本身也可以具有此类属性(例如,一个名称,它将显示在 print
或 Graph.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.vs
和 Graph.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.source
和 Edge.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 列表,您还可以适当地提供 VertexSeq
或 EdgeSeq
实例。在下一章中,您将学习如何将它们精确限制为您想要的顶点或边。
注意
对于某些度量,仅计算少数顶点或边而不是整个图是没有意义的,因为无论如何都会花费相同的时间。在这种情况下,方法将不接受顶点或边 ID,但您仍然可以使用标准列表索引和切片操作符在以后限制结果列表。一个这样的例子是特征向量中心性(Graph.evcent()
)。
除了度数,igraph 还包括内置的例程来计算许多其他中心性属性,包括顶点和边介数(Graph.betweenness
、Graph.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)]
大多数结构属性也可以通过在感兴趣的 VertexSeq
、EdgeSeq
、Vertex
或 Edge
对象上调用相应的方法来检索其子集顶点或边,或单个顶点或边。
>>> 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* 表示属性或特性的名称)
关键字参数 |
含义 |
---|---|
|
属性/特性值必须*等于*关键字参数的值 |
|
属性/特性值必须*不等于*关键字参数的值 |
|
属性/特性值必须*小于*关键字参数的值 |
|
属性/特性值必须*小于或等于*关键字参数的值 |
|
属性/特性值必须*大于*关键字参数的值 |
|
属性/特性值必须*大于或等于*关键字参数的值 |
|
属性/特性值必须*包含在*关键字参数的值中,在这种情况下,该值必须是一个序列 |
|
属性/特性值必须*不包含在*关键字参数的值中,在这种情况下,该值必须是一个序列 |
例如,以下命令在我们的假想社交网络中找出年龄小于 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
属性查找顶点。VertexSeq
和 EdgeSeq
对象提供了 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_
开头。下表总结了它们
方法名称 |
短名称 |
算法描述 |
---|---|---|
|
|
将顶点放置在圆上的确定性布局 |
|
|
Davidson-Harel 模拟退火算法 |
|
|
用于大型图的分布式递归布局算法 |
|
|
Fruchterman-Reingold 强制引导算法 |
|
|
Fruchterman-Reingold 三维强制引导算法 |
|
|
用于大型图的 GraphOpt 算法 |
|
|
规则网格布局 |
|
|
Kamada-Kawai 强制引导算法 |
|
|
Kamada-Kawai 三维强制引导算法 |
|
|
用于大型图的大型图布局算法 |
|
|
多维尺度布局 |
|
|
完全随机放置顶点 |
|
|
在 3D 空间中完全随机放置顶点 |
|
|
Reingold-Tilford 树布局,适用于(几乎)树状图 |
|
|
Reingold-Tilford 树布局,带极坐标后变换,适用于(几乎)树状图 |
|
|
将顶点均匀放置在球体表面的确定性布局 |
布局算法可以直接调用,也可以使用名为 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)
这应该会打开一个外部图像查看器,显示网络的视觉表示,类似于下图所示(尽管由于布局不是确定性的,节点的确切位置在您的机器上可能不同)

使用 Kamada-Kawai 布局算法的社交网络
如果您喜欢使用 matplotlib 作为绘图引擎,请创建一个轴并使用 target
参数
>>> import matplotlib.pyplot as plt
>>> fig, ax = plt.subplots()
>>> ig.plot(g, layout=layout, target=ax)

注意
绘制有根树时,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 后端将被忽略。结果是

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

我们的社交网络——以名称为标签,以性别为颜色
除了将视觉属性指定为顶点和边属性外,您还可以将它们作为关键字参数传递给 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)
最终的绘图显示正式关系用粗线表示,非正式关系用细线表示

我们的社交网络——也显示了哪些关系是正式的
总结一下:存在与图的可视化表示相对应的特殊顶点和边属性。这些属性会覆盖 igraph 的默认设置(有关覆盖系统范围默认设置的信息,请参见配置)。此外,提供给 plot()
的适当关键字参数会覆盖顶点和边属性提供的视觉属性。以下两个表分别总结了最常用的顶点和边的视觉属性
控制图绘制的顶点属性
属性名称 |
关键字参数 |
用途 |
---|---|---|
|
|
顶点的颜色 |
|
|
顶点的字体家族 |
|
|
顶点的标签 |
|
|
顶点标签在围绕顶点的圆上的位置。这是一个以弧度表示的角度,零度位于顶点的右侧。 |
|
|
顶点标签的颜色 |
|
|
顶点标签与顶点本身的距离,相对于顶点大小 |
|
|
顶点标签的字体大小 |
|
|
顶点的绘制顺序。具有较小顺序参数的顶点将首先绘制。 |
|
|
顶点的形状。已知形状有: |
|
|
顶点的像素大小 |
控制图绘制的边属性
属性名称 |
关键字参数 |
用途 |
---|---|---|
|
|
边的颜色 |
|
|
边的弯曲度。正值对应逆时针方向弯曲的边,负值对应顺时针方向弯曲的边。零表示直线边。 |
|
|
边的字体家族 |
|
|
如果图是有向的,箭头的尺寸(长度),相对于 15 像素。 |
|
|
如果图是有向的,箭头的宽度,相对于 10 像素。 |
|
|
自环的大小。可以设置为负数,在这种情况下,它会根据相应顶点的大小进行缩放(例如,-1.0 表示循环与顶点大小相同)。此属性会被非自环的边忽略。此属性仅在 matplotlib 后端可用。 |
|
|
边的像素宽度。 |
|
|
如果指定,它会为边添加一个标签。 |
|
|
如果指定,它会在边标签周围添加一个指定颜色的矩形框(仅限 matplotlib)。 |
|
|
如果为 True,则旋转边标签使其与边方向对齐。倒置的标签会被翻转(仅限 matplotlib)。 |
plot()
的通用关键字参数
这些设置可以作为关键字参数传递给 plot()
函数,以控制绘图的整体外观。
关键字参数 |
用途 |
---|---|
|
是否自动确定具有多条边的图的边的曲率。对于边数少于 10,000 的图,默认值为 |
|
绘图的边界框。这必须是一个包含所需绘图宽度和高度的元组。默认绘图为 600 像素宽和 600 像素高。对于 Matplotlib 后端,此项将被忽略。 |
|
要使用的布局。它可以是 |
|
绘图的顶部、右侧、底部和左侧边距,以像素为单位。此参数必须是列表或元组,如果您指定了少于四个元素的列表或元组,其元素将被重复使用。对于 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) |
|||
邻接矩阵 |
|
||
DIMACS |
|
||
DL |
|
|
暂不支持 |
边列表 |
|
|
|
|
暂不支持 |
|
|
GML |
|
|
|
GraphML |
|
|
|
Gzip 压缩的 GraphML |
|
||
LEDA |
|
暂不支持 |
|
带标签的边列表 |
|
|
|
(又称 NCOL) |
|||
Pajek 格式 |
|
|
|
序列化图 |
|
作为练习,请从此文件
下载著名的扎卡里空手道俱乐部研究的图表示,解压后尝试将其加载到 *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
类的文档。如果您遇到困难,请首先尝试在我们的论坛组中提问——也许有人可以立即帮助您。