当前位置: 首页 > news >正文

基于动漫数据的可视化分析与推荐系统实现

摘要:本文详细阐述了一个针对动漫数据的综合分析项目。通过数据加载、预处理,运用多种可视化手段呈现动漫类型分布、工作室评分、时间趋势等信息,并构建基于类型的推荐系统。借助Python的pandasmatplotlibseabornplotly以及scikit - learn等库实现上述功能,为深入理解动漫数据特征和用户推荐提供了有效方法。

关键词:动漫数据;数据预处理;可视化分析;推荐系统;类型相似度

关于数据集

kaggle原网址:https://www.kaggle.com/datasets/harshrajsahu007/best-new-anime-dataset/data

2500 动漫数据集 – 从 MyAnimeList (MAL) 中抓取

该数据集包含从 MyAnimeList (MAL) 收集的 2,500 部动漫标题的全面信息,MyAnimeList (MAL) 是最大的动漫数据库和粉丝社区之一。它专为动漫流行度分析、推荐系统以及与观众行为、内容趋势和媒体分类相关的各种机器学习项目而设计。

我用夸克网盘分享了「New_Anime_list数据集+源代码+输出可交互的HTML文件」,链接:https://pan.quark.cn/s/211878f9eee0

以下是排名前22的动漫:

标题类型制作公司集数上映日期内容类型观众评分来源
《进击的巨人》动作、剧情、奇幻、悬疑WIT STUDIO252013年4月电视动画84AniList
《鬼灭之刃》动作、冒险、剧情、奇幻、超自然ufotable262019年4月电视动画82AniList
《死亡笔记》悬疑、心理、超自然、惊悚MADHOUSE372006年10月电视动画84AniList
《咒术回战》动作、剧情、超自然Toho242020年10月电视动画85AniList
《我的英雄学院》动作、冒险、喜剧bones132016年4月电视动画76AniList
《全职猎人(2011)》动作、冒险、奇幻MADHOUSE1482011年10月电视动画89AniList
《一拳超人》动作、喜剧、科幻、超自然MADHOUSE122015年10月电视动画83AniList
《东京喰种》动作、剧情、恐怖、悬疑、心理、超自然Studio Pierrot122014年7月电视动画75AniList
《进击的巨人 第二季》动作、剧情、奇幻、悬疑WIT STUDIO122017年4月电视动画84AniList
《刀剑神域》动作、冒险、奇幻、爱情Aniplex252012年7月电视动画69AniList
《钢之炼金术师: Brotherhood》动作、冒险、剧情、奇幻bones642009年4月电视动画90AniList
《海贼王》动作、冒险、喜剧、剧情、奇幻Toei Animation10001999年10月电视动画88AniList
《火影忍者》动作、冒险、喜剧、剧情、奇幻、超自然Studio Pierrot2202002年10月电视动画79AniList
《你的名字》剧情、爱情、超自然CoMix Wave12016年8月电影85AniList
《声之形》剧情、爱情、生活片段Kyoto Animation12016年9月电影88AniList
《进击的巨人 第三季》动作、剧情、奇幻、悬疑WIT STUDIO122018年7月电视动画85AniList
《我的英雄学院 第二季》动作、冒险、喜剧bones252017年4月电视动画79AniList
《约定的梦幻岛》剧情、奇幻、恐怖、悬疑、心理、惊悚CloverWorks122019年1月电视动画83AniList
《进击的巨人 最终季》动作、剧情、奇幻、悬疑MAPPA162020年12月电视动画86AniList
《暗杀教室》动作、喜剧、剧情、超自然Lerche222015年1月电视动画79AniList
《灵能百分百》动作、喜剧、剧情、心理、生活片段、超自然bones122016年7月电视动画84AniList

一、引言

动漫产业在全球范围内迅速发展,积累了丰富的数据资源。对这些数据进行深入分析,不仅有助于动漫制作方了解市场趋势、优化创作方向,也能为观众提供更精准的动漫推荐。本研究旨在通过对动漫相关数据的处理、可视化展示以及推荐系统的构建,挖掘动漫数据背后的潜在信息。

二、数据加载与预处理

2.1 数据加载

定义load_and_preprocess_data函数负责数据的加载与预处理。使用pd.read_csv函数从指定路径加载数据,若文件不存在或加载过程中出现异常,将输出相应错误提示并返回None。代码如下:

def load_and_preprocess_data(filepath):try:df = pd.read_csv(filepath)except FileNotFoundError:print(f"错误:找不到文件 '{filepath}',请检查路径是否正确。")return Noneexcept Exception as e:print(f"错误:加载文件时发生异常:{e}")return None

2.2 数据摘要与基本信息展示

加载数据后,使用df.info()方法输出数据的基本信息,包括各列的数据类型、非空值数量等,以便对数据集有初步了解。

print("\n数据基本信息:")
df.info()

在这里插入图片描述

2.3 日期列处理

release_date列进行处理,先将其中的'Unknown'值替换为pd.NaT,表示缺失日期。之后尝试使用两种常见日期格式'%Y-%m''%b-%y'解析日期(见小注-日期格式解析),若解析失败则保持为pd.NaT。代码如下:

df['release_date'] = df['release_date'].replace('Unknown', pd.NaT)
def parse_date(date_str):if pd.isna(date_str):return pd.NaTtry:return pd.to_datetime(date_str, format='%Y-%m')except:try:return pd.to_datetime(date_str, format='%b-%y')except:return pd.NaT
df['release_date'] = df['release_date'].apply(parse_date)

2.4 添加年份和月份列

根据处理后的release_date列,提取年份和月份信息,分别添加为yearmonth列,方便后续按时间维度进行分析。

df['year'] = df['release_date'].dt.year
df['month'] = df['release_date'].dt.month

2.5 拆分genre

genre列中以逗号分隔的多个类型字符串拆分为列表形式,便于对每个类型进行独立统计和分析。

df['genre'] = df['genre'].str.split(', ')

2.6 删除缺失发布日期的数据

删除release_date列为NaT的行,并输出删除的行数,以保证数据的完整性和分析的准确性。

original_size = df.shape[0]
df = df.dropna(subset=['release_date'])
print(f"删除了 {original_size - df.shape[0]} 行缺失发布日期的数据")

删除了 17 行缺失发布日期的数据

2.7 检查并填充关键列缺失值

viewer_reviewsnumber_of_episodes列检查缺失值情况。若存在缺失值,对于数值类型的列,使用均值进行简单填充,并输出相应提示信息。

for col in ['viewer_reviews', 'number_of_episodes']:if col in df.columns:missing = df[col].isna().sum()if missing > 0:print(f"注意:{col} 列有 {missing} 个缺失值")if df[col].dtype in [int, float]:df[col] = df[col].fillna(df[col].mean())

三、可视化分析

3.1 静态可视化

visualize_data函数中,首先创建一个大小为(16, 10)的图形,准备进行多个子图的绘制。

3.1.1 类型分布分析

genre列展开为单个类型列表,使用Counter统计每个类型出现的次数,选取出现次数最多的前10个类型,使用seabornbarplot绘制类型分布条形图。

all_genres = [genre for sublist in df['genre'] for genre in sublist]
genre_counts = Counter(all_genres)
top_genres = genre_counts.most_common(10)
plt.subplot(2, 2, 1)
sns.barplot(x=[count for _, count in top_genres], y=[genre for genre, _ in top_genres])
plt.title('Top 10动漫类型分布')
plt.xlabel('数量')
plt.ylabel('动漫类型')

1

3.1.2 工作室比较

若数据集中同时存在studioviewer_reviews列,计算每个工作室的平均评分,并选取评分最高的前10个工作室绘制条形图;若缺少相关列,则在相应子图位置显示提示信息并关闭坐标轴。

if'studio' in df.columns and 'viewer_reviews' in df.columns:studio_ratings = df.groupby('studio')['viewer_reviews'].mean().sort_values(ascending=False)[:10]plt.subplot(2, 2, 2)sns.barplot(x=studio_ratings.values, y=studio_ratings.index)plt.title('Top 10工作室平均评分')plt.xlabel('平均评分')plt.ylabel('工作室')
else:plt.subplot(2, 2, 2)plt.text(0.5, 0.5, '缺少工作室或评分数据', ha='center', va='center')plt.axis('off')

2

3.1.3 时间趋势分析

若数据集中存在year列,分别按年份统计动漫数量和平均评分,并绘制折线图展示时间趋势;若缺少年份列,则在相应子图位置显示提示信息并关闭坐标轴。

if 'year' in df.columns:yearly_counts = df.groupby('year').size()yearly_ratings = df.groupby('year')['viewer_reviews'].mean()plt.subplot(2, 2, 3)yearly_counts.plot(kind='line', title='每年动漫数量')plt.xlabel('年份')plt.ylabel('数量')plt.subplot(2, 2, 4)yearly_ratings.plot(kind='line', title='每年平均评分')plt.xlabel('年份')plt.ylabel('平均评分')
else:plt.subplot(2, 2, 3)plt.text(0.5, 0.5, '缺少年份数据', ha='center', va='center')plt.axis('off')plt.subplot(2, 2, 4)plt.text(0.5, 0.5, '缺少年份数据', ha='center', va='center')plt.axis('off')

在这里插入图片描述

在这里插入图片描述

最后,使用plt.tight_layout()优化子图布局,将静态可视化图表保存为'anime_static_visualizations.png',并显示图表。

plt.tight_layout()
plt.savefig('anime_static_visualizations.png', dpi=300, bbox_inches='tight')
print("静态可视化图表已保存为 'anime_static_visualizations.png'")
plt.show()

总

3.2 交互式可视化

3.2.1 类型与评分关系

若数据集中同时存在genreviewer_reviews列,将genre列展开后使用plotly.expressbox函数绘制不同类型动漫的评分分布箱线图,设置图表标题、坐标轴标签,并将图表保存为'genre_rating_distribution.html',然后显示图表;若缺少相关列,则输出提示信息。

if 'genre' in df.columns and 'viewer_reviews' in df.columns:genre_df = df.explode('genre')genre_rating_fig = px.box(genre_df, x='genre', y='viewer_reviews', title='不同类型动漫的评分分布')genre_rating_fig.update_layout(xaxis_title='动漫类型',yaxis_title='观众评分',font=dict(family="SimHei"))genre_rating_fig.write_html('genre_rating_distribution.html')print("类型与评分关系图已保存为 'genre_rating_distribution.html'")genre_rating_fig.show()
else:print("缺少类型或评分数据,无法生成类型与评分关系图")

在这里插入图片描述
在这里插入图片描述

3.2.2 集数与评分关系

若数据集中同时存在number_of_episodesviewer_reviews列,使用plotly.expressscatter函数绘制集数与评分关系散点图,可根据studio列进行颜色区分,并根据title列设置悬停数据,设置图表标题、坐标轴标签,将图表保存为'episodes_rating_relationship.html',然后显示图表;若缺少相关列,则输出提示信息。

if 'number_of_episodes' in df.columns and 'viewer_reviews' in df.columns:color_col ='studio' if'studio' in df.columns else Nonehover_data = ['title'] if 'title' in df.columns else []episodes_rating_fig = px.scatter(df, x='number_of_episodes', y='viewer_reviews', color=color_col, hover_data=hover_data, title='集数与评分关系')episodes_rating_fig.update_layout(xaxis_title='集数',yaxis_title='观众评分',font=dict(family="SimHei"))episodes_rating_fig.write_html('episodes_rating_relationship.html')print("集数与评分关系图已保存为 'episodes_rating_relationship.html'")episodes_rating_fig.show()
else:print("缺少集数或评分数据,无法生成集数与评分关系图")

在这里插入图片描述

3.2.3 月度发布趋势

若数据集中同时存在monthyear列,按年份和月份统计动漫发布数量,将结果转换为日期格式后使用plotly.expressline函数绘制动漫月度发布趋势折线图,设置图表标题、坐标轴标签,将图表保存为'monthly_release_trend.html',然后显示图表;若缺少相关列,则输出提示信息。

if'month' in df.columns and 'year' in df.columns:monthly_counts = df.groupby(['year','month']).size().reset_index(name='count')monthly_counts['date'] = pd.to_datetime(monthly_counts[['year','month']].assign(day=1))monthly_trend_fig = px.line(monthly_counts, x='date', y='count', title='动漫月度发布趋势')monthly_trend_fig.update_layout(xaxis_title='日期',yaxis_title='发布数量',font=dict(family="SimHei"))monthly_trend_fig.write_html('monthly_release_trend.html')print("月度发布趋势图已保存为'monthly_release_trend.html'")monthly_trend_fig.show()
else:print("缺少月份或年份数据,无法生成月度发布趋势图")

在这里插入图片描述

四、推荐系统(基于类型)

4.1 推荐系统实现

recommend_animes函数中实现基于类型的推荐系统。首先检查数据是否有效以及是否包含必要的titlegenre列,若不满足条件则输出相应错误提示并返回None

def recommend_animes(df, title, top_n=5):if df is None or df.empty:print("错误:没有有效数据用于推荐")return Noneif 'title' not in df.columns or 'genre' not in df.columns:print("错误:数据中缺少标题或类型列")return None

4.2 查找目标动漫

在数据集中查找目标动漫,若找不到则输出提示信息并返回None

target_anime = df[df['title'] == title].iloc[0] if title in df['title'].values else None
if target_anime is None:print(f"错误:找不到名为 '{title}' 的动漫")return None

4.3 创建类型向量

提取数据集中所有动漫的类型,构建一个包含所有类型的列表all_genres。对于每部动漫,根据其包含的类型在all_genres中的位置,生成一个类型向量,向量中对应类型位置为1,否则为0。

all_genres = list({genre for sublist in df['genre'] for genre in sublist})
genre_vectors = []
for _, row in df.iterrows():vector = [1 if genre in row['genre'] else 0 for genre in all_genres]genre_vectors.append(vector)

4.4 计算相似度

使用cosine_similarity函数计算目标动漫与其他所有动漫的类型向量余弦相似度,获取相似度最高的前top_n部动漫作为推荐结果。

target_index = df[df['title'] == title].index[0]
similarities = cosine_similarity([genre_vectors[target_index]], genre_vectors)[0]
similar_indices = similarities.argsort()[::-1][1:top_n + 1]
similar_animes = df.iloc[similar_indices]
return similar_animes

在这里插入图片描述

五、主程序

if __name__ == "__main__"代码块中,设置Plotly渲染器为浏览器模式,指定数据文件路径,调用load_and_preprocess_data函数加载并预处理数据。若数据有效,则输出处理完成信息、数据集大小和数据样例,然后依次进行可视化分析和推荐系统演示。

if __name__ == "__main__":pio.renderers.default = "browser"filepath = 'New_Anime_list.csv'df = load_and_preprocess_data(filepath)if df is not None and not df.empty:print("\n数据加载和预处理完成!")print(f"处理后数据集大小: {df.shape}")print("\n数据样例:")print(df.head().to_csv(sep='\t', na_rep='nan'))print("\n开始可视化分析...")visualize_data(df)if 'title' in df.columns and 'genre' in df.columns:sample_title = df['title'].iloc[0] if not df.empty else ""if sample_title:print(f"\n推荐系统演示 - 查找与 '{sample_title}' 相似的动漫:")recommendations = recommend_animes(df, sample_title)if recommendations is not None and not recommendations.empty:print("\n推荐结果:")print(recommendations[['title', 'genre', 'viewer_reviews']].to_csv(sep='\t', na_rep='nan'))else:print("\n数据缺少必要的列,无法运行推荐系统演示")print("\n所有分析完成!")

六、小注

日期格式解析

'%Y-%m'格式解析

  • %Y:表示四位数的年份(例如:2025、2024)。
  • %m:表示两位数的月份(范围01-12,例如:06、12)。
  • 连字符“-”:作为分隔符,用于连接年份和月份。

示例'2025-06'表示“2025年6月”。

'%b-%y'格式解析

  • %b:表示月份的英文缩写(仅取前三个字母,例如:Jan、Jun、Dec)。
  • %y:表示两位数的年份(例如:25、24,对应2025、2024)。
  • 连字符“-”:作为分隔符,用于连接月份缩写和年份。

示例'Jun-25'表示“2025年6月”。

常见日期格式符号对比

符号含义示例(以2025年6月30日为例)
%Y四位数年份2025
%y两位数年份25
%m两位数月份(01-12)06
%b月份英文缩写Jun
%d两位数日期(01-31)30
%B月份全称June
%a星期几英文缩写Mon
%A星期几全称Monday
http://www.dtcms.com/a/263512.html

相关文章:

  • Pyhton-EXCEL与Mysql数据对比
  • Monorepo+Pnpm+Turborepo
  • Vue Vue-route (1)
  • jvm的调优命令jstack打印堆栈信息阐述以及调优
  • Linux信号量
  • 基础算法合集-图论
  • 《AI的“三体进化”:数字基因与超人类思维的奇点降临》
  • Windows 11 24H2更新系统后WiFi不显示故障处理
  • AI编程实战:Cursor黑科技全解析
  • Python 数据分析与机器学习入门 (二):NumPy 核心教程,玩转多维数组
  • 【C语言】知识总结·内存函数
  • CSDN博客大搬家(本地下载markdown合适和图片本地化)
  • I/O I/O基本概念与基本I/O函数 6.30
  • Swift 实现二叉树垂直遍历:LeetCode 314 完整解析与实战示例
  • HTML之常用基础标签
  • Stable Diffusion 项目实战落地:从0到1 掌握ControlNet 第四篇 风格化字体大揭秘:从线稿到涂鸦,ControlNet让文字焕发新生
  • C#索引和范围:简化集合访问的现代特性详解
  • 湖北理元理律师事务所债务解法:从法律技术到生活重建
  • 使用nomachine远程连接ARM设备桌面
  • 【SpringAI】3.结构化输出,初级版
  • 大语言模型 API 进阶指南:DeepSeek 与 Qwen 的深度应用与封装实践
  • C# Winfrom教程(二)----label
  • Unity性能优化-渲染模块(1)-CPU侧(2)-DrawCall优化(2)GPUInstancing
  • StackGAN(堆叠生成对抗网络)
  • Qt Hello World 程序
  • js代码02
  • NVCC编译以及Triton编译介绍
  • 攻防世界-MISC-red_green
  • 【Python使用】嘿马python运维开发全体系教程第2篇:日志管理,Linux概述【附代码文档】
  • 查看CPU支持的指令集和特性