先有评论文本5000条,由两部分组成,前面是评论,后面是分数,1表示好评(后文用5分表示),0表示差评(后文用1分表示)
一直在使用联想电脑,非常相信联想,本品绝对是正品,与描述的完全一致 1
这款电脑很不错的呢运行起来很快 玩游戏也不卡 性价比还是可以的 1
就冲这价格波动及满赠活动最多一星,吗的恶心死我了 0
异响,屏幕漏光,动不动蓝屏,acer的技术支持太差了,难道没有测试的吗?这些都是很明显的问题, 0
买了一天就降价200,还说什么30天保价?京东还可信么? 0
代码流程
- 加载停词表,对1分和5分评论文本进行结巴分词。去掉停词后的数据写入json文件,用于后续使用。
- 统计词频,获取1分和5分评论文本的高频词。
- 实现wordNetwork类,继承networkx里的Graph,重新定义边的添加方法和边的过滤方法。
- 绘制词网,用Gephi软件实现可视化。观察共有词的连接结构。
- 分别用community方法和Gephi软件实现社区分布的可视化
- 计算1分评论和5分评论的所有共有词的向量差异
import json
import jieba
import matplotlib
import matplotlib.pyplot as plt
import networkx as nx
from networkx import Graph
import collections
import numpy as np
import community
from scipy import stats
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
1.加载停词表,对1分和5分评论文本进行结巴分词。去掉停词后分别存入good和bad列表,每个二级列表表示每条评论(已分词),并将数据写入json文件,用于后续使用。
def load_sw(filepath):
sw = [line.strip() for line in open(filepath,'r',encoding='utf8')]
return sw
def cut_words(file, stopwords, seperator='\t'):
good = []
bad = []
i = j = 0
with open(file, 'r', encoding='utf8') as f:
for line in f:
items = line.strip().split(seperator)
if len(items) == 2:
if items[1] == '0':
bad.append([])
for w in jieba.cut(items[0].strip()):
if w not in stopwords and w != ' ':
bad[i].append(w)
i += 1
if items[1] == '1':
good.append([])
for w in jieba.cut(items[0].strip()):
if w not in stopwords and w != ' ':
good[i].append(w)
j += 1
with open('good.json', 'w', encoding='gb18030') as fjson:
json.dump(good, fjson, ensure_ascii=False, indent=2)
with open('bad.json', 'w', encoding='gb18030') as fjson:
json.dump(bad, fjson, ensure_ascii=False, indent=2)
2. 统计词频,获取1分和5分评论文本的高频词。
# 统计词频
def get_freq(wd_list):
wf = {}
for wd in wd_list:
for w in wd:
if w in wf:
wf[w] += 1
else:
wf[w] = 1
return wf
# 获取topn词
def topn_w(topn, sorted_wf):
top_list = []
for i in range(topn):
top_list.append(sorted_wf[i][0])
return top_list
def main():
stopwords = load_sw('stopwords_list.txt')
cut_words('1分和5分评论文本.txt', stopwords)
# 读取json文件
with open('good.json', 'r', encoding='gb18030') as f:
good_data = json.load(f)
with open('bad.json', 'r', encoding='gb18030') as f:
bad_data = json.load(f)
# 好评的高频词
word_freq_good = get_freq(good_data)
sorted_wf_good = sorted(word_freq_good.items(),key=lambda x: x[1],reverse=True)
top_good = topn_w(25, sorted_wf_good)
print('topn good:')
print(top_good)
# 差评的高频词
word_freq_bad = get_freq(bad_data)
sorted_wf_bad = sorted(word_freq_bad.items(),key=lambda x: x[1],reverse=True)
top_bad = topn_w(25, sorted_wf_bad)
print('topn bad:')
print(top_bad)
3. 实现wordNetwork类,继承networkx里的Graph,重新定义边的添加方法和边的过滤方法。
# 继承Graph类
class wordNetwork(Graph):
# 边的添加方法,w默认值为4
def add_edges_from_list(self, wd_list, w=4):
for comment in wd_list:
for wd in comment:
for k in range(comment.index(wd)+1, comment.index(wd)+w):
if k < len(comment):
if (wd, comment[k]) not in self.edges():
self.add_edge(wd, comment[k], weight=1)
else:
self[wd][comment[k]]['weight'] += 1
# 边的过滤方法
def filt_edge(self, t=20):
for edge in self.edges():
if self[edge[0]][edge[1]]['weight'] < t:
self.remove_edge(edge[0],edge[1])
del_list = []
for n, nbrs in self.adj.items():
if not nbrs:
del_list.append(n)
for item in del_list:
self.remove_node(item)
- 绘制词网,输出1分和5分的关键共有词(边的数量最高的前10个词),输出gexf文件,用Gephi进行观察
# 构建评论词网
good_net = wordNetwork()
good_net.add_edges_from_list(good_data)
good_net.filt_edge(t=130)
# 输出边的数量最多的词
good_degree_topn = sorted(good_net.degree,key=lambda x: x[1],reverse=True)
print('topn degree good')
print(good_degree_topn[0:10])
# 输出gexf文件
nx.write_gexf(good_net,'good.gexf')
# 画词网
nx.draw(good_net, pos=nx.random_layout(good_net),with_labels=True, font_size=7,edge_color='r', node_size=300)
plt.show()
bad_net = wordNetwork()
bad_net.add_edges_from_list(bad_data)
bad_net.filt_edge(t=130)
bad_degree_topn = sorted(bad_net.degree, key=lambda x: x[1],reverse=True)
print('topn degree bad')
print(bad_degree_topn[0:10])
nx.write_gexf(bad_net,'bad.gexf')
nx.draw(bad_net, pos=nx.random_layout(bad_net), with_labels=True,font_size=7, edge_color='r', node_size=300)
plt.show()
用gephi进行观察时更清晰,可以看出当t=130时,5分的词网更为复杂,共有词更多,连接也复杂。 5.社区方法 用community实现可视化:
# 分社区
part = community.best_partition(bad_net)
#print(part)
# 计算模块度
mod = community.modularity(part, bad_net)
#print(mod)
# 画图
values = [part.get(node) for node in bad_net.nodes()]
nx.draw_spring(bad_net,cmap=plt.get_cmap('jet'),node_color=values,with_labels=True)
plt.show()
用Gephi实现社区分布的可视化: 5分评论,被分为14个社区。包含的点较多的有3个社区 “电脑”所在的社区有:不错、满意、好看、漂亮、好用等整体的正面评价。 “客服”所在的社区有:解决、解答、态度、耐心等 Win10、开机、系统、运行、流畅等是对电脑性能的评价 1分评论,被分为12个社区。包含的点较多的有3个社区. “电脑”所在的社区有:垃圾、卡、不好等。 差评、后悔、千万别等为一个社区,是购买体验差的评价。 客服、退货等为一个社区,与售后相关。 6.计算1分评论和5分评论的所有共有词的向量差异 (1)找到1分和5分词网中共同出现的节点作为共有词 (2)计算每个共有词在1分和5分评论文本中出现的总次数,作为该词的重要性。(建立一个向量,向量上每列的值为对应词在文本里出现的总频数) (3)向量归一化,freq/sum(freq) (4)计算1分和5分向量的皮尔斯相关系数。
# 共有词向量的相关性
def common_words_simi(good_w, bad_w, good_data, bad_data):
common_w = {} # 找到1分和5分的共有词
for w in good_w:
if w in bad_w:
common_w[w] = 0
common = common_w
print(common_w.keys())
# 5分文本的共有词重要性向量
vec_good = []
for wd in good_data:
for w in wd:
if w in common:
common[w] += 1
for key in common.keys():
vec_good.append(common[key])
# 归一化
total = sum(vec_good)
for i in range(len(vec_good)):
vec_good[i] = vec_good[i]/total
print(vec_good)
common = common_w
vec_bad = []
for wd in bad_data:
for w in wd:
if w in common:
common[w] += 1
for key in common.keys():
vec_bad.append(common[key])
# 归一化
total = sum(vec_bad)
for i in range(len(vec_bad)):
vec_bad[i] = vec_bad[i]/total
print(vec_bad)
# 皮尔斯相关性
print("相关性:")
print(stats.pearsonr(vec_good, vec_bad))
由结果可知,共有词有:‘开机’, ‘货’, ‘电脑’, ‘质量’, ‘送’, ‘东西’, ‘系统’, ‘买’, ‘物流’, ‘收到’, ‘速度’, ‘硬盘’, ‘固态’, ‘服务’, '鼠标 ', ‘卖家’, ‘客服’, ‘几天’, ‘真的’, ‘发现’, ‘第一次’, ‘网上’, ‘态度’, ‘问’, ‘win10’, ‘慢’, ‘新’ 两个向量的皮尔斯相关系数:(0.9447392932592632, 1.2953042118156725e-13) 所得结果的第一个数表示相关系数,体现两个向量线性相关的程度。相关系数为0时表示不相关,相关系数接近1表示正相关。0.9447392932592632说明两个向量高度正相关,说明每个共有词在1分和5分评论文本中出现的次数相差不大,重要性差不多。 1.2953042118156725e-13为P值,P值是配对t检验(paired t-test)计算过程中得到的结果。 无效假设(null hypothesis)H0,两参量间不存在“线性”关联。 备择假设(alternative hypothesis)H1?,两参量间存在“线性”关联。 取显著性水平为0.05,P值<0.05,说明两参量间存在“线性”关联。
|