1. 从一个真实需求说起:为什么词频图和词云值得单独写一篇
做文本分析的朋友大概率都经历过这个场景:手里拿到一批文本数据,可能是用户评论、工单记录、文章标题、聊天日志,老板或者合作方第一句话就是——“能不能先给我看看这批数据里大家都在说什么?”这时候你不可能直接甩一个原始文本文件过去,最直观的方式就是两张图:词频统计图和词云图。
词频统计图解决的是“哪些词出现得最多、排名如何”的问题,它精确、可量化、方便对比;词云图解决的是“一眼看上去整体印象是什么”的问题,它直观、有冲击力、适合放进汇报材料。两者搭配使用,一个负责严谨,一个负责好看,基本能覆盖大部分初步文本探索的需求。
这篇文章要聊的,就是怎么用Python把一份原始文本数据,经过清洗、分词、去停用词、统计词频,最终生成这两张图。整套流程不依赖什么高深模型,纯基础操作,但里面的坑一点都不少——编码问题、中文分词、停用词表选择、字体配置、词频归一化,每一个环节都能让新手卡半天。我会把完整流程拆开讲,包括每一步为什么这么做、参数怎么选、遇到问题怎么排查,代码可以直接拿去改改就用。
适合谁看?如果你已经会一点Python基础语法,知道列表和字典是什么,但还没完整做过一次文本预处理到可视化的流程,那这篇就是写给你的。如果你已经做过几次,也可以看看里面关于停用词策略和词云参数调优的部分,说不定有你没注意到的细节。
2. 整体流程设计与技术选型思路
2.1 为什么选这套技术栈
做中文文本预处理和可视化,Python生态里有好几套方案可选。我最终选的是jieba + collections.Counter + matplotlib + wordcloud这个组合,理由如下。
jieba是中文分词领域最成熟的开源库之一,支持三种分词模式,社区活跃,文档齐全,遇到问题搜索一下基本都有答案。collections.Counter是Python标准库自带的计数器,专门用来做频次统计,底层是字典,性能足够好,而且有most_common()这种开箱即用的方法。matplotlib是Python可视化的万金油,虽然画出来的图默认风格偏“学术”,但胜在可控性强,每个元素都能调。wordcloud库专门做词云,支持自定义字体、蒙版、配色,生成效果比手动用matplotlib拼要好得多。
有人可能会问,为什么不用pandas做统计?pandas当然可以,value_counts()一行就出结果。但如果你只是做词频统计,引入pandas有点重,而且pandas和Counter之间的转换反而多了一步。当然,如果你的数据本身就是表格形式,用pandas读取和处理是合理的,这个后面会提到。
2.2 完整流程拆解
整个流程可以拆成六个步骤,我用一个表格先列出来,后面再逐步展开。
| 步骤 | 核心任务 | 关键工具 | 常见坑点 |
|---|---|---|---|
| 1 | 读取原始文本 | open() / pandas | 编码格式不统一 |
| 2 | 文本清洗 | re正则 | 标点、数字、特殊符号处理 |
| 3 | 中文分词 | jieba | 自定义词典、模式选择 |
| 4 | 去停用词 | 停用词表 | 停用词表选择与维护 |
| 5 | 词频统计 | Counter | 排序、截断、归一化 |
| 6 | 可视化输出 | matplotlib / wordcloud | 字体、布局、配色 |
这个流程看起来线性,但实际操作中经常需要回头调整。比如分词之后发现某些词被切碎了,就要回去补充自定义词典;词云生成后发现某个词大得离谱,就要回去检查停用词表是不是漏了什么。所以不要指望一次跑通,迭代两三轮是正常的。
2.3 数据形态的预判
在动手之前,先想清楚你的数据是什么形态。常见的有三种:
第一种是单个大文本文件,比如一篇长文、一份报告,直接读取整个文件内容作为一个字符串处理。第二种是多行文本,比如每行一条评论、一个标题,需要逐行处理后再合并统计。第三种是表格数据中的某一列,比如Excel或CSV里有一列是“用户反馈”,需要用pandas读取后提取该列。
三种形态的处理逻辑略有不同,但核心步骤一致。本文的示例代码以第二种(多行文本)为主,因为它最接近实际工作中的日志和评论数据,同时会说明另外两种怎么调整。
3. 核心细节解析与实操要点
3.1 文本读取与编码处理
编码问题是中文文本处理的第一道坎。你拿到的文件可能是UTF-8、GBK、GB2312、GB18030,甚至混合编码。最稳妥的做法是先用二进制模式读一小段,检测编码,再决定用什么方式打开。
实际操作中,我一般先用chardet库检测编码,如果没有安装,用open()的errors='ignore'参数兜底。但errors='ignore'会静默丢弃无法解码的字符,可能导致数据缺失,所以只作为临时方案。
import chardet def detect_encoding(file_path): with open(file_path, 'rb') as f: raw = f.read(10000) result = chardet.detect(raw) return result['encoding'] encoding = detect_encoding('data.txt') with open('data.txt', 'r', encoding=encoding, errors='replace') as f: lines = f.readlines()注意:
errors='replace'会把无法解码的字符替换成特殊符号,比ignore更安全,因为你能看到哪些地方出了问题。
如果是CSV或Excel文件,pandas的read_csv()和read_excel()通常能自动处理编码,但遇到GBK编码的CSV时可能需要手动指定encoding='gbk'。Excel文件一般不需要担心编码问题,但要注意.xls和.xlsx格式的区别,前者需要xlrd引擎,后者需要openpyxl。
3.2 文本清洗的策略
原始文本里充满了对分析无用的内容:标点符号、数字、英文、URL、HTML标签、表情符号等。清洗的目标是保留有意义的中文词汇,去掉噪声。
但“有意义”的定义取决于你的分析目标。如果你分析的是电商评论,那么“好评”“差评”“物流”“客服”这些词很重要;如果你分析的是技术论坛帖子,那么“报错”“配置”“版本”这些词是关键。所以清洗规则不能一刀切,要根据场景调整。
我通常的清洗顺序是:先去HTML标签(如果数据来自网页),再去URL和邮箱,然后去掉标点和特殊符号,最后处理数字和英文。中文标点和英文标点都要处理,因为很多文本里混着用。
import re def clean_text(text): # 去除HTML标签 text = re.sub(r'<[^>]+>', '', text) # 去除URL text = re.sub(r'http[s]?://\S+', '', text) # 去除邮箱 text = re.sub(r'\S+@\S+', '', text) # 去除标点符号(保留中文和英文) text = re.sub(r'[^\w\s\u4e00-\u9fff]', '', text) # 去除纯数字 text = re.sub(r'\d+', '', text) # 去除多余空白 text = re.sub(r'\s+', ' ', text).strip() return text这里有个细节:\u4e00-\u9fff是中文Unicode范围,保留中文;\w保留字母数字下划线;\s保留空白。但这样会把英文单词也保留下来,如果你只想分析中文,可以把\w去掉,只留\u4e00-\u9fff和\s。
实操心得:清洗规则不要写得太死,建议把清洗前后的文本各打印几行对比一下,确认没有误删重要内容。我曾经因为正则写得太激进,把“C++”这种词直接删没了,后来分析编程语言讨论时发现数据缺失,回头排查才发现问题。
3.3 中文分词的关键参数
jieba的分词模式有三种:精确模式、全模式和搜索引擎模式。精确模式适合文本分析,它试图把句子最准确地切开;全模式会把所有可能的词都列出来,速度快但有冗余;搜索引擎模式在精确模式基础上对长词再切分,适合做搜索索引。
做词频统计,我一般用精确模式,代码是jieba.lcut(text),返回一个列表。如果你需要保留词性信息,可以用jieba.posseg,但词频统计通常不需要词性。
import jieba text = "自然语言处理是人工智能领域的一个重要方向" words = jieba.lcut(text) print(words) # 输出:['自然语言', '处理', '是', '人工智能', '领域', '的', '一个', '重要', '方向']你会发现“自然语言”被正确切出来了,但“人工智能”也被正确切出来了,这说明jieba的默认词典覆盖了常见术语。但如果你处理的领域有大量专有名词,比如医疗、法律、金融,默认词典可能不够用,需要加载自定义词典。
jieba.load_userdict('custom_dict.txt')自定义词典的格式是每行一个词,可以附带词频和词性,用空格分隔。词频越高,这个词被切出来的概率越大。如果你不确定词频,可以只写词,jieba会用默认值。
注意:加载自定义词典要在分词之前,而且只需要加载一次。如果你在循环里反复加载,会严重影响性能。
3.4 停用词表的构建与维护
停用词是那些对分析没有意义的高频词,比如“的”“了”“是”“在”“和”等。中文停用词表网上有很多版本,哈工大停用词表、百度停用词表、四川大学机器智能实验室停用词库,各有侧重。我一般会合并几个表,再根据具体任务补充。
但停用词表不是越大越好。有些词在通用场景下是停用词,在你的场景下可能是关键词。比如“不”在情感分析里很重要,“一”在数量分析里可能有用。所以我的做法是:先用通用停用词表跑一遍,看看结果里有没有明显不该出现的词,再手动补充。
def load_stopwords(file_path): with open(file_path, 'r', encoding='utf-8') as f: stopwords = set([line.strip() for line in f if line.strip()]) return stopwords stopwords = load_stopwords('stopwords.txt') # 补充自定义停用词 stopwords.update(['回复', '转发', '点赞', '楼主', '沙发'])用set而不是list来存储停用词,是因为判断一个词是否在集合中的时间复杂度是O(1),而列表是O(n)。当停用词表有几千个词、待过滤的词有几万个时,这个差异非常明显。
实操心得:停用词表建议单独存成一个文件,不要硬编码在脚本里。这样不同项目可以复用,也方便版本管理。我习惯在项目目录下建一个
resources文件夹,专门放停用词表、自定义词典、字体文件这些资源。
3.5 词频统计的细节处理
用Counter统计词频很简单,但有几个细节值得注意。
第一,过滤单字。中文里单个字往往信息量不足,比如“的”“了”“是”是停用词,但“人”“事”“物”这种单字可能有用。我的做法是:如果分析目标是主题词,过滤掉长度为1的词;如果分析目标是情感词,保留单字可能更好。这个没有标准答案,看需求。
第二,词频归一化。如果两份文本长度差异很大,直接比较词频不公平。可以用词频除以总词数,得到相对频率。但词云图通常用绝对词频,因为视觉上大词就是大词,归一化后反而不好看。
第三,截断Top N。词频统计图一般只展示前20或前30个词,太多会挤在一起看不清。词云图可以展示更多,但也要控制,一般100到200个词比较合适。
from collections import Counter def get_word_freq(words, stopwords, min_length=2): filtered = [w for w in words if w not in stopwords and len(w) >= min_length] counter = Counter(filtered) return counter counter = get_word_freq(words, stopwords) top_20 = counter.most_common(20) print(top_20)most_common(n)返回前n个高频词及其计数,返回的是列表,每个元素是(词, 频次)的元组。如果你需要字典形式,可以用dict(counter.most_common(n))。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
先把需要的库装好。我习惯用pip安装,如果你用conda也可以,但要注意conda源里的版本可能和pip不一样。
pip install jieba wordcloud matplotlib chardetmatplotlib在有些系统上需要额外配置后端,如果你在服务器上跑没有图形界面,需要设置matplotlib.use('Agg'),否则会报错。这个坑我踩过好几次,尤其是在Docker容器里跑的时候。
import matplotlib matplotlib.use('Agg') # 无图形界面时必须 import matplotlib.pyplot as plt字体问题也是高频坑点。matplotlib默认字体不支持中文,词云库也需要指定中文字体路径。Windows系统可以用C:/Windows/Fonts/simhei.ttf,Linux系统需要找到系统中文字体,macOS可以用/System/Library/Fonts/PingFang.ttc。如果你不确定系统里有什么中文字体,可以用fc-list :lang=zh命令查看(Linux/macOS)。
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows # plt.rcParams['font.sans-serif'] = ['PingFang SC'] # macOS # plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei'] # Linux plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题注意:
axes.unicode_minus这个参数一定要设成False,否则坐标轴上的负号会显示成方块。这个问题在画柱状图时特别明显。
4.2 完整代码实现
下面是一个完整的示例,从读取文件到生成两张图。我把它拆成函数,方便复用和调试。
import re import jieba from collections import Counter import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt from wordcloud import WordCloud import chardet def detect_encoding(file_path): with open(file_path, 'rb') as f: raw = f.read(10000) return chardet.detect(raw)['encoding'] def read_text(file_path): encoding = detect_encoding(file_path) with open(file_path, 'r', encoding=encoding, errors='replace') as f: return f.read() def clean_text(text): text = re.sub(r'<[^>]+>', '', text) text = re.sub(r'http[s]?://\S+', '', text) text = re.sub(r'\S+@\S+', '', text) text = re.sub(r'[^\w\s\u4e00-\u9fff]', '', text) text = re.sub(r'\d+', '', text) text = re.sub(r'\s+', ' ', text).strip() return text def load_stopwords(file_path): with open(file_path, 'r', encoding='utf-8') as f: return set([line.strip() for line in f if line.strip()]) def segment(text, stopwords, min_length=2): words = jieba.lcut(text) return [w for w in words if w not in stopwords and len(w) >= min_length] def plot_bar(counter, top_n=20, save_path='bar.png'): top_words = counter.most_common(top_n) words, counts = zip(*top_words) plt.figure(figsize=(12, 6)) bars = plt.bar(range(len(words)), counts, color='#4C72B0') plt.xticks(range(len(words)), words, rotation=45, ha='right') plt.xlabel('词语') plt.ylabel('出现频次') plt.title(f'词频统计图(Top {top_n})') for bar, count in zip(bars, counts): plt.text(bar.get_x() + bar.get_width()/2, bar.get_height(), str(count), ha='center', va='bottom', fontsize=9) plt.tight_layout() plt.savefig(save_path, dpi=150) plt.close() def plot_wordcloud(counter, font_path, save_path='wordcloud.png'): wc = WordCloud( font_path=font_path, width=1200, height=800, background_color='white', max_words=150, colormap='viridis', random_state=42 ) wc.generate_from_frequencies(counter) plt.figure(figsize=(12, 8)) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.tight_layout() plt.savefig(save_path, dpi=150) plt.close() if __name__ == '__main__': text = read_text('data.txt') text = clean_text(text) stopwords = load_stopwords('stopwords.txt') words = segment(text, stopwords) counter = Counter(words) plot_bar(counter, top_n=20, save_path='bar.png') plot_wordcloud(counter, font_path='C:/Windows/Fonts/simhei.ttf', save_path='wordcloud.png') print(f'总词数:{len(words)}') print(f'唯一词数:{len(counter)}') print(f'Top 10:{counter.most_common(10)}')这段代码可以直接跑,只需要准备好data.txt、stopwords.txt和字体文件。输出是两张PNG图和一个控制台统计摘要。
4.3 参数调优与效果对比
词云图的效果很大程度上取决于参数设置。我拿同一份数据做了几组对比,用表格展示差异。
| 参数 | 取值A | 取值B | 效果差异 |
|---|---|---|---|
| max_words | 50 | 150 | 50个词时每个词更大更清晰,150个词时信息更丰富但小词难辨认 |
| background_color | white | black | 白色背景适合文档,黑色背景适合演示 |
| colormap | viridis | Reds | viridis色彩层次丰富,Reds单色系更统一 |
| width/height | 800x600 | 1200x800 | 大尺寸适合打印,小尺寸适合网页 |
| random_state | None | 42 | 固定随机种子后每次生成布局一致,方便对比 |
我的经验是:如果词云是放在报告里作为配图,用白色背景、150个词、viridis配色;如果是放在PPT里做视觉冲击,用黑色背景、80个词、高对比度配色。random_state建议固定,否则每次生成的布局都不一样,调参时无法对比。
词频统计图这边,主要调的是top_n和图形尺寸。top_n=20适合横向柱状图,top_n=30适合纵向柱状图但需要旋转标签。我一般用横向柱状图,因为中文词的长度不一,横向排列更容易阅读。
def plot_barh(counter, top_n=20, save_path='barh.png'): top_words = counter.most_common(top_n) words, counts = zip(*top_words) plt.figure(figsize=(10, 8)) plt.barh(range(len(words)), counts, color='#55A868') plt.yticks(range(len(words)), words) plt.gca().invert_yaxis() plt.xlabel('出现频次') plt.title(f'词频统计图(Top {top_n})') for i, count in enumerate(counts): plt.text(count, i, str(count), va='center', fontsize=9) plt.tight_layout() plt.savefig(save_path, dpi=150) plt.close()横向柱状图用barh(),然后用invert_yaxis()把最高的词放在顶部。这个细节很多人不知道,默认情况下matplotlib的y轴是从下往上递增的,不反转的话最高频的词在最下面,看起来别扭。
5. 常见问题与排查技巧实录
5.1 编码报错与乱码问题
问题表现:UnicodeDecodeError: 'utf-8' codec can't decode byte...或者读出来的中文是乱码。
排查思路:先用chardet检测编码,如果检测结果置信度低,手动尝试GBK、GB18030、UTF-8-SIG等常见编码。UTF-8-SIG是带BOM的UTF-8,Windows下用记事本保存的文件经常是这种格式。
解决方案:
encodings_to_try = ['utf-8', 'gbk', 'gb18030', 'utf-8-sig', 'big5'] for enc in encodings_to_try: try: with open('data.txt', 'r', encoding=enc) as f: content = f.read() print(f'成功使用编码:{enc}') break except UnicodeDecodeError: continue实操心得:如果所有编码都失败,用
errors='replace'强制读取,然后检查替换字符出现的位置,通常能定位到问题段落。有些文件是混合编码,前半部分GBK后半部分UTF-8,这种情况只能分段处理。
5.2 分词结果不理想
问题表现:该切开的词没切开,比如“机器学习”被切成“机器”和“学习”;或者不该切开的切开了,比如“区块链”被切成“区块”和“链”。
排查思路:先确认jieba版本,老版本词典覆盖不够。然后检查是否需要加载自定义词典。最后考虑调整分词模式。
解决方案:
# 添加单个词 jieba.add_word('区块链', freq=1000, tag='n') # 批量添加 jieba.load_userdict('custom_dict.txt') # 调整词频,freq越高越容易被切出来 jieba.suggest_freq('机器学习', True) # True表示强制成词 jieba.suggest_freq(('机器', '学习'), True) # 强制切开suggest_freq这个方法很实用,它动态调整词频,不需要修改词典文件。但注意它只对当前会话有效,脚本重启后失效。
5.3 词云图显示方块
问题表现:词云图里中文全部显示成方块或空白。
原因:字体路径不对,或者字体文件不支持中文。
解决方案:确认字体文件存在且支持中文。Windows下simhei.ttf、simsun.ttc、msyh.ttc都支持中文;Linux下需要安装中文字体包,常见的有wqy-microhei、wqy-zenhei;macOS下PingFang.ttc、STHeiti Light.ttc都可以。
import os font_candidates = [ 'C:/Windows/Fonts/simhei.ttf', 'C:/Windows/Fonts/msyh.ttc', '/System/Library/Fonts/PingFang.ttc', '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', ] font_path = None for fp in font_candidates: if os.path.exists(fp): font_path = fp break if font_path is None: raise FileNotFoundError('未找到可用的中文字体,请手动指定')注意:
.ttc是字体集合文件,wordcloud库对.ttc的支持在某些版本上有问题,如果报错,尝试用.ttf格式的字体。
5.4 词频统计结果异常
问题表现:统计结果里出现大量无意义的高频词,或者某些明显重要的词频次很低。
排查思路:先检查停用词表是否覆盖了这些无意义词,再检查分词是否正确,最后检查清洗步骤是否误删了重要内容。
常见问题速查表:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| “的”“了”排在前列 | 停用词表未加载或路径错误 | 检查停用词文件路径和编码 |
| 某个词频次异常高 | 该词在文本中重复出现(如页眉页脚) | 在清洗阶段去重或过滤 |
| 重要词频次很低 | 被分词切碎或误删 | 检查分词结果和清洗正则 |
| 统计结果为空 | 清洗后文本为空或停用词过滤过度 | 打印中间结果逐步排查 |
| 词云图只有几个词 | max_words设置过小或数据量不足 | 调整max_words,检查数据量 |
5.5 性能优化建议
当文本量很大时(比如几十万条评论),分词和统计可能比较慢。几个优化方向:
第一,批量分词。jieba支持并行分词,用jieba.enable_parallel(4)开启4个进程,速度能提升3倍左右。但注意并行分词在Windows下不支持,而且会消耗更多内存。
第二,减少重复计算。如果多次运行脚本,可以把分词结果缓存到文件,下次直接读取。用pickle或json都行。
第三,用生成器代替列表。如果内存吃紧,不要一次性把所有词读进列表,用生成器逐行处理。
def iter_words(file_path, stopwords): with open(file_path, 'r', encoding='utf-8') as f: for line in f: line = clean_text(line) for word in jieba.lcut(line): if word not in stopwords and len(word) >= 2: yield word counter = Counter(iter_words('data.txt', stopwords))这样内存占用从O(n)降到O(1),代价是代码稍微复杂一点。
6. 进阶技巧与场景扩展
6.1 词频趋势对比
如果你有多份文本,比如不同时间段的评论,可以分别统计词频然后对比。最简单的做法是画分组柱状图,或者用词频差值排序,找出增长最快和下降最快的词。
def compare_freq(counter_a, counter_b, top_n=15): all_words = set(counter_a.keys()) | set(counter_b.keys()) diffs = [] for word in all_words: diff = counter_b.get(word, 0) - counter_a.get(word, 0) diffs.append((word, diff)) diffs.sort(key=lambda x: abs(x[1]), reverse=True) return diffs[:top_n]这个方法在舆情监控和产品迭代分析里很常用,能快速定位用户关注点的变化。
6.2 词云蒙版与形状定制
wordcloud支持用图片作为蒙版,生成特定形状的词云。比如用公司Logo、产品轮廓、地图轮廓作为蒙版,视觉效果会好很多。
from PIL import Image import numpy as np mask = np.array(Image.open('mask.png')) wc = WordCloud( font_path=font_path, mask=mask, background_color='white', max_words=200, contour_width=1, contour_color='steelblue' )蒙版图片要求是白底黑图,白色区域会被忽略,黑色区域会填充词。contour_width和contour_color用来给蒙版轮廓描边,效果更精致。
实操心得:蒙版图片不要太大,建议控制在1000x1000像素以内,否则生成速度很慢。另外蒙版形状不要太复杂,否则小词会挤在狭窄区域看不清。
6.3 结合TF-IDF做关键词提取
单纯词频统计有个问题:高频词不一定是最有信息量的词。比如“用户”这个词在所有文档里都高频,但它区分度不高。TF-IDF可以解决这个问题,它综合考虑词频和逆文档频率,找出真正有区分度的关键词。
from sklearn.feature_extraction.text import TfidfVectorizer documents = ['文档1内容', '文档2内容', '文档3内容'] vectorizer = TfidfVectorizer(stop_words=stopwords, max_features=100) tfidf_matrix = vectorizer.fit_transform(documents) feature_names = vectorizer.get_feature_names_out()TF-IDF适合多文档场景,单文档场景下退化成词频统计。如果你的数据是单篇长文,用词频就够了;如果是多篇文档,TF-IDF能挖出更有价值的关键词。
6.4 输出格式与自动化
如果这套流程需要定期跑,建议做成命令行工具,支持参数传入文件路径和输出目录。
import argparse parser = argparse.ArgumentParser() parser.add_argument('--input', required=True, help='输入文件路径') parser.add_argument('--output', default='./output', help='输出目录') parser.add_argument('--top_n', type=int, default=20, help='词频图展示词数') parser.add_argument('--max_words', type=int, default=150, help='词云最大词数') args = parser.parse_args()这样每次只需要改命令行参数,不用改代码。配合定时任务,就能实现自动化报告生成。
7. 我在实际项目中的几点体会
这套流程我前后用过不下二十次,从最初的磕磕绊绊到现在的肌肉记忆,有几个体会特别深。
第一,数据质量决定上限。再好的分词和可视化,也救不了一堆乱码和噪声。我现在的习惯是,拿到数据先花十分钟做探索性检查:看编码、看长度分布、看有没有异常字符、看重复率。这十分钟能省掉后面两小时的调试。
第二,停用词表要养。不要指望一个通用停用词表解决所有问题。我维护了一个自己的停用词表,每次做新项目就往里加几个词,日积月累下来覆盖了我常做的几个领域。这个表比任何公开的停用词表都好用,因为它是我自己的数据喂出来的。
第三,可视化是给人看的,不是给机器看的。词频图和词云图的目的是沟通,不是炫技。颜色不要超过三种,标签要清晰,标题要说明数据来源和时间范围。我见过太多词云图,词是挺多,但根本看不出重点,这种图不如不做。
第四,代码要能复用。我现在的做法是把这套流程封装成一个类,初始化时传入文件路径和配置参数,调用run()方法一键生成所有输出。这样下次做类似任务,改几个参数就能跑,不用从头写。
最后分享一个小技巧:如果你的词云图里某个词大得离谱,把其他词都挤没了,可以在生成词云前对词频做一次开方或对数变换。counter[word] = counter[word] ** 0.5能让词的大小差异更温和,视觉效果更均衡。这个技巧在词频分布极度不均匀时特别管用,你可以试试。