社区划分与桑基图绘制

这是山东大学大三上学期数据科学方向的《数据科学导论》课程实验三的补充部分,基于老师发的实验文件的基础上进行了补充。
相关文件在最后给出

问题背景

社交活动会在极端事件(地震、海啸等)的影响下发生变化,在受到影响后,人群的社交结构也会变动,即社区规模会变化。为了研究极端事件对社区规模的影响,可以利用社区发现算法划分出社区,并且绘制桑基图查看了人员流动情况。

解决问题

社区发现算法

在这里,使用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的桑基图中,用户的汇聚程度不如日本,较为分散。

本实验源码地址

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来看看小兔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值