0 说明
使用矩阵方法计算图指标。
FWCN(Frequency-Weighted Common Neighbor):
A和B之间的距离由他们的共同邻居决定。
数据:facebook在Kaggle上的一些公开数据
1 处理
确保具有如下两个包
pip3 install python-louvain 【社区发现】
pip3 install networkx 【网络计算】
读入数据
import networkx as nx
import numpy as np
import pandas as pd
with open('./facebook-combined.txt', 'r') as f:
data = f.readlines()
data[:3]
In [3]: data[:3]
Out[3]: ['0 1\n', '0 2\n', '0 3\n']
# 前三行, 节点0,1 节点0,2, 节点0,3 三条边
In [2]: len(data)
Out[2]: 88234
# 作为图导入
fb = nx.read_edgelist('./facebook-combined.txt',
create_using=nx.Graph(), nodetype=int)
# 图的基本信息
print(nx.info(fb))
In [7]: print(nx.info(fb))
Name:
Type: Graph
Number of nodes: 4039
Number of edges: 88234
Average degree: 43.6910
# ***** 图的展示 *****
# 参考:https://www.kaggle.com/pierrek20/social-network-visualization-and-analysis
nodes = fb.nodes()
degree = fb.degree()
colors = [degree[n] for n in nodes]
pos = nx.kamada_kawai_layout(fb)
cmap = plt.cm.viridis_r
cmap = plt.cm.Greys
fig = plt.figure(figsize=(15, 9), dpi=100)
nx.draw(fb, pos, alpha=0.8, nodelist=nodes, node_color='w', node_size=10,
with_labels=False, font_size=6, width=0.2, cmap=cmap, edge_color='yellow')
fig.set_facecolor('#0B243B')
plt.legend()
plt.show()
2 图的分割
图很容易就会变的非常大,而许多图的算法时间复杂度是比较高的,有些点并不需要加入甲酸。因此要先进行子图分割,然后再计算。
图的分割可以分为硬分割和软分割两种。硬分割是指通过图中显示的边连接将图划分成子图(求连通子图),软分割是指将连通图中的一些边拆掉,形成一批新的子图(社区发现)。
In [12]: # 图的连通性
...: def get_conngraph(C):
...: res = [x for x in sorted(nx.connected_components(C), key=len, rever
...: se=True)]
...: return res
...: cong = get_conngraph(fb)
...:
In [13]: len(cong)
Out[13]: 1
可见,这批数据是一个连通图。因此尝试进行社区分割。模块度(Modularity)用来衡量一个社区的划分是不是相对比较好的结果。一个相对好的结果在社区内部的节点相似度较高,而在社区外部节点的相似度较低。
关于Networkx和Community包的用法可以参考这篇文章
# 图的社区分割
import community
partition = community.best_partition(fb)
node_cluster = pd.Series(partition)
mod = community.modularity(partition,fb)
print('Modularity is ' ,mod)
'''
In[18]: node_cluster.value_counts()
Out[18]:
7 548
4 535
1 457
2 446
3 442
0 350
5 323
9 237
12 226
11 206
6 117
8 73
10 60
13 19
dtype: int64
'''
In [19]: mod = community.modularity(partition,fb)
...: print('Modularity is ' ,mod)
Modularity is 0.834789224221449
可以看到连通图已经被分成了14个社区(Cluster), 需要注意的是:
- 社区不一定是连通图
- 有时候社区不可分(依据模块度判定)
# 选取一个社区,查看其连通性
c4 = fb.subgraph(set(node_cluster[node_cluster==4].index))
c4g = get_conngraph(c4)
# 这个社区是连通的
In [26]: len(c4g)
Out[26]: 1
# 获取(极大)连通子图
c4g0 = c4.subgraph(c4g[0])
#查看图信息
print(nx.info(c4g0))
In [31]: # 获取(极大)连通子图
...: c4g0 = c4.subgraph(c4g[0])
...: #查看图信息
...: print(nx.info(c4g0))
Name:
Type: Graph
Number of nodes: 535
Number of edges: 8691
Average degree: 32.4897
经过分割处理后,现在我们可以在一张较小的图上进行计算。将上面的画图函数封装一下:
def draw_a_graph(g, filename,path='./'):
nodes = g.nodes()
degree = g.degree()
colors = [degree[n] for n in nodes]
pos = nx.kamada_kawai_layout(g)
cmap = plt.cm.viridis_r
cmap = plt.cm.Greys
fig = plt.figure(figsize=(15, 9), dpi=100)
nx.draw(getattr, pos, alpha=0.8, nodelist=nodes, node_color='w', node_size=10,
with_labels=False, font_size=6, width=0.2, cmap=cmap, edge_color='yellow')
fig.set_facecolor('#0B243B')
plt.savefig(path + filename)
print('File Saved ', path+filename)
return True
看一下这个社区:
3 FWCN计算
点和点之间的距离怎么度量?一种度量方式是通过节点对(Node Pair)的共同邻居来判断。公式如下:
FWCN:
# 随便选取一个度不太多的点 -->2678
'''
In [6]: degreeg
Out[6]: DegreeView({2661: 99, 2662: 37, 2665: 50, 2667: 20,
2668: 32, 2671: 6, 2672: 37, 2674: 70, 2675: 38, 2677: 14,
2678: 9, 2682: 16, 2684: 28, 2685: 12, 2686: 24, 2687: 21,
2688: 11, 2690: 30, 2691: 3, 2692: 5, 2693: 18, 2696: 19, 2697
'''
g1_nodes = nx.neighbors(g, 2678)
g1_nodes = set(g1_nodes) |{2678}
g1 = g.subgraph(g1_nodes)
print('Graph Info', nx.info(g1))
draw_a_graph(g1, 'tem.png', node_size=2000, width=1, with_labels=True,font_size=10)
Graph Info Name:
Type: Graph
Number of nodes: 10
Number of edges: 38 (9~ 81)
Average degree: 7.6000
可以得到这样一张比较小的图:
现在我们关注2678-2760这两个点的距离(FWCN)。我们先把这张图中的边打印出来,这意味着我们总有办法把2678和2760的边找出来:
或者直接使用函数寻找这两个点的邻居:
# 2678的邻居
n2678 = list(nx.neighbors(g1, 2678))
print(n2678)
# 2760的邻居
n2760 = list(nx.neighbors(g1, 2760))
print(n2760)
In [44]: # 2678的邻居
...: n2678 = list(nx.neighbors(g1, 2678))
...: print(n2678)
...:
...: # 2760的邻居
...: n2760 = list(nx.neighbors(g1, 2760))
...: print(n2760)
[990, 1505, 1684, 1726, 2760, 3057, 3164, 3222, 3263]
[990, 1505, 1684, 2678, 3057, 3222, 3263]
In [45]: # 2678和2760的共同邻居 CN of Two Nodes
...: cn2678_2760 = list( set(n2678) & set(n2760))
...: print(cn2678_2760)
[1505, 3057, 1684, 3222, 990, 3263]
可以看到,2678和2760共有6个共同邻居(Common Neighbors)。下面可以根据公式,计算2678和2760的FWCN。
# 计算如下
# 计算FWCN
w = 0
cn_g = g1
for i,cn in enumerate(cn2678_2760):
print('%i Common Node :%s' % (i,cn ))
tem_de = cn_g.degree(cn)
tem_w = np.log(tem_de)**-1
print('Degree :', tem_de)
print('Weight Added :', tem_w)
w+=tem_w
print('Accumulated Weight', w)
# ---
0 Common Node :1505
Degree : 5
Weight Added : 0.6213349345596119
Accumulated Weight 0.6213349345596119
1 Common Node :3057
Degree : 8
Weight Added : 0.48089834696298783
Accumulated Weight 1.1022332815225997
2 Common Node :1684
Degree : 9
Weight Added : 0.45511961331341866
Accumulated Weight 1.5573528948360185
3 Common Node :3222
Degree : 7
Weight Added : 0.5138983423697507
Accumulated Weight 2.071251237205769
4 Common Node :990
Degree : 9
Weight Added : 0.45511961331341866
Accumulated Weight 2.5263708505191875
5 Common Node :3263
Degree : 8
Weight Added : 0.48089834696298783
Accumulated Weight 3.0072691974821755
最终获得了2678和2760在子图g1下的FWCN。接下来,我们计算在社区4的子图(c4g0)下计算所有点之间的FWCN。
4 计算子图所有节点对的FWCN (For循环)
封装FWCN的计算函数如下:
def cal_FWCN(g, A, B):
# A的邻居
An = list(nx.neighbors(g, A))
# B的邻居
Bn = list(nx.neighbors(g, B))
# A和B的共同邻居 CN of Two Nodes
ABcn = list(set(An) & set(Bn))
# 计算FWCN
w = 0
if len(ABcn):
for cn in ABcn:
tem_de = g.degree(cn)
tem_w = np.log(tem_de)**-1
w += tem_w
return w
# 测试
In [63]: cal_FWCN(g1, 2678,2760)
Out[63]: 3.0072691974821755
开始载入数据并进行计算:
import networkx as nx
import numpy as np
import pandas as pd
from fb_func1 import draw_a_graph
from fb1 import from_pickle, to_pickle
import matplotlib.pyplot as plt
from fb_func2 import cal_FWCN
from itertools import combinations
# 1 载入图
g = from_pickle('temg')
# 2 计算节点对
nodes = set(g.nodes)
node_pairs = list(combinations(nodes,2))
# 一共有14万对组合(但是不是所有组合都有边)
In [61]: len(node_pairs)
Out[61]: 142845
# 3 开始计算
import time
start = time.time()
res_list = []
for p in node_pairs:
fwcn = cal_FWCN(g, p[0],p[1])
if fwcn >0:
tem_list = [p[0],p[1],fwcn]
res_list.append(tem_list)
print('For Iteration Takes %.3f' %(time.time() - start))
For Iteration Takes 111.431 Seconds
# 由于网络比较紧密,大部分的节点间都有FWCN距离
In [65]: len(res_list)
Out[65]: 142360
可以看得出来,针对一个仅有500个节点的网络就需要计算那么长的时间。期间也就增加了几百兆的内存开销。从空间换时间的角度上,我们看看使用矩阵计算可以带来怎样的结果。
Before:
After:
5 计算子图所有节点对的FWCN (矩阵计算)
如果有使用For循环进行SumProduct的都可以使用矩阵实现。FWCN很明显是计算了节点的CN(Common Neighbors)之和,然后在外层也有一个For循环进行所有节点的一个便利。这两个操作都可以使用矩阵来替代。
FB这个例子的网相对稠密,而有些例子的网不那么稠密,这就意味着并不是所有节点对的FWCN都值得计算。
所以使用矩阵替代的化有三个点:
- 确认哪些点对(Node Pairs)需要计算
- 如何使用向量相乘来控制点对的计算(多个点对的操作可以堆叠在一个矩阵里)
- 如何使用点乘来一次性计算某个节点对的FWCN
import networkx as nx
import numpy as np
import pandas as pd
from fb_func1 import draw_a_graph
from fb1 import from_pickle, to_pickle
import matplotlib.pyplot as plt
g = from_pickle('temg')
g1_nodes = nx.neighbors(g, 2678)
g1_nodes = set(g1_nodes) | {2678}
g1 = g.subgraph(g1_nodes)
print('Graph Info', nx.info(g1))
# 2678的邻居
n2678 = list(nx.neighbors(g1, 2678))
# 2760的邻居
n2760 = list(nx.neighbors(g1, 2760))
# 2678和2760的共同邻居 CN of Two Nodes
cn2678_2760 = list(set(n2678) & set(n2760))
# CN Vector
cn_v1 = np.array([g1.degree(x) for x in cn2678_2760])
cn_v2 = np.log(cn_v1) ** -1
print(cn_v2.sum())
In [74]: print(cn_v2.sum())
3.0072691974821755
# 将图转为邻接矩阵,向量的顺序图节点度的顺序
In [89]: print(pd.Series(g1.degree))
...: g1m = np.array(nx.adjacency_matrix(g1).todense())
...: print(g1m)
...: print(g1m.sum(axis=0))
0 (1505, 5)
1 (2760, 7)
2 (3057, 8)
3 (1684, 9)
4 (990, 9)
5 (3222, 7)
6 (2678, 9)
7 (3164, 7)
8 (1726, 7)
9 (3263, 8)
dtype: object
[[0 1 0 1 1 0 1 1 0 0]
[1 0 1 1 1 1 1 0 0 1]
[0 1 0 1 1 1 1 1 1 1]
[1 1 1 0 1 1 1 1 1 1]
[1 1 1 1 0 1 1 1 1 1]
[0 1 1 1 1 0 1 0 1 1]
[1 1 1 1 1 1 0 1 1 1]
[1 0 1 1 1 0 1 0 1 1]
[0 0 1 1 1 1 1 1 0 1]
[0 1 1 1 1 1 1 1 1 0]]
[5 7 8 9 9 7 9 7 7 8]
# 根据度,计算g1节点的度向量
node_list = []
degree_list = []
for x in g1.degree:
node_list.append(x[0])
degree_list.append(x[1])
# 节点向量
node_vec = np.log(np.array(degree_list))**-1
In [103]: node_vec
Out[103]:
array([0.62133493, 0.51389834, 0.48089835, 0.45511961, 0.45511961,
0.51389834, 0.45511961, 0.51389834, 0.51389834, 0.48089835])
# 2760,2678节点的选择向量
sel2760_2678 = np.array([0, 1, 0, 0, 0, 0, 1, 0, 0, 0])
# 选择2760和2678的共同向量(可以按行相乘,起到类似mask的作用)
sel2760_2678_mat = g1m * sel2760_2678
In [100]: sel2760_2678_mat
Out[100]:
array([[0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0]], dtype=int64)
# 将每行的向量与选择向量比较
sel2760_2678_mat1 = sel2760_2678_mat == sel2760_2678
# 比较的中间结果
In [106]: sel2760_2678_mat1
Out[106]:
array([[ True, True, True, True, True, True, True, True, True,
True],
[ True, False, True, True, True, True, True, True, True,
True],
[ True, True, True, True, True, True, True, True, True,
True],
[ True, True, True, True, True, True, True, True, True,
True],
[ True, True, True, True, True, True, True, True, True,
True],
[ True, True, True, True, True, True, True, True, True,
True],
[ True, True, True, True, True, True, False, True, True,
True],
[ True, False, True, True, True, True, True, True, True,
True],
[ True, False, True, True, True, True, True, True, True,
True],
[ True, True, True, True, True, True, True, True, True,
True]])
# 如果每一行的每个元素都和选择向量对应,那么按横向相加的值等于选则向量长度
sel2760_2678_mat2 = sel2760_2678_mat1.sum(axis=1) == len(sel2760_2678)
# 可以看看选择的变量
np.array(node_list)[sel2760_2678_mat2]
# 和for循环的版本比较是相同
In [114]: np.array(node_list)[sel2760_2678_mat2]
Out[114]: array([1505, 3057, 1684, 990, 3222, 3263])
# 使用点乘来计算该节点对的FWCN
res = np.dot(sel2760_2678_mat2, node_vec)
In [115]: res
Out[115]: 3.0072691974821755
# 同时计算多个向量
In [116]: node_list
Out[116]: [1505, 2760, 3057, 1684, 990, 3222, 2678, 3164, 1726, 3263]
sel2760_2678 = [0, 1, 0, 0, 0, 0, 1, 0, 0, 0]
sel1505_3057 = [1, 0, 1, 0, 0, 0, 0, 0, 0, 0]
pair_list = []
pair_list.append(sel2760_2678)
pair_list.append(sel1505_3057)
pair_mat = np.array(pair_list)
In [126]: pair_mat
Out[126]:
array([[0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0]])
# g1m : 10*10 , pair_mat :2*10
g2m = np.dot(g1m, pair_mat.T)
In [127]: g2m
Out[127]:
array([[2, 0],
[1, 2],
[2, 0],
[2, 2],
[2, 2],
[2, 1],
[1, 2],
[1, 2],
[1, 1],
[2, 1]], dtype=int64)
# 因为是Node Pair,所以mask留下为2的是匹配的
g2m1 = g2m ==2
# g2m1:10*2
print(g2m1.shape)
In [128]: g2m1
Out[128]:
array([[ True, False],
[False, True],
[ True, False],
[ True, True],
[ True, True],
[ True, False],
[False, True],
[False, True],
[False, False],
[ True, False]])
# 点乘,批量计算Node Pair的结果
np.dot(g2m1.T,node_vec)
In [129]: np.dot(g2m1.T,node_vec)
Out[129]: array([3.0072692 , 2.39315552])
# 使用之前的函数计算,验证
In [130]: cal_FWCN(g1, 1505, 3057)
Out[130]: 2.3931555246797576
# BTW, 如果要从选择向量中获取实际选中的变量
np.array(node_list)[np.argwhere(np.array(sel2760_2678) ==1)].ravel()
In [148]: np.array(node_list)[np.argwhere(np.array(sel2760_2678) ==1)].ravel()
Out[148]: array([2760, 2678])
以上基本上确定了计算方法,下面完善几个细节:
- 函数1:给到节点列表,给出选择矩阵
- 函数2:给到选择矩阵和邻接矩阵,计算FWCN
- 函数3:结合选择矩阵和FWCN给出节点对(Node Pair)的FWCN
函数1:
from itertools import combinations
def create_sel_mat(vec):
pos = list(range(len(vec)))
combs = combinations(pos, 2)
matlist = []
for x in combs:
temp = np.zeros(len(vec))
temp[x[0]] = 1
temp[x[1]] = 1
matlist.append(temp)
return np.array(matlist), combs
tem1,tem2 = create_sel_mat(node_list)
tem1 = 选择矩阵,作为函数2的入参
tem2 = 节点对,作为函数3的入参
In [163]: tem1
Out[163]:
array([[1., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
[1., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
[1., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
In [168]: tem2
Out[168]:
[(0, 1),
(0, 2),
(0, 3),
(0, 4),
(0, 5),
(0, 6),
(0, 7),
(0, 8),
# 函数2
# 根据度,计算g1节点的度向量
node_list = []
degree_list = []
for x in g1.degree:
node_list.append(x[0])
degree_list.append(x[1])
# 节点向量
node_vec = np.log(np.array(degree_list))**-1
# 邻接矩阵
adj_mat = np.array(nx.adjacency_matrix(g1).todense())
# 1选择矩阵
# 2节点的度向量
def mat_cal_FWCN(sel_mat, adj_mat,node_vec):
tem_mat = np.dot(adj_mat, sel_mat.T)
tem_mat1 = tem_mat == 2
return np.dot(tem_mat1.T, node_vec)
tem3 = mat_cal_FWCN(tem1,adj_mat,node_vec)
In [170]: tem3
Out[170]:
array([1.36535884, 2.39315552, 1.93803591, 1.93803591, 1.87925718,
1.93803591, 1.36535884, 1.87925718, 2.39315552, 2.36015553,
3.0072692 , 3.0072692 , 2.32715553, 3.0072692 , 2.94849047,
2.84105388, 2.36015553, 3.44673094, 3.44673094, 2.87405387,
# 组装结果
def form_mat_res_df(combs, FWCN,node_list):
node_dict = dict(zip(range(len(node_list)),node_list ))
res_df = pd.DataFrame()
res_df['combs'] = combs
res_df['FWCN'] = FWCN
res_df['NodeA'] = res_df['combs'].apply(lambda x: x[0]).map(node_dict)
res_df['NodeB'] = res_df['combs'].apply(lambda x: x[1]).map(node_dict)
return res_df
tem4 = form_mat_res_df(tem2, tem3,node_list)
In [185]: tem4
Out[185]:
combs FWCN NodeA NodeB
0 (0, 1) 1.365359 1505 2760
1 (0, 2) 2.393156 1505 3057
2 (0, 3) 1.938036 1505 1684
3 (0, 4) 1.938036 1505 990
4 (0, 5) 1.879257 1505 3222
5 (0, 6) 1.938036 1505 2678
6 (0, 7) 1.365359 1505 3164
7 (0, 8) 1.879257 1505 1726
8 (0, 9) 2.393156 1505 3263
9 (1, 2) 2.360156 2760 3057
10 (1, 3) 3.007269 2760 1684
11 (1, 4) 3.007269 2760 990
结果比较
下面对For循环和矩阵计算并比较结果。
# 1 载入图
g = from_pickle('temg')
# 2 计算节点对
nodes = set(g.nodes)
node_pairs = list(combinations(nodes,2))
# 3 开始计算 - For
import time
start = time.time()
res_list = []
for p in node_pairs:
fwcn = cal_FWCN(g, p[0],p[1])
# if fwcn >0:
tem_list = [p[0],p[1],fwcn]
res_list.append(tem_list)
res_df_by_for = pd.DataFrame(res_list)
res_df_by_for.columns = ['NodeA','NodeB','FWCN']
print('For Iteration Takes %.3f Seconds' % (time.time() - start))
# to_pickle(res_df_by_for, 'res_df_by_for')
# 4 矩阵方式计算
start = time.time()
res_df_by_mat = MatFWCN(g)
print('Mat Takes %.3f Seconds' % (time.time() - start))
In [214]: run fb3.py
For Iteration Takes 123.003 Seconds
Mat Takes 5.236 Seconds
In [216]: res_df_by_for.head()
Out[216]:
NodeA NodeB FWCN
0 2661 2662 2.482988
1 2661 2665 3.191722
2 2661 2667 0.636166
3 2661 2668 1.618131
4 2661 2671 0.159273
In [217]: res_df_by_mat.head()
Out[217]:
combs FWCN NodeA NodeB
0 (0, 1) 2.482988 2661 2662
1 (0, 2) 3.191722 2661 2665
2 (0, 3) 0.636166 2661 2667
3 (0, 4) 1.618131 2661 2668
4 (0, 5) 0.159273 2661 2671
# 精度上有微小差异,忽略不计
In [218]: print('Difference of Two',
...: (res_df_by_mat['FWCN'].values - res_df_by_for['FWCN'].values).su
...: m())
Difference of Two -2.1685986340003183e-12
时间上快了至少20倍?