ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python电影票房预测实战:数据清洗、可视化与建模避坑指南

Python电影票房预测实战:数据清洗、可视化与建模避坑指南 简介这份资源是面向计算机相关专业学生与项目实战学习者的电影数据可视化及票房影响因素分析与预测完整项目包可直接用于毕业设计、课程设计或期末大作业。项目围绕豆瓣电影数据展开涵盖数据采集、数据库存储、可视化展示与票房预测等环节包含Python脚本、Jupyter Notebook分析文件、SQL建表脚本及说明文档帮助读者理解从数据到结论的完整分析链路。压缩包共38个文件以24张png结果图、6个py源码、3个ipynb分析笔记为主另含pdf说明、sql脚本与md文档整体约5.18MB结构清晰便于按模块查阅。目前已有425人学习下载。读者可获得可直接运行的源码、数据库结构与可视化图表以及票房预测的建模思路与调试经验适合需要快速搭建分析框架、对照复现结果的学习者。1. 电影数据可视化与票房预测一套 Python 源码能跑出什么结论很多人第一次拿到「电影数据可视化及票房影响因素分析与预测」这类项目时第一反应是打开train.csv直接上模型结果 RMSE 高得离谱回头才发现数据里混着上映前就泄露的字段。这个标题对应的其实是一条完整的分析链路用 Python 把电影数据集清洗成可分析的结构用可视化把票房分布、类型、档期、评分之间的关系画出来再基于这些特征做票房回归或分类预测。它适合两类人一类是刚学完 pandas 和 matplotlib、想找一个有业务含义的练手项目另一类是要做课程设计或数据分析报告需要一套能讲清楚「为什么这些因素影响票房」的完整流程。核心不是模型多复杂而是特征工程和可视化能不能把业务逻辑讲通。2. 数据准备与清洗从原始 CSV 到可建模特征表2.1 先搞清楚字段类型和泄露风险电影票房数据集常见的字段包括预算budget、票房revenue、上映日期release_date、时长runtime、评分vote_average、投票数vote_count、类型genres、导演、主演等。拿到数据后第一件事不是画图而是逐列判断三件事这列是数值还是类别、缺失比例多少、它在上映前是否已知。预算、时长、类型、导演、主演、上映档期属于上映前已知可以作为特征。票房本身是预测目标。评分和投票数要特别小心——它们通常是上映后累积的如果数据集里的评分是最终评分那它和票房高度相关放进模型会造成严重泄露线下指标很好看实际预测新片时完全不可用。我一般会把评分和投票数单独拿出来做「事后分析」不放进预测特征。import pandas as pd import numpy as np df pd.read_csv(movies.csv) # 先看整体缺失和类型 print(df.info()) print(df.isnull().mean().sort_values(ascendingFalse).head(15)) # 标记泄露字段预测时排除 leak_cols [vote_average, vote_count, popularity] feature_cols [c for c in df.columns if c not in leak_cols [revenue, id]]这段代码先做体检info()看每列非空数量和 dtypeisnull().mean()按缺失比例排序快速定位哪些列需要处理。leak_cols是我手动标记的泄露字段后面建模时直接从特征里剔除。参数上缺失比例超过 60% 的列一般直接丢弃30% 到 60% 之间的列要考虑是否能用业务逻辑填补。2.2 预算、票房、时长的清洗与对数变换票房和预算这类金额字段经常带货币符号、千分位逗号甚至是字符串。清洗时先用正则去掉非数字字符再转 float。转完之后你会发现票房分布极度右偏——少数大片拿走大部分票房直接做回归会被极端值带偏。常见做法是对票房和预算取对数让分布接近正态。import re def clean_money(x): if pd.isna(x): return np.nan x str(x) x re.sub(r[^\d.], , x) # 去掉 $ , 等符号 return float(x) if x else np.nan df[budget] df[budget].apply(clean_money) df[revenue] df[revenue].apply(clean_money) # 去掉预算或票房为 0 的异常记录 df df[(df[budget] 0) (df[revenue] 0)].copy() # 对数变换缓解右偏 df[log_budget] np.log1p(df[budget]) df[log_revenue] np.log1p(df[revenue])clean_money用正则把非数字字符全部剔除保留小数点和数字。log1p等价于log(1x)好处是当 x 为 0 时不报错。清洗后一定要检查df.shape和df[[budget,revenue]].describe()确认没有负数或极端异常值。如果预算为 0 的记录很多说明数据源本身有问题要么换数据要么把这些行单独标记。2.3 类型、档期、导演等类别特征的编码类型字段通常是Action|Adventure|Fantasy这种多标签形式不能直接 one-hot。常见做法是先按|拆开统计所有出现过的类型然后为每个类型建一个 0/1 列。档期可以从上映日期里提取月份和季度暑期档5-8 月和圣诞档11-12 月通常是票房高峰。导演和主演如果类别太多直接 one-hot 会维度爆炸可以只保留出现次数前 N 的其余归为 Other。# 多标签类型展开 genres df[genres].fillna().str.split(|) all_genres set(g for sub in genres for g in sub if g) for g in all_genres: df[fgenre_{g}] genres.apply(lambda x: 1 if g in x else 0) # 档期特征 df[release_date] pd.to_datetime(df[release_date], errorscoerce) df[release_month] df[release_date].dt.month df[is_summer] df[release_month].isin([5,6,7,8]).astype(int) df[is_holiday] df[release_month].isin([11,12]).astype(int) # 导演频率编码只保留 top 50 top_directors df[director].value_counts().head(50).index df[director_enc] df[director].apply(lambda x: x if x in top_directors else Other) df pd.get_dummies(df, columns[director_enc], prefixdir)类型展开后列数会明显增加但每列都是 0/1适合树模型。档期特征用月份和两个布尔列表达比直接放日期字符串更稳。导演用频率编码加 one-hot只保留 top 50避免几千个导演每人一列。做完这些特征表基本可以进入可视化和建模阶段。3. 可视化用图表把票房影响因素讲清楚3.1 票房分布与对数变换前后的对比可视化第一步是看目标变量分布。原始票房是典型的长尾分布直方图上一根大柱子挤在左边右边拖很长。取对数后再画分布会接近钟形这对回归模型更友好。用 matplotlib 或 seaborn 都可以关键是两张图放一起对比让读者一眼看出变换的必要性。import matplotlib.pyplot as plt import seaborn as sns fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.histplot(df[revenue], bins50, axaxes[0]) axes[0].set_title(原始票房分布) sns.histplot(df[log_revenue], bins50, axaxes[1]) axes[1].set_title(对数票房分布) plt.tight_layout() plt.savefig(revenue_dist.png, dpi150)bins50是经验值数据量几千到几万都适用。dpi150保证保存的图在报告里清晰。如果原始分布里出现明显断崖或异常尖峰要回头检查是否有货币单位混用比如有的行是美元有的是人民币。3.2 预算与票房散点图关系有多强离群点在哪预算和票房的关系是这类项目最核心的一张图。散点图上横轴预算、纵轴票房再画一条回归线能直观看出正相关。但更重要的是看离群点有些低预算高票房的黑马有些高预算低票房的翻车案例。这些点往往对应特定类型或档期是后续分析的好素材。plt.figure(figsize(8, 6)) sns.scatterplot(datadf, xlog_budget, ylog_revenue, alpha0.4) sns.regplot(datadf, xlog_budget, ylog_revenue, scatterFalse, colorred) plt.xlabel(对数预算) plt.ylabel(对数票房) plt.title(预算与票房关系) plt.savefig(budget_revenue.png, dpi150)alpha0.4让重叠点半透明避免大片实心。regplot的scatterFalse只画回归线不重复画点。如果回归线斜率明显小于 1说明预算增加带来的票房增长不是线性的高预算的边际收益在下降。3.3 类型、档期与票房的箱线图对比类型和档期是类别变量用箱线图比较不同组的中位数和离散程度最合适。比如把类型展开后的 0/1 列和log_revenue放一起画每个类型「属于该类型」和「不属于该类型」的票房箱线图。档期同理比较暑期档、圣诞档和其他月份。top_genres [Action, Drama, Comedy, Horror, Animation] fig, axes plt.subplots(1, len(top_genres), figsize(18, 4), shareyTrue) for ax, g in zip(axes, top_genres): sns.boxplot(datadf, xfgenre_{g}, ylog_revenue, axax) ax.set_title(g) ax.set_xlabel(是否属于该类型) plt.tight_layout() plt.savefig(genre_box.png, dpi150)shareyTrue让所有子图共用纵轴方便横向比较。箱线图的中位数线位置比均值更能说明典型票房水平。如果某个类型的箱子整体偏高且窄说明该类型票房稳定且偏高如果箱子很宽说明该类型内部差异大风险高。4. 票房预测建模从线性回归到梯度提升4.1 特征矩阵与训练集划分建模前先把特征列和目标列整理好。特征包括log_budget、runtime、类型 0/1 列、档期列、导演 one-hot 列。目标用log_revenue。划分训练集和测试集时用train_test_splitrandom_state固定保证可复现。如果数据有时间属性更严谨的做法是按上映日期切分用早期电影训练、后期电影测试模拟真实预测场景。from sklearn.model_selection import train_test_split feature_cols [log_budget, runtime, release_month, is_summer, is_holiday] \ [c for c in df.columns if c.startswith(genre_)] \ [c for c in df.columns if c.startswith(dir_)] X df[feature_cols].fillna(0) y df[log_revenue] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42)fillna(0)对 0/1 列安全对数值列要谨慎——如果runtime缺失填 0 会引入错误信号最好填中位数。test_size0.2是常见比例数据量小于 1000 时可以用 0.3。4.2 线性回归基线先看 R² 和残差任何预测任务都应该先跑一个线性回归当基线。它简单、可解释能告诉你特征和目标之间大致是什么关系。跑完看测试集 R² 和残差图如果 R² 很低说明特征不够或关系非线性如果残差有明显模式说明模型欠拟合。from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score, mean_squared_error lr LinearRegression() lr.fit(X_train, y_train) pred_lr lr.predict(X_test) print(R2:, r2_score(y_test, pred_lr)) print(RMSE:, mean_squared_error(y_test, pred_lr, squaredFalse)) # 看系数理解哪些特征影响大 coef pd.Series(lr.coef_, indexX_train.columns).sort_values() print(coef.head(10)) print(coef.tail(10))r2_score越接近 1 越好但票房预测通常能到 0.5 到 0.7 就不错。mean_squared_error(..., squaredFalse)直接输出 RMSE单位和对数票房一致。系数排序能看出log_budget通常是正影响最大的特征某些类型如 Horror可能系数为正但不大说明低预算恐怖片有稳定回报。4.3 梯度提升树处理非线性和特征交互线性回归假设特征独立线性叠加但预算和类型、档期之间可能有交互。梯度提升树如GradientBoostingRegressor或HistGradientBoostingRegressor能自动捕捉非线性和交互通常比线性回归表现好。参数上重点调n_estimators、learning_rate、max_depth。from sklearn.ensemble import HistGradientBoostingRegressor gbr HistGradientBoostingRegressor( max_iter300, learning_rate0.05, max_depth6, random_state42 ) gbr.fit(X_train, y_train) pred_gbr gbr.predict(X_test) print(R2:, r2_score(y_test, pred_gbr)) print(RMSE:, mean_squared_error(y_test, pred_gbr, squaredFalse))max_iter300是迭代次数太少欠拟合太多容易过拟合。learning_rate0.05配合 300 次迭代比较稳如果调到 0.1迭代次数可以降到 150 左右。max_depth6控制每棵树深度票房数据特征不算特别多6 层足够。跑完对比线性回归的 R²如果提升明显说明非线性关系确实存在。4.4 特征重要性与模型解释树模型可以直接输出特征重要性但HistGradientBoostingRegressor不直接提供feature_importances_可以用permutation_importance替代。它通过打乱每个特征看性能下降多少来衡量重要性更可靠。from sklearn.inspection import permutation_importance result permutation_importance( gbr, X_test, y_test, n_repeats10, random_state42) imp pd.Series(result.importances_mean, indexX_test.columns) print(imp.sort_values(ascendingFalse).head(15))n_repeats10表示每个特征重复打乱 10 次取平均次数越多越稳但越慢。重要性排前的一般是log_budget、runtime、几个主要类型和档期。如果某个导演 one-hot 列重要性异常高要检查是不是数据泄露——比如该导演只有一部电影且票房极高。5. 避坑与排查票房预测里最容易翻车的 5 个点5.1 评分和投票数泄露线下 R² 0.9上线全崩现象模型在测试集上 R² 超过 0.9但拿新电影数据预测时误差巨大。原因vote_average和vote_count是上映后累积的和票房同期甚至更晚放进特征等于把答案提前告诉模型。解决建模前明确列出所有「上映后才知道」的字段一律排除。如果要做事后归因分析可以单独建一个不用于预测的分析表。5.2 预算为 0 或缺失填 0 还是填中位数现象清洗后budget列有大量 0模型把 0 预算电影预测成极低票房。原因原始数据里 0 往往代表缺失不是真实预算。解决先把 0 替换为 NaN再按类型分组填中位数或者直接丢弃预算缺失的行。丢弃前统计一下占比超过 20% 就要考虑换数据源。5.3 类型多标签展开后维度爆炸现象类型展开后多了几百列训练极慢模型还过拟合。原因有些冷门类型只出现一两次one-hot 后全是噪声。解决只保留出现次数前 15 到 20 的类型其余归为genre_Other。或者用目标编码但目标编码必须只在训练集上计算再映射到测试集否则泄露。5.4 对数变换后忘记还原RMSE 看着小实际差几千万现象测试集 RMSE 只有 0.3觉得模型很准但还原成原始票房后误差上千万。原因RMSE 是在对数尺度上算的对数差 0.3 对应原始值可能差很多。解决评估时同时输出对数尺度和原始尺度的指标。用np.expm1(pred)还原预测值再算 MAE 或 MAPE更直观。5.5 随机划分导致时间泄露现象随机划分训练测试集R² 很高但按时间切分后骤降。原因随机划分让未来电影进了训练集模型学到了未来才有的模式。解决按release_date排序前 80% 做训练后 20% 做测试。如果数据量够还可以做滚动窗口验证。6. 进阶技巧用 SHAP 解释单部电影的预测结果模型跑通之后最有价值的不是那个 R² 数字而是能回答「为什么这部片子预测票房高/低」。SHAP 是目前解释树模型最实用的工具它能给出每个特征对单条预测的贡献值。安装shap后对HistGradientBoostingRegressor用TreeExplainer即可。import shap explainer shap.TreeExplainer(gbr) shap_values explainer.shap_values(X_test) # 全局特征重要性 shap.summary_plot(shap_values, X_test, plot_typebar) # 单部电影解释 shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])TreeExplainer对树模型计算很快shap_values形状和X_test一致。summary_plot的 bar 图给出全局重要性排序和 permutation importance 互相印证。force_plot展示单条预测哪些特征把预测推高哪些拉低。比如一部电影log_budget很高SHAP 会给正贡献如果它是 Horror 类型可能给负贡献因为恐怖片整体票房偏低。我一般会挑几部典型电影做 SHAP 解释一部高预算大片、一部低预算黑马、一部高预算翻车片。把这三张 force_plot 放在报告里比任何文字都更能说明「票房影响因素」到底怎么起作用。参数上X_test.iloc[0]换成你想解释的那一行索引即可。如果数据量大shap_values计算会占内存可以只取测试集前 500 行做全局图。最后说一个血泪教训我早期做这类项目时花了两天调模型参数R² 从 0.62 提到 0.65但后来发现数据里有一列homepage是否为空空值率 90%填完缺失后模型直接学到了「有主页高票房」这其实是数据收集偏差不是真实因果关系。从那以后我养成了一个习惯任何特征进模型前先问一句「这个字段在上映前真的能拿到吗它的缺失本身是不是一种信号」。希望帮到你。本文还有配套的精品资源点击获取
返回列表