wwj99

介绍

看电影是目前人们休闲娱乐,消遣时光的选择之一。我们都知道,有些电影的票房很高,有的电影票房却很低,那么决定票房的因素是什么呢?本次将介绍,如何根据电影上映前的一些信息来预测出该电影的票房。

知识点

  • 数据预处理
  • 建立预测模型

电影票房预测介绍

电影产业在 2018 年估计达到 417 亿美元,电影业比以往任何时候都更受欢迎。 那么电影可能跟哪些因素有关呢?我们可以联想到以下几个因素。

  • 导演
  • 演员
  • 预算
  • 预告片

那是否是这些因素决定了一部电影的最终票房呢?我们可以分析 Kaggle 提供的数据来回答这一问题。数据详情可以参考 Kaggle 官方页面 ,其主要是电影数据库中的 7000 多部过去电影的元数据。提供的数据信息包括演员,工作人员,情节关键词,预算,海报,发布日期,语言,制作公司和国家等。

图片描述

本次的目的则是根据提供的这些信息来预测一部电影最终的票房。

数据导入

我们先导入数据,并查看数据的前 5 份。

链接:https://pan.baidu.com/s/1AsZBZNVEHZkoeGm8v2wtGg 提取码:76bx

import pandas as pd

df = pd.read_csv("TMDB.csv")
df.head()

可以看到数据总共含有 23 列,最后一列 revenue 为票房。现在查看数据的形状。

df.shape

可以看到数据总共含有 3000 份,查看数据的基本信息。

df.info()

df.describe()

查看数据集中字符串型特征的描述。

df.describe(include=[\'O\'])

先来观察票房前 10 的都是哪些电影。

df.sort_values(by=\'revenue\', ascending=False).head(10)[
    [\'title\', \'revenue\', \'release_date\']]

可以看到,票房最高的是 《The Avengers》 ,也就是我们常说的《复仇者联盟》。其次是《 Furious 7 》,即《速度与激情 7》。第三是《Avengers: Age of Ultron》,即《复仇者联盟:奥创纪元》。

数据预处理

数据预处理主要是对数据进行清洗,填充缺失值等操作。

上映时间

在数据集中,其中有一列为 release_date,即电影的上映时间,我们先来对该列进行处理。在处理时,将电影上的年、月、日这些信息分开,以便后续的分析。

def date_features(df):
    df[\'release_date\'] = pd.to_datetime(df[\'release_date\'])  # 转换为时间戳
    df[\'release_year\'] = df[\'release_date\'].dt.year  # 提取年
    df[\'release_month\'] = df[\'release_date\'].dt.month     # 提取月
    df[\'release_day\'] = df[\'release_date\'].dt.day  # 提取日
    df[\'release_quarter\'] = df[\'release_date\'].dt.quarter  # 提取季度
    return df


df = date_features(df)
df[\'release_year\'].head()

查看一下数据是否存在异常值,即电影上映时间超过 2019 年,因为收集的数据是 2019 年之前的,所以不可能存在 2019 年之后上映的电影。因此将这些 2019 年之后上映的电影视为异常值。

import numpy as np
# 查看大于 2019 的数据
df[\'release_year\'].iloc[np.where(df[\'release_year\'] > 2019)][:10]

从上面的结果可以看出,的确存在不少异常值,现在对这些值进行处理。

# 大于 2019 的减去 100
df[\'release_year\'] = np.where(
    df[\'release_year\'] > 2019, df[\'release_year\']-100, df[\'release_year\'])
df[\'release_year\'].iloc[np.where(df[\'release_year\'] > 2019)][:10]

处理完成之后,可以看到,已经不存在异常值。

现在查看一下关于日期的数据是否存在缺失值。

cols = [\'release_year\', \'release_month\',
        \'release_day\']
df[cols].isnull().sum()

从上面的结果可以看到,关于电影上映日期的数据不存在缺失值。

现在查看一下,每个月的平均电影票房。

from matplotlib import pyplot as plt
%matplotlib inline

fig = plt.figure(figsize=(14, 4))

df.groupby(\'release_month\').agg(\'mean\')[\'revenue\'].plot(kind=\'bar\', rot=0)
plt.ylabel(\'Revenue (100 million dollars)\')

从上图可以看到,电影的上映时间主要集中在 6 月和 12 月。这可能的原因是这两段时间都是假期,因此很多同学有更多的时间去电影院看电影。所以这两段时间的电影票房要高一点。

接下来再来看每年的电影平均票房数。

release_year_mean_data = df.groupby([\'release_year\'])[\'revenue\'].mean()
fig = plt.figure(figsize=(14, 5))  # 设置画布大小
plt.plot(release_year_mean_data)
plt.ylabel(\'Mean revenue value\')  # 设置 y 轴的标签
plt.title(\'Mean revenue Over Years\')  # 设置标题

从上图可以看到,电影的每年平均票房都是逐年递增的,这可能跟我们的经济增长有关,因为人们越来越有钱了,花费在精神上的消费比例也越来越大了。

接下来看电影的时长跟年份的关系。

release_year_mean_data = df.groupby([\'release_year\'])[\'runtime\'].mean()
fig = plt.figure(figsize=(14, 5))  # 设置画布大小
plt.plot(release_year_mean_data)
plt.ylabel(\'Mean popularity value\')  # 设置 y 轴的标签
plt.title(\'Mean popularity Over Years\')  # 设置标题

从上图中可以发现,在 1980 年之前,电影的平均时长都是不定的,而 1980 年之后,趋向于稳定,差不多是 100 多分钟。

收藏集

现在来看 belongs_to_collection 列,先打印该列的前 5 个数据来进行观察。

for i, e in enumerate(df[\'belongs_to_collection\'][:5]):
    print(i, e)
    print(type(e))

从上面的结果可以看到,该列主要包括名字、海报等信息。同时还可以看到,存在许多值为 nan ,也就是缺失值。现在统计一下存在多少个缺失值。这里需要注意的是通过判断该列的值是否是字符串来判断是否存在值或为空值。

df[\'belongs_to_collection\'].apply(
    lambda x: 1 if type(x) == str else 0).value_counts()

从上面的结果看出,在 3000 份数据中,该列的缺失值就有 2396。我们从该列中提取 name 属性。且创建一列保存是否缺失。

df[\'collection_name\'] = df[\'belongs_to_collection\'].apply(
    lambda x: eval(x)[0][\'name\'] if type(x) == str else 0)
df[\'has_collection\'] = df[\'belongs_to_collection\'].apply(
    lambda x: 1 if type(x) == str else 0)
df[[\'collection_name\', \'has_collection\']].head()

电影类型

同样的方法,把 genres 列也处理一下。

for i, e in enumerate(df[\'genres\'][:5]):
    print(i, e)

从上可以看出,genres 列主要用来存放电影的类型,例如:喜剧、剧情等。我们可以统计每中类型的电影数量,先统计每部电影都含哪些类别。

list_of_genres = list(df[\'genres\'].apply(lambda x: [i[\'name\']
                                                    for i in eval(x)] if type(x) == str else []).values)
list_of_genres[:5]

计算每种电影类型出现的数量。

from collections import Counter

most_common_genres = Counter(
    [i for j in list_of_genres for i in j]).most_common()
most_common_genres

绘制出图形。

fig = plt.figure(figsize=(10, 6))
data = dict(most_common_genres)
names = list(data.keys())
values = list(data.values())

plt.barh(sorted(range(len(data)), reverse=True),
         values, tick_label=names, color=\'teal\')
plt.xlabel(\'Count\')
plt.title(\'Movie Genre Count\')
plt.show()

从上图可知,电影数量最多的题材为剧情(Drama),其次是喜剧(Comedy)。我们还可以使用词图的方法来直观的画出。先安装 词云库 wordcloud

!pip install wordcloud

画出词云图。

from wordcloud import WordCloud

plt.figure(figsize=(12, 8))
text = \' \'.join([i for j in list_of_genres for i in j])
# 设置参数
wordcloud = WordCloud(max_font_size=None, background_color=\'white\', collocations=False,
                      width=1200, height=1000).generate(text)
plt.imshow(wordcloud)
plt.title(\'Top genres\')
plt.axis("off")
plt.show()

在上面的词图中,词的字体越大,表示该词数量越多,即出现的频率越高。

在该列中,我们可以提取一部电影包含类型的数量,以及该电影所属的全部类型。

df[\'num_genres\'] = df[\'genres\'].apply(
    lambda x: len(eval(x)) if type(x) == str else 0)
df[\'all_genres\'] = df[\'genres\'].apply(lambda x: \' \'.join(
    sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\')
top_genres = [m[0] for m in Counter(
    [i for j in list_of_genres for i in j]).most_common(15)]
for g in top_genres:
    df[\'genre_\' + g] = df[\'all_genres\'].apply(lambda x: 1 if g in x else 0)
cols = [i for i in df.columns if \'genre_\' in str(i)]
df[cols].head()

在上面显示的结果中,genre_Drama、 genre_Comedy 等列即是我们所提取的特征列,其表示的含义是如果一部电影属于该类型,则在该列中的值为 1 否则为 0。这种处理思路类似我们常见的 One-Hot 编码。

前面我们统计出来每种类型的电影数量。现在统计出类型与票房和上映年份的关系。这里我们会使用到 plotly 库来进行绘图,先安装相关的绘图工具库。

!pip install plotly

导入相关的库。

import plotly.graph_objs as go
import plotly.offline as py
py.init_notebook_mode(connected=False)

画出三者的关系图。

drama = df.loc[df[\'genre_Drama\'] == 1, ]  # 得到所有电影类型为 Drama 的数据
comedy = df.loc[df[\'genre_Comedy\'] == 1, ]
action = df.loc[df[\'genre_Action\'] == 1, ]
thriller = df.loc[df[\'genre_Thriller\'] == 1, ]

drama_revenue = drama.groupby([\'release_year\']).mean()[\'revenue\']  # 求出票房的平均值
comedy_revenue = comedy.groupby([\'release_year\']).mean()[\'revenue\']
action_revenue = action_revenue = action.groupby(
    [\'release_year\']).mean()[\'revenue\']
thriller_revenue = thriller.groupby([\'release_year\']).mean()[\'revenue\']

revenue_concat = pd.concat([drama_revenue,    # 将数据合并为一份
                            comedy_revenue,
                            action_revenue,
                            thriller_revenue],
                           axis=1)

revenue_concat.columns = [\'drama\', \'comedy\', \'action\', \'thriller\']
revenue_concat.index = df.groupby([\'release_year\']).mean().index

data = [go.Scatter(x=revenue_concat.index, y=revenue_concat.drama, name=\'drama\'),
        go.Scatter(x=revenue_concat.index,
                   y=revenue_concat.comedy, name=\'comedy\'),
        go.Scatter(x=revenue_concat.index,
                   y=revenue_concat.action, name=\'action\'),
        go.Scatter(x=revenue_concat.index, y=revenue_concat.thriller, name=\'thriller\')]
# 画出图形
layout = go.Layout(dict(title=\'Mean Revenue by Top 4 Movie Genres Over Years\',
                        xaxis=dict(title=\'Year\'),
                        yaxis=dict(title=\'Revenue\'),
                        ), legend=dict(
    orientation="v"))

py.iplot(dict(data=data, layout=layout))

从上图可以知道,在 2000 年之后,动作(Action)题材的电影的票房在逐渐增加,这也从侧面显示了动作电影越来越受观众的青睐。

制片公司

同样的方法,现在来看制片公司(production_companies)。

for i, e in enumerate(df[\'production_companies\'][:5]):
    print(i, e)

从上面可知,同一个电影可能来源于多个制片公司。现在来画出制片公司发行的电影数量。

list_of_companies = list(df[\'production_companies\'].apply(
    lambda x: [i[\'name\'] for i in eval(x)] if type(x) == str else []).values)
# 得到每个公司的电影发行量
most_common_companies = Counter(
    [i for j in list_of_companies for i in j]).most_common(20)
fig = plt.figure(figsize=(10, 6))
data = dict(most_common_companies)
names = list(data.keys())
values = list(data.values())

plt.barh(sorted(range(len(data)), reverse=True),
         values, tick_label=names, color=\'brown\')
plt.xlabel(\'Count\')
plt.title(\'Top 20 Production Company Count\')
plt.show()

从上图可知,Warner Bros 制作的电影最多。Warner Bros 也即是著名的华纳兄弟娱乐公司。

同样,我们现在要从该列中提取一些重要的信息。这里与电影类型的提取类似。

df[\'num_companies\'] = df[\'production_companies\'].apply(
    lambda x: len(x) if type(x) == str else 0)
df[\'all_production_companies\'] = df[\'production_companies\'].apply(
    lambda x: \' \'.join(sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\')
top_companies = [m[0] for m in Counter(
    [i for j in list_of_companies for i in j]).most_common(30)]
for g in top_companies:
    df[\'production_company_\' +
        g] = df[\'all_production_companies\'].apply(lambda x: 1 if g in x else 0)

cols = [i for i in df.columns if \'production_company\' in str(i)]
df[cols].head()

在上面的提取结果中,production_company_Warner Bros、production_company_Universal Pictures 等列即是我们所提取的列,其表示的含义是如果一部电影属于该公司出产,那么该电影在该公司所对应的的列的值为 1 否则为 0。

进行上面的提取之后,我们现在来画出几个公司制作的电影票房数量。

Warner_Bros = df.loc[df[\'production_company_Warner Bros.\'] == 1, ]
Universal_Pictures = df.loc[df[\'production_company_Universal Pictures\'] == 1, ]
Twentieth_Century_Fox_Film = df.loc[
    df[\'production_company_Twentieth Century Fox Film Corporation\'] == 1, ]
Columbia_Pictures = df.loc[df[\'production_company_Columbia Pictures\'] == 1, ]

Warner_Bros_revenue = Warner_Bros.groupby([\'release_year\']).mean()[\'revenue\']
Universal_Pictures_revenue = Universal_Pictures.groupby(
    [\'release_year\']).mean()[\'revenue\']
Twentieth_Century_Fox_Film_revenue = Twentieth_Century_Fox_Film.groupby(
    [\'release_year\']).mean()[\'revenue\']
Columbia_Pictures_revenue = Columbia_Pictures.groupby(
    [\'release_year\']).mean()[\'revenue\']

prod_revenue_concat = pd.concat([Warner_Bros_revenue,
                                 Universal_Pictures_revenue,
                                 Twentieth_Century_Fox_Film_revenue,
                                 Columbia_Pictures_revenue], axis=1)
prod_revenue_concat.columns = [\'Warner_Bros\',
                               \'Universal_Pictures\',
                               \'Twentieth_Century_Fox_Film\',
                               \'Columbia_Pictures\']

fig = plt.figure(figsize=(13, 5))
prod_revenue_concat.agg("mean", axis=\'rows\').sort_values(ascending=True).plot(kind=\'barh\',
                                                                              x=\'Production Companies\',
                                                                              y=\'Revenue\',
                                                                              title=\'Mean Revenue (100 million dollars) of Most Common Production Companies\')
plt.xlabel(\'Revenue (100 million dollars)\')

现在来分析制片公司与年份和票房的关系。

data = [go.Scatter(x=prod_revenue_concat.index, y=prod_revenue_concat.Warner_Bros, name=\'Warner_Bros\'),
        go.Scatter(x=prod_revenue_concat.index,
                   y=prod_revenue_concat.Universal_Pictures, name=\'Universal_Pictures\'),
        go.Scatter(x=prod_revenue_concat.index,
                   y=prod_revenue_concat.Twentieth_Century_Fox_Film, name=\'Twentieth_Century_Fox_Film\'),
        go.Scatter(x=prod_revenue_concat.index, y=prod_revenue_concat.Columbia_Pictures, name=\'Columbia_Pictures\'), ]

layout = go.Layout(dict(title=\'Mean Revenue of Movie Production Companies over Years\',
                        xaxis=dict(title=\'Year\'),
                        yaxis=dict(title=\'Revenue\'),
                        ), legend=dict(
    orientation="v"))
py.iplot(dict(data=data, layout=layout))

出版国家

上面一小节主要分析了制片公司,现在来分析一下电影的出版国家,即电影是哪一个国家搞出来的。

for i, e in enumerate(df[\'production_countries\'][:5]):
    print(i, e)

从上面可以看到,在 production_countries 中,name 表示的是国家的全称,而 iso_3166_1 表示的是国家的简称。现在我们来看一下哪个国家出产的电影更多。

list_of_countries = list(df[\'production_countries\'].apply(
    lambda x: [i[\'name\'] for i in eval(x)] if type(x) == str else []).values)
most_common_countries = Counter(
    [i for j in list_of_countries for i in j]).most_common(20)

fig = plt.figure(figsize=(10, 6))
data = dict(most_common_countries)
names = list(data.keys())
values = list(data.values())

plt.barh(sorted(range(len(data)), reverse=True),
         values, tick_label=names, color=\'purple\')
plt.xlabel(\'Count\')
plt.title(\'Country Count\')
plt.show()

从上图可以看出,美国出版的电影最多;其次是英国;再次是法国。而中国香港的票房几乎与中国内地持平,这似乎有点出乎意料。

同样的方法,我们现在来对电影出产国家进行特征提取。

df[\'num_countries\'] = df[\'production_countries\'].apply(
    lambda x: len(eval(x)) if type(x) == str else 0)
df[\'all_countries\'] = df[\'production_countries\'].apply(lambda x: \' \'.join(
    sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\')
top_countries = [m[0] for m in Counter(
    [i for j in list_of_countries for i in j]).most_common(25)]
for g in top_countries:
    df[\'production_country_\' +
        g] = df[\'all_countries\'].apply(lambda x: 1 if g in x else 0)

cols = [i for i in df.columns if \'production_country\' in str(i)]
df[cols].head()

在所提取到的特征列中,如果一部电影属于某个国家,那么该电影在某个国家所对应的的列中的值为 1 ,否则为 0。

电影语言

我们都知道,不同国家可能使用不同的语言,所以电影的语言也不尽相同。现在来看电影语言列(spoken_languages)。

for i, e in enumerate(df[\'spoken_languages\'][:5]):
    print(i, e)

在该列中,name 表示电影语言,iso_639_1 表示语言的简写。同时还可以看到,一部电影可能还有多个语言。现在对语言进行统计,查看一下什么语言的电影最多。

list_of_languages = list(df[\'spoken_languages\'].apply(
    lambda x: [i[\'name\'] for i in eval(x)] if type(x) == str else []).values)

most_common_languages = Counter(
    [i for j in list_of_languages for i in j]).most_common(20)

fig = plt.figure(figsize=(10, 6))
data = dict(most_common_languages)
names = list(data.keys())
values = list(data.values())

plt.barh(sorted(range(len(data)), reverse=True), values, tick_label=names)
plt.xlabel(\'Count\')
plt.title(\'Language Count\')
plt.show()

可能你也已经猜到,英语肯定是最多的,从上图显示的结果也的确如此。同样的方法来对语言提取特征。

df[\'num_languages\'] = df[\'spoken_languages\'].apply(
    lambda x: len(eval(x)) if type(x) == str else 0)
df[\'all_languages\'] = df[\'spoken_languages\'].apply(lambda x: \' \'.join(
    sorted([i[\'iso_639_1\'] for i in eval(x)])) if type(x) == str else \'\')
top_languages = [m[0] for m in Counter(
    [i for j in list_of_languages for i in j]).most_common(30)]
for g in top_languages:
    df[\'language_\' +
        g] = df[\'all_languages\'].apply(lambda x: 1 if g in x else 0)
cols = [i for i in df.columns if \'language_\' in str(i)]
df[cols].head()

关键字

在数据集中,存在一个关键字列(Keywords)。我们使用同样的方法来处理该列。

for i, e in enumerate(df[\'Keywords\'][:5]):
    print(i, e)

关键字表示的一部电影的主题内容。例如在犯罪题材的电影中,关键字就可能有警察、毒枭等关键字。现在对关键字进行统计。

list_of_keywords = list(df[\'Keywords\'].apply(
    lambda x: [i[\'name\'] for i in eval(x)] if type(x) == str else []).values)

most_common_keywords = Counter(
    [i for j in list_of_keywords for i in j]).most_common(20)

fig = plt.figure(figsize=(10, 6))
data = dict(most_common_keywords)
names = list(data.keys())
values = list(data.values())

plt.barh(sorted(range(len(data)), reverse=True),
         values, tick_label=names, color=\'purple\')
plt.xlabel(\'Count\')
plt.title(\'Top 20 Most Common Keyword Count\')
plt.show()

从上面的结果看出,女导演(woman director)出现的次数最多。现在我们可以分析一下,一些电影题材的关键字。

text_drama = " ".join(review for review in drama[\'Keywords\'].apply(
    lambda x: \' \'.join(sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\'))
text_comedy = " ".join(review for review in comedy[\'Keywords\'].apply(
    lambda x: \' \'.join(sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\'))
text_action = " ".join(review for review in action[\'Keywords\'].apply(
    lambda x: \' \'.join(sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\'))
text_thriller = " ".join(review for review in thriller[\'Keywords\'].apply(
    lambda x: \' \'.join(sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\'))

wordcloud1 = WordCloud(background_color="white",
                       colormap="Reds").generate(text_drama)
wordcloud2 = WordCloud(background_color="white",
                       colormap="Blues").generate(text_comedy)
wordcloud3 = WordCloud(background_color="white",
                       colormap="Greens").generate(text_action)
wordcloud4 = WordCloud(background_color="white",
                       colormap="Greys").generate(text_thriller)


fig = plt.figure(figsize=(25, 20))

plt.subplot(221)
plt.imshow(wordcloud1, interpolation=\'bilinear\')
plt.title(\'Drama Keywords\')
plt.axis("off")

plt.subplot(222)
plt.imshow(wordcloud2, interpolation=\'bilinear\')
plt.title(\'Comedy Keywords\')
plt.axis("off")
plt.show()

fig = plt.figure(figsize=(25, 20))

plt.subplot(223)
plt.imshow(wordcloud3, interpolation=\'bilinear\')
plt.title(\'Action Keywords\')
plt.axis("off")

plt.subplot(224)
plt.imshow(wordcloud4, interpolation=\'bilinear\')
plt.title(\'Thriller Keywords\')
plt.axis("off")
plt.show()

从上面的词云图可以看出,剧情(Drama)类和喜剧类(Comedy)电影的关键字大都都含有家庭(family)、女性(woman)基于小说改编(based novel)等,而动作类(Action)和犯罪类(Thriller)则出现警察(police)、死亡(death)等关键词最多。

同样的方法来对该列进行特征提取。

df[\'num_Keywords\'] = df[\'Keywords\'].apply(
    lambda x: len(eval(x)) if type(x) == str else 0)
df[\'all_Keywords\'] = df[\'Keywords\'].apply(lambda x: \' \'.join(
    sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\')
top_keywords = [m[0] for m in Counter(
    [i for j in list_of_keywords for i in j]).most_common(30)]
for g in top_keywords:
    df[\'keyword_\' + g] = df[\'all_Keywords\'].apply(lambda x: 1 if g in x else 0)
cols = [i for i in df.columns if \'keyword_\' in str(i)]
df[cols].head()

演员

电影的好坏,演员在很多层面上也取到一定的作用。因此现在来看演员列。

for i, e in enumerate(df[\'cast\'][:1]):
    print(i, e)

从上面的结果可以看到,演员的信息包括性别(gender)、姓名(name)等。现在统计一下哪些演员演过的电影最多。

list_of_cast_names = list(df[\'cast\'].apply(
    lambda x: [i[\'name\'] for i in eval(x)] if type(x) == str else []).values)
most_common_keywords = Counter(
    [i for j in list_of_cast_names for i in j]).most_common(20)

fig = plt.figure(figsize=(10, 6))
data = dict(most_common_keywords)
names = list(data.keys())
values = list(data.values())

plt.barh(sorted(range(len(data)), reverse=True),
         values, tick_label=names, color=\'purple\')
plt.xlabel(\'Count\')
plt.title(\'Top 20 Most Common Keyword Count\')
plt.show()

从上的结果可以看到,塞缪尔·杰克逊(Samuel L. Jackson)演过的电影最多。对于很多中国人来说,可能很多的国人名字不是很容易记住,我们现在来看一下,这些演员的图片。

img

相信看过美国大片的你对上面的演员会很熟悉。现在来提取特征。

df[\'num_cast\'] = df[\'cast\'].apply(
    lambda x: len(eval(x)) if type(x) == str else 0)
df[\'all_cast\'] = df[\'cast\'].apply(lambda x: \' \'.join(
    sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\')
top_cast_names = [m[0] for m in Counter(
    [i for j in list_of_cast_names for i in j]).most_common(30)]
for g in top_cast_names:
    df[\'cast_name_\' + g] = df[\'all_cast\'].apply(lambda x: 1 if g in x else 0)
cols = [i for i in df.columns if \'cast_name\' in str(i)]
df[cols].head()

画出参演数量最多的演员所获得的电影票房情况。

cast_name_Samuel_L_Jackson = df.loc[df[\'cast_name_Samuel L. Jackson\'] == 1, ]
cast_name_Robert_De_Niro = df.loc[df[\'cast_name_Robert De Niro\'] == 1, ]
cast_name_Morgan_Freeman = df.loc[df[\'cast_name_Morgan Freeman\'] == 1, ]
cast_name_J_K_Simmons = df.loc[df[\'cast_name_J.K. Simmons\'] == 1, ]


cast_name_Samuel_L_Jackson_revenue = cast_name_Samuel_L_Jackson.mean()[
    \'revenue\']
cast_name_Robert_De_Niro_revenue = cast_name_Robert_De_Niro.mean()[\'revenue\']
cast_name_Morgan_Freeman_revenue = cast_name_Morgan_Freeman.mean()[\'revenue\']
cast_name_J_K_Simmons_revenue = cast_name_J_K_Simmons.mean()[\'revenue\']


cast_revenue_concat = pd.Series([cast_name_Samuel_L_Jackson_revenue,
                                 cast_name_Robert_De_Niro_revenue,
                                 cast_name_Morgan_Freeman_revenue,
                                 cast_name_J_K_Simmons_revenue])

cast_revenue_concat.index = [\'Samuel L. Jackson\',
                             \'Robert De Niro\',
                             \'Morgan Freeman\',
                             \'J.K. Simmons\', ]

fig = plt.figure(figsize=(13, 5))
cast_revenue_concat.sort_values(ascending=True).plot(
    kind=\'barh\', title=\'Mean Revenue (100 million dollars) by Top 4 Most Common Cast\')
plt.xlabel(\'Revenue (100 million dollars)\')

现在对演员性别等特征进行提取。

list_of_cast_genders = list(df[\'cast\'].apply(
    lambda x: [i[\'gender\'] for i in eval(x)] if type(x) == str else []).values)
list_of_cast_characters = list(df[\'cast\'].apply(
    lambda x: [i[\'character\'] for i in eval(x)] if type(x) == str else []).values)

df[\'genders_0\'] = sum([1 for i in list_of_cast_genders if i == 0])
df[\'genders_1\'] = sum([1 for i in list_of_cast_genders if i == 1])
df[\'genders_2\'] = sum([1 for i in list_of_cast_genders if i == 2])
top_cast_characters = [m[0] for m in Counter(
    [i for j in list_of_cast_characters for i in j]).most_common(15)]
for g in top_cast_characters:
    df[\'cast_character_\' +
        g] = df[\'cast\'].apply(lambda x: 1 if type(x) == str and g in x else 0)
cols = [i for i in df.columns if \'cast_cha\' in str(i)]
dfcols].head()

制作团队

一部电影的好坏与制作团队的也是分不开的,现在来看电影的制作团队。

for i, e in enumerate(df[\'crew\'][:1]):
    print(i, e)

从上面的结果可以看出,制作团队包括导演,副导演、电影配乐等信息。现在来统计一下团队人物制作的电影数量。

list_of_crew_names = list(df[\'crew\'].apply(
    lambda x: [i[\'name\'] for i in eval(x)] if type(x) == str else []).values)
most_common_keywords = Counter(
    [i for j in list_of_crew_names for i in j]).most_common(20)

fig = plt.figure(figsize=(10, 6))
data = dict(most_common_keywords)
names = list(data.keys())
values = list(data.values())

plt.barh(sorted(range(len(data)), reverse=True),
         values, tick_label=names, color=\'purple\')
plt.xlabel(\'Count\')
plt.title(\'Top 20 Most Common Keyword Count\')
plt.show()

从上面可以看到 avy Kaufman,Robert Rodriguez 等导演参与制作的电影最多。现在进行特征提取。

df[\'num_crew\'] = df[\'crew\'].apply(
    lambda x: len(eval(x)) if type(x) == str else 0)
df[\'all_crew\'] = df[\'crew\'].apply(lambda x: \' \'.join(
    sorted([i[\'name\'] for i in eval(x)])) if type(x) == str else \'\')
top_crew_names = [m[0] for m in Counter(
    [i for j in list_of_crew_names for i in j]).most_common(30)]
for g in top_crew_names:
    df[\'crew_name_\' +
        g] = df[\'all_crew\'].apply(lambda x: 1 if type(x) == str and g in x else 0)
cols = [i for i in df.columns if \'crew_name\' in str(i)]
df[cols].head()

同样对排名前 4 位导演进行分析。

crew_name_Avy_Kaufman = df.loc[df[\'crew_name_Avy Kaufman\'] == 1, ]
crew_name_Robert_Rodriguez = df.loc[df[\'crew_name_Robert Rodriguez\'] == 1, ]
crew_name_Deborah_Aquila = df.loc[df[\'crew_name_Deborah Aquila\'] == 1, ]
crew_name_James_Newton_Howard = df.loc[df[\'crew_name_James Newton Howard\'] == 1, ]

crew_name_Avy_Kaufman_revenue = crew_name_Avy_Kaufman.mean()[\'revenue\']
crew_name_Robert_Rodriguez_revenue = crew_name_Robert_Rodriguez.mean()[
    \'revenue\']
crew_name_Deborah_Aquila_revenue = crew_name_Deborah_Aquila.mean()[\'revenue\']
crew_name_James_Newton_Howard_revenue = crew_name_James_Newton_Howard.mean()[
    \'revenue\']


crew_revenue_concat = pd.Series([crew_name_Avy_Kaufman_revenue,
                                 crew_name_Robert_Rodriguez_revenue,
                                 crew_name_Deborah_Aquila_revenue,
                                 crew_name_James_Newton_Howard_revenue])
crew_revenue_concat.index = [\'Avy Kaufman\',
                             \'Robert Rodriguez\',
                             \'Deborah Aquila\',
                             \'James Newton Howard\']


fig = plt.figure(figsize=(13, 5))
crew_revenue_concat.sort_values(ascending=True).plot(
    kind=\'barh\', title=\'Mean Revenue (100 million dollars) by Top 10 Most Common Crew\')
plt.xlabel(\'Revenue (100 million dollars)\')

从上面的显示结果可以看到,电影票房最高的制作人员是詹姆斯·纽顿·霍华德(James Newton Howard),其是一名音乐家,主要负责电影的配乐。

特征工程

因为票房数据并不平衡,所以要用对数变换来处理倾斜的数据。

fig = plt.figure(figsize=(15, 10))

plt.subplot(221)
df[\'revenue\'].plot(kind=\'hist\', bins=100)
plt.title(\'Distribution of Revenue\')
plt.xlabel(\'Revenue\')

plt.subplot(222)
np.log1p(df[\'revenue\']).plot(kind=\'hist\', bins=100)
plt.title(\'Train Log Revenue Distribution\')
plt.xlabel(\'Log Revenue\')

对预计票房列也做同样的变换。

fig = plt.figure(figsize=(15, 10))

plt.subplot(221)
df[\'budget\'].plot(kind=\'hist\', bins=100)
plt.title(\'Train Budget Distribution\')
plt.xlabel(\'Budget\')

plt.subplot(222)
np.log1p(df[\'budget\']).plot(kind=\'hist\', bins=100)
plt.title(\'Train Log Budget Distribution\')
plt.xlabel(\'Log Budget\')
plt.show()

前面我们主要提取了时间、演员、导演等特征,而数据集还存在电影标题、电影编号等特征,这些特征对预测结果可能没有多大影响,因此,现在删除掉这些特征,仅保留前面我们所提取的特征列。

drop_columns = [\'homepage\', \'imdb_id\', \'poster_path\', \'status\',
                \'title\', \'release_date\', \'tagline\', \'overview\',
                \'original_title\', \'all_genres\', \'all_cast\',
                \'original_language\', \'collection_name\', \'all_crew\',
                \'belongs_to_collection\', \'genres\', \'production_companies\',
                \'all_production_companies\', \'production_countries\',
                \'all_countries\', \'spoken_languages\', \'all_languages\',
                \'Keywords\', \'all_Keywords\', \'cast\', \'crew\']

df_drop = df.drop(drop_columns, axis=1).dropna(axis=1, how=\'any\')

查看最终的数据。

df_drop.head()

划分训练集和测试集。

from sklearn.model_selection import train_test_split

data_X = df_drop.drop([\'id\', \'revenue\'], axis=1)
data_y = np.log1p(df_drop[\'revenue\'])
train_X, test_X, train_y, test_y = train_test_split(
    data_X, data_y.values, test_size=0.2)

构建预测模型,并进行训练和预测。这里使用线性回归的改进版模型 Lasso 。

from sklearn.linear_model import Lasso
from sklearn.metrics import mean_squared_error
model = Lasso()
model.fit(train_X, train_y)  # 构建模型
y_pred = model.predict(test_X)  # 训练模型
mean_squared_error(y_pred, test_y)  # 预测模型

Lasso 回归的预测结果与真实值的均方差为 6 到 7 左右。同样的方法,使用岭回归(Ridge)重新构建模型。

from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error
model = Ridge()
model.fit(train_X, train_y)
y_pred = model.predict(test_X)
mean_squared_error(y_pred, test_y)

从上面的结果可知,Ridge 回归要相比 Lasso 回归要好一点。

总结

本次是对电影票房进行预测,是一个典型的回归任务。在数据预处理时,主要是通过手动来提取特征,并可视化。在数据预处理完成之后,我们还对原始数据的票房列和预估列进行了平滑。在构建预测模型时,主要使用常见的 Lasso 模型和 Ridge 模型。

分类:

技术点:

相关文章: