1 背景介绍
前一个notebook《马蜂窝游记文本分词后以词语间距为筛选条件生成选词矩阵和匹配结果表 》在游记文本上做了实验,本notebook针对知乎回答做实验。因为所有回答来自于同一个问题,应该可以观察到更好的话题聚类效果。另外,前一个notebook没有把新生成的共词矩阵存盘,本篇做一个圆满的功能,可以生成过滤过的选词匹配表、选词矩阵表、共词矩阵表。
考虑篇幅大小,本notebook只演示过程,详细解释参看《马蜂窝游记文本分词后以词语间距为筛选条件生成选词矩阵和匹配结果表 》。
1.1 实验目的
前两天我们发布了《知乎话题文本分词后的共词矩阵怎样引入词语距离因素 》这篇Notebook, 这是针对使用Gooseeker文本分词和情感分析软件 做毕业设计的同学提出的以下问题的答复:
共词矩阵生成时能否考虑共现词之间的位置距离?比如2个词的距离小于某个值时才算作共现词?
像知乎回答这样的文本,长短不一,而且差距很大,很有必要根据词语间的距离进一步筛选共词关系;另外,长文本很普遍的内容分析任务,例如,政策文件等等,可以选择不同的分析粒度,除了整个文章以外,还可以是段落和句子。但是,这往往需要手工切分。此时,利用词语间距进行自动过滤、避免繁琐的手工处理,也是一个很好的处理方法。
另外还有一项重要的目的:Gooseeker文本分词和情感分析软件 在分词处理过程中,会把长文本切成多段,通常每段都不超过1万字。这样,共现关系就变了。比如,一对词在前一段中共现了,在后一段中又共现了,如果切分成两段,就是说这对词在两个文档中共现过,一共共现了两次。然而,本质是只共现了一次。所以,本notebook首先要把切分开的文本进行合并,纠正重复计数的问题。【注意】Gooseeker文本分词和情感分析软件 并没有合并,下一个版本才会实现合并功能。当前(2024年1月31日)得依赖于这个notebook进行合并。
1.2 实验数据来源
使用知乎_独立话题动态内容采集这个快捷采集 ,爬取知乎上关于二舅话题 的讨论,导出excel。把该excel导入GooSeeker文本分词和情感分析软件 ,经过自动分词,人工选词和过滤,共词匹配后,导出“选词匹配表”,“选词矩阵表”,“分词效果表”:
图1.GooSeeker文本分词和情感分析工具导出分析结果
1.3 本notebook使用方法
基本操作顺序是:
在GooSeeker分词和文本分析软件 上创建分析任务并导入包含原始内容的excel 在自动分词完成后进行人工选词,然后导出“选词匹配表”,“选词矩阵表”,“分词效果表” 将导出的选词匹配表放在本notebook的data/raw文件夹中 从头到尾执行本notebook的单元 在本notebook的data/processed文件夹中新生成“选词匹配已参考距离值-xxxxxx.xlsx”,“选词矩阵已参考距离值-xxxxxx.xlsx”。修改后的表和原有的raw目录下的原表结构一样,不过矩阵中的数值经过了修改,词语列表里的词会有减少。
注意 :每个notebook项目目录都预先规划好了,具体参看Jupyter Notebook项目目录规划参考 。如果要做多个分析项目,把整个模板目录专门拷贝一份给每个分析项目。
2 准备程序环境
导入必要的Python程序包,设定要分析的文件名变量。在这一系列notebook中,我们都使用以下变量对应GooSeeker分词结果表:
file_word_freq:词频表 file_seg_effect: 分词效果表 file_word_choice_matrix: 选词矩阵表 file_word_choice_match: 选词匹配表 file_word_choice_result: 选词结果表 file_co_word_matrix: 共词矩阵表 file_co_occ_matrix_modified:共词矩阵引入词语距离后的表 file_word_choice_matrix_modified: 选词矩阵引入词语距离后的表 file_word_choice_match_modified: 选词匹配引入词语距离后的表
2.1 导入程序包并声明变量
import pandas as pd
import os
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import pylab
from time import time
import datetime
%xmode Verbose
import warnings
warnings.filterwarnings('ignore')
from IPython.core.interactiveshell import InteractiveShell
warnings.filterwarnings("ignore", category=DeprecationWarning)
# 运行在一个cell中的多个输出都显示
InteractiveShell.ast_node_interactivity = "all"
# 存原始数据的目录
raw_data_dir = os.path.join(os.getcwd(), '..\\..\\data\\raw')
# 存处理后的数据的目录
processed_data_dir = os.path.join(os.getcwd(), '..\\..\\data\\processed')
filename_temp = pd.Series(['分词效果','选词匹配','选词矩阵'])
2.2 检测data\raw目录下是否有分词效果表
以下的演示以GooSeeker分词和文本分析软件 生成的分词效果和共词矩阵excel表为例,需要把分词效果和共词矩阵excel表放到本notebook的data/raw文件夹下,如果没有数据表,下面代码执行后将提示“不存在”
# 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵'
file_seg_effect = ''
print(raw_data_dir + '\r\n')
for item_filename in os.listdir(raw_data_dir):
if filename_temp[0] in item_filename:
file_seg_effect = item_filename
continue
if file_seg_effect:
print("分词效果excel表:", "data\\raw\\", file_seg_effect)
else:
print("分词效果excel表:不存在")
输出结果:
C:\Users\work\workspace_xxxxxxxxx\notebook\eda\..\..\data\raw
分词效果excel表: data\raw\ 分词效果_202401261056249690.xlsx
2.3 检测data\raw目录下是否有选词匹配表
# 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵'
file_word_choice_match = ''
file_word_choice_match_modified = ''
print(raw_data_dir + '\r\n')
for item_filename in os.listdir(raw_data_dir):
if filename_temp[1] in item_filename:
file_word_choice_match = item_filename
continue
if file_word_choice_match:
print(filename_temp[1],"excel表:", "data\\raw\\",file_word_choice_match)
file_word_choice_match_modified = file_word_choice_match.replace('选词匹配','选词匹配已引入距离值')
else:
print(filename_temp[1],"excel表:不存在")
输出结果:
C:\Users\work\workspace_xxxxxxxx\notebook\eda\..\..\data\raw
选词匹配 excel表: data\raw\ 选词匹配_202401261056253990.xlsx
2.4 检测data\raw目录下是否有选词矩阵表
# 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵'
file_word_choice_matrix = ''
file_word_choice_matrix_modified = ''
print(raw_data_dir + '\r\n')
for item_filename in os.listdir(raw_data_dir):
if filename_temp[2] in item_filename:
file_word_choice_matrix = item_filename
continue
if file_word_choice_matrix:
print(filename_temp[2],"excel表:", "data\\raw\\",file_word_choice_matrix)
file_word_choice_matrix_modified = file_word_choice_matrix.replace('选词矩阵','选词矩阵已引入距离值')
else:
print(filename_temp[2],"excel表:不存在")
输出结果:
C:\Users\work\workspace_xxxxxxxx\notebook\eda\..\..\data\raw
选词矩阵 excel表: data\raw\ 选词矩阵_202401261056254960.xlsx
3 定义词语位置距离阀门值
如果某段文中2个词语的位置距离高于该阀门值,则不计入共现次数。知乎很多回答的语句通常比较规整,相对于其它社交媒体也会比较长一些,所以,我们将距离参数设置大一些。下面设置成10,估计是在句子粒度上筛选。还可以试试设置大一些,跨句子筛选。
k_word_distance = 10
4 读入分词效果表并合并序号
4.1 读取分词效果表
df_file_seg_effect = pd.read_excel(os.path.join(raw_data_dir, file_seg_effect))
4.2 合并相同序号的记录
我们查看原数据,发现有多行数据对应同一个序号的,这种情况是因为原数据太长,分词工具就把原数据分成了几段来处理。为了后面处理的方便和准确,我们把1个序号对应多条记录的合并成1条记录
df_file_seg_effect_agg = df_file_seg_effect.groupby('序号').agg({'原数据':lambda x:''.join(str(x) for x in x.values),'分词数据':lambda x:' '.join(str(x) for x in x.values),'关键词':lambda x:','.join(str(x) for x in x.values) })
df_file_seg_effect_agg = df_file_seg_effect_agg.reset_index()
5 读入选词匹配表并合并序号
5.1 读取选词匹配表
df_file_word_choice_match = pd.read_excel(os.path.join(raw_data_dir, file_word_choice_match))
5.2 合并相同序号的记录
df_file_word_choice_match_agg = df_file_word_choice_match.groupby('序号').agg({'原数据':lambda x:''.join(str(x) for x in x.values), '打标词':lambda x:', '.join(str(x) for x in x.values)})
# df_file_word_choice_match_temp = df_file_word_choice_match.groupby('序号').agg({'打标词':lambda x:', '.join(str(x) for x in x.values)})
# df_file_word_choice_match_agg = pd.concat([df_file_word_choice_match_agg, df_file_word_choice_match_temp], axis=1)
df_file_word_choice_match_agg = df_file_word_choice_match_agg.reset_index()
6 读入选词矩阵表并合并序号
6.1 读取选词矩阵表
df_file_word_choice_matrix = pd.read_excel(os.path.join(raw_data_dir, file_word_choice_matrix))
6.3 取选词矩阵表头所有打标词的列表
word_list = df_file_word_choice_matrix.columns.values[2:]
word_list
6.4 合并相同序号的记录
合并选词矩阵同序号的数据,比上面对其它2张表的合并要麻烦些,因为矩阵的列数是不固定的,需要使用循环把所有的列进行合并运算。
# 先合并'正文'这一列
df_file_word_choice_matrix_agg = df_file_word_choice_matrix.groupby('序号').agg({'正文':lambda x:''.join(str(x) for x in x.values)})
df_file_word_choice_matrix_agg.head(10)
# 循环合并其它列
for column in word_list:
df_file_word_choice_matrix_agg_temp = df_file_word_choice_matrix.groupby('序号').agg({column:sum})
df_file_word_choice_matrix_agg = pd.concat([df_file_word_choice_matrix_agg, df_file_word_choice_matrix_agg_temp], axis=1)
df_file_word_choice_matrix_agg = df_file_word_choice_matrix_agg.reset_index()
7 基于词语位置距离修改共词矩阵表
7.1 创建函数cal_word_distance(text, word1, word2)
函数功能:
在入参文本text中查找词语word1和词语word2的位置,并计算位置距离
函数返回值:
-1:表示2个词在该text文本中没有共现关系 大于等于0的整数:该值为2个词的位置距离
def cal_word_distance(text, word1, word2):
text_array = text.split(' ')
word_distance = -1
word1_pos = -1
word2_pos = -1
if word1 in text and word2 in text:
for i,item in enumerate(text_array):
if item == word1 and word1_pos == -1:
word1_pos = i
if item == word2 and word2_pos == -1:
word2_pos = i
if word1_pos > -1 and word2_pos > -1:
word_distance = word1_pos - word2_pos if word1_pos > word2_pos else word2_pos - word1_pos
break
return word_distance
7.2 创建函数is_within_distance(text, word, wordlist, distance_threshold)
函数功能:
在入参文本text中查找并计算词语word和wordlist列表中所有词的位置距离,只要有一对词的距离大于distance_threshold,就返回False。否则返回True
函数返回值:
True:入参词word和词列表wordlist中所有词的距离均小于等于distance_threshold False:词列表wordlist中至少有1个词和入参词word的距离大于distance_threshold
def is_within_distance(text, word, wordlist, distance_threshold):
return_value = True
for word2 in wordlist:
if word == word2:
continue
else:
# word和wordlist中的每个词计算距离,出现大于distance_threshold的情况则退出循环返回False
if cal_word_distance(str(text),word,word2) > distance_threshold:
return_value = False
break
return return_value
7.3 复制dataframe
下面的修改都在复制的dataframe上做,dataframe名字中去掉_file_,以示区别。保留两个dataframe是为了后面画图对比。
df_word_choice_match = df_file_word_choice_match_agg.copy(deep=True)
df_word_choice_matrix = df_file_word_choice_matrix_agg.copy(deep=True)
7.4 取选词矩阵表头所有打标词的列表
word_list = df_word_choice_matrix.columns.values[2:]
word_list
7.5 修改选词匹配表和选词矩阵表数据
修改规则是:
针对选词匹配表每行正文对应的所有打标词,每次取一个打标词,判断这个打标词和其它打标词的距离是否大于预定义的距离阈值k_word_distance。 如果当前词和某个词的距离大于k_word_distance,则将当前词从该行正文的打标词列表里删除。 如果当前词和其它词的距离都小于等于k_word_distance,则保留当前词。 对于已删除的打标词,根据正文序号在选词矩阵里找到该词的值并置为0
print('修改开始',datetime.datetime.now())
# 遍历选词匹配表
for index, row in df_word_choice_match.iterrows():
# 取序号,下面会使用序号查找分词效果表,并更新选词矩阵表和选词匹配表
sn = row['序号']
# 取当前行的打标词字段
word_choice = row['打标词']
# 把打标词含有的多个词字符串转换为词列表
word_choice_list = word_choice.split(', ')
# 此变量存储当前行更新后的打标词
word_choice_updated = []
if len(word_choice_list) > 0:
# 逐个从当前行的打标词列表里取当前词
for word1 in word_choice_list:
# 计算当前词和其它词的距离是否有大于k_word_distance的情况
if is_within_distance(df_file_seg_effect_agg.loc[df_file_seg_effect_agg['序号'] == sn, '分词数据'].values[0], word1, word_choice_list, k_word_distance):
# 当前词和其它词的距离都不大于k_word_distance,当前词继续保留在打标词列表中
word_choice_updated.append(word1)
else:
# 更新选词矩阵:当前词和其它词的距离存在大于k_word_distance的情况,则矩阵中的对应值置为0
df_word_choice_matrix.loc[df_word_choice_matrix['序号'] == sn, word1] = 0
# 更新选词匹配表中当前行的打标词字段
df_word_choice_match.loc[df_word_choice_match['序号'] == sn, '打标词'] = ', '.join(word_choice_updated)
print('修改完成',datetime.datetime.now())
输出结果:
修改开始 2024-02-04 11:44:29.765408
修改完成 2024-02-04 11:44:40.917026
7.6 删除选词矩阵中全部列值都为0的列
如果在修改后的选词矩阵中,某个词和所有正文的值都为0,则删除该列
print('删除开始',datetime.datetime.now())
for column in df_word_choice_matrix.columns[2:]:
if df_word_choice_matrix[column].sum() == 0:
df_word_choice_matrix = df_word_choice_matrix.drop(column, axis=1)
print('删除完成',datetime.datetime.now())
输出结果:
删除开始 2024-02-04 11:44:40.941963
删除完成 2024-02-04 11:44:41.169350
7.7 查看修改后的选词矩阵
使用head(10)查看前10行
7.8 保存修改后的选词矩阵
保存到文件中,后续分析可以使用。
df_word_choice_matrix=df_word_choice_matrix.set_index('序号')
df_word_choice_matrix.to_excel(os.path.join(processed_data_dir, file_word_choice_matrix_modified))
df_word_choice_matrix=df_word_choice_matrix.reset_index()
7.9 查看修改后的选词匹配表
使用head(10)查看前10行
df_word_choice_match.head(10)
7.10 保存修改后的选词匹配表
保存到文件中,后续分析可以使用。
df_word_choice_match=df_word_choice_match.set_index('序号')
df_word_choice_match.to_excel(os.path.join(processed_data_dir, file_word_choice_match_modified))
df_word_choice_match=df_word_choice_match.reset_index()
8 生成共词矩阵
在《共词分析中的共词关系是怎么得到的? 》这篇notebook中,我们介绍了选词矩阵与共词矩阵的关系。现在我们利用修改后的选词矩阵生成新的共词矩阵
8.1 修改选词矩阵的值
选词矩阵中的数值目前表示词频,下面修改成0或者1,表示是否出现该词。
8.1.1 切出共现词数组
将用来画图
column_names = df_word_choice_matrix.columns.values[2:]
print("There are ", len(column_names), " words")
column_names
8.1.2 将dataframe转换成数组
数组中每个元素表示词频数
array_word_frequence = df_word_choice_matrix.values[:, 2:]
array_word_frequence
array_word_frequence.shape
输出结果:
(769, 148)
8.1.3 词频值改成0或1表示是否出现
array_word_occurrence = array_word_frequence.copy()
array_word_occurrence[array_word_occurrence > 0] = 1
array_word_occurrence
8.2 矩阵相乘得到共词矩阵
matrix_coword = np.dot(np.transpose(array_word_occurrence), array_word_occurrence)
matrix_coword
matrix_coword.shape
输出结果
(148, 148)
# 把矩阵的对角线上的值设置为0
for i in range(len(matrix_coword)):
matrix_coword[i][i] = 0
8.3 保存矩阵相乘得到的共词矩阵
#从numpy.ndarray转成dataframe
df_matrix_coword = pd.DataFrame(matrix_coword)
#查看前5行
df_matrix_coword.head(5)
#修改列名
df_matrix_coword.columns = column_names
#dataframe新增1列并设置为索引
# 第一个参数指插入的位置,0表示第一列
# 第二个参数指这一列的名字
# 第三个参数为插入的数据
df_matrix_coword.insert(0, 'word', column_names)
df_matrix_coword.set_index('word',inplace=True)
#查看前5行
df_matrix_coword.head(5)
#把这个共词矩阵表保存到data/processed目录下
file_co_occ_matrix_modified = file_word_choice_matrix_modified.replace('选词矩阵','共词矩阵')
df_matrix_coword.to_excel(os.path.join(processed_data_dir, file_co_occ_matrix_modified))
8.4 画网络关系图
8.4.1 从NumPy数组生成networkx图
转换成浮点型数据,不然后面画图会出错
matrix_coword = matrix_coword[:, :].astype(float)
matrix_coword
graph_matrix_coword = nx.from_numpy_array(matrix_coword)
#print(nx.info(graph_matrix_coword))
#graph_matrix_coword.edges(data=True)
8.4.2 给图节点标上中文词
coword_labels = nx.get_node_attributes(graph_matrix_coword,'labels')
for idx, node in enumerate(graph_matrix_coword.nodes()):
print("idx=", idx, "; node=", node)
coword_labels[node] = column_names[idx]
graph_matrix_coword = nx.relabel_nodes(graph_matrix_coword, coword_labels)
sorted(graph_matrix_coword)
8.4.3 引入中文字体
plt.rcParams['font.sans-serif']=['SimHei']
# 上面一行在macOS上没有效果,所以,使用下面的字体
#plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
8.4.4 画图
pos = nx.spring_layout(graph_matrix_coword)
plt.figure(1,figsize=(30,30))
nx.draw(graph_matrix_coword, pos, node_size=10, with_labels=True, font_size=16, font_color="red")
plt.show()
输出结果:
8.4.5 删除孤立点
graph_matrix_coword.remove_nodes_from(list(nx.isolates(graph_matrix_coword)))
#pos = nx.circular_layout(graph_matrix_coword)
pos = nx.spring_layout(graph_matrix_coword)
plt.figure(1,figsize=(30,30))
nx.draw(graph_matrix_coword, pos, node_size=10, with_labels=True, font_size=16, font_color="red")
plt.show()
输出结果:
相比前一个notebook《马蜂窝游记文本分词后以词语间距为筛选条件生成选词矩阵和匹配结果表 》生成的共词关系网络图,这一次得到的图更加密集一些,有可能是知乎话题的语义更加集中。后面我们将用其他算法进一步探索这一次生成的结果数据。
9 总结
本Notebook使用Gooseeker文本分词和情感分析软件导出的分词效果、选词匹配和选词矩阵excel表格,在python下对共现词的距离进行计算并对选词匹配和选词矩阵excel表格数据进行修改,修改后的表格会以一个新的名称保存在data/processed目录下,然后利用选词矩阵表计算得到共词矩阵表,也把共词矩阵表存成excel。
10 下载源代码
Jupyter Notebook源代码可以点击下载: 根据词语间距筛选共现词后用excel保存共词矩阵