TMDb電影票房分析 & 電影評分預測器
來自專欄猴子聊數據分析15 人贊了文章
太長不看版
幾個有趣的發現:
- 電影的投資回報率在穩步上升,至2015年約為2.5倍,電影行業前景光明。
- 電影票房與評分的相關性不大。
- 動畫片是平均票房最高的影片類型,但位於票房最頂端的往往是科幻+動作片。劇情片數量最多,但票房表現平平。
- 如果預算有限又想獲得高收益,紀錄片是非常好的選擇。
- 4月和5月是上映影片的最佳檔期,競爭小,容易獲得高票房。
電影評分預測器,請拉到最底~~~
前言
Hi! 這是我的第一個數據分析項目,展示了問題分解、數據清洗、數據分析與可視化的過程,最後給出了一個簡單的預測模型。我從中學到了很多,在此分享出來,請多多指教!
數據來源是Kaggle提供的Internet Movie Database(TMDb)數據集,包括了近五千部電影的信息。數據原地址:TMDB 5000 Movie Dataset
我已將代碼放到GitHub上,如果想看更多的分析和更詳細的代碼解釋,請點這裡:
Yu-Chiao/TMDb_Movies
本文的框架如下:
- 準備工作1.1 數據載入和預覽1.2 數據清洗和加工1.3 數據篩選
- 票房分析2.0 概覽(票房Top 10、變化趨勢)2.1 類型(不同類型影片的數量變化趨勢、票房)2.2 導演(Top 10、票房分布)2.3 主演(Top 10、票房分布)2.4 檔期(數量分布、票房分布)
- 電影評分預測
3.1 相似度計算
3.2 評分預測
1 - 準備工作
1.1 - 數據載入和預覽
數據載入:
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltplt.style.use(ggplot)%matplotlib inlinefile = rC:UsersDownloadsTMDB Movies mdb_5000_movies.csvmovies = pd.read_csv(file)file = rC:UsersDownloadsTMDB Movies mdb_5000_credits.csvcredits = pd.read_csv(file)
數據預覽:
movies.info()movies.head(2)
credits.info()credits.head(2)
這兩個數據集提供了4803部電影的預算、票房、評分、職員表等諸多信息,而且數據比較完整(缺失值很少)。
1.2 - 數據清洗和加工
數據清洗主要包括查漏補缺、去重、糾錯。
- 查漏補缺:數據集中homepage、runtime、tagline有缺失,但它們也不是我們關心的信息,可以忽略。
- 糾錯:目前看不出有沒有錯誤,在後續分析中再考慮。
- 去重:movies數據集中的id是每部電影的識別碼,以此來看看有沒有重複數據:
len(movies.id.unique())
有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]
我們實際需要的是name欄位對應的名字,因此將這些數據轉為名字列表方便後續分析。
#使用json.loads對數據進行讀取:import jsonjson_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 colex_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]
導演信息儲存在crew一列中,將其提取出來。對於crew中沒有該職位的情況,用空值代替:
def extractDirector(crew, job): name = for di in crew: if di[job] == job: name = di[name] break else: pass return namemovies_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()
評分人數(vote_count)過低的電影,其評分(vote_average)也不具有統計意義,這裡篩選評分人數大於40的數據。其他票房、預算等為0的數據也應該篩去。此外,電影上映年份的跨度很大,如果考慮很大的時間跨度,會引入諸多影響因子(例如通貨膨脹對票房金額的影響),不利於發現規律,因此有必要篩選以縮小研究範圍。這裡截取2000年之後的數據:
movies[movies.year >= 2000].groupby(year).size().plot(kind = bar)
從圖上看,每年的電影數量都在200部左右(實際上映電影數量更多,說明該數據有一定的局限性),但是2016年的數據遠小於200部,可以認為該數據集並未收集全2016年的數據。因此,我們的研究範圍限制在2000年至2015年之間:
movies_15 = movies[(movies.year >= 2000) & (movies.year < 2016) & (movies.vote_count > 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]
- 這10部影片預算是億級(美元)的,票房是十億級的,屬於高投入高收入的影片。
- 有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()
2000年至2015年間,電影製作的經費投入並沒有顯著增長,但票房收入呈上升趨勢,相應地,ROI從2000年的1.4升到了2015年的2.5,電影行業正處於穩步上升的階段。
2.0.3 - 票房的影響因素(數值型變數)
import seaborn as snssns.heatmap(movies_15.corr(), annot=True, vmax=1, square=True, cmap="Blues")
- 票房與預算、評論數的相關性較大,但評論數和票房一樣,只能等到電影上映後才知道具體數值。
- 票房與評分的相關性不大,也就是說「叫好」與「叫座」沒什麼關係。
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)
- 影片有18種類型,劇情、喜劇、驚悚、動作這4種類型的影片最多,西部片和紀錄片最少。
- 每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)
- 各種類型的數量佔比有一定的浮動,但總體趨勢變化不大。
- 劇情片一直是拍攝電影的首選,近年來還有上漲的態勢。
- 近年來喜劇片的佔比下滑,冒險類的佔比升高。
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()
- 票房最高的影片類型是:動畫、奇幻和冒險,其次是家庭、科幻和動作,它們比其餘類型影片的票房高了一大截,當然它們的預算也相對較高。
- 票房高的影片類型,其投資回報率也是不錯的。
- 劇情片和喜劇雖然熱門(影片數量多),但投資回報率表現平平。
- 投資回報率最高的是較為小眾的紀錄片,如果預算有限又想獲得高收益,紀錄片不失為一個好選擇。
- 西部片、歷史片和戰爭片的投資回報率墊底,拍攝此類影片需謹慎。
影片類型對票房的影響,還可以進行更深入的分析,比如:科幻+劇情的影片是否比單純的科幻片或單純的劇情片有更高的投資回報率?我們算一下看看:
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)
科幻+劇情的影片確實比單純的科幻片有著更高的投資回報率,因此,拍攝科幻片的時候不能一味地追求特效而忽略的劇本的質量,否則會對投資回報率造成負面影響。
其他類型疊加的效果也可以進行類似的分析,這裡就不再展開了。
2.2 - 影片導演的影響
2.2.1 - 導演的票房分布
revenue_of_director = movies_15.groupby(director).revenue.mean() #平均票房revenue_of_director.hist(bins=100, figsize=(8,3))
典型的長尾分布,極少數導演的吸金能力特彆強,下一節我們來看下他們是誰。
2.2.2 - 票房最高的導演Top 10
revenue_of_director.sort_values().tail(10).plot(kind = barh, title = Directors with Top Revenue)
卡梅隆導演一枝獨秀,由他執導的影片的票房遠遠超過了其他導演。第二至四名都是動畫片導演,從前面的分析我們已得知,動畫片是平均票房最高的影片類型,因此這幾位導演未必真的比拍其他類型影片的導演更有吸金能力,而可能只是有動畫片這個類型的加成。若要排除類型的干擾,可以分類型進行排序。例如,我們看一下科幻片中吸金能力最強的導演:
除了卡梅隆,執導《鋼鐵俠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)
同樣是長尾分布,不過演員之間的票房差距沒有導演之間的那麼大。
2.3.2 - 票房最高的主演Top 10
movies_by_actors.revenue.sort_values().tail(10).plot(kind = barh)
參演電影票房最高的是飾演哈利波特的丹尼爾,緊隨其後的是出演了阿凡達的薩姆和出演了指環王的伊利亞伍德。
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()
- 電影的出版方最喜歡在12月發布新片,其次是1月份。這兩個月份的競爭會比較激烈。
- 4月和5月電影的上映數量是最少的,競爭最小。
來看看在12月份每一天的上映數量:
movies_15[movies_15.month == 12].day.hist()
聖誕節這一天上映的電影數量最多,在聖誕節前的兩個星期競爭就開始變得激烈了。
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)
- 5月份的電影的平均票房最高,1月份的平均票房最低。
- 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 binarymovies_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 spatialdef 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》的夾角
angle(movies_15.iloc[0], movies_15.iloc[6]) #《阿凡達》與《長發公主》的夾角
計算結果表明《阿凡達》與《蜘蛛俠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)
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)
IMDb上《敦刻爾克》的評分是8.0分,我們的預測值是7.88,準確度還是挺高的。
我已將代碼放到了GitHub上(鏈接在前言一節),如果大家喜歡這個評分預測器,可以下載到自己的Python上運行。Enjoy yourself!
推薦閱讀:
※Python數據分析之numpy學習(一)
※一張圖學會python
※數據科學從業人員如何選擇 python IDE
※Python人工智慧庫ailearn使用說明0.1.8
※慶祝法國隊奪冠:用Python放一場煙花秀