完整的 Python 数据分析案例:电影票房预测

176 阅读6分钟

1.

案例背景

电影行业竞争激烈,准确预测电影票房对于电影制作公司、发行商以及投资方至关重要。通过分析电影的各种特征,如导演、演员阵容、电影类型、上映时间等,构建预测模型来预估电影票房,有助于合理安排制作预算、制定营销策略以及评估投资回报率。

代码实现

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_squared_error, r2_score

# 数据读取
data = pd.read_csv('movie_data.csv')

# 数据探索性分析
print('数据基本信息:')
data.info()

# 查看数据集行数和列数
rows, columns = data.shape

if rows < 1000:
    # 小数据集(行数少于 1000)查看全量数据信息
    print('数据全部内容信息:')
    print(data.to_csv(sep='\t', na_rep='nan'))
else:
    # 大数据集查看数据前几行信息
    print('数据前几行内容信息:')
    print(data.head().to_csv(sep='\t', na_rep='nan'))

# 数据清洗
# 处理缺失值
data['runtime'] = data['runtime'].fillna(data['runtime'].mean())
data = data.dropna(subset=['box_office'])

# 特征工程
# 提取上映日期中的月份和星期几
data['release_month'] = pd.to_datetime(data['release_date']).dt.month
data['release_day_of_week'] = pd.to_datetime(data['release_date']).dt.dayofweek

# 对电影类型进行独热编码
genre_dummies = pd.get_dummies(data['genre'], prefix='genre')
data = pd.concat([data, genre_dummies], axis=1)

# 特征选择
features = ['runtime', 'budget', 'release_month', 'release_day_of_week'] + list(genre_dummies.columns)
target = 'box_office'
X = data[features]
y = data[target]

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 模型训练
model = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1, random_state=42)
model.fit(X_train, y_train)

# 模型预测
y_pred = model.predict(X_test)

# 模型评估
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
print(f"均方误差 (MSE): {mse}")
print(f"均方根误差 (RMSE): {rmse}")
print(f"决定系数 (R²): {r2}")

# 数据可视化
# 不同电影类型的平均票房柱状图
genre_avg_box_office = data.groupby('genre')['box_office'].mean()
plt.figure(figsize=(10, 6))
sns.barplot(x=genre_avg_box_office.index, y=genre_avg_box_office.values)
plt.title('不同电影类型的平均票房')
plt.xlabel('电影类型')
plt.xticks(rotation=45)
plt.ylabel('平均票房')
plt.show()

# 电影预算与票房的散点图
plt.figure(figsize=(10, 6))
plt.scatter(data['budget'], data['box_office'])
plt.title('电影预算与票房的关系')
plt.xlabel('电影预算')
plt.ylabel('票房')
plt.show()

2. 主要的代码难点解析

2.1 数据清洗 - 缺失值处理

data['runtime'] = data['runtime'].fillna(data['runtime'].mean())
data = data.dropna(subset=['box_office'])
  • 难点:对于 runtime 列使用均值填充缺失值,可能会使数据分布发生变化,不能准确反映实际情况。直接删除 box_office 列的缺失值会减少样本量,影响模型的泛化能力。
  • 解决思路:可以考虑根据电影类型、导演等因素对 runtime 进行分组,然后用每组的均值填充缺失值,这样更符合实际情况。对于 box_office 列的缺失值,可以先分析缺失原因,若可能与其他特征有关,尝试使用插值法或基于机器学习模型进行预测填充。

2.2 特征工程 - 日期特征提取与独热编码

data['release_month'] = pd.to_datetime(data['release_date']).dt.month
data['release_day_of_week'] = pd.to_datetime(data['release_date']).dt.dayofweek
genre_dummies = pd.get_dummies(data['genre'], prefix='genre')
  • 难点:日期特征提取需要正确使用 pd.to_datetime() 和 dt 访问器,要注意日期格式的一致性。独热编码会增加数据的维度,可能导致模型训练变慢和过拟合问题。
  • 解决思路:在进行日期处理前,先检查日期格式,确保能正确转换。对于独热编码增加的维度,可以使用特征选择方法(如相关性分析、随机森林的特征重要性排序)筛选出重要特征,或者使用降维技术(如主成分分析)。

2.3 特征选择

features = ['runtime', 'budget', 'release_month', 'release_day_of_week'] + list(genre_dummies.columns)
  • 难点:要从众多可能的特征中挑选出对电影票房有显著影响的特征。特征选择不当会导致模型过拟合或欠拟合,且需要考虑特征之间的相关性。
  • 解决思路:可以通过计算特征之间的相关性矩阵,去除相关性过高的特征。也可以使用机器学习算法(如随机森林、梯度提升树)的特征重要性排序功能,筛选出重要特征。

2.4 模型训练与评估

model = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1, random_state=42)
model.fit(X_train, y_train)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
  • 难点:梯度提升回归模型的参数(如 n_estimatorslearning_rate)选择会影响模型的性能。仅使用均方误差(MSE)、均方根误差(RMSE)和决定系数(R²)评估模型,可能不能全面反映模型的预测能力。
  • 解决思路:使用网格搜索或随机搜索等方法对模型参数进行调优。同时,结合其他评估指标(如平均绝对误差、平均绝对百分比误差)全面评估模型性能。

2.5 数据可视化

sns.barplot(x=genre_avg_box_office.index, y=genre_avg_box_office.values)
plt.scatter(data['budget'], data['box_office'])
  • 难点:选择合适的可视化方式展示数据特征和关系。柱状图和散点图虽能直观呈现信息,但要注意图表的标题、坐标轴标签、数据标签等设置,以提高可视化效果和可读性。
  • 解决思路:根据数据类型和分析目的选择合适的可视化图表。在绘制图表时,仔细调整图表的各种参数,添加必要的注释和说明,使读者能清晰理解图表传达的信息。

3. 可能改进的代码

3.1 数据清洗与特征工程改进

# 按电影类型分组填充时长缺失值
data['runtime'] = data.groupby('genre')['runtime'].transform(lambda x: x.fillna(x.mean()))

# 添加更多特征交互项
data['budget_runtime_ratio'] = data['budget'] / data['runtime']

3.2 模型改进

# 使用网格搜索调优梯度提升回归模型参数
from sklearn.model_selection import GridSearchCV
param_grid = {
    'n_estimators': [50, 100, 200],
    'learning_rate': [0.01, 0.1, 0.2],
    'max_depth': [3, 4, 5]
}
grid_search = GridSearchCV(GradientBoostingRegressor(random_state=42), param_grid, cv=5)
grid_search.fit(X_train, y_train)
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)

# 评估模型时使用更多指标
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error
mae = mean_absolute_error(y_test, y_pred)
mape = mean_absolute_percentage_error(y_test, y_pred)
print(f"平均绝对误差 (MAE): {mae}")
print(f"平均绝对百分比误差 (MAPE): {mape}")

3.3 可视化改进

# 不同电影类型的平均票房柱状图添加数据标签
ax = sns.barplot(x=genre_avg_box_office.index, y=genre_avg_box_office.values)
for p in ax.patches:
    width = p.get_width()
    height = p.get_height()
    x, y = p.get_xy()
    ax.annotate(f'{height:.2f}', (x + width/2, y + height), ha='center', va='bottom')
plt.title('不同电影类型的平均票房')
plt.xlabel('电影类型')
plt.xticks(rotation=45)
plt.ylabel('平均票房')
plt.show()

# 电影预算与票房的散点图添加趋势线
plt.figure(figsize=(10, 6))
plt.scatter(data['budget'], data['box_office'])
z = np.polyfit(data['budget'], data['box_office'], 1)
p = np.poly1d(z)
plt.plot(data['budget'], p(data['budget']), "r--")
plt.title('电影预算与票房的关系')
plt.xlabel('电影预算')
plt.ylabel('票房')
plt.show()