赞
踩
这是山东大学大三上学期数据科学方向的《数据科学导论》课程实验三的补充部分,基于老师发的实验文件的基础上进行了补充。
相关文件在最后给出
社交活动会在极端事件(地震、海啸等)的影响下发生变化,在受到影响后,人群的社交结构也会变动,即社区规模会变化。为了研究极端事件对社区规模的影响,可以利用社区发现算法划分出社区,并且绘制桑基图查看了人员流动情况。
在这里,使用networkx.algorithms.community中的k_clique_communities函数,这个函数有三个参数:
G:NetworkX图
k:一个整数,代表了一个社区所包含的最小节点数
cliques:list或generator,使用nx.find_cliques(G)生成的所有社区,默认为None
clique渗透算法简介:
对于一个图G而言,如果其中有一个完全子图(任意两个节点之间均存在边),节点数是k,那么这个完全子图就可称为一个k-clique。
进而,如果两个k-clique之间存在k-1个共同的节点,那么就称这两个clique是“相邻”的。彼此相邻的这样一串clique构成最大集合,就可以称为一个社区(而且这样的社区是可以重叠的,即所谓的overlapping community,就是说有些节点可以同时属于多个社区)。下面第一组图表示两个3-clique形成了一个社区,第二组图是一个重叠社区的示意图。
以下为这个函数的具体实现:
首先K≥2,否则不能构成社区。
如果cliques为默认的None,那么cliques就为所有发现的社区
之后把满足节点要求,即节点数量大于k的社区筛选出来
if k < 2:
raise nx.NetworkXError(f"k={k}, k must be greater than 1.")
if cliques is None:
cliques = nx.find_cliques(G)
cliques = [frozenset(c) for c in cliques if len(c) >= k]
之后建立起节点与社区的字典
membership_dict = defaultdict(list)
for clique in cliques:
for node in clique:
membership_dict[node].append(clique)
对于每个社区,查看哪些社区互相有渗透
def _get_adjacent_cliques(clique, membership_dict):
adjacent_cliques = set()
for n in clique:
for adj_clique in membership_dict[n]:
if clique != adj_clique:
adjacent_cliques.add(adj_clique)
return adjacent_cliques
perc_graph = nx.Graph()
perc_graph.add_nodes_from(cliques)
for clique in cliques:
for adj_clique in _get_adjacent_cliques(clique, membership_dict):
if len(clique.intersection(adj_clique)) >= (k - 1):
perc_graph.add_edge(clique, adj_clique)
具有perc边的连通子图是渗透社区
for component in nx.connected_components(perc_graph):
yield (frozenset.union(*component))
根据地震前后构建的图G1, G2来划分社区
# 发现社区 def find_communities(G1, G2): cliques = {'Before': None, 'After': None} # 社区发现算法,k代表社区所拥有的最小节点数 clique = k_clique_communities(G1, 4) clique = list(clique) print(clique) #获得每个社区的人数 clique_size = [len(cl) for cl in clique] cliques['Before'] = clique print("震前社区数量:{}".format(len(clique))) print("震前社区最大人数:{}".format(max(clique_size))) clique = k_clique_communities(G2, 4) clique = list(clique) clique_size = [len(cl) for cl in clique] cliques['After'] = clique print("震后社区数量:{}".format(len(clique))) print("震后社区最大人数:{}".format(max(clique_size))) return cliques
这里我使用的k=4,clique为得到的社区,clique_size为各个社区的节点数量
以JP的数据为例:
震前震后的社区数据我在before.txt和after.txt中给出(已排序,排序过程在下面的数据整理部分),其包含了每个社区及其节点名称,最终返回的cliques为一个字典,key为’Before’和’After’,value即为我们得到的社区信息
# 得到桑基图数据 def get_Sankey_data(cliques): # 建立节点与社区的字典,从而确定节点处于排名第几的社区 clique_dic = {'Before': {}, 'After': {}} for type in ['Before', 'After']: # 给社区按人数多少排序 cliques[type].sort(reverse=True, key=len) print(type) print(cliques[type]) # 对于人数排名前10的社区,利用字典记录社区内节点其所属的社区 for i in range(10): for name in cliques[type][i]: if name != '': clique_dic[type][name] = i + 1 print(clique_dic) # 构建DataFrame,数据方向为索引 df1 = pd.DataFrame.from_dict(clique_dic['Before'], orient='index', columns=['source']) df2 = pd.DataFrame.from_dict(clique_dic['After'], orient='index', columns=['target']) # 计算两个表格的内连接 df3 = df1.join(df2, how='inner').astype('Int64') df3.columns = ['source', 'target'] # 这里设置value为1是为了求和计算每条路径的流量 df3['value'] = 1 df3 = df3.groupby(['source', 'target']).sum() df3 = df3.reset_index() return df3
构建节点与其所处社区排名对应关系的字典,示例数据我在clique_dic中给出。
DataFrame是一种表格型数据结构,它含有一组有序的列,每列可以是不同的值。并且DataFrame有连接操作,可以通过内连接知道节点在震前和震后分别处于哪个社区。
桑基图(Sankey diagram),即桑基能量分流图,也叫桑基能量平衡图。它是一种特定类型的流程图,图中延伸的分支的宽度对应数据流量的大小,通常应用于能源、材料成分、金融等数据的可视化分析。因1898年Matthew Henry Phineas Riall Sankey绘制的“蒸汽机的能源效率图”而闻名,此后便以其名字命名为“桑基图”。
我引入了pyecharts.charts来绘制桑基图。
构建桑基图需要节点nodes和边links,点的结构为[{‘name’:’节点1’},{‘name’:’节点2’}],边的结构为[{‘source’:’源1’,’traget’:’目标1’,’value’:’值1’},{‘source’:’源2’,’traget’:’目标2’,’value’:’值2’}]。
注意source和targer里的节点名称必须在name里存在,且name不能有重复值,否则桑基图无法正常显示。因此我在起点和终点前加了不同的前缀用以区分。
from networkx.algorithms.community import k_clique_communities from pyecharts.charts import Sankey from pyecharts import options as opts # 绘制桑基图 def draw_Sankey(df, name): nodes = [] vales = df.iloc[:, 0].unique() for value in vales: dic1 = {} dic1['name'] = '1-' + str(value) nodes.append(dic1) dic2 = {} dic2['name'] = '2-' + str(value) nodes.append(dic2) print(nodes) links = [] for i in df.values: dic = {} dic['source'] = '1-' + str(i[0]) dic['target'] = '2-' + str(i[1]) dic['value'] = int(i[2]) links.append(dic) print(links) pic = ( Sankey().add( '', # 图例名称 nodes, # 传入节点数据 links, # 传入边和流量数据 # 设置透明度、弯曲度、颜色 linestyle_opt=opts.LineStyleOpts(opacity=0.3, curve=0.5, color='source'), # 标签显示位置 label_opts=opts.LabelOpts(position='right'), # 节点之间的距离 node_gap=30, ) .set_global_opts(title_opts=opts.TitleOpts(title='{}桑基图'.format(name))) ) pic.render('{}.html'.format(name))
在代码同目录下生成了相应的桑基图html文件,打开即可看到桑基图
可以看出,JP的桑基图中几乎所有社区的用户都汇聚到一起,社区紧密度提升。而EN的桑基图中,用户的汇聚程度不如日本,较为分散。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。