基于动漫数据的可视化分析与推荐系统实现
摘要:本文详细阐述了一个针对动漫数据的综合分析项目。通过数据加载、预处理,运用多种可视化手段呈现动漫类型分布、工作室评分、时间趋势等信息,并构建基于类型的推荐系统。借助Python的pandas
、matplotlib
、seaborn
、plotly
以及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 STUDIO | 25 | 2013年4月 | 电视动画 | 84 | AniList |
《鬼灭之刃》 | 动作、冒险、剧情、奇幻、超自然 | ufotable | 26 | 2019年4月 | 电视动画 | 82 | AniList |
《死亡笔记》 | 悬疑、心理、超自然、惊悚 | MADHOUSE | 37 | 2006年10月 | 电视动画 | 84 | AniList |
《咒术回战》 | 动作、剧情、超自然 | Toho | 24 | 2020年10月 | 电视动画 | 85 | AniList |
《我的英雄学院》 | 动作、冒险、喜剧 | bones | 13 | 2016年4月 | 电视动画 | 76 | AniList |
《全职猎人(2011)》 | 动作、冒险、奇幻 | MADHOUSE | 148 | 2011年10月 | 电视动画 | 89 | AniList |
《一拳超人》 | 动作、喜剧、科幻、超自然 | MADHOUSE | 12 | 2015年10月 | 电视动画 | 83 | AniList |
《东京喰种》 | 动作、剧情、恐怖、悬疑、心理、超自然 | Studio Pierrot | 12 | 2014年7月 | 电视动画 | 75 | AniList |
《进击的巨人 第二季》 | 动作、剧情、奇幻、悬疑 | WIT STUDIO | 12 | 2017年4月 | 电视动画 | 84 | AniList |
《刀剑神域》 | 动作、冒险、奇幻、爱情 | Aniplex | 25 | 2012年7月 | 电视动画 | 69 | AniList |
《钢之炼金术师: Brotherhood》 | 动作、冒险、剧情、奇幻 | bones | 64 | 2009年4月 | 电视动画 | 90 | AniList |
《海贼王》 | 动作、冒险、喜剧、剧情、奇幻 | Toei Animation | 1000 | 1999年10月 | 电视动画 | 88 | AniList |
《火影忍者》 | 动作、冒险、喜剧、剧情、奇幻、超自然 | Studio Pierrot | 220 | 2002年10月 | 电视动画 | 79 | AniList |
《你的名字》 | 剧情、爱情、超自然 | CoMix Wave | 1 | 2016年8月 | 电影 | 85 | AniList |
《声之形》 | 剧情、爱情、生活片段 | Kyoto Animation | 1 | 2016年9月 | 电影 | 88 | AniList |
《进击的巨人 第三季》 | 动作、剧情、奇幻、悬疑 | WIT STUDIO | 12 | 2018年7月 | 电视动画 | 85 | AniList |
《我的英雄学院 第二季》 | 动作、冒险、喜剧 | bones | 25 | 2017年4月 | 电视动画 | 79 | AniList |
《约定的梦幻岛》 | 剧情、奇幻、恐怖、悬疑、心理、惊悚 | CloverWorks | 12 | 2019年1月 | 电视动画 | 83 | AniList |
《进击的巨人 最终季》 | 动作、剧情、奇幻、悬疑 | MAPPA | 16 | 2020年12月 | 电视动画 | 86 | AniList |
《暗杀教室》 | 动作、喜剧、剧情、超自然 | Lerche | 22 | 2015年1月 | 电视动画 | 79 | AniList |
《灵能百分百》 | 动作、喜剧、剧情、心理、生活片段、超自然 | bones | 12 | 2016年7月 | 电视动画 | 84 | AniList |
一、引言
动漫产业在全球范围内迅速发展,积累了丰富的数据资源。对这些数据进行深入分析,不仅有助于动漫制作方了解市场趋势、优化创作方向,也能为观众提供更精准的动漫推荐。本研究旨在通过对动漫相关数据的处理、可视化展示以及推荐系统的构建,挖掘动漫数据背后的潜在信息。
二、数据加载与预处理
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
列,提取年份和月份信息,分别添加为year
和month
列,方便后续按时间维度进行分析。
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_reviews
和number_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个类型,使用seaborn
的barplot
绘制类型分布条形图。
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('动漫类型')
3.1.2 工作室比较
若数据集中同时存在studio
和viewer_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')
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 类型与评分关系
若数据集中同时存在genre
和viewer_reviews
列,将genre
列展开后使用plotly.express
的box
函数绘制不同类型动漫的评分分布箱线图,设置图表标题、坐标轴标签,并将图表保存为'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_episodes
和viewer_reviews
列,使用plotly.express
的scatter
函数绘制集数与评分关系散点图,可根据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 月度发布趋势
若数据集中同时存在month
和year
列,按年份和月份统计动漫发布数量,将结果转换为日期格式后使用plotly.express
的line
函数绘制动漫月度发布趋势折线图,设置图表标题、坐标轴标签,将图表保存为'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
函数中实现基于类型的推荐系统。首先检查数据是否有效以及是否包含必要的title
和genre
列,若不满足条件则输出相应错误提示并返回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 |