TMDb电影票房分析 电影评分预测器

太长不看版

几个有趣的发现:

  1. 电影的投资回报率在稳步上升,至2015年约为2.5倍,电影行业前景光明。
  2. 电影票房评分的相关性不大。
  3. 动画片是平均票房最高的影片类型,但位于票房最顶端的往往是科幻+动作片。剧情片数量最多,但票房表现平平。
  4. 如果预算有限又想获得高收益,纪录片是非常好的选择。
  5. 4月和5月是上映影片的最佳档期,竞争小,容易获得高票房。

电影评分预测器,请拉到最底~~~

前言

Hi! 这是我的第一个数据分析项目,展示了问题分解、数据清洗、数据分析与可视化的过程,最后给出了一个简单的预测模型。我从中学到了很多,在此分享出来,请多多指教!

数据来源是Kaggle提供的Internet Movie Database(TMDb)数据集,包括了近五千部电影的信息。数据原地址:TMDB 5000 Movie Dataset

我已将代码放到GitHub上,如果想看更多的分析和更详细的代码解释,请点这里:

Yu-Chiao/TMDb_Movies

本文的框架如下:

  1. 准备工作
    1.1 数据载入和预览
    1.2 数据清洗和加工
    1.3 数据筛选
  2. 票房分析
    2.0 概览(票房Top 10、变化趋势)
    2.1 类型(不同类型影片的数量变化趋势、票房)
    2.2 导演(Top 10、票房分布)
    2.3 主演(Top 10、票房分布)
    2.4 档期(数量分布、票房分布)
  3. 电影评分预测
    3.1 相似度计算
    3.2 评分预测

1 - 准备工作

1.1 - 数据载入和预览

数据载入:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('ggplot')
%matplotlib inline
file = r'C:UsersDownloadsTMDB Moviestmdb_5000_movies.csv'
movies = pd.read_csv(file)
file = r'C:UsersDownloadsTMDB Moviestmdb_5000_credits.csv'
credits = pd.read_csv(file)

数据预览:

movies.info()
movies.head(2)

TMDb电影票房分析  电影评分预测器

credits.info()
credits.head(2)

TMDb电影票房分析  电影评分预测器

这两个数据集提供了4803部电影的预算、票房、评分、职员表等诸多信息,而且数据比较完整(缺失值很少)。

1.2 - 数据清洗和加工

数据清洗主要包括查漏补缺、去重、纠错。

  • 查漏补缺:数据集中homepage、runtime、tagline有缺失,但它们也不是我们关心的信息,可以忽略。
  • 纠错:目前看不出有没有错误,在后续分析中再考虑。
  • 去重:movies数据集中的id是每部电影的识别码,以此来看看有没有重复数据:
len(movies.id.unique())

TMDb电影票房分析  电影评分预测器

有4803个不重复的id,可以认为没有重复数据。

数据加工主要是对一些字段进行提取和转换。 两个数据集都有电影id,用merge将两个表合并:

movies_credits = movies.merge(credits, left_on = 'id', right_on = 'movie_id', how = 'left')

从预览中可以观察到,genres(类型)、cast(职员表)这些数据都是json格式的(如下图)

movies_credits.genres[0]

TMDb电影票房分析  电影评分预测器

我们实际需要的是name字段对应的名字,因此将这些数据转为名字列表方便后续分析。

#使用json.loads对数据进行读取:
import json
json_columns = ['genres', 'keywords', 'production_companies', 'production_countries', 'cast', 'crew']
for column in json_columns:
    movies_credits[column] = movies_credits[column].apply(json.loads)

#将数据中name字段对应的名字提取出来,用列表推导式可以简洁地构造提取函数:
def extractName(column):
    col = [[di['name'] for di in row] for row in column]
    return col
ex_name = ['genres', 'production_companies', 'production_countries']
for column in ex_name:
    movies_credits[column] = extractName(movies_credits[column])
movies_credits['actors'] = [[di['name'] for di in row[0:4]] for row in movies_credits['cast']]    #演员只取前4位

现在genres等列变为名字列表了

movies_credits.genres[0]

TMDb电影票房分析  电影评分预测器

导演信息储存在crew一列中,将其提取出来。对于crew中没有该职位的情况,用空值代替:

def extractDirector(crew, job):
    name = ''
    for di in crew:
        if di['job'] == job:
            name = di['name']
            break 
        else:
            pass
    return name
movies_credits['director'] = [extractDirector(crew, 'Director') for crew in movies_credits.crew]

提取电影发布时间(release_date)一列中的年份,方便后续按年份来统计:

movies_credits['year'] = pd.to_datetime(movies_credits['release_date']).apply(lambda x: x.year)

1.3 - 数据筛选

提取出我们后续分析需要的变量,去掉空值,看看数学统计:

movies = movies_credits[['title_x', 'genres', 'director', 'actors', 'budget', 'revenue', 'vote_average', 'vote_count', 'year']].dropna()
movies.rename(columns={'title_x': 'title'}, inplace = True)
movies.year = movies.year.astype(int)
movies.describe()

TMDb电影票房分析  电影评分预测器

评分人数(vote_count)过低的电影,其评分(vote_average)也不具有统计意义,这里筛选评分人数大于40的数据。其他票房、预算等为0的数据也应该筛去。此外,电影上映年份的跨度很大,如果考虑很大的时间跨度,会引入诸多影响因子(例如通货膨胀对票房金额的影响),不利于发现规律,因此有必要筛选以缩小研究范围。这里截取2000年之后的数据:

movies[movies.year >= 2000].groupby('year').size().plot(kind = 'bar')

TMDb电影票房分析  电影评分预测器

从图上看,每年的电影数量都在200部左右(实际上映电影数量更多,说明该数据有一定的局限性),但是2016年的数据远小于200部,可以认为该数据集并未收集全2016年的数据。因此,我们的研究范围限制在2000年至2015年之间:

movies_15 = movies[(movies.year >= 2000) & (movies.year  40) &(movies.budget * movies.revenue * movies.vote_average !=0)].reset_index(drop = 'True')

2 - 票房分析

这个数据集提供了丰富的信息,可以从多种维度进行分析。下面我的分析主要是为了回答这个问题:怎样的电影更有可能获得高票房?票房的影响因素有不少,例如影片类型、导演、主演、档期等,我将对这4个因素进行分析。

2.0 - 票房概览

2.0.1 - 票房Top 10

movies_15.sort_values('revenue', ascending=False)[['title', 'revenue', 'budget', 'genres']][0:10]

TMDb电影票房分析  电影评分预测器

  1. 这10部影片预算是亿级(美元)的,票房是十亿级的,属于高投入高收入的影片。
  2. 有8部是动作/科幻片,2部动画片,显然电影类型对票房是有影响的,那么是不是动作/科幻片就一定带来高票房呢?后面会进行相关分析。

2.0.2 票房、预算和投资回报率(ROI)变化趋势

b_r = movies_15.groupby('year')['budget', 'revenue'].sum()
b_r['ROI'] = (b_r.revenue - b_r.budget) / b_r.budget
#作图:
fig, axes = plt.subplots(2, 1, figsize=(6, 6))
b_r.iloc[:, 0:2].plot(kind='bar', ax=axes[0], title='Budget and Revenue')
axes[0].set_ylabel('Dollar')
b_r.ROI.plot(ax=axes[1], title='Evolution of ROI')
fig.tight_layout()

TMDb电影票房分析  电影评分预测器

2000年至2015年间,电影制作的经费投入并没有显著增长,但票房收入呈上升趋势,相应地,ROI从2000年的1.4升到了2015年的2.5,电影行业正处于稳步上升的阶段。

2.0.3 - 票房的影响因素(数值型变量)

import seaborn as sns
sns.heatmap(movies_15.corr(), annot=True, vmax=1, square=True, cmap="Blues")

TMDb电影票房分析  电影评分预测器

  1. 票房与预算、评论数的相关性较大,但评论数和票房一样,只能等到电影上映后才知道具体数值。
  2. 票房与评分的相关性不大,也就是说“叫好”与“叫座”没什么关系。

2.1 - 影片类型的影响

2.1.1 - 影片类型的变化趋势

一部电影可归为多种类型,先统计一下各种类型出现的次数。通过定义一个计数函数来实现:

def countN(column):
    count = dict()
    for row in column:
        for ele in row:
            if ele in count:
                count[ele] += 1
            else:
                count[ele] = 1
    return count

每种类型出现的次数除以总的影片数,以此作为该种类型的频数百分比:

genres = pd.Series(countN(movies_15.genres)).sort_values()
genres_avg = genres / len(movies_15)
genres_avg.plot(kind = 'barh', title = 'Frequency of Genrese')

TMDb电影票房分析  电影评分预测器

  1. 影片有18种类型,剧情、喜剧、惊悚、动作这4种类型的影片最多,西部片和纪录片最少。
  2. 每100部影片中就有约45部属于剧情片,是大家拍摄电影的首选。

选取前9种类型,观察它们在这15年间每年的数量与当年影片总数之比的变化:

genres_by_year = movies_15.groupby('year').genres.sum()
genres_count = pd.DataFrame([], index = genres_by_year.index, columns = genres.index[0:9])
for g in genres_count.columns:
    for y in genres_count.index:
        genres_count.loc[y,g] = genres_by_year[y].count(g) / len(genres_by_year[y])
genres_count.plot(figsize = (10,6), title = 'Evolution of Movies in 9 Genres')

TMDb电影票房分析  电影评分预测器

  1. 各种类型的数量占比有一定的浮动,但总体趋势变化不大。
  2. 剧情片一直是拍摄电影的首选,近年来还有上涨的态势。
  3. 近年来喜剧片的占比下滑,冒险类的占比升高。

2.1.2 - 不同类型影片的票房

计算方法:票房的影响因素有很多,这里单纯考虑类型对票房的影响。对于某种类型,计算所有该类影片的票房,再除以该类影片的数量。对于预算也采用同样的计算方法。

movies_by_genres = pd.DataFrame(0, index = genres.index, columns = ['revenue', 'budget', 'vote'])
for i in range(len(movies_15)):
    for g in movies_15.genres[i]:
        movies_by_genres.loc[g, 'revenue'] += movies_15.revenue[i]    #该类影片的总票房
        movies_by_genres.loc[g, 'budget'] += movies_15.budget[i]    #该类型影片的总均预算
        movies_by_genres.loc[g, 'vote'] += movies_15.vote_average[i]    #该类型影片的总评分
movies_by_genres = movies_by_genres.div(genres.values, axis=0)
movies_by_genres['ROI'] = (movies_by_genres.revenue - movies_by_genres.budget) / movies_by_genres.budget

#作图:
fig, axes = plt.subplots(2, 1, figsize=(8, 8))
movies_by_genres.sort_values('revenue', ascending=False)[['revenue', 'budget']].plot(ax=axes[0], kind = 'bar', title='Average Revenue and Budget in Different Genres')
movies_by_genres.sort_values('revenue', ascending=False)['ROI'].plot(ax=axes[1], kind = 'bar', title='ROI in Different Genres')
fig.tight_layout()

TMDb电影票房分析  电影评分预测器

  1. 票房最高的影片类型是:动画、奇幻和冒险,其次是家庭、科幻和动作,它们比其余类型影片的票房高了一大截,当然它们的预算也相对较高。
  2. 票房高的影片类型,其投资回报率也是不错的。
  3. 剧情片和喜剧虽然热门(影片数量多),但投资回报率表现平平。
  4. 投资回报率最高的是较为小众的纪录片,如果预算有限又想获得高收益,纪录片不失为一个好选择。
  5. 西部片、历史片和战争片的投资回报率垫底,拍摄此类影片需谨慎。

影片类型对票房的影响,还可以进行更深入的分析,比如:科幻+剧情的影片是否比单纯的科幻片或单纯的剧情片有更高的投资回报率?我们算一下看看:

genres_c = pd.Series()
movies_dra_sci = movies_15[movies_15.genres.str.contains('Action', regex=False) & movies_15.genres.str.contains('Science Fiction', regex=False)]    #科幻+剧情
genres_c['Drama and Science Fiction'] = (movies_dra_sci.revenue.sum() - movies_dra_sci.budget.sum()) / movies_dra_sci.budget.sum()
movies_dra = movies_15[movies_15.genres.str.contains('Action', regex=False) & ~movies_15.genres.str.contains('Science Fiction', regex=False)]    #只有剧情
genres_c['Drama'] = (movies_dra.revenue.sum() - movies_dra.budget.sum()) / movies_dra.budget.sum()
movies_sci = movies_15[~movies_15.genres.str.contains('Action', regex=False) & movies_15.genres.str.contains('Science Fiction', regex=False)]    #只有科幻
genres_c['Science Fiction'] = (movies_sci.revenue.sum() - movies_sci.budget.sum()) / movies_sci.budget.sum()
genres_c.plot(kind = 'barh', title = 'ROI')

TMDb电影票房分析  电影评分预测器

科幻+剧情的影片确实比单纯的科幻片有着更高的投资回报率,因此,拍摄科幻片的时候不能一味地追求特效而忽略的剧本的质量,否则会对投资回报率造成负面影响。

其他类型叠加的效果也可以进行类似的分析,这里就不再展开了。

2.2 - 影片导演的影响

2.2.1 - 导演的票房分布

revenue_of_director = movies_15.groupby('director').revenue.mean()    #平均票房
revenue_of_director.hist(bins=100, figsize=(8,3))

TMDb电影票房分析  电影评分预测器

典型的长尾分布,极少数导演的吸金能力特别强,下一节我们来看下他们是谁。

2.2.2 - 票房最高的导演Top 10

revenue_of_director.sort_values().tail(10).plot(kind = 'barh', title = 'Directors with Top Revenue')

TMDb电影票房分析  电影评分预测器

卡梅隆导演一枝独秀,由他执导的影片的票房远远超过了其他导演。第二至四名都是动画片导演,从前面的分析我们已得知,动画片是平均票房最高的影片类型,因此这几位导演未必真的比拍其他类型影片的导演更有吸金能力,而可能只是有动画片这个类型的加成。若要排除类型的干扰,可以分类型进行排序。例如,我们看一下科幻片中吸金能力最强的导演:

TMDb电影票房分析  电影评分预测器

除了卡梅隆,执导《钢铁侠3》的沙恩布莱克等人也是较为优秀的科幻片导演。

2.3 - 影片主演的影响

2.3.1 - 主演的票房分布

这里不考虑动画片配音,因此把动画片先排除:

movies_noani = movies_15[~movies_15.genres.str.contains('Animation', regex=False)].reset_index(drop = 'True') 

我们知道电影主演对票房的贡献有轻重之分,如果忽略这一点,使用和电影类型一样的计算方法,则计算结果可能会显示常演配角的人比常演主角的人的票房更高。这里尝试通过一个加权系数体现这个区别。我们只考虑前4位主演,每位主演对票房的贡献按下面的列表来计算:

actors = pd.Series(countN(movies_noani.actors)).sort_values()
movies_by_actors = pd.DataFrame(0, index = actors.index, columns = ['revenue', 'vote'])
#按不同权重统计演员的票房:
r4 = [0.4, 0.3, 0.2, 0.1]    #如果有4位主演,按此加权,以下类似
r3 = [0.4, 0.3, 0.3]
r2 = [0.6, 0.4]
r1 = [1]
r = [r1, r2, r3, r4]
for i in range(len(movies_noani)):
    actorlist = movies_noani.actors[i][0:4]
    for j in range(len(actorlist)):
        movies_by_actors.loc[actorlist[j], 'revenue'] += movies_noani.revenue[i] * r[len(actorlist)-1][j]    #一个演员的总票房
        movies_by_actors.loc[actorlist[j], 'vote'] += movies_noani.vote_average[i]    #一个演员的总评分
movies_by_actors = movies_by_actors.div(actors.values, axis=0)    #求平均值

#作图
movies_by_actors.revenue.hist(bins=100)

TMDb电影票房分析  电影评分预测器

同样是长尾分布,不过演员之间的票房差距没有导演之间的那么大。

2.3.2 - 票房最高的主演Top 10

movies_by_actors.revenue.sort_values().tail(10).plot(kind = 'barh')

TMDb电影票房分析  电影评分预测器

参演电影票房最高的是饰演哈利波特的丹尼尔,紧随其后的是出演了阿凡达的萨姆和出演了指环王的伊利亚伍德。

2.4 - 档期的影响

2.4.1 - 档期的分布

movies_15['month'] = pd.to_datetime(movies_credits['release_date']).apply(lambda x: x.month)
movies_15['day'] = pd.to_datetime(movies_credits['release_date']).apply(lambda x: x.day)
movies_15.month.hist()

TMDb电影票房分析  电影评分预测器

  1. 电影的出版方最喜欢在12月发布新片,其次是1月份。这两个月份的竞争会比较激烈。
  2. 4月和5月电影的上映数量是最少的,竞争最小。

来看看在12月份每一天的上映数量:

movies_15[movies_15.month == 12].day.hist()

TMDb电影票房分析  电影评分预测器

圣诞节这一天上映的电影数量最多,在圣诞节前的两个星期竞争就开始变得激烈了。

2.4.2 - 票房与档期的关系

计算每个月单部影片的平均票房:

revenue_month = movies_15.groupby('month').revenue.sum() / movies_15.groupby('month').size()
revenue_month.plot(kind='bar', title='Average Revenue per Month')

TMDb电影票房分析  电影评分预测器

  1. 5月份的电影的平均票房最高,1月份的平均票房最低。
  2. 4月和5月上映的电影数量少,平均票房高,是新电影安排档期的最佳选择。

3 - 电影评分预测器

在这一节我们尝试构造一个评分的预测器。为什么这里不做票房预测器呢?因为票房还需要考虑大环境(经济环境、电影行业趋势)的影响,基于这个数据集提供的数据,对评分可以进行更准确的预测。

预测思路:假设评分的主要影响因素是影片类型、导演和主演,对于待预测的影片,筛选出这3个因素与之相似程度最高的5部影片,计算它们的平均评分,作为待预测影片的评分。

3.1 - 相似程度计算

计算方法:以类型为例,假设现有3中影片类型(科幻、动作、剧情),A影片为科幻+动作,B影片为动作,构造一个二元数组来表示影片的类型,A影片为[1, 1, 0],B影片为[0, 1, 0]。两部影片的相似程度可以用它们的向量夹角(cos(A, B))来表示,值越大说明越不相似。

首先对于每部影片都构造二元数组表示类型、导演和主演:

def binary(wordlist0, wordlist):
    binary = []
    for word in wordlist0.index:
        if word in wordlist:
            binary.append(1)
        else:
            binary.append(0)
    return binary
movies_15['genres_bin'] = [binary(genres, x) for x in movies_15.genres]    #影片类型的二元数组
directors = movies_15.groupby('director').size().sort_values(ascending=False)
movies_15['director_bin'] = [binary(directors, x) for x in movies_15.director]    #影片导演的二元数组
actors = pd.Series(countN(movies_15.actors)).sort_values(ascending=False)
movies_15['actors_bin'] = [binary(actors, x) for x in movies_15.actors]    #影片主演的二元数组

定义一个函数计算两部影片的夹角(即不相似度):

from scipy import spatial
def angle(movie1, movie2):
    dis_tot = 0
    iterlist = [[movie1.genres_bin, movie2.genres_bin],
                [movie1.director_bin, movie2.director_bin],
                [movie1.actors_bin, movie2.actors_bin]]                
    for b1, b2 in iterlist:
        if (1 not in b1) or (1 not in b2):
            dis = 1
        else:
            dis = spatial.distance.cosine(b1, b2)
        dis_tot += dis
    return dis_tot

找3部影片试验一下距离计算的效果。movies_15数据集中,第1部影片是《阿凡达》,第6部影片是《蜘蛛侠3》,第7部影片是《长发公主》。

angle(movies_15.iloc[0], movies_15.iloc[5])    #《阿凡达》与《蜘蛛侠3》的夹角

TMDb电影票房分析  电影评分预测器

angle(movies_15.iloc[0], movies_15.iloc[6])    #《阿凡达》与《长发公主》的夹角

TMDb电影票房分析  电影评分预测器

计算结果表明《阿凡达》与《蜘蛛侠3》更相似,实际也正是这样,因为他们都是动作片。

3.2 - 评分预测

def predictor(new_movie):
    movie_bin = pd.Series()
    movie_bin['genres_bin'] = binary(genres, new_movie['genres'])
    movie_bin['director_bin'] = binary(directors, new_movie['director'])
    movie_bin['actors_bin'] = binary(actors, new_movie['actors'])
    vote = movies_15.copy()
    vote['angle'] = [angle(vote.iloc[i], movie_bin) for i in range(len(vote))]
    vote = vote.sort_values('angle')
    vote_avg = np.mean(vote.vote_average[0:5])
    return vote_avg

这样我们的电影评分预测器就构造好了,来试验一下吧!

《正义联盟》是2017年上映的电影,不在这个数据集中,我们来预测一下它的评分。将它的类型、导演和演员记入一个字典中:

Justice_league = {'genres': ['Action', 'Adventure', 'Fantasy', 'Science Fiction'], 'director': ['Zack Snyder'], 'actors': ['Ben Affleck', 'Henry Cavill', 'Amy Adams', 'Gal Gadot', 'Ezra Miller']}
predictor(Justice_league)

TMDb电影票房分析  电影评分预测器

IMDb上《正义联盟》的评分是6.6分,我们的预测值6.64可以说是非常准确了!

再接再厉,《敦刻尔克》也是2017年才上映的电影,不在这个数据集中,我们来预测一下它的评分:

Dunkirk = {'genres': ['Action', 'Drama', 'History', 'Thriller', 'War'], 'director': ['Christopher Nolan'], 'actors': ['Fionn Whitehead', 'Damien Bonnard', 'Aneurin Barnard', 'Lee Armstrong', 'James Bloor']}
predictor(Dunkirk)

TMDb电影票房分析  电影评分预测器

IMDb上《敦刻尔克》的评分是8.0分,我们的预测值是7.88,准确度还是挺高的。

我已将代码放到了GitHub上(链接在前言一节),如果大家喜欢这个评分预测器,可以下载到自己的Python上运行。Enjoy yourself!

  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
afiregame
  • 本文由 发表于 2021年12月12日00:27:59
  • 转载请务必保留本文链接:https://www.afiregame.com/zixun/14079/
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: