番剧《间谍过家家》的评论分析
Contents
0.1 分析目的和项目背景
目的:分析间谍过家家的评价,了解用户对该番剧的评价 , ,数据源:kaggle上的Bilibili Spy X Family数据集
0.2 数据准备
#导入所需库
,import pandas as pd
,# pd.plotting.register_matplotlib_converters()
,import matplotlib.pyplot as plt
,#%matplotlib inline#可省,但保留可提高可移植性,以养成规范化习惯
,import seaborn as sns
,print("Setup Complete")
,
,# 设置中文字体显示
,plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体显示中文
,plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
,
,
,#画图结束,代码单元格结尾,以养成规范化习惯
,plt.show() # 保留以确保代码可移植输出结果
Setup Complete
filepath="data/spyxfamily.csv"
,# 读取CSV文件
,df = pd.read_csv(filepath,index_col="id")
,# 数据检查
,df.head()
,输出结果
0.3 数据预处理
,1. 缺失值处理:找到comment列有的缺失值,可以直接删除该行数据,以保证评论内容的完整性。
# 检查数据集中是否存在空值
,print("数据集中空值的数量:")
,print(df.isnull().sum())
,print("\n总空值数量:", df.isnull().sum().sum())
,# 删除comment列中的空值行
,df = df.dropna(subset=['comment'])
,df.reset_index(drop=True, inplace=True)
,# 检查author列的重复值数量
,duplicate_authors = df['author'].duplicated().sum()
,print(f"作者名重复的数量: {duplicate_authors}")输出结果
数据集中空值的数量:
,author 0
,comment 1
,stars1 0
,stars2 0
,stars3 0
,stars4 0
,stars5 0
,unlike 2849
,like 3138
,dtype: int64
,
,总空值数量: 5988
,作者名重复的数量: 0
- 提取评分信息
# 将评分数据中的星星图标替换为数字1
,df = df.replace('icon-star icon-star-light', '1')
,# 将评分数据中的星星图标替换为数字0
,df = df.replace('icon-star', '0')
,# 创建score列,计算每行星级评分的总和
,columns_to_convert = [f'stars{i}' for i in range(1, 6)] # 生成['stars1', 'stars2', ..., 'stars5']
,# 2. 循环转换每一列(处理可能的非数值内容)
,for col in columns_to_convert:
, # errors='coerce'表示无法转换的值会被设为NaN,方便后续处理
, df[col] = pd.to_numeric(df[col], errors='coerce')
,df[columns_to_convert] = df[columns_to_convert].fillna(0)
,score = df['stars1'] + df['stars2'] + df['stars3'] + df['stars4'] + df['stars5']
,df['score'] = score
,# 1. 定义需要删除的列名
,columns_to_drop = ['stars1', 'stars2', 'stars3', 'stars4', 'stars5']
,
,# 2. 删除指定列(两种方式可选)
,# 方式1:原地删除(直接修改原df,不返回新对象)
,#df.drop(columns=columns_to_drop, axis=1, inplace=True)
,
,# 方式2:不原地删除(返回删除列后的新df,原df保持不变)
,df = df.drop(columns=columns_to_drop, axis=1)
,#检查星级转换评分是否成功
,df.head()输出结果
0.4 分析:舆论倾向分类
,1. 基于评分的分类,将评论分为正面评价(4,5),负面评价(0,1,2),中性评价(3)
# 根据评分添加情感倾向列
,def get_sentiment(score):
, if score >= 4:
, return "正面"
, elif score <= 2:
, return "负面"
, else:
, return "中性"
,
,df['倾向'] = df['score'].apply(get_sentiment)
,# 检查情感倾向列是否添加成功
,df.head()输出结果
# 统计各种评论倾向的占比
,tendency_counts = df['倾向'].value_counts()
,tendency_percentages = tendency_counts / len(df) * 100
,
,# 创建饼图展示倾向分布
,plt.figure(figsize=(10, 8))
,plt.pie(tendency_percentages,
, labels=tendency_counts.index,
, autopct='%1.1f%%',
, colors=['lightgreen', 'lightcoral', 'lightskyblue'])
,plt.title('评论倾向分布')
,plt.axis('equal')
,plt.show()
,
,# 打印具体数值
,print("\n各倾向评论数量及占比:")
,for tendency, count in tendency_counts.items():
, percentage = tendency_percentages[tendency]
, print(f"{tendency}: {count}条评论 ({percentage:.1f}%)")输出结果
<Figure size 1000x800 with 1 Axes>
,各倾向评论数量及占比:
,正面: 3059条评论 (97.5%)
,中性: 46条评论 (1.5%)
,负面: 32条评论 (1.0%)
- 基于评论内容的分类
# 定义关键词字典,用于匹配不同类别
,category_keywords = {
, "剧情": ["剧情", "情节", "故事线", "发展", "漏洞", "设定"],
, "角色": ["角色", "人物", "性格", "可爱", "喜欢", "讨厌"],
, "画风": ["画风", "画面", "色彩", "绘制"],
,}
,
,
,def classify_comment(comment):
, """
, 根据评论内容和关键词字典对评论进行分类
, """
, for category, keywords in category_keywords.items():
, for keyword in keywords:
, if keyword in str(comment):
, return category
, return "其他"
,
,
,# 对数据集中的评论进行分类
,df['category'] = df['comment'].apply(classify_comment)
,df.head()输出结果
author comment unlike \
,0 琴-格雷 我只能说,非常好,次瓜真的很好磕 149.0
,1 憨Sir_r 整个第一季很好看,但看久了不难发现现有设定是支撑不起继续往下走了。纯当一部搞笑日常番还是很棒... 65.0
,2 K_YokoHama 多少年没看到这种正经又轻松的番了呜呜呜 47.0
,3 physicalwilling 轻喜剧中交织着家庭、爱、子女、国家与责任,喻重于轻,罕见的佳作! 4.0
,4 迷之小依然 当做搞笑休闲番来看就好了,感觉剧情发展太过于慢而拖,总体上还是不错了(超喜欢可爱的阿妮娅) 2.0
,
, like score 倾向 category
,0 NaN 5 正面 其他
,1 NaN 4 正面 剧情
,2 NaN 5 正面 其他
,3 NaN 5 正面 其他
,4 NaN 4 正面 剧情
# 统计各类评论的占比
,tendency_counts = df['category'].value_counts()
,tendency_percentages = tendency_counts / len(df) * 100
,
,# 创建饼图展示评论类型分布
,plt.figure(figsize=(10, 8))
,plt.pie(tendency_percentages,
, labels=tendency_counts.index,
, autopct='%1.1f%%',
, colors=['lightgreen', 'lightcoral', 'lightskyblue','orange'])
,
,plt.title('评论类型分布')
,plt.axis('equal')
,plt.show()
,
,# 打印具体数值
,print("\n各类别评论数量及占比:")
,for tendency, count in tendency_counts.items():
, percentage = tendency_percentages[tendency]
, print(f"{tendency}: {count}条评论 ({percentage:.1f}%)")输出结果
<Figure size 1000x800 with 1 Axes>
,各类别评论数量及占比:
,其他: 2537条评论 (80.9%)
,角色: 451条评论 (14.4%)
,剧情: 143条评论 (4.6%)
,画风: 6条评论 (0.2%)
# 按category分组并找出每组like数最多的5条评论
,top_comments = df.sort_values('unlike', ascending=False).groupby('category').head(3)
,
,# 展示结果
,print("各类别下获赞最多的评论:")
,print("-" * 50)
,for idx, row in top_comments.iterrows():
, print(f"类别: {row["category"]}")
, print(f"评论内容: {row['comment']}")
, print(f"获赞数: {row['unlike']}")
, print("-" * 50)输出结果
各类别下获赞最多的评论:
,--------------------------------------------------
,类别: 其他
,评论内容: 期待十月的第二季!
,获赞数: 220.0
,--------------------------------------------------
,类别: 其他
,评论内容: 我只能说,非常好,次瓜真的很好磕
,获赞数: 149.0
,--------------------------------------------------
,类别: 其他
,评论内容: 在粗制滥造的穿越动漫横生的时期,这部动漫的制作挺不错。这部动漫很适合放松,让人愉快。不要拿来和那些经典神作比,能有什么好比的。
,获赞数: 90.0
,--------------------------------------------------
,类别: 剧情
,评论内容: 我觉得动画做的挺好的,本身这个就是这种比较平淡的日常,但是又莫名其妙的有趣,而且评分高不代表神作啊,只能说大部分人觉得剧情没问题,有看点,给高分又惹到谁了?
,获赞数: 70.0
,--------------------------------------------------
,类别: 角色
,评论内容: 好可爱啊阿妮亚好可爱
,获赞数: 70.0
,--------------------------------------------------
,类别: 剧情
,评论内容: 整个第一季很好看,但看久了不难发现现有设定是支撑不起继续往下走了。纯当一部搞笑日常番还是很棒的,但只要一往间谍情报方面想,就显得干瘪有漏洞了。
,获赞数: 65.0
,--------------------------------------------------
,类别: 角色
,评论内容: 整体很好,但只适合当搞笑番,像是上帝视角看菜鸡互啄的感觉。情感不随人物变化,含义并不深刻。不过应该算是符合当今时代潮流了。
,获赞数: 44.0
,--------------------------------------------------
,类别: 剧情
,评论内容: 剧情一般
,获赞数: 7.0
,--------------------------------------------------
,类别: 角色
,评论内容: 超能力女儿阿尼亚可可爱爱,与间谍父亲、杀手母亲一起开始过家家的旅行,一个搞笑、感动、日常的故事非常轻松和治愈。最后一集有点仓促不算作结束,但可以期待十月的第二季!哇库哇库!表情帝阿尼亚实在太可爱啦!
,获赞数: 5.0
,--------------------------------------------------
,类别: 画风
,评论内容: 感觉制作不太好,画面太单调,其他的还行
,获赞数: 2.0
,--------------------------------------------------
,类别: 画风
,评论内容: 虽然这部作品有点偏低龄化,我也是抱着当乐子看的,不过这部番确实带给了我不是乐趣,动画风格也很像是一部喜剧,动画制作质量也算得上一部佳作
,获赞数: 2.0
,--------------------------------------------------
,类别: 画风
,评论内容: 画风精致的日常番
,获赞数: nan
,--------------------------------------------------
- 负面评价分析
import re
,import jieba
,from collections import Counter
,from wordcloud import WordCloud
,import seaborn as sns
,
,# 定义负面评论的判断规则(基于星级标识,stars5为单星时视为负面)
,negative_comments = df[df['score'] < 4]['comment'].dropna() # 过滤空值
,
,
,# 加载中文停用词表
,with open('data/stopwords-zh.txt', 'r', encoding='utf-8') as f:
, stopwords = set([line.strip() for line in f])
,
,
,# 中文文本预处理(清洗+分词+去停用词)
,def preprocess_text(text):
, # 去除特殊字符和数字
, text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z]', ' ', str(text))
, # 分词
, words = jieba.cut(text)
, # 去除停用词和空字符串
, words = [word for word in words if word.strip() and word not in stopwords]
, return words
,
,# 对负面评论进行预处理
,processed_comments = []
,for comment in negative_comments:
, processed_comments.extend(preprocess_text(comment))
,
,# 统计高频词(问题点)
,word_counts = Counter(processed_comments)
,top_issues = word_counts.most_common(15) # 取前15个高频问题点
,
,# 输出结果
,# print("负面评论中高频出现的问题点:")
,# for issue, count in top_issues:
,# print(f"{issue}: {count}次")
,
,
,# 使用wordcloud生成词云图
,# 导入所需库
,import numpy as np
,
,
,# 创建数据
,words = [item[0] for item in top_issues]
,frequencies = [item[1] for item in top_issues]
,
,# 将数据重塑成矩阵形式
,data_matrix = np.array(frequencies).reshape(3, 5) # 3x5的矩阵布局
,
,
,# 创建热力图
,plt.figure(figsize=(12, 6))
,sns.heatmap(data_matrix,
, annot=np.array(words).reshape(3, 5), # 显示关键词文本
, fmt='', # 文本格式
, cmap='YlOrRd', # 使用红色系配色
, cbar_kws={'label': '出现频次'})
,
,plt.title('负面评论关键词热力图')
,plt.xlabel('关键词')
,plt.ylabel('关键词')
,plt.tight_layout()
,plt.show()
,
,# 将词频转换为字典格式
,word_freq_dict = dict(top_issues)
,
,# 配置词云图参数
,wc = WordCloud(
, font_path=r"C:\Windows\Fonts\msyh.ttc", # 使用黑体字体以正确显示中文
, width=800,
, height=400,
, background_color='white',
, max_words=100,
, max_font_size=100
,)
,
,# 生成词云
,wc.generate_from_frequencies(word_freq_dict)
,
,# 显示词云图
,plt.figure(figsize=(10, 6))
,plt.imshow(wc, interpolation='bilinear')
,plt.axis('off')
,plt.title('负面评论关键词云图')
,plt.show()输出结果
<Figure size 1200x600 with 2 Axes>
<Figure size 1000x600 with 1 Axes>
- 中文情感分析
# 1. 加载情感词典(处理空行和格式错误)
,def load_sentiment_dict(path):
, sentiment_dict = {}
, with open(path, 'r', encoding='utf-8') as f:
, for line_num, line in enumerate(f, 1): # 记录行号,方便排查错误
, line = line.strip()
, # 跳过空行
, if not line:
, continue
, # 按空格分割,确保得到2个元素
, parts = line.split()
, if len(parts) != 2:
, print(f"警告:第{line_num}行格式错误,已跳过 -> {line}")
, continue
, word, score = parts
, try:
, sentiment_dict[word] = float(score)
, except ValueError:
, print(f"警告:第{line_num}行分数格式错误,已跳过 -> {line}")
, return sentiment_dict
,
,
,# 2. 加载停用词表
,def load_stopwords(path):
, with open(path, 'r', encoding='utf-8') as f:
, return set([line.strip() for line in f])
,
,# 3. 文本预处理(清洗+分词+去停用词)
,def preprocess(text, stopwords):
, # 去除特殊字符和数字
, text = re.sub(r'[^\u4e00-\u9fa5]', ' ', text)
, # 分词
, words = jieba.cut(text)
, # 过滤停用词
, return [word for word in words if word.strip() and word not in stopwords]
,
,# 4. 计算情感得分
,def get_sentiment_score(words, sentiment_dict):
, score = 0.0
, for word in words:
, if word in sentiment_dict:
, score += sentiment_dict[word] # 正面词加分,负面词减分
, return score
,
,# 5. 判断情感倾向(自定义阈值)
,def classify_sentiment(score, threshold=0.5):
, if score > threshold:
, return "积极", score
, elif score < -threshold:
, return "消极", score
, else:
, return "中性", score
,sentiment_dict = load_sentiment_dict("data/BosonNLP_sentiment_score.txt")
,
,stopwords = load_stopwords("data/stopwords-zh.txt") # 使用之前提供的停用词表
,
,for text in df["comment"]:
, words = preprocess(text, stopwords)
, score = get_sentiment_score(words, sentiment_dict)
, sentiment, score = classify_sentiment(score)
, # 将分词结果和情感倾向添加到DataFrame中
, df.at[df[df['comment'] == text].index[0], 'words'] = sentiment
,# 检查情感分析结果
,df.head()
,输出结果
# 统计情感分布
,sentiment_counts = df['words'].value_counts()
,
,# 创建饼图
,plt.figure(figsize=(8, 8))
,plt.pie(sentiment_counts, labels=sentiment_counts.index, autopct='%1.1f%%')
,plt.title('评论情感分布')
,
,# 添加图例
,plt.legend(loc='best')
,
,# 确保饼图是圆形的
,plt.axis('equal')
,
,# 显示图表
,plt.show()输出结果
<Figure size 800x800 with 1 Axes>
0.5 结论
,这是一部广泛好评的搞笑欢快型日常番.剧情搞笑,角色设计讨喜,是一部值得推荐的动画.