
简介一份针对电影数据的数据分析与可视化案例包面向数据分析初学者和从业者帮助掌握从数据清洗、探索性分析到图表呈现的完整流程。数据集涵盖电影名称、导演、演员、类型、上映年份、票房、评分等字段可开展市场趋势、类型与票房关系、观众喜好以及多因素关联性分析。压缩包内共5个文件包括2个Jupyter Notebook代码文件、1个CSV与1个Excel数据集以及1个HTML可视化报告整体仅1.1MB便于下载与复用。目前已有304人学习浏览可作为数据分析课程作业、毕业设计或自学实践的参考。通过配套代码读者可以获得票房与类型、评分与导演/演员等关系的可视化分析思路并掌握条形图、饼图、散点图、时间序列图、网络图等多种可视化工具的实际应用从而更直观地理解电影市场规律。1. 电影数据可视化分析拿到“数据集代码”压缩包后先想清楚的几件事一个标着“电影数据可视化分析数据集代码.rar”的压缩包解压后大概率是一份 CSV 或 Excel 加几个没写注释的 .py 文件。很多人直接跑脚本看到几张图出来就以为完事但真正的可视化工作是从压缩包变成可复现结果的全过程读入数据、清洗字段、计算指标、选择图表、控制画布细节。真实的电影票房数据集不是教科书里的 iris它包含年份缺失、分钟数为 0 的脏行、评分全被取整、类型字段用竖线分隔等一堆问题。这篇文章从拿到数据集开始逐步完成一套可直接发布的电影数据可视化方案覆盖了 pandas 数据处理、聚合逻辑和 matplotlib 出图的完整链路适合能把脚本跑通但总是卡在“数据长什么样”这一步的从业者。2. 第一步把压缩包里的数据集安全读进 pandas并完成字段规整2.1 读 CSV 时先搞清楚分隔符、编码和列类型再动手拿到 .rar 里的数据文件我一般不会直接双击打开而是先在命令行里用一个小命令确认文件编码和结构避免后续 read_csv 报 UnicodeDecodeError 或者把整数列读成字符串。# 在文件所在目录执行查看前几行内容判断分隔符和编码 file movie_data.csv head -n 5 movie_data.csvimport pandas as pd df pd.read_csv( movie_data.csv, sep,, encodingutf-8, enginepython, on_bad_linesskip, ) print(df.head()) print(df.info())参数说明sep,多数导出文件用逗号分隔但如果数据里出现英文逗号比如电影原名 “Love, Actually”行解析就会错位。此时改用sep\t或者指定quotingcsv.QUOTE_ALL更稳妥前者要求源文件是 Tab 分隔后者要求字段都用引号包裹。encodingutf-8网络下载的数据集常是 UTF-8但国内整理的数据集经常是 GBK。读乱码时先试encodinggbk还不行再用encodinggb18030后者能多覆盖一些生僻字。enginepython当文件里存在多行字段或引号混乱时C 引擎会直接报错Python 引擎容错能力更好。代价是读取速度慢几十万行以内的数据完全可接受。on_bad_linesskip遇到结构损坏的行直接跳过但这里有个风险——你并不知道跳过了多少行。更严谨的做法是先不加这个参数让 pandas 把所有坏行信息打印出来评估损坏比例后再决定是否跳过。df.info()输出里最值得看的是 Dtype 列。电影数据里常见的错误有年份被读成 float因为存在 NaN票房被读成 object因为带了美元符号或千分位逗号时长列出现 0 值。这些都会在绘图阶段变成刻度错乱所以读入后的第一件事不是画图而是逐个字段确认类型是否符合预期。2.2 用 pandas 做标准化分钟数、年份、票房字段的三类典型脏数据读入数据后先看df.describe(includeall)的输出再针对三个高频字段做清洗。下面是处理分钟数、年份、票房的常见逻辑# 分钟数清洗非数值统一置为 NaN0 值也视为缺失 df[minutes] pd.to_numeric(df[minutes], errorscoerce) df.loc[df[minutes] 0, minutes] pd.NaT # 年份清洗优先从上映日期提取取不到再回填原字段 df[year] pd.to_numeric(df[year], errorscoerce) df[year] df[year].fillna(pd.to_datetime(df[release_date], errorscoerce).dt.year) # 票房清洗去掉美元符号、逗号和空格后转数值 df[box_office] ( df[box_office] .astype(str) .str.replace(r[$,], , regexTrue) .str.replace(r\s, , regexTrue) ) df[box_office] pd.to_numeric(df[box_office], errorscoerce)逻辑说明pd.to_numeric(errorscoerce)会把无法解析的值变成 NaN比直接删除行更安全。后续画图时NaN 在 matplotlib 里默认不显示相当于自动忽略脏行。年份回填时用了release_date字段这是很多数据集里同时存在的冗余信息。优先用日期列推导年份能修复那些“年份写错但日期正确”的记录。票房清洗里用的是正则替换\$和,没有做汇率换算。如果数据集来自全球市场币种不统一的问题比格式问题更严重此时应该再增加一个currency字段做分组统计而不是直接合并。清洗完成后用df.isna().sum()检查每列的缺失量。对于可视化分析缺失率低于 5% 的字段可以直接保留 NaN高于 20% 的字段要考虑是否值得画图比如“预算”字段经常缺失严重硬画出来的条形图会误导读者。2.3 类型字段的多标签拆分从“动作|冒险”到可聚合的干净列电影数据的 genres 字段常以竖线分隔多标签直接 groupby 会把整个字符串当成一个分类导致统计失真。处理方式如下# 拆分类型字段并生成透视表 df[genres] df[genres].fillna(Unknown) genre_df df.dropna(subset[title]).copy() genre_df[genre_list] genre_df[genres].str.split(|) genre_exploded genre_df.explode(genre_list) genre_stats ( genre_exploded.groupby(genre_list) .agg( movie_count(title, count), avg_box(box_office, mean), med_box(box_office, median), ) .reset_index() .sort_values(movie_count, ascendingFalse) ) print(genre_stats.head(10))这个拆分逻辑用了两个关键 pandas 能力str.split(|)把字符串转成列表注意分隔符在正则里有特殊含义但str.split默认按字面量匹配所以不需要转义。explode把列表展开成多行每一行保留原记录的其他字段。展开后的数据集行数会膨胀比如一部电影同时属于“动作”“冒险”“科幻”展开后变成三行。统计时用movie_count计数它表示“属于该类型的电影数”所有类型计数之和会大于电影总数这是预期中的行为。表格里同时给出平均值和分位数是有原因的票房分布高度右偏平均值被头部大片拉高中位数能反映该类电影的典型水平。avg_box和med_box相差超过 5 倍的类型说明该类型严重依赖爆款做对比分析时要看清。3. 电影数据可视化分析中的单变量分布刻画票房、时长、评分的三种图3.1 先看描述统计再决定票房画直方图还是箱线图票房字段洗干净后第一件事是跑df[box_office].describe()重点看 min、max 和分位数。电影票房数据的典型特征是长尾分布中位数在几千万均值被少数十亿级大片拉高。此时直接画普通直方图大部分柱子会挤在最左边整个图几乎没有信息量。import numpy as np import matplotlib.pyplot as plt box df[box_office].dropna() pcts np.percentile(box, [50, 75, 90, 95, 99, 100]) print(关键分位数:, pcts) # 用对数坐标展示票房分布 fig, ax plt.subplots(figsize(8, 4)) ax.hist(box, bins50, color#4C72B0, edgecolorwhite) ax.set_xscale(log) ax.set_xlabel(Box Office (log scale)) ax.set_ylabel(Count) plt.tight_layout() plt.show()参数说明np.percentile比describe()里的分位数更灵活可以自由指定比例。长尾数据里99 分位到 100 分位之间往往是票房总量的巨大占比这一段的电影数量少但影响极大。bins50是直方图的分箱数量。数据量在万条级别时50 个箱足够体现分布形状而不会过于稀疏。如果分箱后出现明显锯齿优先把 bins 降到 30 或调整范围而不是增加箱数。ax.set_xscale(log)是对数横轴。票房从百万到十亿跨越三个数量级线性坐标下低票房段的细节完全丢失对数坐标能让 90% 的数据占满画面宽度这正是电影票房可视化里常用对数坐标的原因。3.2 时长分布的分箱处理把连续变量变成“短片 / 标准 / 长片”电影时长是连续变量但观众认知是分档的。画图前先做分箱会让图表更接近业务直觉。分箱的边界选择要结合数据集本身分布而不是随便取整。bins [0, 60, 90, 120, 150, 180, 240, float(inf)] labels [60m, 60-90m, 90-120m, 120-150m, 150-180m, 180-240m, 240m] df[duration_bin] pd.cut( df[minutes], binsbins, labelslabels, rightFalse, ) duration_stats ( df.groupby(duration_bin, observedFalse) .agg(movie_count(title, count), avg_rating(rating, mean)) .dropna() ) print(duration_stats)分箱参数rightFalse表示区间左闭右开即 60 分钟归入 “60-90m”不会同时出现在两个箱里。pd.cut默认要求 bins 覆盖数据的 min 和 maxfloat(inf)兜底所有超大值。时长字段如果存在恶性脏值比如 9999 分钟会被240m吸收后续统计更真实。做这一步时要注意把observedFalse加上否则 pandas 会因为箱内存在空分类而在某些版本里报 FutureWarning对后续结果没有影响但输出信息会让脚本看起来不干净。3.3 评分的“取整修复”为什么均值不可信以及分布图的正确画法网络下载的电影评分数据常被批量取整比如 IMDb 分数只保留到个位或半星。未经处理的评分列画出来会在整数位置出现尖峰这不是真实分布。# 评分修复把像 7.5 这种半星取值拆成连续分布不现实但至少可以去除明显乱码 df[rating] pd.to_numeric(df[rating], errorscoerce) df df[df[rating].between(1, 10)] fig, ax plt.subplots(figsize(8, 4)) ax.hist(df[rating], binsnp.arange(1, 10.1, 0.5), color#55A868, edgecolorwhite) ax.set_xlabel(Rating) ax.set_ylabel(Count) plt.show()评分数据的处理有个悖论取整是数据源的行为偏差不是我们能完全修复的。我们能做的是先过滤掉 1 分以下和 10 分以上的非法值再用 0.5 的 bin 宽度画图让半星分布自然呈现。如果 bin 宽度取 0.1反而会制造大量空柱看起来像是数据缺失。评分均值算出来后别忘了对比中位数。均值受极端低分影响大中位数反映典型电影水平两个指标差距超过 0.5 分说明数据里存在大量刷低分或两极分化现象可以在图上加一条均值线说明这种偏差。4. 电影数据可视化分析中的多变量关系导演、类型、票房与评分的交叉验证4.1 用散点图与相关系数检验“高评分带来高票房”是否成立电影分析里最常见的命题是评分和票房的关系。用散点图加回归线能在一张图里同时呈现相关性方向和离群点。import numpy as np scatter_df df.dropna(subset[rating, box_office]).copy() # 计算 Spearman 相关系数评分与票房都不是正态分布 rho scatter_df[rating].corr(scatter_df[box_office], methodspearman) print(fSpearman rho: {rho:.3f}) fig, ax plt.subplots(figsize(8, 5)) ax.scatter( scatter_df[rating], scatter_df[box_office], alpha0.3, s10, color#8172B2, ) ax.set_yscale(log) ax.set_xlabel(Rating) ax.set_ylabel(Box Office (log)) ax.set_title(fRating vs Box Office (rho{rho:.2f})) plt.show()这里有几个容易踩的坑为什么不用 Pearson 相关系数因为票房严重右偏评分是离散值两个变量都不满足正态性假设Pearson 对离群点极其敏感一个超级大片就能把相关系数从 0.2 拉到 0.6。Spearman 基于秩次计算更稳健。alpha0.3和s10是散点图的两个关键渲染参数。数据量大时不调透明度会出现严重的重叠绘制图片一片黑完全看不出密度差异。s10是点的大小可视化成最终大图时要相对缩小。set_yscale(log)让票房在纵向拉开。注意这张图不适合再加线性回归线因为对数坐标下回归线是直线但真实关系未必是线性的加了容易让读者误解。实际分析结果通常是 rho 在 0.1 到 0.25 之间弱正相关。这说明评分高的电影票房略高但预测能力非常弱很多高分文艺片票房惨淡大量中低分商业片票房极高。这个结论本身就是可视化分析的价值——它推翻了“口碑好就一定卖座”的直觉。4.2 用分组聚合与透视表对比类型和导演的“票房中位数”类型分析比单变量更进一步的维度是主创影响。只算导演平均票房容易被一两部爆款带偏更合理的方式是同时看导演的作品数量和票房中位数。director_stats ( df.dropna(subset[director, box_office]) .groupby(director) .agg( movie_count(title, count), median_box(box_office, median), max_box(box_office, max), ) .reset_index() ) # 过滤掉只拍过 1 部电影的导演样本量不足时统计不稳定 director_stats director_stats[director_stats[movie_count] 3] director_stats director_stats.sort_values(median_box, ascendingFalse) print(director_stats.head(10))代码里movie_count 3是一个重要的样本量门槛。只拍过一部电影的导演如果恰好拍了一部爆款会被排在榜单最前面但完全不具代表性。设定一个最低作品数过滤器能避免这类误导。同样中位数比均值更能反映导演的典型表现因为它不受单部极端值影响。制作可视化时可以画一张“作品数 vs. 中位数票房”的散点图用气泡大小代表作品数一眼看出哪些导演是“高产稳定型”哪些是“孤品爆款型”。4.3 类型 年份的堆叠趋势图看整体市场变化# 按年份和类型计算票房总额 year_pivot ( genre_exploded[genre_exploded[year].between(2000, 2023)] .groupby([year, genre_list])[box_office] .sum() .unstack(fill_value0) ) # 取每年票房总额最高的 6 个类型 top_genres year_pivot.sum().nlargest(6).index year_pivot_top year_pivot[top_genres] fig, ax plt.subplots(figsize(12, 5)) year_pivot_top.plot.area(axax, alpha0.7) ax.set_xlabel(Year) ax.set_ylabel(Total Box Office) plt.show()unstack(fill_value0)把类型从行转为列缺失组合填充为 0。这一步不做的话pandas 在绘图时会对缺失值产生断裂堆叠面积图会出现空洞。选择 top N 类型时用的nlargest(6)依据是年度总和这个选择会影响图形可读性类型过多时堆叠图上层被完全遮住6 个类型刚好能看出主次结构。用 area 图展示比例变化时要注意这类图只能表达“总量占比”无法精确读出某个类型的确切数值如果需要精确对比改用百分比堆叠柱状图更合适。5. 图表交付前中文字体、画布尺寸与数据按钮的三件事5.1 matplotlib 中文字体配置避免生成满屏方框中文字体缺失是电影数据可视化最常翻车的地方因为电影名、导演名都是中文。直接给 matplotlibrc 设置font.sans-serif是常见做法但不同系统的字体名不同代码需要具备迁移性。from matplotlib import font_manager # 查找系统中已安装的可支持中文的字体 font_list font_manager.findSystemFonts() cn_candidate [ f for f in font_list if any(k in f.lower() for k in [msyh, simhei, notosanscjk, sourcehansans]) ] if cn_candidate: font_path cn_candidate[0] font_prop font_manager.FontProperties(fnamefont_path) font_manager.fontManager.addfont(font_path) plt.rcParams[font.family] font_prop.get_name() else: # 兜底方案直接指定一个中文字体名失败时用英文标签 plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False这段配置的核心在于axes.unicode_minus必须设为 False否则坐标轴负号会显示成乱码方块。findSystemFonts是在运行时动态探测系统字体比硬编码字体名更可靠换一台服务器跑也不会出现中文全没的情况。字体查找失败时的兜底方案会在标题里出现英文标签但代码不会崩溃这在自动化执行场景里比报错更重要。5.2 画布分辨率与坐标轴范围控制让图放进报告不模糊数据可视化的最终出口通常是博客配图、报告插图或大屏面板。不同场景对画布的要求不同博客配图宽 800 像素足够投屏需要 1920 宽度。但 matplotlib 默认的figsize(6.4, 4.8)和dpi100出图偏小放大后文字会糊。推荐统一设置# 输出前的统一配置块 plt.rcParams[figure.figsize] (12, 6) plt.rcParams[figure.dpi] 150 plt.rcParams[savefig.dpi] 300 plt.rcParams[savefig.bbox] tight这里涉及三个层次的分辨率概念figure.dpi是屏幕显示分辨率设为 150 让 Jupyter 里的图清晰。savefig.dpi是保存文件的分辨率300 dpi 适用于印刷和报告但文件体积会增大如果只发博客150 足够。savefig.bboxtight会自动裁剪掉图表周围的空白边距避免保存后的图片左右留白过多。这个参数唯一的问题是它可能让图片宽度不可控如果有多张图要拼成一张大图关闭此选项并手动设置subplots_adjust更统一。文本标签的显示质量也受字体渲染顺序影响。plt.tight_layout()在每次show()之前调用可以防止坐标轴标题被裁掉但这个函数对bbox_inchestight不生效二者效果叠加时会再次改变画布尺寸。5.3 最终交付文件筛选表与代码组织建议可视化分析完成后交付的不仅是图还有一份可复现的代码路径。建议把所有输出图统一保存到一个output/目录文件名用可读前缀区分数据维度。文件前缀内容数据字段推荐格式dist_单变量分布图票房、时长、评分PNG 150dpirel_关系图评分-票房、年份-票房PNG 300dpicompare_对比图类型、导演、预算区间PDF 矢量版保存所有图表的代码统一写在脚本末尾用plt.savefig循环导出不要手工在 Jupyter 里逐个右键保存。循环导出时文件名不要用图1.png这样的命名而是用英文前缀加上具体维度比如dist_box_office_log.png这样后续做批量二次处理时按前缀分组就会非常省事。最后把清洗前后的行数和缺失值比例输出到文本文件方便回顾数据质量变化。对这些图表做二次加工时可以导入到 Tableau 或 Power BI 里补充交互式筛选但原始图表的静态输出始终是数据分析的基础交付物。本文还有配套的精品资源点击获取