TMDB電影數據分析報告

本報告的數據來源於Kaggle平台上的項目TMDB(The Movie Database),共計4803部電影,主要為美國地區一百年間(1916-2017)的電影作品。

通過對電影數據的分析,利用數據可視化的方法,發現流行趨勢,找到投資方向,為本行業新入局者提供一定參考建議。

本文重點在於數據可視化方法,未能面面俱到敬請諒解。


〇. 前言

再複雜的數據分析任務都需要遵從一個標準流程,有了流程指導,分析思路和處理過程才不會混亂。

數據分析的流程:

  1. 提出問題
  2. 理解數據
  3. 數據清洗
  4. 建立模型
  5. 數據可視化
  6. 形成報告

一. 提出問題

本次數據分析的核心任務是:通過對歷史電影數據的統計分析,為行業新入局者提供洞見

細化為下述幾個小問題:

  • 問題1:電影風格隨時間的變化趨勢
  • 問題2:不同風格電影的收益能力
  • 問題3:不同風格電影的受歡迎程度
  • 問題4:不同風格電影的平均評分
  • 問題5:不同風格電影的平均評價次數
  • 問題6:比較行業內Universal Pictures與Paramount Pictures兩家巨頭公司的業績
  • 問題7:原創電影與改編電影的對比
  • 問題8:票房收入與哪些因素最相關

二. 理解數據

從Kaggle平台上下載2個原始數據集:tmdb_5000_movies.csv和tmdb_5000_credits.csv,前者存放電影的基本信息,後者存放電影的演職員名單。

導入後通過對數據的查看,篩選出本次重點研究的變數:

本次分析用到的變數

三. 數據清洗

數據清洗主要分三步:1.數據預處理;2.特徵提取;3.特徵選取。

3.1 數據預處理

在數據預處理時,主要包括:發現和填補缺失值、數據類型轉換、異常值刪除等。

本數據集的release_date列缺失1條數據,runtime列缺失2條數據,均可通過索引的方式找到具體是哪一部電影,然後上網搜索準確的缺失數據,將其填補(詳見後續代碼)。

對於release_date列,需將其轉換為日期類型,然後提取出「年份」數據。

3.2 特徵提取

原始數據從網頁爬取之後,數據集中的genres、keywords、production_companies列為JSON結構,不便於數據分析。

處理過程:通過json.loads先將JSON字元串轉換為字典列表"[{},{},{}]"的形式,再遍歷每個字典,取出鍵(key)為『name』所對應的值(value),並將這些值(value)用「|」分隔,形成一個「多選題」的結構。在進行具體問題分析的時候,再將「多選題」編碼為虛擬變數,即所有多選題的每一個不重複的選項,拿出來作為新變數,每一條觀測包含該選項則填1,否則填0。

3.3 特徵選取

在分析每一個小問題之前,最重要的步驟是需要通過特徵選取,構造出適合分析的結構,只有結構「造」對了,後續的可視化才能得出正確的圖形。

這裡的小竅門就是:分析每一個小問題時,盡量新建一個數據框,存放要分析的變數,而不是在原始數據框上「亂塗亂畫」,否則最後必定抓狂。

四. 數據可視化

對於可視化分析問題,建立模型一般都和特徵選取、新建數據框一同完成,本項目不屬於機器學習範疇,故無需建模。

數據可視化則是根據每一個問題,以及特徵選取之後新建的數據框進行可視化展示,便於發現數據背後的規律和真相。

本次使用到的圖形類型有:水平條形圖、柱狀圖、餅圖、直方圖、折線圖、散點圖。

五. 形成報告


代碼部分:

二、理解數據

#導入各種包import numpy as npimport pandas as pdimport matplotlib.pyplot as plt%matplotlib inlineimport jsonimport warningswarnings.filterwarnings(ignore)import seaborn as snssns.set(color_codes=True)#導入數據movies = pd.read_csv(./tmdb_5000_movies.csv)credits = pd.read_csv(./tmdb_5000_credits.csv)#查看數據movies.head(2)#查看變數movies.columns#查看數據credits.head(2)#查看變數credits.columns#兩個數據框中的title列重複了,刪除credits中的title列del credits[title]#movies中的id列與credits中的movie_id列實際上等同,可當做主鍵合併數據框full = pd.merge(movies, credits, left_on=id, right_on=movie_id, how=left)#某些列不在本次研究範圍,將其刪除full.drop([homepage,original_title,overview,spoken_languages,status,tagline,movie_id],axis=1,inplace=True)#查看數據信息full.info()

三、數據清洗

release_date列有1條缺失數據,將其查找出來

full.loc[full[release_date].isnull()==True]#經上網搜索,該影片上映日期為2014年6月1日,填補該值full[release_date] = full[release_date].fillna(2014-06-01)

runtime列有2條缺失數據,將其查找出來

full.loc[full[runtime].isnull()==True]#經上網搜索,影片時長分別為94分鐘和240分鐘,填補缺失值full[runtime] = full[runtime].fillna(94, limit=1) #limit=1,限制每次只填補一個值full[runtime] = full[runtime].fillna(240, limit=1)

將release_date列轉換為日期類型

#應用了日期型數據的dt方法,然後轉換為「年份」full[release_date] = pd.to_datetime(full[release_date], format=%Y-%m-%d, errors=coerce).dt.yeargenres/keywords/production_companies/production_countries/cast/crew列為json類型需要解析json數據,分兩步:1. json本身為字元串類型,先轉換為字典列表2. 再將字典列錶轉換為,以|分割的字元串#定義一個json類型的列名列表json_column = [genres,keywords,production_companies,production_countries,cast,crew]#將各json列轉換為字典列表for column in json_column: full[column]=full[column].map(json.loads)#函數功能:將字典內的鍵『name』對應的值取出,生成用|分隔的字元串def getname(x): list = [] for i in x: list.append(i[name]) return |.join(list)#對genres/keywords/production_companies/production_countries列執行函數for column in json_column[0:4]: full[column] = full[column].map(getname)#定義提取2名主演的函數:def getcharacter(x): list = [] for i in x: list.append(i[character]) return |.join(list[0:2])#對cast列執行函數full[cast]=full[cast].map(getcharacter)#定義提取導演的函數:def getdirector(x): list=[] for i in x: if i[job]==Director: list.append(i[name]) return "|".join(list)#對crew列執行函數full[crew]=full[crew].map(getdirector)#重命名列rename_dict = {release_date:year,cast:actor,crew:director}full.rename(columns=rename_dict, inplace=True)#查看數據full.head(2)#備份原始數據框original_dforiginal_df = full.copy()

四、數據分析及可視化

問題1:研究電影風格隨時間的變化趨勢

#提取所有的電影風格,存儲在有去重功能的集合中genre_set = set()for x in full[genres]: genre_set.update(x.split(|))genre_set.discard()#對各種電影風格genre,進行one-hot編碼genre_df = pd.DataFrame()for genre in genre_set: #如果一個值中包含特定內容,則編碼為1,否則編碼為0 genre_df[genre] = full[genres].str.contains(genre).map(lambda x:1 if x else 0)#將原數據集中的year列,添加至genre_dfgenre_df[year]=full[year]#將genre_df按year分組,計算每組之和。genre_by_year = genre_df.groupby(year).sum() #groupby之後,year列通過默認參數as_index=True自動轉化為df.indexgenresum_by_year = genre_by_year.sum().sort_values(ascending=False)#計算每個風格genre的電影總數目,並降序排列,再可視化fig = plt.figure(figsize=(15,11))ax = plt.subplot(1,1,1)ax = genresum_by_year.plot.bar()plt.xticks(rotation=60)plt.title(Film genre by year, fontsize=18)plt.xlabel(count, fontsize=18)plt.ylabel(genre, fontsize=18)#保存圖片fig.savefig(film genre by year.png)#篩選齣電影風格TOP8genre_by_year = genre_by_year[[Drama,Comedy,Thriller,Romance, Adventure,Crime, Science Fiction,Horror]].loc[1960:,:]year_min = full[year].min()year_max = full[year].max()year_min,year_max#可視化電影風格genre隨時間的變化趨勢(1960年至今)fig = plt.figure(figsize=(10,8))ax1 = plt.subplot(1,1,1)plt.plot(genre_by_year)plt.xlabel(Year, fontsize=12)plt.ylabel(Film count, fontsize=12)plt.title(Film count by year, fontsize=15)plt.xticks(range(1960, 2017, 10)) #橫坐標每隔10年一個刻度plt.legend(loc=best,ncol=2)fig.savefig(film count by year.png)

可以看出,從上世紀90年代開始,整個電影市場呈現爆髮式增長。其中,排名前五的戲劇類(Drama)、喜劇類(Comedy)、驚悚類(Thriller)、浪漫類(Romance)、冒險類(Adventure)電影數量增長顯著。

問題2:不同風格電影的收益能力

#增加收益列full[profit] = full[revenue]-full[budget]#創建收益數據框profit_df = pd.DataFrame()profit_df = pd.concat([genre_df.iloc[:,:-1],full[profit]],axis=1)profit_df.head()#創建一個Series,其index為各個genre,值為按genre分類計算的profit之和profit_by_genre = pd.Series(index=genre_set)for genre in genre_set: profit_by_genre.loc[genre]=profit_df.loc[:,[genre,profit]].groupby(genre, as_index=False).sum().loc[1,profit]profit_by_genre#創建一個Series,其index為各個genre,值為按genre分類計算的budget之和budget_df = pd.concat([genre_df.iloc[:,:-1],full[budget]],axis=1)budget_df.head(2)budget_by_genre = pd.Series(index=genre_set)for genre in genre_set: budget_by_genre.loc[genre]=budget_df.loc[:,[genre,budget]].groupby(genre,as_index=False).sum().loc[1,budget]budget_by_genre#橫向合併數據框profit_rate = pd.concat([profit_by_genre, budget_by_genre],axis=1)profit_rate.columns=[profit,budget]#添加收益率列。乘以100是為了方便後續可視化以百分比顯示坐標軸刻度標籤profit_rate[profit_rate] = (profit_rate[profit]/profit_rate[budget])*100profit_rate.sort_values(by=[profit,profit_rate], ascending=False, inplace=True)profit_rate#x為索引長度的序列x = list(range(len(profit_rate.index)))#xl為索引實際值xl = profit_rate.index#可視化不同風格電影的收益(柱狀圖)和收益率(折線圖)fig = plt.figure(figsize=(18,13))ax1 = fig.add_subplot(111)plt.bar(x, profit_rate[profit],label=profit,alpha=0.7)plt.xticks(x,xl,rotation=60,fontsize=12)plt.yticks(fontsize=12)ax1.set_title(Profit by genres, fontsize=20)ax1.set_ylabel(Film Profit,fontsize=18)ax1.set_xlabel(Genre,fontsize=18)ax1.set_ylim(0,1.2e11)ax1.legend(loc=2,fontsize=15)#次縱坐標軸標籤設置為百分比顯示import matplotlib.ticker as mtickax2 = ax1.twinx()ax2.plot(x, profit_rate[profit_rate],ro-,lw=2,label=profit_rate)fmt=%.2f%%yticks = mtick.FormatStrFormatter(fmt)ax2.yaxis.set_major_formatter(yticks)plt.xticks(x,xl,fontsize=12,rotation=60)plt.yticks(fontsize=15)ax2.set_ylabel(Profit_rate,fontsize=18)ax2.legend(loc=1,fontsize=15)plt.grid(False)#保存圖片fig.savefig(profit by genres.png)

可以看出,冒險類(Adventure)、動作類(Action)、喜劇類(Comedy)、戲劇類(Drama)、驚悚類(Thriller)電影收益最高;同時,這五類電影的收益率也都處於較高水平(150%以上)。

喜劇類(Comedy)、戲劇類(Drama)、驚悚類(Thriller)、冒險類(Adventure)電影收益率與電影數量排名基本吻合。

註:收益=票房收入-預算

收益率=(收益/預算)*100%

問題3:不同風格電影的受歡迎程度

#創建受歡迎程度數據框popu_df = pd.DataFrame()popu_df = pd.concat([genre_df.iloc[:,:-1], full[popularity]],axis=1)popu_df.head()#計算每個風格電影的受歡迎程度的均值popu_mean_list=[]for genre in genre_set: popu_mean_list.append(popu_df.groupby(genre, as_index=False).mean().loc[1,popularity])popu_by_genre = pd.DataFrame(index=genre_set)popu_by_genre[popu_mean] = popu_mean_listpopu_by_genre.sort_values(popu_mean,inplace=True)#可視化不同風格電影的平均受歡迎程度fig = plt.figure(figsize=(14,8))ax = plt.subplot(1,1,1)popu_by_genre.plot(ax=ax,kind=barh)plt.title(Popularity by genre, fontsize=18)plt.ylabel(Film genres,fontsize=15)plt.xlabel(Mean of popularity, fontsize=15)plt.xticks(fontsize=12)plt.yticks(fontsize=12)plt.legend(fontsize=12)fig.savefig(popularity by genre)

可以看出,冒險類(Adventure)、動畫類(Animation)最受歡迎

問題4:不同風格電影的平均評分

#創建平均評分數據框vote_avg_df = pd.concat([genre_df.iloc[:,:-1], full[vote_average]],axis=1)vote_avg_df.head(2)#計算不同風格電影的平均評分voteavg_mean_list=[]for genre in genre_set: voteavg_mean_list.append(vote_avg_df.groupby(genre,as_index=False).mean().loc[1,vote_average])#形成目標數據框voteavg_mean_by_genre = pd.DataFrame(index=genre_set)voteavg_mean_by_genre[voteavg_mean]=voteavg_mean_list#受歡迎度和平均評分的相關係數full[popularity].corr(full[vote_average])

可以看出:不同風格的電影平均受歡迎程度與平均評分相關性很低

#排序voteavg_mean_by_genre.sort_values(voteavg_mean,ascending=False,inplace=True)#可視化不同風格電影的平均評分fig = plt.figure(figsize=(10,9))ax = fig.add_subplot(111)voteavg_mean_by_genre.plot.bar(ax=ax)plt.title(vote_average by genre,fontsize=15)plt.xlabel(genre,fontsize=13)plt.ylabel(vote_average,fontsize=13)plt.xticks(rotation=45)plt.legend(fontsize=13)plt.ylim(5,7,0.5)fig.savefig(vote_average by genre.png)

不同風格電影的平均評分相差並不懸殊,最高與最低只差了1分有餘。

#可視化所有電影的評分分布fig,ax = plt.subplots(figsize=(8,5))ax = sns.distplot(full[vote_average],bins=10)plt.xticks(np.arange(11))plt.title(Distribution of vote_average, fontsize=15)plt.xlabel(vote_average, fontsize=12)plt.ylabel(quantities, fontsize=12)

* 大部分電影的平均評分分布在5-8分之間 *

#保存圖片fig.savefig(Distribution of vote_average.png)

問題5:不同電影風格與評價次數的關係

#創建評價次數數據框vote_count_df = pd.concat([genre_df.iloc[:,:-1],full[vote_count]],axis=1)vote_count_df.head(2)#計算平均評價次數vote_count_mean_list=[]for genre in genre_set: vote_count_mean_list.append(vote_count_df.groupby(genre,as_index=False).mean().loc[1,vote_count])#形成目標數據框vote_countmean_by_genre = pd.DataFrame(index=genre_set)vote_countmean_by_genre[vote_mean]=vote_count_mean_list#排序vote_countmean_by_genre.sort_values(vote_mean,ascending=False,inplace=True)#可視化不同風格電影的平均評價次數fig = plt.figure(figsize=(10,9))ax = fig.add_subplot(111)vote_countmean_by_genre.plot(ax = ax, kind=bar)plt.title(vote_count_mean by genre,fontsize=15)ax.set_xlabel(genre,fontsize=13)ax.set_ylabel(vote_mean,fontsize=13)plt.xticks(rotation=45)plt.legend(fontsize=13)fig.savefig(vote_count_mean by genre.png)

* 平均評價次數較高的類型:冒險類(Adventure)、科幻類(Science Fiction)*

問題6:比較Universal Pictures與Paramount Pictures兩家巨頭公司的業績

#創建公司數據框company_list = [Universal Pictures, Paramount Pictures]company_df = pd.DataFrame()for company in company_list: company_df[company]=full[production_companies].str.contains(company).map(lambda x:1 if x else 0)company_df = pd.concat([company_df,genre_df.iloc[:,:-1],full[revenue]],axis=1)#創建巨頭對比數據框Uni_vs_Para = pd.DataFrame(index=[Universal Pictures, Paramount Pictures],columns=company_df.columns[2:])#計算二公司收益總額Uni_vs_Para.loc[Universal Pictures]=company_df.groupby(Universal Pictures,as_index=False).sum().iloc[1,2:]Uni_vs_Para.loc[Paramount Pictures]=company_df.groupby(Paramount Pictures,as_index=False).sum().iloc[1,2:]#可視化二公司票房收入對比fig = plt.figure(figsize=(4,3))ax = fig.add_subplot(111)Uni_vs_Para[revenue].plot(ax=ax,kind=bar)plt.xticks(rotation=0)plt.title(Universal VS. Paramount)plt.ylabel(Revenue)fig.savefig(Universal vs Paramount by revenue.png)

Universal Pictrues總票房收入高於Paramount Pictures

#轉置Uni_vs_Para = Uni_vs_Para.T#拆分出二公司數據框universal = Uni_vs_Para[Universal Pictures].iloc[:-1]paramount = Uni_vs_Para[Paramount Pictures].iloc[:-1]#將數量排名9之後的加和,命名為othersuniversal[others]=universal.sort_values(ascending=False).iloc[8:].sum()universal = universal.sort_values(ascending=True).iloc[-9:]universal#將數量排名9之後的加和,命名為othersparamount[others]=paramount.sort_values(ascending=False).iloc[8:].sum()paramount = paramount.sort_values(ascending=True).iloc[-9:]paramount#可視化二公司電影風格數量佔比fig = plt.figure(figsize=(13,6))ax1 = plt.subplot(1,2,1)ax1 = plt.pie(universal, labels=universal.index, autopct=%.2f%%,startangle=90,pctdistance=0.75)plt.title(Universal Pictures,fontsize=15)ax2 = plt.subplot(1,2,2)ax2 = plt.pie(paramount, labels=paramount.index, autopct=%.2f%%,startangle=90,pctdistance=0.75)plt.title(Paramount Pictures,fontsize=15)

* 兩家公司的主要電影類型幾乎一致:喜劇類(Comedy)、戲劇類(Drama)、驚悚類(Thriller)、動作類(Action)*

#存圖fig.savefig(Univesal VS Paramount by genre.png)

問題7:原創電影與改編電影對比

#創建關於原創性的數據框orginal_novel = pd.DataFrame()orginal_novel[keywords] = full[keywords].str.contains(based on).map(lambda x:1 if x else 0)orginal_novel[[revenue,budget]]=full[[revenue,budget]]orginal_novel[profit]=full[revenue]-full[budget]orginal_novel = orginal_novel.groupby(keywords,as_index=False).mean()orginal_novel#創建原創與改編對比數據框org_vs_novel = pd.DataFrame()org_vs_novel[count] = [full.shape[0]-full[keywords].str.contains(based on).sum(), full[keywords].str.contains(based on).sum()]org_vs_novel[profit]=orginal_novel[profit]org_vs_novel.index=[orginal works,based on novel]org_vs_novel#可視化原創與改編電影的數量佔比(餅圖),和片均收益(柱狀圖)fig = plt.figure(figsize=(11,5))ax1 = plt.subplot(1,2,1)ax1 = plt.pie(org_vs_novel[count], labels=org_vs_novel.index, autopct=%.2f%%,startangle=90,pctdistance=0.6)plt.title(Film quantities Comparison
Original works VS based on novel,fontsize=13)ax2 = plt.subplot(1,2,2)ax2 = org_vs_novel[profit].plot.bar()plt.xticks(rotation=0)plt.ylabel(Profit,fontsize=12)plt.title(Profit Comparison
Original works VS based on novel,fontsize=13)

* 可以看出:雖然改編電影的數量相對較少,但是平均每部改編電影的收益卻很高*

#存圖fig.savefig(Comparison of original works and based on novel.png)

問題8:看看票房與哪些因素有關

#計算相關係數矩陣full[[runtime,popularity,vote_average,vote_count,budget,revenue]].corr()

* 受歡迎度和票房相關性:0.64 *

* 評價次數和票房相關性:0.78 *

* 電影預算和票房相關性:0.73 *

#創建票房收入數據框revenue = full[[popularity,vote_count,budget,revenue]]#可視化票房收入分別與受歡迎度(藍)、評價次數(綠)、電影預算(紅)的相關性散點圖,並配線性回歸線。fig = plt.figure(figsize=(17,5))ax1 = plt.subplot(1,3,1)ax1 = sns.regplot(x=popularity, y=revenue, data=revenue, x_jitter=.1)ax1.text(400,3e9,r=0.64,fontsize=15)plt.title(revenue by popularity,fontsize=15)plt.xlabel(popularity,fontsize=13)plt.ylabel(revenue,fontsize=13)ax2 = plt.subplot(1,3,2)ax2 = sns.regplot(x=vote_count, y=revenue, data=revenue, x_jitter=.1,color=g,marker=+)ax2.text(5800,2.2e9,r=0.78,fontsize=15)plt.title(revenue by vote_count,fontsize=15)plt.xlabel(vote_count,fontsize=13)plt.ylabel(revenue,fontsize=13)ax3 = plt.subplot(1,3,3)ax3 = sns.regplot(x=budget, y=revenue, data=revenue, x_jitter=.1,color=r,marker=^)ax3.text(1.6e8,2.2e9,r=0.73,fontsize=15)plt.title(revenue by budget,fontsize=15)plt.xlabel(budget,fontsize=13)plt.ylabel(revenue,fontsize=13)#存圖fig.savefig(revenue.png)


總結

  1. 必須要明確分析目的:要解決的問題是什麼
  2. 原始數據集先備份,避免原地修改導致後續混亂
  3. 每個子問題均需創建單獨的數據框,同樣是避免數據混亂
  4. 道重於術,分析思路最重要,代碼實現方法可隨時上網搜索
  5. 對於數據分析來講,代碼寫繁瑣一點不可怕,重點在分析,而不在演算法速度優化,切不可嫌麻煩而中途放棄
  6. 複雜的循環如果一上來搞不懂,可先處理一個特殊元素,再用循環語句替代
  7. 可視化的世界豐富多彩,應在日常工作中,多觀察,多實踐,讓圖更美一些
  8. 辛辛苦苦的總結,希望對致力於學習的人有一點幫助

推薦閱讀:

為什麼python中不建議在for循環中修改列表?
深度學習中的Python語言2:Numpy數組、索引、數據類型、運算、廣播的介紹
用SVM看看技術指標有沒有用
高顏值的Python編輯器:Visual Studio Code
來編寫你的 setup 腳本(一)

TAG:數據分析 | Python | 數據可視化 |