1 介绍 在《对共词关系求协方差矩阵后是否有更好的社会网络分析结果?》一篇我们演练了怎样用协方差矩阵表示共词矩阵,同时讲解了使用协方差矩阵的意义,并且设想了更进一步编程探索的方向。那么,是否可以计算皮尔森相关系数来表示共词关系?我认为是可以,也许在某些分析任务中会更合适一些。同时,就像运用在PCA运算时一样,争论一样是有的。比如,在PCA运算中,更应该使用协方差呢还是皮尔森相关系数,随便在网络上一搜,就有很多类似这样的争论: 在PCA计算中我倾向于使用covariance,因为书上就是那样算的。不管争论怎样,为了这套notebook的完整性,本notebook尝试一下皮尔森相关系数,看看有什么发现。 2 使用方法 为了执行本notebook的分析任务,操作顺序是: - 在GooSeeker分词和文本分析软件上创建文本分析任务并导入包含待分析内容的excel,分析完成后导出选词矩阵表
- 将导出的excel表放在本notebook的data/raw文件夹中
- 从头到尾执行本notebook的单元
注意:GooSeeker发布的每个notebook项目目录都预先规划好了,具体参看Jupyter Notebook项目目录规划参考。如果要新做一个分析项目,把整个模板目录拷贝一份给新项目,然后编写notebook目录下的ipynb文件。 3 修改历史 2022-08-21:第一版发布 4 版权说明 本notebook是GooSeeker大数据分析团队开发的,所分析的源数据是GooSeeker分词和文本分析软件生成的,本notebook中的代码可自由共享使用,包括转发、复制、修改、用于其他项目中。 5 准备运行环境 5.1 引入需要用到的库 # -*- coding: utf-8 -*- import os import numpy as np import pandas as pd import networkx as nx import matplotlib.pyplot as plt import pylab %xmode Verbose import warnings # 软件包之间配套时可能会使用过时的接口,把这类告警忽略掉可以让输出信息简练一些 warnings.filterwarnings("ignore", category=DeprecationWarning) # 把RuntimeWarning忽略掉,不然画图的时候有太多告警了 warnings.filterwarnings("ignore", category=RuntimeWarning)
5.2 设置中文字体 因为含有中文,plt画图会显示下面的错误信息: C:\ProgramData\Anaconda3\lib\site-packages\matplotlib\backends\backend_agg.py:238: RuntimeWarning: Glyph 32993 missing from current font. font.set_text(s, 0.0, flags=flags)
为了防止plt显示找不到字体的问题,先做如下设置。参看glyph-23130-missing-from-current-font #plt.rcParams['font.sans-serif']=['SimHei'] # 上面一行在macOS上没有效果,所以,使用下面的字体 plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] plt.rcParams['axes.unicode_minus']=False
5.3 常量和配置 在我们发布的一系列Jupyter Notebook中,凡是处理GooSeeker分词软件导出的结果文件的,都给各种导出文件起了固定的名字。为了方便大家使用,只要把导出文件放在data/raw文件夹,notebook就会找到导出文件,赋值给对应的文件名变量。下面罗列了可能用到的文件名变量: - file_all_word:词频表
- file_chosen_word: 选词结果表
- file_seg_effect: 分词效果表
- file_word_occurrence_matrix: 选词矩阵表(是否出现)
- file_word_frequency_matrix: 文档词频对应矩阵
- file_word_document_match: 选词匹配表
- file_co_word_matrix: 共词矩阵表
代码如下: pd.set_option('display.width', 1000) # 设置字符显示宽度 pd.set_option('display.max_rows', None) # 设置显示最大 # np.set_printoptions(threshold=np.inf) # threshold 指定超过多少使用省略号,np.inf代表无限大 # 存原始数据的目录 raw_data_dir = os.path.join(os.getcwd(), '../../data/raw') # 存处理后的数据的目录 processed_data_dir = os.path.join(os.getcwd(), '../../data/processed') filename_temp = pd.Series(['词频','分词效果','选词矩阵','选词匹配','选词结果','共词矩阵']) file_all_word = '' file_seg_effect = '' file_word_occurrence_matrix = '' file_word_frequency_matrix = '' file_word_document_match = '' file_chosen_word = '' file_co_word_matrix = ''
5.4 检测data\raw目录下是否有GooSeeker分词结果表 在本notebook使用选词矩阵表,不再是共词矩阵表,因为选词矩阵表含有更丰富的内容:每个词在每篇文档中出现的次数。下面的代码将检查data/raw中有没有这个表,如果没有会报错,后面的程序就没法执行了。 # 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵' print(raw_data_dir + '\r\n') for item_filename in os.listdir(raw_data_dir): if filename_temp[2] in item_filename: file_word_frequency_matrix = item_filename continue if file_word_frequency_matrix: print("选词矩阵表:", "data/raw/", file_word_frequency_matrix) else: print("选词矩阵表:不存在")
输出结果像这样: C:\Users\work\workspace\notebook\二舅\用皮尔森相关系数表示共词矩阵是否能用来做社会网络分析\notebook\eda\../../data/raw 选词矩阵表: data/raw/ 选词矩阵-知乎-二舅.xlsx
6 读取选词矩阵表并存入矩阵 读入过程不展开讲解,具体参看《共词分析中的共词关系是怎么得到的?》 6.1 用pandas dataframe读入选词矩阵 df_word_frequency_matrix = pd.read_excel(os.path.join(raw_data_dir, file_word_frequency_matrix)) df_word_frequency_matrix.head(2)
6.2 提取字段名 将用于给graph的node命名 coword_names = df_word_frequency_matrix.columns.values[2:] print("There are ", len(coword_names), " words") coword_names
输出结果如下: There are 133 words array(['二舅', '视频', '苦难', '精神', '内耗', '故事', '问题', '社会', '时代', '人生', '时候', '世界', '作者', '残疾', '中国', '作品', '农村', '城市', '现实', '电影', '命运', '兴趣', '人民', '东西', '底层', '媒体', '文化', '年轻人', '人们', '事情', '感觉', '观众', '普通人', '孩子', '收入', '一生', '经历', '彭叔', '内容', '编剧', '鸡汤', '价值', '年代', '时间', '原因', '网友', '能力', '老人', '评论', '能量', '励志', '村里', '文案', '资本', '医生', '文艺创作', '艺术', '流量', '国家', '内心', '个人', '情绪', '大众', '朋友', '农民', '意义', '母亲', '悲剧', '好人', '大学', '观点', '机会', '残疾人', '思想', '文艺', '机制', '压力', '力量', '公寓', '角度', '父母', '分钟', '心理', '外甥', '小镇', '方式', '政府', '环境', '人物', '老师', '物质', '态度', '父亲', '条件', '个体', '关系', '群众', '历史', '情况', '穷人', '房子', '人人', '本质', '回村', '木匠', '状态', '官方', '主流', '平台', '心态', '想法', '周劼', '办法', '视角', '生命', '青年', '热度', '公子', '文学', '经济', '人类', '源泉', '资源', '代表', '地方', '宁宁', '文艺作品', '医疗', '目的', '日子', '身体', '村子', '过程'], dtype=object)
6.3 生成矩阵数据结构 # 使用astype函数对数据类型进行转换,否则,下面画图的时候可能会报错 array_word_frequence_matrix = df_word_frequency_matrix.values[:, 2:].astype(float) array_word_frequence_matrix
输出结果: array([[0., 1., 0., ..., 0., 0., 0.], [0., 0., 0., ..., 0., 0., 0.], [0., 0., 0., ..., 0., 0., 0.], ..., [0., 0., 0., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.], [1., 0., 0., ..., 0., 0., 0.]])
7 求皮尔森相关系数 在《共词分析中的共词关系是怎么得到的?》我们说过,如果选词矩阵称为R,那么,RTR(由于没法为公式排版,这串字符看起来不知道是什么,其实是指R的转置乘以R)就是共词矩阵(这里假定R中只有0和1两个值表示是否出现这个词,我们下面的计算所用的不只是1,而是>=1的数值表示词频,原理一样)。如果先把R去中心化得到矩阵B(转换成mean deviation form),那么,BTB(这串字符的含义类似RTR)就是协方差矩阵。那么再进一步,除以两个标准差,就得到了皮尔森相关系数。 而在numpy程序包中,只需调用一个函数就能完成上面说的过程。 pearson_corr = np.corrcoef(array_word_frequence_matrix, rowvar = False) pearson_corr
输出结果: array([[ 1. , 0.53004538, 0.28459398, ..., 0.18865428, 0.10032949, 0.10133586], [ 0.53004538, 1. , 0.21525746, ..., 0.09881271, 0.04032969, 0.09035983], [ 0.28459398, 0.21525746, 1. , ..., 0.02588627, 0.00654365, -0.00807632], ..., [ 0.18865428, 0.09881271, 0.02588627, ..., 1. , 0.05056049, 0.10530439], [ 0.10032949, 0.04032969, 0.00654365, ..., 0.05056049, 1. , -0.01411261], [ 0.10133586, 0.09035983, -0.00807632, ..., 0.10530439, -0.01411261, 1. ]])
Pearson correlation coefficient矩阵是一个对称矩阵,对角线上的值是1,为了画图的时候不画环回的边,我们把对角线的所有值赋0 np.fill_diagonal(pearson_corr, 0) pearson_corr
输出结果: array([[ 0. , 0.53004538, 0.28459398, ..., 0.18865428, 0.10032949, 0.10133586], [ 0.53004538, 0. , 0.21525746, ..., 0.09881271, 0.04032969, 0.09035983], [ 0.28459398, 0.21525746, 0. , ..., 0.02588627, 0.00654365, -0.00807632], ..., [ 0.18865428, 0.09881271, 0.02588627, ..., 0. , 0.05056049, 0.10530439], [ 0.10032949, 0.04032969, 0.00654365, ..., 0.05056049, 0. , -0.01411261], [ 0.10133586, 0.09035983, -0.00807632, ..., 0.10530439, -0.01411261, 0. ]])
8 生成图并进行探索 8.1 从NumPy数组生成networkx图 参看networkx文档,有专门的函数从其他数据结构直接生成graph graph_pearson_corr = nx.from_numpy_array(pearson_corr) print(nx.info(graph_pearson_corr)) #graph_pearson_corr.edges(data=True)
输出结果: Name: Type: Graph Number of nodes: 133 Number of edges: 8778 Average degree: 132.0000
8.2 给node加上label 对程序代码的解释参看《用networkx和python编程可视化分析共词关系图》,不再赘述。 coword_labels = {} for idx, node in enumerate(graph_pearson_corr.nodes()): print("idx=", idx, "; node=", node) coword_labels[node] = coword_names[idx] graph_pearson_corr = nx.relabel_nodes(graph_pearson_corr, coword_labels) sorted(graph_pearson_corr)
8.3 画图 figure函数的使用方法参看pyplot官网 。其他参考资料: 由于是一个全连接图,就没有画的必要了,下面的代码都注释掉了。 #pos = nx.spring_layout(graph_pearson_corr) #plt.figure(1,figsize=(20,20)) #nx.draw(graph_pearson_corr, pos, node_size=10, with_labels=True, font_size=22, font_color="red") #plt.show()
9 用MST(maximum spanning tree)删减边 9.1 MST计算 graph_pearson_corr_mst = nx.maximum_spanning_tree(graph_pearson_corr) print(nx.info(graph_pearson_corr_mst)) # graph_pearson_corr_mst.edges(data=True)
输出结果: Name: Type: Graph Number of nodes: 133 Number of edges: 132 Average degree: 1.9850
9.2 画MST后的图 # 方案2: #pos = nx.circular_layout(graph_pearson_corr_mst) pos = nx.spring_layout(graph_pearson_corr_mst) plt.figure(2,figsize=(20,20)) nx.draw(graph_pearson_corr_mst, pos, node_size=50, with_labels=True, font_size=22, font_color="red") plt.show()
10 设定阈值删减边 10.1 选择阈值 删掉多少边比较好呢?我们先看看这些位置上的边权重是多少: coword_median = np.median(pearson_corr) coword_median
输出结果:0.041998505931604424 coword_max = np.max(pearson_corr) coword_max
输出结果:0.9992040385619895 coword_min = np.min(pearson_corr) coword_min
输出结果:-0.06463870286477978 coword_per10 = np.percentile(pearson_corr, 90) coword_per10
输出结果:0.21065152297277612 coword_per2 = np.percentile(pearson_corr, 98) coword_per2
输出结果:0.48291207729502966 10.2 删除权重小于2%分位的边 我们不挨个尝试删减度了,直接实验重度删减后的效果 graph_pearson_corr_per2 = graph_pearson_corr.copy() graph_pearson_corr_per2.remove_edges_from([(n1, n2) for n1, n2, w in graph_pearson_corr_per2.edges(data="weight") if w < coword_per2]) pos = nx.spring_layout(graph_pearson_corr_per2) plt.figure(1,figsize=(30,30)) nx.draw(graph_pearson_corr_per2, pos, node_size=10, with_labels=True, font_size=22, font_color="red") plt.show()
10.3 删除基于2%分位裁剪的图的孤立点 graph_pearson_corr_per2.remove_nodes_from(list(nx.isolates(graph_pearson_corr_per2))) #pos = nx.circular_layout(graph_pearson_corr_per2) pos = nx.spring_layout(graph_pearson_corr_per2) plt.figure(1,figsize=(30,30)) nx.draw(graph_pearson_corr_per2, pos, node_size=10, with_labels=True, font_size=22, font_color="blue") plt.show()
11 点度中心性分析 11.1 定义一个公共画图函数 下面的代码来自NetworkX的中心性分析案例:plot_degree.html。将用来从多个角度观察点度中心性。 def diplay_graph_degree(G): seq_degree = sorted((d for n, d in G.degree()), reverse=True) dmax = max(seq_degree) fig = plt.figure("Degree of the count graph", figsize=(8, 8)) # Create a gridspec for adding subplots of different sizes axgrid = fig.add_gridspec(5, 4) ax0 = fig.add_subplot(axgrid[0:3, :]) Gcc = G.subgraph(sorted(nx.connected_components(G), key=len, reverse=True)[0]) pos = nx.spring_layout(Gcc, seed=10396953) nx.draw_networkx_nodes(Gcc, pos, ax=ax0, node_size=20) nx.draw_networkx_edges(Gcc, pos, ax=ax0, alpha=0.4) ax0.set_title("Connected components of G") ax0.set_axis_off() ax1 = fig.add_subplot(axgrid[3:, :2]) ax1.plot(seq_degree, "b-", marker="o") ax1.set_title("Degree Rank Plot") ax1.set_ylabel("Degree") ax1.set_xlabel("Rank") ax2 = fig.add_subplot(axgrid[3:, 2:]) ax2.bar(*np.unique(seq_degree, return_counts=True)) ax2.set_title("Degree histogram") ax2.set_xlabel("Degree") ax2.set_ylabel("# of Nodes") fig.tight_layout() plt.show()
11.2 针对MST裁剪的图的处理 11.2.1 对点度中心性排序 sorted(graph_pearson_corr_mst.degree(), key=lambda x: x[1], reverse=True)
11.2.2 综合展示点度中心性 diplay_graph_degree(graph_pearson_corr_mst)
11.3 针对按2%分位数裁剪的图的处理 11.3.1 对点度中心性排序 sorted(graph_pearson_corr_per2.degree(), key=lambda x: x[1], reverse=True)
11.3.2 综合展示点度中心性 diplay_graph_degree(graph_pearson_corr_per2)
12 总结 12.1 结论 在前面的notebook中,我们使用共词矩阵做社会网络分析和画图,当时比较困扰的一个问题是:“二舅”和“视频”这两个高度集中词形成了绝对的核心,经过MST处理以后,几乎是一个星状结构。计算了协方差以后,有一些改善,但是,又出现了新问题:”彭叔“,”公寓“的文档频率很低,只出现了一次,却被大幅度提升了。本notebook计算了皮尔森相关系数以后,图变散了,“彭叔”被压制了,二舅和视频实际上也被压制的没有那么突出了。 个人认为: 皮尔森相关系数和余弦相似度系数是一类的,他们度量的是夹角的cosine值;而协方差和点积(共词矩阵是点积的结果)是一类的,他们度量的是一个向量向另一个向量投影后的大小。但是,在向量空间中,这些变化都不能改善普遍词和稀有词造成的影响。前面的notebook曾经说过维度诅咒,作为指导原则,还是要精选词。 所以,效果明显且便捷的解决问题的方法是:在GooSeeker分词和情感分析软件上,根据文档频率排序,将普遍词和稀有词都删除,重新导出选词矩阵做实验,就会发现社会网络图的整个拓扑关系都变了,分析结果将极大幅度改善。在接下来的notebook中,我们将专门做一些对比实验。 12.2 对比:精选词后皮尔森相关系数图的MST结果 下图可见,MST后很分散,但是还保留了一个中心“城市”代表的发展平台
13 下载源代码 下载Jupytor notebook上写的notebook源代码请进:对共词关系求皮尔森相关系数后观察社会网络分析效果 |