文本数据(评论、反馈、工单)是了解用户声音的宝库。本案例综合运用 pandas 的文本能力(结合第 9 章),完成中文评论分析全流程:清洗 → 分词 → 词频统计 → 情感判定 → 主题归类 → 可视化 → 洞察。
目录
1. 数据模拟
2. 评论文本清洗
3. 中文分词与停用词
4. 词频与高频词分析
5. 情感分析
6. 主题归类
7. 商品维度对比
8. 可视化报告
9. 结论与行动建议
10. 扩展练习
1. 数据模拟
import pandas as pd
import numpy as np
import re
# 模拟商品评论
comments = [
"物流很快,包装完好,质量非常好,很满意",
"快递太慢了,等了一周才到,差评",
"质量不错,价格实惠,性价比高,推荐购买",
"客服态度差,问问题半天不回复,体验不好",
"产品很好用,操作简单,老婆很喜欢",
"收到货有破损,客服处理也慢,不太满意",
"用了两周,感觉质量一般,有点失望",
"发货快,质量好,物美价廉,下次还来",
"包装简陋,有挤压痕迹,但东西还能用",
"非常好用,功能强大,朋友推荐来买的",
"价格偏高,同款别家更便宜,性价比一般",
"售后很给力,换货很快,整体满意",
"界面难看,说明书不清楚,上手困难",
"质量杠杠的,用了半年没出问题,值得信赖",
"物流慢就算了,还丢件了,非常生气",
]
np.random.seed(1)
df = pd.DataFrame({
"评论ID": range(1, len(comments) + 1),
"商品": np.random.choice(["智能手表", "蓝牙耳机", "充电宝"], len(comments)),
"评分": np.random.choice([1, 2, 3, 4, 5], len(comments), p=[0.1, 0.1, 0.2, 0.3, 0.3]),
"评论文本": comments,
})
print(df)
2. 评论文本清洗
def clean_text(s):
if pd.isna(s):
return ""
s = str(s)
s = s.lower() # 统一小写
s = re.sub(r"<[^>]+>", "", s) # 去 HTML
s = re.sub(r"https?://\S+", "", s) # 去链接
s = re.sub(r"[\d\W_]+", " ", s) # 去数字和标点(保留中文和字母)
s = re.sub(r"\s+", " ", s).strip() # 压缩空白
return s
df["清洗后"] = df["评论文本"].apply(clean_text)
print(df[["评论文本", "清洗后"]].head(3))
用.apply做逐条文本清洗是合理的(正则逻辑难以向量化),数据量大时可用str.replace组合向量化。
# 文本长度特征
df["文本长度"] = df["清洗后"].str.len()
print(df["文本长度"].describe().astype(int))
3. 中文分词与停用词
# 安装:pip install jieba
import jieba
# 停用词(示例)
STOPWORDS = set("""的 了 和 是 在 我 有 就 不 人 都 一 一个 上 也 很 到 说 要 去 会 着 没有 看 好 自己 这 那 你 他 她 它 我们 你们 他们 非常 比较 有点 感觉 东西 真的 还是 但是 所以 然后 因为 如果 虽然 而且 或者 呀 啊 呢 吗 吧 哦 嗯 的 了 还 就 都 也 很 太 更 最""".split())
def tokenize(s):
words = jieba.lcut(s)
return [w for w in words if w.strip() and w not in STOPWORDS and len(w) > 1]
df["分词"] = df["清洗后"].apply(tokenize)
print(df["分词"].head(3))
0 [物流, 很快, 包装, 完好, 质量, 非常, 满意]
1 [快递, 太慢, 等, 一周, 才到, 差评]
...
4. 词频与高频词分析
# 展开所有词
all_words = df["分词"].explode().dropna()
print(all_words.head())
# 词频统计
word_freq = all_words.value_counts()
print(word_freq.head(20))
质量 7
满意 5
快递 4
物流 4
好 3
很快 3
价格 3
...
# 高频词 Top15 条形图
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
word_freq.head(15).plot(kind="barh", color="steelblue")
plt.title("评论高频词 Top15")
plt.xlabel("出现次数")
plt.gca().invert_yaxis()
plt.show()
# 按商品看高频词
for product in df["商品"].unique():
sub_words = df[df["商品"] == product]["分词"].explode()
print(f"\n{product} Top5:")
print(sub_words.value_counts().head(5))
5. 情感分析
5.1 基于情感词典的简单判定
POSITIVE = set("好 满意 喜欢 快 快 不错 好用 强大 实惠 推荐 给力 值得 信赖 满意 喜欢 推荐 便宜 耐用 物美价廉 很好 非常").split()
NEGATIVE = set("慢 差 差评 贵 破损 简陋 失望 生气 难 丢件 太慢 不满意 不好 一般 偏高 落后 投诉").split()
def sentiment(s):
words = tokenize(s)
pos = sum(1 for w in words if w in POSITIVE)
neg = sum(1 for w in words if w in NEGATIVE)
if pos > neg:
return "正面"
if neg > pos:
return "负面"
return "中性"
df["情感"] = df["清洗后"].apply(sentiment)
print(df["情感"].value_counts())
print(df[df["情感"] != "中性"][["评论文本", "情感"]].head(8))
正面 7
负面 5
中性 3
5.2 与评分交叉验证
cross = pd.crosstab(df["评分"], df["情感"])
print(cross)
# 一致性:高评分应多为正面
agreement = (df[(df["评分"] >= 4) & (df["情感"] == "正面")].shape[0] +
df[(df["评分"] <= 2) & (df["情感"] == "负面")].shape[0]) / len(df)
print(f"评分与情感一致性: {agreement:.0%}")
词典法简单快速,但覆盖有限。生产可用 SnowNLP、预训练模型(如 BERT)等。
6. 主题归类
按关键词给评论打主题标签(物流、质量、价格、服务、体验):
def topic(s):
if any(w in s for w in ["物流", "快递", "发货", "送货", "包裹", "包装"]):
return "物流"
if any(w in s for w in ["质量", "好用", "耐用", "做工", "破损", "坏"]):
return "质量"
if any(w in s for w in ["价格", "实惠", "贵", "便宜", "性价比", "划算"]):
return "价格"
if any(w in s for w in ["客服", "售后", "服务", "回复", "处理"]):
return "服务"
if any(w in s for w in ["体验", "满意", "喜欢", "上手", "操作", "界面", "功能"]):
return "体验"
return "其他"
df["主题"] = df["评论文本"].apply(topic)
topic_summary = df.groupby("主题").agg(
评论数=("评论ID", "count"),
平均评分=("评分", "mean"),
负面数=("情感", lambda x: (x == "负面").sum()),
)
print(topic_summary)
评论数 平均评分 负面数
主题
其他 1 4.000000 0
价格 2 3.500000 0
体验 3 4.000000 0
服务 2 2.000000 1
物流 4 2.500000 3
质量 3 4.333333 1
洞察:物流主题平均评分最低(2.5)、负面最多(3/4)→ 物流是主要痛点。
7. 商品维度对比
# 各商品的平均评分与情感分布
prod = df.groupby("商品").agg(
评论数=("评论ID", "count"),
平均评分=("评分", "mean"),
正面占比=("情感", lambda x: (x == "正面").mean()),
负面占比=("情感", lambda x: (x == "负面").mean()),
).round(2)
print(prod)
# 商品 × 主题 交叉
cross_topic = pd.crosstab(df["商品"], df["主题"])
print(cross_topic)
8. 可视化报告
fig, axes = plt.subplots(2, 2, figsize=(13, 9))
# 1. 情感分布
df["情感"].value_counts().plot(kind="pie", autopct="%.1f%%", ax=axes[0, 0],
title="情感分布", colors=["#2E86AB", "#E67E22", "#C0392B"])
axes[0, 0].set_ylabel("")
# 2. 主题平均评分
topic_summary["平均评分"].sort_values().plot(kind="barh", ax=axes[0, 1],
title="各主题平均评分", color="#8E44AD")
axes[0, 1].set_xlabel("评分")
# 3. 高频词
word_freq.head(10).sort_values().plot(kind="barh", ax=axes[1, 0],
title="高频词 Top10", color="steelblue")
axes[1, 0].set_xlabel("次数")
# 4. 商品平均评分
prod["平均评分"].plot(kind="bar", ax=axes[1, 1], title="商品平均评分", color="coral")
axes[1, 1].set_xticklabels(axes[1, 1].get_xticklabels(), rotation=0)
axes[1, 1].axhline(df["评分"].mean(), color="gray", linestyle="--", label="整体均值")
axes[1, 1].legend()
plt.tight_layout()
plt.savefig("评论分析.png", dpi=150)
plt.show()
9. 结论与行动建议
=== 结论 ===
1. 高频词集中在"质量、满意、物流、快递",质量是核心卖点、物流是核心痛点;
2. 情感分布:正面 7 / 负面 5 / 中性 3,负面评论主要围绕物流和服务;
3. 物流主题平均评分 2.5、负面最多;服务主题评分 2.0 且"客服不回复"被反复提及;
4. 商品间差异:质量口碑最好(评分 4.3+),需定位拉低均值的具体商品。
=== 行动建议 ===
1. 物流:更换/考核快递合作方,增加物流异常主动通知;
2. 客服:优化响应 SLA,高峰期智能客服兜底;
3. 包装:针对"破损、简陋"反馈改进缓冲材料;
4. 将高频正面词(质量、好用)用于商品卖点文案;
5. 建立评论监控:每日负面评论告警,重大舆情第一时间响应。
10. 扩展练习
1. 用 SnowNLP 计算评论情感得分(0~1),与词典法对比。
2. 用 wordcloud 生成评论词云图。
3. 对"负面评论"做共现词分析,找出高频搭配(如"物流-慢"、"客服-差")。
4. 按周聚合评论情感趋势,观察运营动作前后变化。
5. 用 TF-IDF(sklearn.feature_extraction.text.TfidfVectorizer)提取每类主题的代表词。
全系列 29 章到这里就全部完成了。回到总目录,继续用真实数据动手练习吧!
文章回复
0 条公开回复