最近恶心的项目中期检查,我被分配到做社交图的分析,然而事实上我并不知道弄啥。虽然不是我自己答辩,但是考虑到还是不要太坑dalao,我决定不管怎样至少得搞点图撑撑场面免得尴尬,这几天就赶鸭子上架倒腾了下graph_tool
这个专门用于对图进行可视化的python库。虽然网上中文资料不足,但是他的英文文档还是非常全面的,很多设计的小细节也在文档里提及了,非常简单容易上手。下面就从一个初学者的记录下我的学习历程。
虽然是个python库,但是毕竟是要做大量数据计算的,因此graphtool在底层使用了Boost, CGAL 和 expat这几个C++库(Boost是扩展的标准库,CGAL是一个计算几何算法库,expat是一个XML解析器)。这就导致了使用通常的pip和easyinstall不太好直接安装。正确的安装姿势大概有三种:
这里主要参考graph-tool installation的各种安装说明。 从最基本的源码安装,到封装好的包管理器安装,再到最万金油的docker安装一应俱全。 看上去一片美好。 然而真正用的时候发现,如果使用docker,官方提供的镜像还是太过庞大,用起来还是太臃肿了;如果采用更新源+包管理器安装的化,要是系统的版本比较新还好,要是系统版本老了,就会报各种依赖不足的问题。。。最坑爹的是,即使采用源码安装的方式,由于开发人员比较勤快,经常更新技术,很多库都需要较新的版本,容易导致老的机器上无法编译,而且由于项目源码是托管在他自己搭的git服务器上,这个服务器还很坑,旧的版本经常下不下来。。。最近不知道为什么官网的服务器崩了,还好我之前下了源码,于是把他fork到了我的github上,做个备份。
不过还好,虽然我的服务器比较旧,但是我的台式机比较新,没啥问题。。。
这个项目的文档非常详尽,可以参考他的quickstart,类的API在这里。下面稍微整理下大概用途,方便查找:
graph_tool主要用于是图的加载、构建、删除、持久化、迭代graph_tool.centrality主要用于计算与图的中心度相关的信息graph_tool.clustering主要用于计算图的各种聚类系数graph_tool.collection保存了十几个供参考的数据集graph_tool.correlations主要用与计算各种相关度信息graph_tool.draw封装了一些常用的绘图算法graph_tool.flow处理图的最大流最小割一类的问题graph_tool.generation主要用于预先生成一些图,比如完全图,随机图,晶格图等等graph_tool.inference主要用于推断图的社区结构graph_tool.search包装了一些图的搜索算法,比如bfs,dijikstra,astar等等graph_tool.spectral主要用于计算图的矩阵信息graph_tool.stats主要用于图的简单直方图统计等信息graph_tool.topology主要包装了图的拓扑性质,比如最短路,最小生成树,拓扑排序等等graph_tool.util主要是一些节点和边的查找方法
这个对象是graph_tool
封装的一个映射工具,文档在这里,以后经常会用到。他其实是对C++中的Map进行的一个封装,键的类型被限定为了'e','v','g',而值可以映射为int,float,vector等多种c++类型,用法如下:
import graph_tool.all as gt
g=gt.price_network(200)
prop_v=g.new_vertex_property("int")#创建一个节点到int的映射
#prop_e=g.new_edge_property("int")#创建一个边到int的映射
#prop_g=g.new_graph_property("int")#创建一个图到int的映射
prop_v.a=np.ones(g.num_vertices()) #为prop_v赋值
#print prop_v.a#直接获取映射数组
#print prop_v[2]#按下标取值
#print prop_v.value_type()#值的类型
#print prop_v.key_type()#键的类型
我们也可以直接把PropertyMap跟Graph绑定:
g.properties[("e", "foo")] = g.new_edge_property("vector<double>")
g.vertex_properties["foo"] = g.new_vertex_property("double")
g.vertex_properties["bar"] = g.new_vertex_property("python::object")
g.graph_properties["gnat"] = g.new_graph_property("string", "hi there!")
我的目的是画图,那么我就重点研究了下用来画"图"的函数graph_draw。这个函数必选参数只有一个,但是可选参数却有十几个,我们选几个重要的一一讨论。 下面是函数原型:
graph_tool.draw.graph_draw(g, pos=None, vprops=None, eprops=None, vorder=None, eorder=None, nodesfirst=False, output_size=(600, 600), fit_view=True, inline=False, mplfig=None, output=None, fmt='auto', **kwargs)
必备参数 必备的参数肯定是g,是指我们需要画的Graph对象。
pos参数
pos参数是指我们通过一些布局算法计算出来的对于每一个节点的坐标,是一个PropertyMap对象。这个PropertyMap对象其实就是一个映射,每一个节点的下标对应了该节点的x,y坐标。这个对象是通过布局算法生成的就行了。默认情况下我们用的是sfdp_layout()
方法来生成这一系列坐标。
vorder,eorder,nodefirst参数 这些参数控制的是绘图的顺序,主要是考虑了一些点线的覆盖问题。
output参数 这个参数指定的是文件的输出文件名和格式,可以根据后缀判断,支持png,svg,pdf,ps四种文件格式。当然也可以用fmt参数直接指定格式。通常情况下png方便显示,但是容易失真。pdf和svg是基于矢量的,放大不失真。 有趣的是如果不指定这个参数,那么他会生成一个可以交互的界面:
这个界面至少有下面的功能:
vertex_*,edge_*
属性
这些属性可以指定具体画图的样式,样式表可以参见文档,下面用几个例子说明。
import graph_tool.all as gt
import numpy as np
g = gt.price_network(1500)
deg = g.degree_property_map("in")
deg.a = 4 * (np.sqrt(deg.a) * 0.5 + 0.4)
ebet = gt.betweenness(g)[1]
ebet.a /= ebet.a.max() / 10.
eorder = ebet.copy()
eorder.a *= -1
pos = gt.sfdp_layout(g)
gt.graph_draw(g, pos=pos, vertex_size=deg, vertex_fill_color=deg,
vorder=deg,edge_color=ebet, eorder=eorder, edge_pen_width=ebet,
output="output.png")
这个例子非常简单而且经典,他做了下面的事情:
graphtool提供了至少7种布局算法,非常丰富。同时他也提供了社区分块算法与层级绘图算法,这里就不细说了。
最后链一个简单实验:Weibo_Data_Analysis