
简介面向计算机相关专业正在做毕业设计的学生以及需要项目实战练习的学习者基于 Python 的电影数据可视化与票房影响因素分析预测完整项目可以帮助读者快速搭建从数据采集、存储分析到结果呈现的毕设方案。压缩包共 38 个文件、约 5.18MB包含 Python 源码、3 个 Jupyter Notebook 分析文档、SQL 数据库文件、PDF 文档说明以及 24 张可视化结果图类型覆盖数据抓取、数据库设计、Pandas/SQL 多维度可视化、票房预测等多个模块目录结构清晰便于逐环节学习。项目内置数据库脚本和核心功能封装预测部分提供 Notebook 实验流程结合结果图表和 README 说明能够帮助理解数据清洗、特征探索、模型构建与结果复现的整体思路。目前已有 425 人学习下载适合作为毕业设计、课程设计或期末大作业的参考模板下载后稍作调试即可运行节省从零搭建项目的时间。1. 为什么这份源码值得你拆开看数据、可视化、预测一条线能回答什么拿到一份「基于python的电影数据可视化及票房影响因素分析与预测源码文档说明.zip」我一般不会急着解压跑 demo而是先想清楚它到底解决什么问题你手里有一批电影数据——预算、评分、类型、上映日期、全球票房——怎么把它们整理成一张干净的表画成让业务看得懂的图回答「哪些特征真正影响票房」最后对新片票房做预判。这就是一整套数据分析闭环。适合正在学 python 数据分析和机器学习的人也适合想把同一套方法论迁移到电商、内容付费、农产品价格预测这些场景的从业者。它最值钱的不是某张图而是「数据→特征→建模→结论」每个环节都有可复现的代码和判断依据。2. 数据清洗与特征工程决定票房预测上限的第一道关口2.1 定口径比写代码更早先搞清楚「票房」到底指哪个票房这类源码包里的原始数据常见是公开电影数据接口采集后落成的 csv或者直接给一份整理好的汇总表。字段一般包括 id、title、budget、revenue、runtime、genres、release_date、vote_average、vote_count、popularity、production_companies。你确实可以用 pandas 读进来瞄一眼但我的习惯是动手前先开一份字段说明回答三个问题。第一revenue 到底是全球累计票房还是单一市场票房不同数据源口径差异很大有的给全球总收有的只给北美票房混在一起做回归系数会被系统性带偏。第二币种统一了吗美元和人民币混在一起量纲是乱的后面画图和建模都会出问题。第三budget 字段里的 0 是「预算真的是 0」还是「未公开」绝大多数情况下是后者。把 0 当真实预算预算与票房的相关性会被严重稀释模型也更难收敛。口径确定后再定目标列。常见做法是新建一列 log_revenue log1p(revenue) 作为回归目标因为票房分布长尾严重直接预测原始值会被几部超级大片拉高误差取对数后分布更接近正态模型拟合更稳评估指标也更有业务解释力。不要小看这一步团队里一半以上的「预测不准」问题最后都能回溯到目标和特征的口径没对齐。2.2 pandas 清洗脚本重复值、缺失值、日期解析一次做完下面这个脚本是这类项目里最常见的第一步保存为 data_clean.py输入原始 csv输出清洗后的 clean_movies.csv。它做的事情是「标记缺失」而不是「删光缺失」这两者在后面的建模阶段会导向完全不同的结果。import pandas as pd import numpy as np def clean_movie_data(input_path, output_path): df pd.read_csv(input_path) # 关键列统一成数值型非数值转 NaN避免 12,345 这种带千分位的字符串捣乱 for col in [budget, revenue, vote_average, vote_count]: df[col] pd.to_numeric(df[col], errorscoerce) # 去掉完全重复的行同一个 id 和标题只保留第一条 df df.drop_duplicates(subset[id, title], keepfirst) # 日期解析errorscoerce 让坏日期变成 NaT后续统一过滤 df[release_date] pd.to_datetime(df[release_date], errorscoerce) df df.dropna(subset[release_date]) # 预算为 0 或缺失先标记出来不直接删后面按类型均值填充 df[budget_missing] (df[budget] 0) | df[budget].isna() # 票房目标列revenue 0 的样本单独标记建模时可剔除或加权 df[revenue_missing] (df[revenue] 0) | df[revenue].isna() # 上映年份与月份供后续档期特征使用 df[release_year] df[release_date].dt.year df[release_month] df[release_date].dt.month df.to_csv(output_path, indexFalse) print(f清洗完成剩余 {len(df)} 条记录)逻辑说明这段代码的核心是「不删缺失而是标记缺失」。budget_missing 和 revenue_missing 两个布尔列既能保留样本量又让模型有机会学到「未公开预算的电影票房往往也难公开」这个现实偏差。很多人一上来就删掉所有缺失行结果训练集只剩下头部大片分布完全变形后面的可视化、回归、预测全在扭曲的数据上跑。参数说明errorscoerce 是关键参数它把无法解析的字符串转成 NaN 而不是抛异常中断脚本dropna(subset[release_date]) 过滤掉日期坏值因为后面提取月份和档期都依赖它。如果你发现 read_csv 阶段中文乱码把 encoding 参数改成 utf-8-sig 或 gbk国内导出的 csv 里 GBK 编码很常见pandas 默认的 utf-8 读不了。提示洗数据时千万不要急着删行先用 info() 和 isna().sum() 看看每一列的缺失分布再决定「剔除、填充、标记」三种策略各用在哪一列。2.3 特征工程档期不等于月份genres 必须拆成多标签清洗只是第一步决定模型上限的是特征设计。这个项目里最值得做的三个改造是月份档期化、类型多标签化、运行时分段。# 继续在清洗后的 df 上操作 month_map {1: 淡季, 2: 淡季, 3: 淡季, 4: 淡季, 5: 淡季, 6: 年中档, 7: 暑期档, 8: 暑期档, 9: 淡季, 10: 淡季, 11: 淡季, 12: 贺岁档} df[season] df[release_month].map(month_map) # genres 是 Action|Adventure|SciFi 这样的长字符串先拆开再转多标签 df[genres_list] df[genres].fillna().str.split(|) all_genres [Action, Adventure, Animation, Comedy, Crime, Drama, Fantasy, Horror, Romance, SciFi, Thriller] for g in all_genres: df[fgenre_{g}] df[genres_list].apply(lambda x: 1 if g in x else 0) # 运行时切分区间长片和短片对票房的影响是非线性的 df[runtime_bin] pd.cut(df[runtime], bins[0, 90, 120, 150, 300], labels[短, 中, 长, 超长])逻辑说明档期是中文语境里绕不开的特征但直接用月份当数值特征很别扭月份 7 和 12 的档期效应完全不同中间隔着 5 个月用「4 月比 7 月档期差 3 个月」这种数字关系表达不了票房的跳跃。用类别映射成 season 更合理。genres 用 one-hot 展开成 11 列是因为一部电影往往同时属于多个类型「动作科幻」和「动作爱情」的票房逻辑差别很大多标签才能保留这种组合信息。参数说明pd.cut 的 bins 边界要结合样本分布调整。我这里按常见片长区间切如果你的数据集中在 100 分钟上下建议把边界改成 [0, 90, 110, 130, 300] 让每个桶样本更均匀。档期映射表也要做本地化春节档在公历 1 到 2 月间漂移如果数据跨多个年份最好按当年的农历正月初一日期单独标记这一步对国内市场数据的预测提升往往比换模型还明显。3. 数据可视化层用 pyecharts 把票房规律转成可交互页面3.1 为什么是 pyecharts给业务看的图交互比好看更重要探索阶段我用 matplotlib 和 pandas 自带的 plot 就能快速看分布但交付阶段我会换成 pyecharts。原因很直接业务方看图表时几乎必然要问「这个点是谁」「某个区间的电影有多少」pyecharts 的悬停提示、数据缩放、拖拽、图例开关都是默认支持matplotlib 要写不少事件绑定代码才能做到一部分体验。另外这类项目如果带了 Flask 展示层pyecharts 可以把图表对象直接渲染成 HTML 片段塞进网页模板和 flaskecharts 这套经典组合天然匹配。它还支持导出 png领导开会要截图时不用重新画图。你解压后看到的可视化代码大概率就是按「先算聚合结果再渲染图表」的顺序组织的和下面这节的写法基本一致。3.2 从清洗结果到三张核心图柱状图、散点图、趋势图可视化不是乱画我的习惯是复用上一章的聚合结果各类型平均票房用柱状图预算与票房关系用散点图年度票房趋势用折线图。这样每一张图背后都有清晰的业务问题而不是单纯把字段拖进去。from pyecharts.charts import Bar, Scatter, Line from pyecharts import options as opts import pandas as pd df pd.read_csv(clean_movies.csv) # 图1各类型平均票房一部电影可能属于多个类型先透视再求均值 type_cols [c for c in df.columns if c.startswith(genre_)] type_revenue [] for c in type_cols: sub df[df[c] 1] type_revenue.append((c.replace(genre_, ), sub[revenue].mean())) bar ( Bar() .add_xaxis([t for t, _ in type_revenue]) .add_yaxis(平均票房, [v for _, v in type_revenue]) .set_global_opts( title_optsopts.TitleOpts(title各类型平均票房), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate-30)), ) ) bar.render(avg_revenue_by_genre.html) # 图2预算 vs 票房散点去掉 budget0 的噪声样本再看 scatter_data df[df[budget] 0].sample(2000, random_state42) scatter ( Scatter() .add_xaxis(scatter_data[budget].tolist()) .add_yaxis(票房, scatter_data[revenue].tolist()) .set_global_opts( title_optsopts.TitleOpts(title预算与票房散点图), xaxis_optsopts.AxisOpts(name预算, type_log), yaxis_optsopts.AxisOpts(name票房, type_log), ) ) scatter.render(budget_revenue_scatter.html)逻辑说明散点图这里故意对两个坐标轴用了对数刻度。票房和预算都是长尾分布线性坐标下 90% 的点挤在左下角只有几部大片飞到右上完全看不出相关趋势取对数后数据铺开相关性才直观可见。抽样 2000 个点是为了避免上万帧同时渲染导致浏览器卡顿不是精度损失散点图本身表达的是分布形态。参数说明add_yaxis 的数据必须传 python list不能直接传 pandas Seriespyecharts 2.x 会对类型做严格校验传 Series 会在运行时报错或画成空图。type_log 让坐标轴自动做对数刻度但注意数据里不能有 0 或负数所以要先过滤 budget 0。rotate-30 是防止类型名重叠类型多的项目里几乎必开。3.3 Flask 包一层把图表以网页形式交付给业务单张 html 文件拿着不方便也不像一个完整的交付物。常见做法是用 Flask 开一个本地服务把图表和关键结论挂在一个统一页面上。下面是这类项目里最小可用的 Flask 路由写法。from flask import Flask, render_template from pyecharts.charts import Bar from pyecharts import options as opts app Flask(__name__) app.route(/) def index(): bar ( Bar() .add_xaxis([Action, Drama, Comedy, Horror]) .add_yaxis(平均票房, [8000, 4000, 3500, 1800]) .set_global_opts(title_optsopts.TitleOpts(title各类型平均票房示例)) ) # 关键用 render_embed 把图表转成内嵌 HTML而不是生成独立文件 chart_html bar.render_embed() return render_template(dashboard.html, chart_htmlchart_html) if __name__ __main__: app.run(debugFalse, host127.0.0.1, port8000)逻辑说明render_embed() 是 pyecharts 2.x 的标准做法它把图表需要的 echarts 库和配置一起打包进 html不依赖外网 CDN内网环境也能正常打开。注意浏览器每次刷新页面都会重新执行一次图表初始化如果页面上挂了十几个图表会比较重后面的排查章节会专门讲怎么优化。参数说明debugFalse 是给业务方看页面时的基本素养。debugTrue 会把错误堆栈直接暴露在页面上而且每次请求额外加载调试器内网展示完全没必要。host 用 127.0.0.1 只允许本机访问想给同组同事看就改成 0.0.0.0但要确认端口没被占用。port 选 8000 是常见默认值如果被占用换 8080 或 5000 都行。4. 票房影响因素分析与预测建模从相关系数到 LSTM 的一条完整链路4.1 相关性分析先做相关矩阵但别急着删特征先跑相关矩阵找异常再做回归。这里要强调一句相关系数高不代表因果相关系数低也不代表没价值。预算和票房在大样本里通常有稳定的正相关但预算本身是投入不是「可预判信号」上映前你只知道预算规模不知道观众买不买账。评分和票房也有正相关但评分有明显的内生性——高分往往伴随高关注而且评价数据滞后于上映放进预测模型要非常小心。import pandas as pd import numpy as np df pd.read_csv(clean_movies.csv) # 挑数值型特征看相关矩阵预算缺失标记列也保留防止丢失信息 num_cols [budget, vote_average, vote_count, runtime, release_year, revenue] corr df[num_cols].corr(methodspearman) print(corr[revenue].sort_values(ascendingFalse))逻辑说明用 spearman 而不是默认的 pearson是因为票房数据长尾严重且包含离群大片spearman 看的是排序相关性对单调关系更鲁棒不容易被几个 outliers 带飞。相关矩阵在这里的作用不是「筛特征」而是发现异常高相关的字段对比如 vote_count 和 revenue 相关系数如果超过 0.8你就要警惕两者是否在定义上重叠——很多数据集里 vote_count 本身就是热度指标间接编码了票房规模。参数说明corr(methodspearman) 支持 spearman、pearson、kendall 三种探索阶段优先 spearman。如果你后续要做线性回归可以同时用 statsmodels 算一下 VIF 方差膨胀因子VIF 超过 10 的变量组合再做取舍这一步能避免回归系数符号和预期相反这种尴尬事。4.2 多元线性回归与随机森林基准切分数据时最容易埋雷的地方建模前的切分有一个铁律所有特征必须在上映当天或之前可得。release_date 的滞后特征错位是这个项目里「预测结果好得离谱」的最常见原因不是模型厉害是信息泄漏。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score import numpy as np # 特征矩阵只保留上映前或上映当天可知的信息 feature_cols [budget, vote_average, vote_count, runtime, release_year, release_month] \ [c for c in df.columns if c.startswith(genre_)] df_model df.dropna(subset[revenue]).copy() df_model df_model[df_model[revenue] 0].copy() X df_model[feature_cols].fillna(0) y np.log1p(df_model[revenue]) # 对票房取对数缩小长尾 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) lr LinearRegression() lr.fit(X_train, y_train) y_pred_lr lr.predict(X_test) print(LinearRegression R2:, r2_score(y_test, y_pred_lr)) print(LinearRegression MAE:, mean_absolute_error(y_test, y_pred_lr)) rf RandomForestRegressor(n_estimators300, max_depth12, random_state42) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) print(RandomForest R2:, r2_score(y_test, y_pred_rf)) print(RandomForest MAE:, mean_absolute_error(y_test, y_pred_rf))逻辑说明特征列表里没有评论数、没有首周票房、没有获奖情况因为这些信息在上映当天拿不到。放进模型就会数据泄漏回测时测试集特别好看真到了「明天上映、今天给个预判」的现实场景直接崩。对票房取对数再训练评估指标都在 log 空间汇报时再 expm1 还原成原始币种。随机森林在这里的调参思路是树的数量给足深度控制住过拟合基本靠 max_depth 和 min_samples_leaf 来管。参数说明train_test_split 的 random_state 必须固定否则每次跑结果都不一样没法做版本对比test_size0.2 是默认习惯样本只有几千条时可以提高到 0.25 或 0.3。fillna(0) 只对数值特征做预算缺失已经用 budget_missing 布尔列标记过填 0 相当于隐式告诉模型这个样本预算未知而不是预算真为 0。两个模型都看 R2 和 MAER2 看整体解释力MAE 看预测误差绝对值业务汇报时 MAE 说人话。4.3 时序化票房预测与 LSTM窗口怎么构造参数怎么调如果你手上的数据不是每部电影一行而是「每部电影每天一条票房记录」那就具备做时序预测的基础。很多电商、媒体项目会在这一步套上 LSTM原因是票房走势有明显的时序依赖首周表现与最终票房之间存在非线性滞后关系。年度汇总面板做 LSTM 意义不大我一般把它定位成「票房走势预测」而不是「单字段回归」。from keras.models import Sequential from keras.layers import LSTM, Dense from keras.callbacks import EarlyStopping from sklearn.preprocessing import MinMaxScaler import numpy as np # 构造滑动窗口用过去 lookback 天的票房预测下一天 def make_windows(data, lookback10): X, y [], [] for i in range(len(data) - lookback - 1): X.append(data[i:ilookback]) y.append(data[ilookback]) return np.array(X), np.array(y) # 归一化只 fit 训练集避免测试集信息泄漏进 scaler scaler MinMaxScaler(feature_range(0, 1)) series np.log1p(df[df[movie_id] 1].sort_values(day)[revenue].values) lookback 10 X, y make_windows(series, lookback) split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] X_train X_train.reshape(X_train.shape[0], lookback, 1) X_test X_test.reshape(X_test.shape[0], lookback, 1) model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(lookback, 1))) model.add(LSTM(32)) model.add(Dense(1)) model.compile(optimizeradam, lossmse) early_stop EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue) model.fit(X_train, y_train, epochs50, batch_size32, validation_split0.1, callbacks[early_stop], verbose0)逻辑说明make_windows 生成的样本是重叠的所以时间序列不能随机切分必须按时间顺序切这里用前 80% 训练、后 20% 测试模拟「用历史预测未来」的真实场景。归一化只 fit 训练序列这是时序模型最容易犯的错后面排查章节单独展开。LSTM 第一层 return_sequencesTrue 保证第二个 LSTM 层能接到完整的序列输出最后一层 Dense(1) 输出单点值。参数说明lookback 是敏感参数。票房数据一般用 7 或 10对应一周到两周的走势窗口。窗口过小模型捕捉不到趋势窗口过大训练变慢且容易把注意力摊薄。epochs50 配合 EarlyStopping 的 patience5意思是「最多跑 50 轮连续 5 轮验证损失不降就停」既防过拟合又不浪费算力。LSTM 单元数选 64 再接 32对几千条样本的序列任务足够往上加层数收益不明显反而更容易过拟合。LSTM 调参在黑盒里有点玄学成分所以务必和上一节的随机森林结果对照不要默认 LSTM 一定更好。5. 票房数据项目的常见坑与排查现象、原因、解决5.1 数据与特征环节的三个高发问题坑一票房为 0 的记录一大片模型预测全是负数或极端值。现象revenue 字段里有几千条是 0清洗时没处理训练出的回归模型在测试集上 R2 很低预测值还出现大量负数业务方看了直接摇头。原因源数据里的 0 绝大多数是「未公开或未统计」不是真实票房为 0。把缺失当真实值模型看到的 y 分布被强行拉出一个 0 的尖峰回归斜率被严重扭曲。这类数据往往还伴随 budget 同时为 0属于「信息缺失对」而不是「真实低成本影片」。解决按第 2 章的脚本先统计两个比例——票房为 0 的样本占比、预算为 0 的样本占比。样本量超过 5000 条就剔除这部门样本单独跑一版对比样本量不足就用同类型、同档期的正常样本均值填充并把「是否缺失」作为特征传进模型。核心原则是不要让模型用 0 去学「真实低收入」的语义。提示0 在票房数据里几乎可以当「未公开」的同义词对待凡是涉及 budget 和 revenue 的清洗优先考虑标记而不是直接删行。坑二日期解析成功但月份错位档期特征整体失真。现象release_date 能正常解析成 datetime但画月份分布图时发现3 月上映的电影数量异常集中数据量明显不符合行业常识。原因to_datetime 没指定 format 时采用自动推断对像 03/04/2022 这种月日不明的日期存在歧义一部分行被当成 3 月 4 日另一部分被当成 4 月 3 日。这种静默错位在特征工程阶段几乎发现不了到可视化阶段才暴露出来如果跳过可视化直接建模错误会一直潜伏下去。解决统一指定 format%d/%m/%Y 或 %Y-%m-%d二选一并写进清洗脚本。解析后立刻提取 release_year 和 release_month再加一个校验步骤统计每个月样本量是否符合常识比如贺岁档、暑期档占比偏高就是正常信号突然某个月占比异常就要回头查格式。坑三把上映后才产生的数据当特征预测得分虚高得像假项目。现象模型 R2 冲到 0.9 以上文件里跑给你看的人很兴奋但拿给一部明天上映的新片预测误差大得离谱。原因典型特征泄漏。最常见的泄漏源是首周末票房、上映后评论数、获奖情况、甚至「上映后一周口碑评分」跑进了特征矩阵。模型学到的是「拿结果预测结果」的捷径而不是真的理解了票房的驱动因素。解决建模前给每个特征列一张「数据可得时间」表上映当天能拿到的留下拿不到的一律不进模型。最稳妥的组合是上映日期、预算、类型、导演和主演如果数据里有、运行时。之后再单独做时间序列版本去预测首周末之后逐步更新的曲线而不是假装首周票房在上映前就知道。5.2 建模与可视化环节的两个高频问题坑四归一化在全部序列上 fitLSTM 测试集表现虚高换数据就翻车。现象时序模型训练损失很小测试集 MAE 也很低但换一段真实新数据后模型输出几乎全是同一个值或者预测曲线比真实曲线整体滞后。原因构造窗口前用 MinMaxScaler 对整个序列做了 fit等于把测试段的取值范围提前暴露给了 scaler。LSTM 对归一化边界极其敏感测试段的值被缩放进了训练段的区间模型在测试时「见过」了不该见的上下限信息。这个坑在老代码里几乎必踩。解决严格按时间切出训练序列和测试序列再分别执行 scaler.fit_transform 和 scaler.transform。更严谨的做法是只保存训练段的 min 和 max测试段完全用训练段的边界缩放。遇到「预测曲线贴着真实曲线但整体滞后」这个 LSTM 典型现象先检查归一化再检查窗口重叠基本能定位到根因。坑五pyecharts 图表在 Flask 页面里渲染空白或样式错乱。现象单独运行 render() 生成的 html 能正常打开但把同样的图表嵌入 Flask 模板后页面空白浏览器控制台报 Cannot read properties of undefined。原因pyecharts 2.x 里使用 render_embed() 返回的片段时又在外层 html 手工重复引入了 echarts.min.js导致全局变量被覆盖或组件注册失败。另一个常见原因是版本混装比如 pyecharts 2.x 和 echarts 5.x 配合时个别组件比如词云、地图需要额外注册。解决固定一组能协同工作的版本不要混装。用 render_embed() 就不要再在模板里手动加 echarts 的 script 标签。如果页面图表很多改成一次性引入 echarts 基础库再把每个图表的 option 选项通过 json 注入页面用 ECharts 原生初始化方法渲染这样还能顺带解决十几个图表重复初始化导致的页面卡顿。6. 把整套流程做成可复用的「选片评估」脚本封装、验证与迭代习惯这套分析如果只跑一次就扔掉价值打折一大半。我的做法是把它封装成命令行工具让清洗、画图、训练、预测四个环节可以用一条命令独立执行。比如把逻辑拆成 clean.py、features.py、visualize.py、train.py 四个模块再包一个入口脚本python -m movie_insight build --data raw_movies.csv --out ./output python -m movie_insight train --config config.yaml python -m movie_insight predict --movie 新片标题 --date 2025-06-01参数说明build 负责清洗和特征工程产出干净特征表train 读取特征表训练回归模型和时序模型predict 接收一部新片的信息输出预测票房区间。--config 参数让你不用改代码就能调 lookback、test_size、随机种子这些敏感项文档说明里把这三个命令的预期输出写清楚后面任何人接手都能直接跑。验证模型值不值得用我一般和三个基准对比中位数预测、均值预测、上一部同类影片的实际票房。建立一个简单表格记录每个模型在验证集上的 MAE 和 R2不要只跑一次就下结论用 K 折交叉验证看波动模型稳定才敢往上汇报。我自己犯过的最大错误是拿到新数据集先跑模型把 LSTM 调参当成主战场后来才发现字段口径和特征泄漏才是决定项目成败的地方。数据光鲜不代表模型可信每次换数据源都要重新走一遍清洗和口径确认这步偷的懒后面全要加倍还回去。希望帮到你。本文还有配套的精品资源点击获取