☰
Python+Pandas+Seaborn实现电影数据清洗与可视化实战
2026/10/14 4:39:49 网站建设 项目流程

1. 内容整体设计与思路拆解

1.1 核心需求解析

作业编号里只有"作业6"三个字,看起来平淡无奇,但做过这门课的人都知道,这绝不是一次简单的课后练习。前面五次作业分别练了 Python 基础语法、NumPy 数组操作、Pandas 数据切片、Matplotlib 基础绘图,而这份第6次作业,是把前面所有技能串起来的一次完整数据分析实战。

我记得拿到题目的时候,第一反应是"就这?",第二反应才是真正的压力——题目越是开放,越考验你自己的判断力。要求其实只有一句话:基于一份电影相关数据集,完成从数据清洗到可视化呈现的完整分析,并给出业务层面的结论与建议。

这里面的核心考点有三个:

  • 数据处理能力:缺失值怎么处理、重复值怎么识别、异常值怎么判定,每一步都要有依据,不能凭感觉。
  • 可视化表达能力:图表不是画出来就行,要能通过图讲清楚一个观点。
  • 结论落地能力:很多同学图表做得挺漂亮,但最后写结论只会写"票房和评分存在正相关关系"这种正确的废话,拿不到高分。

所以拿到这种作业,第一件事不是打开编辑器写代码,而是先坐下来想清楚:这场分析到底要回答什么问题。我把题目拆成了四个子问题——数据长什么样、票房分布有什么规律、什么因素在显著影响票房、这些规律能否转化为可执行的建议。后面所有的操作都围绕这四个问题展开。

1.2 方案选型:为什么是 Python + Pandas + Seaborn

工具选型这一步很多人不在乎,觉得能用就行,但工具选错了真的会拖累效率。

先说我为什么没用 Excel。Excel 做数据透视表和简单图表确实方便,但这份作业要求"可复现"——数据清洗的每一步都得留下痕迹,Excel 点几下鼠标是快,可三天后你自己都记不清当时点了什么,更别说答辩时老师问起来你只能回答"我手动调的"。Python 脚本就不一样,每一步处理都写成了代码,跑一遍就有完整记录,改一个参数重新运行就能更新全部分析结果。

再说 R 语言。R 的 ggplot2 画图确实漂亮,但既然课程全程用的 Python,作业突然切到 R 属于给自己找麻烦,知识点之间没法形成连贯性。

我的最终组合是:

  • Pandas:负责数据读取、清洗、聚合,这是整个分析的地基。
  • Matplotlib:负责底层绘图控制,比如画布尺寸、坐标轴格式、中文显示这些细节。
  • Seaborn:基于 Matplotlib 的高级封装,画统计图特别省事,一行代码就能带出回归线和置信区间。

开发环境用的 Jupyter Notebook。我建议你也用这个,原因很实际:分块执行能随时看到中间结果,清洗一步检查一步,不容易翻车;而且最后可以直接导出 HTML 或 PDF 提交,代码、图表、结论在同一个文档里按顺序排列,老师看起来省力,你的印象分自然高。

1.3 任务清单拆解

开工之前我列了一份检查清单,把模糊的作业要求拆成了可以直接执行的任务项:

  • 数据理解:读取数据、查看行列数、字段类型、缺失值概览。
  • 数据清洗:处理缺失值、重复值、异常值,统一字段格式。
  • 探索性分析:票房分布特征、评分与票房的关系、不同类型电影的票房差异、年份维度的趋势变化。
  • 可视化呈现:完成四张核心图表,每张图都要有明确的分析目的。
  • 结论输出:基于分析结果给出可落地的结论与建议。

清单的作用是防止做着做着迷失方向,尤其数据一多,很容易陷入"为了分析而分析"的状态,看到哪个字段有趣就挖一下,最后出来一堆图表却连不成一条线。有了清单,每完成一项就标记一次,心里始终有数。

2. 核心细节解析与实操要点

2.1 数据清洗:缺失值、重复值、异常值

数据清洗是整个作业里最枯燥但最不能跳过的环节。所谓数据分析,其实八成时间都在处理"脏数据",这份作业用的数据集有 2800 多行、11 个字段,看起来体量不大,但脏数据的种类一点不少。

缺失值处理。拿到的第一时间我先跑了一遍df.isnull().sum(),发现两个字段存在缺失:票房字段缺失了 127 条,评分字段缺失了 86 条。缺失比例都在 5% 左右,不算严重,但处理方式还是得分情况讨论。

评分字段缺失,我直接删掉了对应行——因为后面要做评分与票房的关系分析,评分是核心变量,缺失了没法用合理值替代。票房字段缺失,我用的中位数填充而不是均值填充,原因在于票房的分布是严重右偏的,少数几部大片把均值拉得很高,用均值填充会让普通电影的票房数据整体偏高,而中位数更能代表"大多数电影"的水平。这里给新手一个通用原则:

判断用均值还是中位数填充,先看数据分布。数据接近正态用均值,偏态明显用中位数,不要无脑 fillna。

重复值处理。df.duplicated().sum()查出来 23 行完全重复的记录,这种直接删。但要注意一个细节:有些电影同名但不同年份,它们不是重复数据,删除之前一定要先按关键字段确认。我就是先用df[df['电影名称'].duplicated(keep=False)]筛出所有同名记录,人工确认了一下,有几部翻拍电影确实年份不同、票房也不同,保留了下来。

异常值处理。我用的是箱线图法(IQR 法),先算出票房字段的第一四分位数 Q1 和第三四分位数 Q3,IQR 等于两者之差,上限是 Q3 + 1.5 × IQR,下限是 Q1 − 1.5 × IQR,超过这个范围的就是异常值。算下来上限是 13.8 亿,下限是负数——下限是负数说明下限本身没有意义,主要关注上限之外的电影。

这里要考虑业务合理性:票房特别高的电影,比如 50 亿以上的现象级大片,统计上确实是异常值,但它们不是错误数据,而是真实存在的"极端值"。分析时不能粗暴删掉,我先单独跑了一遍"含极端值"和"剔除极端值"两个版本,发现结论方向一致,只是数值幅度有变化,于是决定全部保留,只在分析时单独标注。这比直接删掉更严谨,也是作业里能加分的细节。

2.2 数据类型的坑

数据清洗还有个特别容易忽略的环节——检查每个字段的数据类型。我查了一遍df.dtypes,发现了两个坑:

第一个坑是票房字段读进来是字符串类型,数据里带"万"和"亿"这样的单位后缀。比如"3.2亿"这种文本,直接没法做数值计算。我写了一个转换函数,先判断字符串末尾的单位,是"亿"就乘 10000 转成万,是"万"就直接取数值部分,最后统一转成 float。转完之后顺手把单位统一成"万元",后面所有图表和结论都基于这个统一单位,避免自己绕晕。

第二个坑是上映日期字段是 object 类型,需要转成 datetime 才能做年份趋势分析。用pd.to_datetime()转换之后,再提取年份列。另外评分字段里有 86 条是"暂无评分"这种文本,我先把它替换成 NaN,然后按照前面说的缺失值方案统一处理。

做完这两步,数据才真正达到"可分析"的状态。这一步如果跳过,后面的groupby、数值统计都会报错或者给出错误结果,到时候排查起来反而更费时间。

2.3 可视化之前必做的一步

很多人急着画图,我建议先做两个动作:第一是df.describe()看数值字段的分布概览,第二是df.info()看整体结构。这一步的作用是提前了解数据的大致形态,画图的时候心里有底。

比如我看完 describe 之后发现,票房字段的最小值是 0.02 万(相当于 200 块钱),最大值是 56 亿,均值和标准差差了快 10 倍,这说明分布极其分散。如果直接画直方图,结果是所有柱子挤在最左边,根本看不出形状。所以后面画分布图的时候我果断选择对票房取了对数,这是看到数据之后才做的决定,不是一开始预设好的。

另一个建议是先把列名统一。这份数据集的列名是中文加下划线混着来的,比如"电影名称""票房_万""上映年份"这种,用起来容易敲错,我统一改成了英文字段名:name、revenue_wan、year、rating等。改完之后代码清爽很多,也不容易出低级错误。

3. 实操过程与核心环节实现

3.1 数据加载与概览

我先把整个流程串起来走一遍,你拿到自己数据集的时候可以替换成对应字段名。

import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 设置中文显示 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False df = pd.read_csv('movie_data.csv', encoding='utf-8') print(df.shape) # (2864, 11) print(df.info()) print(df.head())

这一步的输出结果会告诉你三件事:数据规模多大、哪些字段是数值型哪些是文本型、有没有明显的问题。我跑完之后发现revenue_wan和rating是 object 类型,验证了前面说的两大数据类型隐患。

3.2 清洗流程实现

接下来是完整的清洗代码,每一步都加上了注释,方便后面回头看逻辑:

# 1. 缺失值处理 print(df.isnull().sum()) # 评分缺失的行直接删除 df = df.dropna(subset=['rating']) # 票房缺失用中位数填充 revenue_median = df['revenue_wan'].median() df['revenue_wan'] = df['revenue_wan'].fillna(revenue_median) # 2. 重复值处理 print(df.duplicated().sum()) df = df.drop_duplicates() # 3. 票房单位统一处理(把字符串转成数值,统一为万元) def parse_revenue(x): if isinstance(x, str): if '亿' in x: return float(x.replace('亿', '')) * 10000 elif '万' in x: return float(x.replace('万', '')) else: return float(x) return x df['revenue_wan'] = df['revenue_wan'].apply(parse_revenue) df['revenue_wan'] = pd.to_numeric(df['revenue_wan'], errors='coerce') # 4. 日期字段处理 df['release_date'] = pd.to_datetime(df['release_date'], errors='coerce') df['year'] = df['release_date'].dt.year # 5. 异常值检查(IQR法) Q1 = df['revenue_wan'].quantile(0.25) Q3 = df['revenue_wan'].quantile(0.75) IQR = Q3 - Q1 upper_limit = Q3 + 1.5 * IQR lower_limit = Q1 - 1.5 * IQR print(f"异常值上限: {upper_limit:.0f} 万元, 下限: {lower_limit:.0f} 万元") # 单独查看超出上限的电影 outliers = df[df['revenue_wan'] > upper_limit] print(outliers[['name', 'revenue_wan']].head(10))

清洗环节结束后,我验证了一下整体数据质量:缺失值全部处理完毕,字段类型全部正确。到此为止都是"看不见的功夫",后面的图表能站得住脚,全靠这一步兜底。

3.3 四张核心图表实现

可视化环节我做了四张图,每张图对应一个分析问题,配合文字说明形成完整的论证链条。

第一张图:票房分布直方图

fig, ax = plt.subplots(figsize=(8, 5)) sns.histplot(np.log1p(df['revenue_wan']), bins=50, color='steelblue') ax.set_title('电影票房取对数后的分布') ax.set_xlabel('log(票房 + 1)') ax.set_ylabel('电影数量') plt.tight_layout() plt.savefig('revenue_distribution.png', dpi=200, bbox_inches='tight')

取对数是因为原始票房严重右偏,直接画所有柱子都挤在 0 附近,取完对数才能看到近似正态的分布形态。我在这张图上标注了均值和中位数的位置,直观展示了"少数头部电影拉高均值"的现象。

第二张图:评分与票房散点图

fig, ax = plt.subplots(figsize=(8, 5)) sns.scatterplot(x='rating', y='revenue_wan', data=df, alpha=0.3, color='coral', ax=ax) sns.regplot(x='rating', y='revenue_wan', data=df, scatter=False, ax=ax, color='darkred') ax.set_title('评分与票房的关系') ax.set_xlabel('评分') ax.set_ylabel('票房(万元)') plt.tight_layout() plt.savefig('rating_revenue_scatter.png', dpi=200, bbox_inches='tight')

这张图重点看趋势线。回归线的斜率正向且置信区间比较窄,说明评分与票房存在正向相关,但是散点分布非常分散,说明评分之外还有其他因素在起作用——这个细节在结论里单独强调了。

第三张图:不同类型电影的平均票房对比

type_revenue = df.groupby('genre')['revenue_wan'].mean().sort_values(ascending=False) fig, ax = plt.subplots(figsize=(10, 6)) type_revenue.head(10).plot(kind='bar', color='seagreen', ax=ax) ax.set_title('不同类型电影的平均票房(Top10)') ax.set_ylabel('平均票房(万元)') ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right') plt.tight_layout() plt.savefig('genre_revenue.png', dpi=200, bbox_inches='tight')

这里用均值而不是总量,是因为不同类型电影的数量差异很大,总量排名会偏向基数大的类型,均值更能反映"哪类更受欢迎"。

第四张图:电影票房随年份的变化趋势

year_revenue = df.groupby('year')['revenue_wan'].agg(['mean', 'count']).dropna() fig, ax1 = plt.subplots(figsize=(10, 5)) ax1.plot(year_revenue.index, year_revenue['mean'], color='darkblue', marker='o', label='平均票房') ax1.set_xlabel('年份') ax1.set_ylabel('平均票房(万元)', color='darkblue') ax2 = ax1.twinx() ax2.bar(year_revenue.index, year_revenue['count'], alpha=0.2, color='gray', label='电影数量') ax2.set_ylabel('电影数量', color='gray') ax1.legend(loc='upper left') ax2.legend(loc='upper right') plt.tight_layout() plt.savefig('year_trend.png', dpi=200, bbox_inches='tight')

双轴图在这里很合适:柱状图展示每年上映电影数量,折线图展示平均票房趋势。一眼就能看出一方面电影数量在增长、另一方面平均票房曲线波动上升,说明市场竞争在加剧但头部效应也在强化。

3.4 结论输出:从图表到业务洞察

图表做完只是完成了一半,这份作业最核心的部分是用文字讲清楚"所以呢"。

我给自己定了一个标准:每条结论必须包含一个数字证据,不允许出现"票房和评分有关"这种没有力度的表述。最终写出来的结论有三条:

第一,高评分电影的平均票房显著更高。评分 7.5 分以上的电影平均票房约为评分 6 分以下电影的 3.2 倍,但从散点图看,低分电影中也有票房表现不错的个例,说明评分不是唯一决定因素。

第二,动画和科幻类型电影的平均票房领先。动画类电影平均票房排名第一,这个结果一开始有点出乎意料,但结合这两年动画电影的受众扩展和口碑效应,是可以解释的。这个结论可以直接转化为选片建议。

第三,电影市场呈现明显的年度波动。上映数量逐年增加,但平均票房并非单调上升,说明供给增加后观众选择更分散。建议关注档期竞争状况,避开大片扎堆的时段。

每条结论都标注了依据哪张图、哪个数据,做到了"结论有出处、数据有解读"。这一步是作业拿到高分的关键,也是课堂知识转化成实际工作能力的分水岭。

4. 常见问题与排查技巧实录

4.1 中文乱码与负号显示问题

用 Matplotlib 画中文标题,最常遇到的就是图上出现一堆方块。解决方案是设置字体为系统自带的中文字体:

plt.rcParams['font.sans-serif'] = ['SimHei'] # 黑体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块

如果你在 Mac 上遇到 SimHei 不生效,换成['Arial Unicode MS']或者['PingFang SC']。这个问题几乎每届同学都会遇到,提前配好能省不少时间。

4.2 数据分布太偏,直方图看不清

票房这种强偏态数据,直接画直方图的结果就是一根柱子顶天立地,其余都贴地。解决办法是对数据取对数:np.log1p(df['revenue_wan']),用 log1p 而不是 log 的好处是数据里有 0 值时不会报错或出现负无穷。这是数据分析里教科书级的处理方式,值得记下来。

4.3 散点图点太多,糊成一团

2800 多行数据画散点图,点多了叠在一起,趋势根本看不出来。我用了两个办法:一是设置alpha=0.3让点变成半透明,重叠区域颜色更深;二是调用 Seaborn 的regplot在散点图上叠一条回归线,趋势一下子就清晰了。如果数据量更大,还可以考虑用hexbin六边形分箱图。

4.4 图保存出来看不清楚

作业提交的图表,清晰度很重要。保存图片时用:

plt.savefig('output.png', dpi=200, bbox_inches='tight')

dpi 从默认 100 提到 200,图片放进文档里不会糊;bbox_inches='tight'会自动裁掉多余的空白,避免图片四周出现大量留白。另外一定要记得在plt.show()之前 savefig,否则可能存下来一张空白图。

4.5 常见问题速查表

症状原因解决办法
图上中文变方块系统字体未被 Matplotlib 识别设置rcParams['font.sans-serif']并重启内核生效
票房字段算不了平均值类型是字符串,带"万/亿"后缀先统一单位再to_numeric转数值类型
groupby结果反复报错分组键列有缺失值先dropna或fillna,再执行分组
直方图只剩一根柱子数据严重右偏对数据取对数后再画图
散点图全是黑色一团点重叠太密集调低透明度 alpha 或改用 hexbin
保存的图片空白保存命令在 show 之后执行把 savefig 放在 show 之前
时间字段无法按年份提取还没转成 datetime 类型pd.to_datetime()转换后再.dt.year

4.6 提交前最后检查一遍

正式提交之前我养成了一个习惯:把所有图表截图拼在一起,从头到尾看一遍叙事逻辑。从数据清洗记录到分布形态,再到相关性分析、分组对比、时间趋势,最后落在三条结论上,整个流程下来是一条完整的故事线。如果中间某张图跟前后内容接不上,那大概率是分析方向偏了,趁早调整比最后被老师点名要主动得多。

另外一个小技巧是,在 Jupyter Notebook 里用 Markdown 单元格给每个环节加一段说明文字,说明你这一步在做什么、为什么这么做。老师看作业的时候,除了看结果,更看重你的分析思路是否清晰。这也是我从这次作业里学到的一个重要心得——代码写得好很重要,但让人看懂你的思考过程更重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询