前言:研究生期间主要做复杂网络聚类,也称为社区检测。临毕业前,老师让之前发表的论文里的算法代码C化,并写出界面进行可视化。由于之前虽然做过可视化,但基本上都是将聚类结果导入到pajek或者gephi这类专门的软件里进行绘制的。想要将社区检测结果实时的进行绘制并且要通过C\C++直接绘制,确实没有什么头绪。后来,通过浏览博客知道igaph这个包可以使用,由于想要熟悉下python,于是选择了Python的igraph包,而放弃使用C语言的igraph包。这样,我就可以将算法产生的聚类结果直接导入Python代码,并利用Python的igraph包进行网络划分结果的绘制。然后,再用C++调用Python代码,并将Python产生的聚类结果图直接加载到MFC中,这样就能实现实时地对社区检测结果进行绘制。
一. 利用Python进行网络聚类图的绘制
当然我们需要实现安装Python和igraph包。本人使用的系统是win7系统,安装的软件是anaconda,内含Python2.7版本,基本上需要的东西anaconda都集成好了,一键安装很是方便。
其次,我们需要安装python-igraph包。这是官方网址。直接使用pip安装会出现问题(Windows系统本身的问题),因此本人选用的非官方网址的安装包,见这里。这个网址在官网也提供了,一定要选对对应的版本。Python3.6以上的版本好像并没有对应的igraph包,因此安装的时候会出现平台不支持的提示。然后在dos系统环境下pip install 安装包,即可实现igraph的安装。
接下来,我们还需要安装支持igraph绘制图形的Cairo库。这个库同样在上面提供的非官方网址上可以找到,下载对应系统的版本,并进行pip安装即可。这里不再详细介绍。
前提工作做好,接下里进行网络社区图的绘制。如果不熟悉igraph包的使用,可以参见官网的使用手册。里面也有关于网络图绘制的介绍。下面给出相关代码:
# Python 2.7
from igraph import *
from PIL import Image
colors_type = ["yellow", "red", "green", "coral", "alice blue", "cyan", "pink",
"gray", "blue", "green yellow", "orange", "light blue", "hot pink", "light green", "gold"]
def PlotNetworks(net_file, detected_label, real_label = "Unknown Type"):
## read files
network = Graph.Read_Adjacency(net_file)
Graph.to_undirected(network)
f1 = open(detected_label)
line = f1.readline()
line = line.strip()
str_line = line.split('\t')
dlabel = [int(ele) for ele in str_line]
network.vs["dlabel"] = dlabel
if(real_label != "Unknown Type"):
f2 = open(real_label)
line = f2.readline()
line = line.strip()
str_line = line.split('\t')
rlabel = [int(ele) for ele in str_line]
network.vs["rlabel"] = rlabel
# plot networks
nnodes = len(network.vs)
network.vs["name"] = [str(i+1) for i in range(nnodes)]
layout = network.layout("drl")
visual_style = {}
if(nnodes < 100):
visual_style["vertex_size"] = 22
else:
visual_style["vertex_size"] = 18
visual_style["vertex_label"] = network.vs["name"]
visual_style["layout"] = layout
visual_style["bbox"] = (500,500)
visual_style["margin"] = 20
visual_style["edge_curved"] = 0.3
visual_style["vertex_color"] = [colors_type[i-1] for i in network.vs["dlabel"]]
plot(network, "social_network1.png", **visual_style)
figure1 = Image.open("social_network1.png")
figure1.save("social_network1.bmp")
if(real_label != "Unknown Type"):
visual_style["vertex_color"] = [colors_type[i-1] for i in network.vs["rlabel"]]
plot(network, "social_network2.png", **visual_style)
figure2 = Image.open("social_network2.png")
figure2.save("social_network2.bmp")
代码写的稍微有些繁琐,主要是为了之后C++代码的调用方便。函数需要三个参数,均为文件路径名。第一个文件是复杂网络的邻接矩阵,我们可以使用Graph.Read_Adjacency()来直接读取,不过这个方法默认读取的是有向网络,而我使用的均是无向网络,所以需要使用Graph.to_undirected()将其转换为无向网络。第二个文件为算法的聚类结果文件,文件是由1-k个整数标签表示社区检测结果,k表示一共检测到k个社区。第三个文件表示真实的网络划分结果,对于有些网络,我们往往并不知道真实的网络划分结果,这里是一个可选文件。
接着,绘制网络的可视化效果参数。使用字典可以直接将可视化参数设置好,这里我们用visual_style来表示。相关参数均可以在上述提供的官方手册找到。其中着重介绍下visual_style["layout"]这个参数,它是一种网络的布局算法,熟悉pajek软件的同学对这个参数应该也有所了解。其中“drl”是大图的分布式递归布局算法,常用的还有“kk”,“fr”等,这些都能使自己的网络节点布局合理,比较美观。由于这些参数都是非确定型的绘图方式,也就是说,每次节点的布局都会有所差异。更多参数见官网手册。
这里的color_style给了很多颜色,因为后期要绘制美国大学生足球队网络,需要至少12种颜色,这里Wikipedia提供了多达上百种的颜色,只需要将颜色的首字母小写即可。最后需要说明一下,由于我的MFC代码里需要调用bmp图像,而plot不能存储为bmp格式,这里多了一步将png转换为bmp格式的过程。
下面给出MFC界面绘制社区检测效果图,图像绘制如下:
1、Zachary's karate network(即跆拳道网络):
2、dolphin social network
3、American football network
这里为了突出检测图和真实图的区别,故意选择了效果一般时的参数。勿介意(手动滑稽)。。。
二、MFC调用Python代码
前面已经给出了绘制社区检测效果图的代码。这里简单介绍下怎样使用C++代码调用Python代码。这篇【博客】基本实现了vs配置使用C++调用python代码,这里需要强调几点:第五步应该在第三步之前进行实施,因为我配置完第五步之后,发现原先配置好的第三步又变回了原始配置,所以我们可以先配置第五步再接着配置第三步;此外,由于我们安装的是anaconda,所以我们需要在anaconda的相关文件下找到include目录和libs目录,并进行配置而非Python27目录,其他不需要改变。这样我们就可以配置好vs环境。
下面代码实现了C++调用Python代码:
void callPython(char *str1, char *str2, char *str3)
{
Py_Initialize();
PyObject *pModule = NULL;
PyObject *pFunc = NULL;
pModule = PyImport_ImportModule("PlotNetworks");
pFunc = PyObject_GetAttrString(pModule, "PlotNetworks");
PyObject *pArgs = PyTuple_New(3);
PyTuple_SetItem(pArgs, 0, Py_BuildValue("s", str1));
PyTuple_SetItem(pArgs, 1, Py_BuildValue("s", str2));
PyTuple_SetItem(pArgs, 2, Py_BuildValue("s", str3));
PyEval_CallObject(pFunc, pArgs);
Py_Finalize();
}
str1, str2, str3就是我们Python代码里所需要的三个文件,PyImport_ImportModule()调用的是我们的python文件名,下面的函数调用“PlotNetworks”
是python代码内的函数名。这里的pArgs对应Python里的一个元组,下面的参数“s”表示该参数是字符串类型。通过pArgs可以实现将参数传递给pFunc。
这篇【博客】可以进行参考。最后,在使用MFC绘制网络图的时候,发现了一个问题。即当我第二次选择文件时,在点击聚类就会出现问题,提示显示
python调用那一块出现问题。找了一些资料,发现Py_Initialize()和Py_Finalize()在一个程序中不能多次使用,原因是Py_Initialize初始化的占用的内存并
不能被Py_Finalize()完全释放(大概是这个意思)。于是我将这两个函数分开来,一个放在MFC窗口的初始化中,一个放在关闭窗口的消息函数中,最
终解决了这个问题。
结束语:写这个博客的原因一方面为了记录下这几天的工作,另一方面发现网上写R使用igraph包绘制复杂网络图的博客较多,对于Python绘制复
杂网络图的很少。于是写下这一篇博客,希望能为研究复杂网络的小伙伴提供一些思路。