共现矩阵 + 网络关系图:3 步把 10 万条评论里的”隐藏关系”挖出来

老板们最常问:“差评”和”客服”是同一拨人说的吗?”物流”和”退货”哪个先出现?

答案就在共现关系里——两个词在同一段文本里同时出现的频次。我们的 共现矩阵和网络关系图生成软件 把这事儿做成了一键式:导入 TXT → 出 Excel 矩阵 + 网络图。

下面拆解原理和实战代码。

一、什么是共现?为什么要做?

共现(co-occurrence):两个词在同一窗口(句子/段落/文档)里同时出现。

业务价值

二、3 步法实战

第 1 步:分词 + 关键词提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import jieba
import jieba.analyse

# 1. 自定义词典(避免"小米手机"被切散)
jieba.load_userdict('custom_words.txt')

# 2. 读 TXT(一行一条评论)
with open('comments.txt', encoding='utf-8') as f:
raw_lines = [line.strip() for line in f if line.strip()]

# 3. 提取每条评论的关键词(用 TextRank)
# 也可以直接用 jieba.lcut 全分词
per_line_keywords = []
for line in raw_lines:
# 提取 Top 10 关键词
keywords = jieba.analyse.textrank(line, topK=10, withWeight=False)
per_line_keywords.append(keywords)

print(per_line_keywords[0])
# ['小米', '手机', '续航', '充电', '发热', ...]

两种策略

建议用关键词提取——全分词会把”很/不/是”这种停用词拉进矩阵,污染结果。

第 2 步:滑窗统计共现次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from itertools import combinations
from collections import defaultdict
import numpy as np

# 关键词列表(从整批语料里提取的高频词)
all_keywords = [w for line in per_line_keywords for w in line]
from collections import Counter
keyword_freq = Counter(all_keywords)
top_keywords = [w for w, c in keyword_freq.most_common(50)] # 取 Top 50

# 共现矩阵
K = len(top_keywords)
co_matrix = np.zeros((K, K), dtype=int)
word_to_idx = {w: i for i, w in enumerate(top_keywords)}

# 滑窗:在每条评论内,任意两个关键词的共现 +1
for line_keywords in per_line_keywords:
# 只保留在 top_keywords 里的词
present = [w for w in line_keywords if w in word_to_idx]
# 任意两两组合
for w1, w2 in combinations(set(present), 2):
i, j = word_to_idx[w1], word_to_idx[w2]
co_matrix[i][j] += 1
co_matrix[j][i] += 1 # 对称

# 转成 DataFrame 方便看
import pandas as pd
df_matrix = pd.DataFrame(co_matrix, index=top_keywords, columns=top_keywords)
df_matrix.to_excel('cooccurrence_matrix.xlsx')

关键点

第 3 步:画网络关系图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import networkx as nx
import matplotlib.pyplot as plt

# 1. 建图(只保留共现 >= threshold 的边,避免太密集)
G = nx.Graph()
threshold = 5 # 共现 5 次以上才画边
for i in range(K):
for j in range(i+1, K):
if co_matrix[i][j] >= threshold:
w1, w2 = top_keywords[i], top_keywords[j]
G.add_edge(w1, w2, weight=int(co_matrix[i][j]))

# 2. 节点大小 = 词频
for word in G.nodes():
G.nodes[word]['freq'] = keyword_freq[word]

# 3. 布局(弹簧布局)
pos = nx.spring_layout(G, k=0.5, iterations=50, seed=42)

# 4. 画图
fig, ax = plt.subplots(figsize=(14, 10))
node_sizes = [G.nodes[n]['freq'] * 20 for n in G.nodes()]
edge_widths = [G[u][v]['weight'] / 5 for u, v in G.edges()]

nx.draw_networkx_nodes(G, pos, node_size=node_sizes,
node_color='#6e5cff', alpha=0.85, ax=ax)
nx.draw_networkx_edges(G, pos, width=edge_widths,
edge_color='rgba(0, 212, 255, 0.4)', ax=ax)
nx.draw_networkx_labels(G, pos, font_size=10,
font_color='#fff', font_family='sans-serif', ax=ax)

# 边标签(显示共现次数)
edge_labels = {(u, v): G[u][v]['weight'] for u, v in G.edges() if G[u][v]['weight'] >= 20}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8, ax=ax)

ax.set_title('关键词共现网络图(共现 ≥ 5 次)', fontsize=15)
ax.axis('off')
plt.tight_layout()
plt.savefig('cooccurrence_network.png', dpi=150, bbox_inches='tight')

三、共现矩阵的”边”该怎么用?

过滤阈值非常关键:

threshold 效果
1 几乎所有词都连,画面像毛线球
5 保留主要关系,适合初次探索
20 只保留强关联,适合”找核心议题”
50 只剩 5-10 个 hub 节点,适合精修

踩坑:threshold 一刀切可能漏掉”中频但重要”的关系。建议多画几张(5/20/50 各一张),对比着看。

四、进阶:社区发现

共现网络图里团在一起的词,往往是同一议题。可以用社区发现算法自动识别:

1
2
3
4
5
6
7
8
9
10
from networkx.algorithms.community import greedy_modularity_communities

# 社区发现(Greedy Modularity)
communities = greedy_modularity_communities(G, resolution=1.0)
print(f'识别出 {len(communities)} 个社区')
for i, comm in enumerate(communities, 1):
print(f'社区 {i} ({len(comm)} 词): {", ".join(sorted(comm, key=lambda w: -G.nodes[w]["freq"])[:5])}')
# 社区 1 (8 词): 物流, 快递, 速度, 慢, 不到
# 社区 2 (6 词): 客服, 态度, 差, 投诉, 退款
# 社区 3 (5 词): 续航, 电池, 充电, 发热, 耗电

业务解读

这是老板们最爱的产出——不用自己读 10 万条评论,3 个社区就把”用户在抱怨什么”拎出来了。

五、加权 PageRank 找”枢纽词”

有些词连接多个社区——比如”质量”既跟”做工”近、也跟”客服”近、也跟”物流”近。这样的”枢纽词”用 PageRank 算:

1
2
3
4
5
6
7
8
9
pr = nx.pagerank(G, weight='weight')
hub_words = sorted(pr.items(), key=lambda x: -x[1])[:10]
print('枢纽词 Top 10:')
for word, score in hub_words:
print(f' {word}: {score:.4f}')
# 质量: 0.0876
# 客服: 0.0654
# 物流: 0.0543
# ...

枢纽词就是”产品总抓手”——提升这词相关的体验,能带动多个社区。

六、共现 vs LDA:什么时候用哪个?

维度 共现矩阵 LDA 主题
输出形式 词-词关系网 文档-主题分布
算法 简单统计 概率图模型
训练时间 几秒 几分钟-几小时
适合 探索性分析、找关系 主题抽取、文档归类
输出解读难度 直观(看图) 较抽象(看 Top 词)

业务建议:先用共现矩阵快速摸底,再用 LDA 做精细主题分析。

七、踩坑大全

1. 共现窗口大小:默认”同一行评论”= 1 个窗口。也可以”同一段”= 滑窗 size=5(前后各 5 个词)。窗口越大,关系越宽泛但矩阵越密。

2. 中文分词:自定义词典不加,”小米/手机”被切散,”小米”和”手机”的共现次数会异常高(因为每个含”小米手机”的句子都被算作”小米+手机”共现)。

3. 停用词:必须砍!否则”的/了/是”会和所有词共现,矩阵被噪声淹没。

4. 节点标签颜色:黑底深色主题下,黑色标签会消失。要么用浅色标签,要么节点加白边。

5. 节点重叠:spring_layout 对密集图会节点重叠。解决:换 nx.kamada_kawai_layout(G) 或增加 k 参数(节点间距)。

6. 大图导出:100+ 节点 + DPI 300 会卡 5 分钟。建议输出 SVG 矢量格式,CDR 可以直接打开。

八、典型业务应用


我们的 共现矩阵和网络关系图生成软件 把上面所有步骤封装成可视化界面:导入 TXT → 自动分词 → 自动统计共现 → 一键出 Excel 矩阵 + 矢量网络图(SVG 格式,CDR 可直接编辑)。支持自定义关键词库、停用词库、阈值滑块——业务人员不用写代码也能挖出文本里的隐藏关系。