使用networkx绘制网络图及模块使用
主要练习了包和模块的生成,并且学习了各种可视化的操作方法,包括networkx库(对于网络的生成)、使用matplotlib、seaborn画柱形图,折线图等及对其的保存。同时,还学习了Gephi工具。
包的设计
包作为一个文件夹,需要在文件夹目录中添加一个__ init __.py文件,表示这是一个模块
一些在测试中使用的文件
模块节点操作
import csv
import networkx as nx
def init_node(path_node):#生成字典,每个id对应一个字典,字典里包含所有属性
f=open(path_node,"r",encoding="utf-8")
file=csv.reader(f)
file = list(file)
f.close()
dict_user = {} # 保存每个用户/节点的信息
index = [0, 1, 2, 3, 4, 6, 7, 8] # 表示对应每一个id的属性
for item in file[1:]:
info = {'views': "NAN", 'mature': "NAN", 'life_time': "NAN",
'created_at': "NAN", 'updated_at': "NAN", 'dead_account': "NAN", 'language': "NAN", 'affiliate': "NAN"}
for i in index:
info[file[0][i]] = item[i]
dict_user[item[5]] = info
return dict_user
#list(dict_user.keys())返回键
def update_dict_user(dict_user,id,attribute,attribute_value):#之后要构建图之类的,可能需要对节点更新信息,之后发现貌似不需要
dict_user[id][attribute] = attribute_value
return dict_user
def get_attribute(id,G,attribute):#输入节点的id,返回需要的属性值
if attribute=="degree":
ans=G.degree(id)
else:
ans=G.nodes[id][attribute]
return ans
def print_node(G,id):
print("id:{0:},{1:},'degree':{2:}".format(id, G.nodes[id],G.degree[id]))
改进方法:
#字典初始化
#为了达到该函数的普适性,我不希望在函数中出现具体属性的名字,而是通过索引的方式,当然id的位置可能没办法,但是当作为字典再去调用id的方式或者用列表索引的方式找id也是可以的,此处不再赘述
info=dict.fromkeys(file[0],"NAN")#前面表示key值,后面表示赋值
G的一些关于节点的函数
G.nodes()返回G的所有节点,如果要打印最好转为list形式
G.nodes[i] 返回节点的所有属性,且返回字典形式
G.degree[id]返回节点的度
对于打印所有的属性的函数,可以采用**dic[id],**调用可变长度的字典,或者*调用可变长度的元组
构建网络
import csv
def init_edge(path_edge):
f = open(path_edge, "r", encoding="utf-8")
file = csv.reader(f)
file = list(file)
edge_lis=file[1:]#排除第一行的列名
f.close()
return edge_lis
将边转化为构建图像使用的样式
import networkx as nx
import pickle as pick
def init_graph(dict_user,edge_lis):#对于传入的参数为id列表,因此需要对字典输出做一个键的列表化
G=nx.Graph()#生成一个空白图
for id in dict_user:
for key in dict_user[id]:
G.add_node(id)#插入点
G.nodes[id][key]=dict_user[id][key]#添加属性
G.add_edges_from(edge_lis)#连接节点
return G
def save_graph(G,path):
f=open(path,"wb")#注意应该是二进制,因为序列化需要
pick.dump(G,f)
f.close()
def load_graph(path):
#反序列化需要将文件以二进制读取"rb",且load参数应为数据流
f=open(path,"rb")
result =pick.load(f)
f.close()
return result
说明:在init_graph函数里采用了两重循环,其实是比较费时的,但是这样可以保证输出的属性结果比较好看。
之后发现使用可变长度变量能够非常好的解决这个问题,最后也输出了同样的结果
for id in dict_user:
G.add_node(id,**(dict_user[id]))#插入点
在使用序列化和反序列化的操作中,要注意两个点:1、解码和写入都用的是二进制方法。2、pickle.dump(序列化对象,文件指针),pickle.load(文件指针),而不是直接输入文件路径
构建完序列化的文件在之后可以直接拿来用,就不需要打开各种数据文件,重新生成一遍,会快很多,当我发现这个问题时,已经来不及了。。。
图像分析
import networkx as nx
def get_node_degree(id,G):#返回某一个节点的度
ans=G.degree(id)
return ans
def get_node_number(G):
number=nx.number_of_nodes(G)
return number
def get_edge_number(G):
number=nx.number_of_edges(G)
return number
def cal_average_degree(G):
node=G.nodes()#返回degree列表
sum=0
for i in node:
sum+=G.degree(i)
ans=sum/len(node)
return ans
def cal_degree_distribution(G):#返回度的分布序列
degree_dis=nx.degree_histogram(G)#统计从0到最大度的频次
lis = [z / float(sum(degree_dis)) for z in degree_dis]#生成密度列表
return lis
def caL_view_distribution(G):
view_dic={}
for id in G.nodes():#生成字典
view_dic[G.nodes[id]["views"]]=view_dic.get(G.nodes[id]["views"],0)+1
sum=0
for key in view_dic.keys():
sum+=view_dic[key]
for key in view_dic.keys():
view_dic[key]=view_dic[key]/sum
return view_dic
一些简单的计算和函数调用。
需要说明的就是几个函数:
nx.number_of_nodes(G)统计节点数,或者直接对节点列表取len也一样;nx.number_of_edges(G)取边的数量,或者直接使用G.edges()取列表长度也是一样的
nx.degree_histogram(G)统计的是图中所有的度,从0到最大度,如果为0则标0
词频统计的一个方法dict[key]=dict.get(key,0)+1;dict.keys() and dict.values() can return two lists.
可视化
可视化的方式有很多,当时考虑了一些处理数据的方式,毕竟数据量实在过于庞大,对于可视化的效果并不好
节点视图可视化
import networkx as nx
import matplotlib.pyplot as plt
def plot_ego_2(G,node):##由于有600多万条边和16万多点,这种画法如果在考虑度的角度,点就过多,考虑点的角度,度就过大
plt.figure(figsize=(16,16))
nx.draw_networkx_nodes(G,pos=nx.spring_layout(G),nodelist=node,node_color='r', label=True) # 画节点
nx.draw_networkx_edges(G,pos=nx.spring_layout(G)) # 画边
plt.axis('off') # 去掉坐标刻度
plt.savefig("D:\\经管大三\\现代程序设计\\week4\\ego_2.png")
plt.show()
def plot_ego(G,node):
res=G.subgraph(node)
nx.draw_networkx(res,pos=nx.spring_layout(res))
plt.savefig("D:\\经管大三\\现代程序设计\\week4\\ego.png")
plt.show()
def plot_ego_3(G,node):
res = G.subgraph(node)
nx.draw(res,
pos = nx.circular_layout(G),
node_color = 'b',
edge_color = 'r',
with_labels = True,
font_size =10,
node_size =20)
plt.savefig("D:\\经管大三\\现代程序设计\\week4\\ego_3.png")
plt.show()
def plotdegree_distribution(degree_dis):
x=[i for i in range(len(degree_dis))]#生成横坐标
y=degree_dis
x2=[i for i in range(100)]#因为通过可视化发现100的分布较多,而由于度上限很大,使得该部分分布密集
y2=degree_dis[0:100]
plt.subplot(1,2,1)
plt.plot(x,y,color="red",linewidth=3.0)
plt.title("degree distribution")
plt.xlabel("degree")
plt.ylabel("scale")
plt.subplot(1,2,2)
plt.plot(x2,y2,color="blue",linewidth=3.0)
plt.title("degree distribution")
plt.xlabel("degree")
plt.ylabel("scale")
plt.show()
for i in G.nodes():
if G.degree(i)==60:
id=i
break
lis1=[id]
neighbor=list(G.neighbors(id))
lis1=lis1+neighbor
H=G.subgraph(lis1)#返回结果不错
nx.draw(H,pos=nx.circular_layout(H),with_labels= True)
plt.show()
单纯的从度的角度或者节点的角度出发结果并不是很好
问题最大的地方不在于如何画图,在于如何选取点,最后发现采用上述方法找度为60的点及其邻接点,在这种情况下将会产生61个点,利用subgraph绘制子图即可。下图中我们可以看出82是与其他节点连接最密集的节点,当然我们找的度为60的节点就是82!
函数详解
plt.figure() 对画布窗口的设置
nx.draw_networkx_nodes(G,pos=nx.spring_layout(G),nodelist=node,node_color=‘r’, label=True)
nx.draw_networkx_edges(G,pos=nx.spring_layout(G))
G我认为更像一个网络图的画布,我们可以通过draw函数在上面任意加点和线,利用draw_networkx_nodes 和draw_networkx_edges可以自定义网络图,pos表示绘图的方式,有五种基本的绘图方式,nodelist表示绘制点的列表,在绘制边的函数参数中也可以如此添加
我从同学那又看到一个筛选节点的方法,就是直接在图上删除节点G.remove_node(i)
plt.axis()设置坐标轴格式;plt.show()展示绘制的视图;plt.savefig(file)可以将图片按照希望的格式保存,也可以保存为矢量图
下面是绘制度的分布图:
如果对所有的度进行统计会发现差别特别大,度大于某一个值之后分布变化及其不明显,因此我将0-100的度分出,做一个局部的分析判断。结果显示有大量的节点并没有邻接点,且随着度的增大有明显的下降趋势。
其中用了plt.subplot()函数用来实现子图处理;plt.plo()t画折线图
网络图可视化
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import networkx as nx
#第一版的plot方法由于x轴太密
import matplotlib.ticker as ticker
import math
def plot_nodes_attribute(G,attribute):
node=G.nodes()
key=['views', 'mature', 'life_time', 'created_at', 'updated_at', 'numeric_id', 'dead_account', 'language', 'affiliate']
if attribute in key:
key_dic={}
for id in node:
key_dic[G.nodes[id][attribute]]=key_dic.get(G.nodes[id][attribute],0)+1
#画图
x=list(key_dic.keys())
y=list(key_dic.values())
length=math.floor(len(x)/7)
ax=sns.barplot(x,y)
ax.xaxis.set_major_locator(ticker.MultipleLocator(base=length)) # 解决X轴密集问题
plt.title(f"{attribute:}")
plt.savefig(f"D:\\经管大三\\现代程序设计\\week4\\{attribute:}分布.png")
elif attribute == "degree":
y=nx.degree_histogram(G)
x=[i for i in range(len(y))]
length = math.floor(len(x) / 7)
ax=sns.barplot(x, y)
ax.xaxis.set_major_locator(ticker.MultipleLocator(base=length)) # 解决X轴密集问题
plt.title(f"{attribute:}")
plt.savefig(f"D:\\经管大三\\现代程序设计\\week4\\{attribute:}分布.png")
else:
print("not find")
def plot_nodes_attribute_2(G,attribute):#画直方图和拟合曲线
node=G.nodes()
key=['views', 'mature', 'life_time', 'created_at', 'updated_at', 'numeric_id', 'dead_account', 'language', 'affiliate']
if attribute in key:
lis=[]
for id in node:
lis.append(G.nodes[id][attribute])
#画图
length = math.floor(len(lis) / 7)#由于间隔太密,因此提取部分x轴坐标
ax=sns.distplot(lis,kde=True,rug=True)
ax.xaxis.set_major_locator(ticker.MultipleLocator(base=length)) # 解决X轴密集问题
plt.title(f"{attribute:}")
plt.savefig(f"D:\\经管大三\\现代程序设计\\week4\\{attribute:}分布_2.png")
elif attribute == "degree":
lid=[]
for id in node:
lid.append(G.degree(id))
sns.distplot(lid)
plt.title(f"{attribute:}")
plt.savefig(f"D:\\经管大三\\现代程序设计\\week4\\{attribute:}分布_2.png")
else:
print("not find")
以上函数的编写部分我觉得简洁性欠佳,其实可以对之前显示节点所有属性的模块函数进行调用,这样就不需要重新提取属性了。最开始做的时候,发现没有注意坐标轴的问题,导致坐标轴相互交叠根本看不清楚。
length = math.floor(len(lis) / 7)#由于间隔太密,因此提取部分x轴坐标
ax=sns.distplot(lis,kde=True,rug=True)
ax.xaxis.set_major_locator(ticker.MultipleLocator(base=length)) # 解决X轴密集问题
因此我的处理方式就是将所有可能值的总长度分成7段,计算每段的长度,设置x轴的格式来进行简化
第一个函数用了sns.bar(x,y)绘制条形图,第二个函数用sns.distplot(lis,kde=True,rug=True)绘制条形图和拟合曲线,kde=True表示绘制核密度,rug=True表示用直线表示核密度
main函数
import GraphStat.Networkbuilder.node as GNN
import GraphStat.Networkbuilder.edge as GNE
import GraphStat.Networkbuilder.graph as GNG
import GraphStat.Networkbuilder.stat as GNS
import GraphStat.Visualization.plotgraph as GVP
import GraphStat.Visualization.plotnodes as GVN
path="D:\\经管大三\\现代程序设计\\week4\\twitch_gamers\\large_twitch_features.csv"
path2="D:\\经管大三\\现代程序设计\\week4\\twitch_gamers\\large_twitch_edges.csv"
path3="D:\\经管大三\\现代程序设计\\week4\\G.txt"
dic=GNN.init_node(path)
edge=GNE.init_edge(path2)
G=GNG.init_graph(dic,edge)
#GNN.print_node(G,"8000")
#ans=GNN.get_attribute("8000",G,"language")
#print(ans)
#X=GNS.get_edge_number(G)
#Y=GNS.get_node_number(G)
#Z=GNS.caL_view_distribution(G)
#Q=GNS.cal_average_degree(G)
#W=GNS.cal_degree_distribution(G)
#E=GNS.get_node_degree("8000",G)
#node=[]
#for i in G.nodes():
# if G.degree(i)==120:#之前以度为标定
# node.append(i)
#GVP.plot_ego_3(G,node)
#GVP.plotdegree_distribution(W)
GVN.plot_nodes_attribute(G,"views")
Gephi工具的使用以及对图的处理
最开始提取了前50个id,想要返回他们子图的边的关系,但是发现子图中只有一条边,这显然是不符合要求的
path1="D:\\经管大三\\现代程序设计\\week4\\twitch_gamers\\large_twitch_features.csv"
#path2="D:\\经管大三\\现代程序设计\\week4\\twitch_gamers\\large_twitch_edges.csv"
path3="D:\\经管大三\\现代程序设计\\week4\\Gephi_node.csv"
path4="D:\\经管大三\\现代程序设计\\week4\\Gephi_edge.csv"
f1=open(path1,"r",encoding="utf-8")
#f2=open(path2,"r",encoding="utf-8")
lis1=list(csv.reader(f1))
lis1=[item[5] for item in lis1[1:51]]#惊奇的发现这个数据似乎是按照id的增序排列的,这么干好像又多此一举了
#lis2=list(csv.reader(f2))
G=GNG.load_graph("D:\\经管大三\\现代程序设计\\week4\\G.txt")
H=G.subgraph(lis1)
print(list(H.edges))
f1.close()
#f2.close()
#我们在使用Gephi时发现随机抽取边使得点的数量爆炸,哪怕只抽了100000条边,因此搜索一些边来进行操作
f1=open(path3,"w",encoding="utf-8",newline='')
f2=open(path4,"w",encoding="utf-8",newline='')#newline=''参数可以防止多余的换行
g1=csv.writer(f1)
g2=csv.writer(f2)
g1.writerow(lis1[0])
g1.writerows(lis1[1:201])
#g2.writerow(lis2[0])
#g2.writerows(lis2[1:100001])
于是我决定选择一个度为60的节点,然后反向寻找该节点的邻接点,生成一个新的子图
import csv
import GraphStat.Networkbuilder.graph as GNG
import networkx as nx
path3="D:\\经管大三\\现代程序设计\\week4\\Gephi_node.csv"#Gephi貌似只处理csv文件
path4="D:\\经管大三\\现代程序设计\\week4\\Gephi_edge.csv"#对于Gephi工具传入边的关系自动分析
G=GNG.load_graph("D:\\经管大三\\现代程序设计\\week4\\G.txt")
for i in G.nodes():
if G.degree(i)==60:
id=i
break
lis1=[id]
neighbor=list(G.neighbors(id))
lis1=lis1+neighbor
H=G.subgraph(lis1)#返回结果不错
for it in range(len(lis1)):
lis1[it]=[lis1[it]]#这一步是为了生成csv文件
H_edges=list(H.edges)
f1=open(path3,"w",encoding="utf-8",newline='')
f2=open(path4,"w",encoding="utf-8",newline='')#newline=''参数可以防止多余的换行
g1=csv.writer(f1)
g2=csv.writer(f2)
init_edge=["node1","node2"]
g1.writerow(["node"])
g1.writerows(lis1)
g2.writerow(init_edge)
g2.writerows(H_edges)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9VKoTFn-1664261383998)(D:\经管大三\现代程序设计\week4\Gephi子图测试.png)]
id))
lis1=lis1+neighbor
H=G.subgraph(lis1)#返回结果不错
for it in range(len(lis1)):
lis1[it]=[lis1[it]]#这一步是为了生成csv文件
H_edges=list(H.edges)
f1=open(path3,“w”,encoding=“utf-8”,newline=‘’)
f2=open(path4,“w”,encoding=“utf-8”,newline=‘’)#newline=''参数可以防止多余的换行
g1=csv.writer(f1)
g2=csv.writer(f2)
init_edge=[“node1”,“node2”]
g1.writerow([“node”])
g1.writerows(lis1)
g2.writerow(init_edge)
g2.writerows(H_edges)
![在这里插入图片描述](https://img-blog.csdnimg.cn/34479c9022744efc8dbf7cc91fca890c.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/d32fd66562da488e8d12fd148fd43026.png#pic_center)