MathorCup 高校數學建模挑戰賽——觀影大數據


練習題:觀影大數據分析

S 聰想要在海外開拓萬 D 電影的市場,這次他在考慮:怎么拍商業電影才能賺錢?畢竟一些制作成本超過 1 億美元的大型電影也會失敗。這個問題對電影業來說比以往任何時候都更加重要。 所以,他就請來了你(數據分析師)來幫他解決問題,給出一些建議,根據數據分析一下商業電影的成功是否存在統一公式?以幫助他更好地進行決策。

解決的終極問題是:電影票房的影響因素有哪些? 接下來我們就分不同的維度分析:

· 觀眾喜歡什么電影類型?有什么主題關鍵詞?

  • 電影風格隨時間是如何變化的?
  • 電影預算高低是否影響票房?
  • 高票房或者高評分的導演有哪些?
  • 電影的發行時間最好選在啥時候?
  • 拍原創電影好還是改編電影好?

本次使用的數據來自於 Kaggle 平台(TMDb 5000 Movie Database)。收錄了美國地區 1916-2017 年近 5000 部電影的數據,包含預算、導演、票房、電影評

分等信息。原始數據集包含 2 個文件:

  • tmdb_5000_movies:電影基本信息,包含 20 個變量
  • tmdb_5000_credits:演職員信息,包含 4 個變量請使用 Python  編程,完成下列問題:

(1) 使用附件中的 tmdb_5000_movies.csv  tmdb_5000_credits.csv 數據集,進行數據清洗、數據挖掘、數據分析和數據可視化等,研究電影票房的影響因素有哪些?從不同的維度分析電影,討論並分析你的結果。

(2) 附件 tmdb_1000_predict.csv 中包含 1000 部電影的基本信息,請你選擇合適的指標,進行特征提取,建立機器學習的預測模型,預測 1000 部電影的vote_average  vote_count,並保存為 tmdb_1000_predicted.csv。

 

 

數據清洗

導入數據

 

import matplotlib as matplotlib

import numpy as np

import pandas as pd

from pandas import DataFrame, Series

 

# 可視化顯示在界面

# matplotlib inline

import matplotlib

import matplotlib.pyplot as plt

 

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用來顯示中文

plt.rcParams['axes.unicode_minus'] = False  # 用來正常顯示負號

 

# 學習seaborn參考:https://www.jianshu.com/p/c26bc5ccf604

 

import json

import warnings

 

warnings.filterwarnings('ignore')

# 設置顯示的最大列、寬等參數,消掉打印不完全中間的省略號

# pd.set_option('display.max_columns', 1000)

pd.set_option('display.width', 1000)#加了這一行那表格的一行就不會分段出現了

# pd.set_option('display.max_colwidth', 1000)

# pd.set_option('display.height', 1000)

#顯示所有列

pd.set_option('display.max_columns', None)

#顯示所有行

pd.set_option('display.max_rows', None)

movies = pd.read_csv('C:\\Users\\張子鑫\\Desktop\\軟件工程\\2021年秋季大型數據庫技術\\觀影大數據\\data\\tmdb_5000_movies.csv', encoding='utf_8')

credits = pd.read_csv('C:\\Users\\張子鑫\\Desktop\\軟件工程\\2021年秋季大型數據庫技術\\觀影大數據\\data\\tmdb_5000_credits.csv', encoding='utf_8')

movies.info()  # 查看信息

credits.info()

# 兩個數據框都有title列,以及movies.riginal_title

# 以上三個數據列重復,刪除兩個

del credits['title']

del movies['original_title']

 

# 連接兩個csv文件

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

 

# 刪除不需要分析的列

df = merged.drop(['homepage', 'overview', 'spoken_languages', 'status', 'tagline', 'movie_id'], axis=1)

df.info()

 

結果:

 

 

 

 

 

缺失值處理

 

缺失記錄僅 3條,采取網上搜索,補全信息。

 

2.1 補全 release_date

# 查找缺失值記錄-release_date

df[df.release_date.isnull()]

結果:

 

 

 

 

 

缺失記錄的電影 runtime 分別為 94min 和 240min。

缺失記錄的電影標題為《 America Is Still the Place》,日期為 2014-06-01

 

2.2 補全 runtime

 

# 查找缺失值記錄-runtime

df[df.runtime.isnull()]

結果:

 

 

 

 

缺失記錄的電影 runtime 分別為   94   min 和   240    min。

 

 

重復值處理

 

 

len(df.id.unique())

結果:

 

 

 

 

 

運行結果:有 4803個不重復的 id,可以認為沒有重復數據。

 

 

日期值處理

 

release_date 列轉換為日期類型:

df['release_year'] = pd.to_datetime(df.release_date, format = '%Y-%m-%d',errors='coerce').dt.year

df['release_month'] = pd.to_datetime(df.release_date).apply(lambda x: x.month)

df['release_day'] = pd.to_datetime(df.release_date).apply(lambda x: x.day)

df.info()

結果:

 

 

 

 

 

篩選數據

 

使用數據分析師最喜歡的一個語法:df.describe()

 

df.describe()

 

結果:

 

 

 

 

 

 

 

票房、預算、受歡迎程度、評分為 0的數據應該去除;

評分人數過低的電影,評分不具有統計意義,篩選評分人數大於 50的數據。

 

df = df[(df.vote_count >= 50) &(df.budget * df.revenue * df.popularity * df.vote_average !=0)].reset_index(drop = 'True')

 

df

結果:

 

 

 

 

 

此時剩余 2961條數據,包含 19個字段。

 

json 數據轉換

 

**說明:**genres,keywords,production_companies,production_countries,cast,crew 6 列都是

 

json 數據,需要處理為列表進行分析。處理方法:

json 本身為字符串類型,先轉換為字典列表,再將字典列表轉換為,以’,'分割的字符串

 

 

json_column = ['genres','keywords','production_companies','production_countries','cast','crew']

 

# 1-json本身為字符串類型,先轉換為字典列表

for i in json_column:

    df[i] = df[i].apply(json.loads)

    

# 提取name

# 2-將字典列表轉換為以','分割的字符串

def get_name(x):

    return ','.join([i['name'] for i in x])

        

df['cast'] = df['cast'].apply(get_name)

 

# 提取derector

def get_director(x):

    for i in x:

        if i['job'] == 'Director':

            return i['name']

 

df['crew'] = df['crew'].apply(get_director)

 

for j in json_column[0:4]:

    df[j] = df[j].apply(get_name)

 

#重命名

rename_dict = {'cast':'actor','crew':'director'}

df.rename(columns=rename_dict, inplace=True)

df.info()

df.head(5)

 

結果:

 

 

 

 

數據備份

 

# 備份原始數據框original_df

org_df = df.copy()

df.reset_index().to_csv("TMDB_5000_Movie_Dataset_Cleaned.csv")

 

 

 

數據分析

 

5.1 why

 

想要探索影響票房的因素,從電影市場趨勢,觀眾喜好類型,電影導演,發行時間,評分與關鍵詞等維度着手,給從業者提供合適的建議。

5.2 what

 

5.2.1 電影類型:定義一個集合,獲取所有的電影類型

 

# 定義一個集合,獲取所有的電影類型

genre = set()

for i in df['genres'].str.split(','): # 去掉字符串之間的分隔符,得到單個電影類型

    genre = set().union(i,genre)    # 集合求並集

    # genre.update(i) #或者使用update方法

 

print(genre)

 

結果:

 

 

 

 

 

注意到集合中存在多余的元素:空的單引號,所以需要去除。

 

genre.discard('') # 去除多余的元素

genre

 

結果:

 

 

 

 

 

#將genre轉變成列表

genre_list = list(genre)

 

# 創建數據框-電影類型

genre_df = pd.DataFrame()  

 

#對電影類型進行one-hot編碼

for i in genre_list:

    # 如果包含類型 i,則編碼為1,否則編碼為0

    genre_df[i] = df['genres'].str.contains(i).apply(lambda x: 1 if x else 0)    

 

#將數據框的索引變為年份

genre_df.index = df['release_year']

genre_df.head(5)

 

 

 

 

5.2.1.1 電影類型數量(繪制條形圖)

 

 

# 計算得到每種類型的電影總數目,並降序排列

grnre_sum = genre_df.sum().sort_values(ascending = False)

# 可視化

 

colors = ['tomato','C0']

plt.rcParams['font.sans-serif'] = ['SimHei']  #用來顯示中文

grnre_sum.plot(kind='bar',label='genres',color=colors,figsize=(12,9))

plt.title('不同類型的電影數量總計',fontsize=20)

plt.xticks(rotation=60)

plt.xlabel('電影類型',fontsize=16)

plt.ylabel('數量',fontsize=16)

plt.grid(False)

plt.savefig("不同電影類型數量-條形圖.png",dpi=300) #在 plt.show() 之前調用 plt.savefig()

plt.show()

 

結果:

 

 

 

 

 

 

5.2.1.2 電影類型占比(繪制餅圖)

 

 

結果:

 

gen_shares = grnre_sum / grnre_sum.sum()

# 設置other類,當電影類型所占比例小於%1時,全部歸到other類中

others = 0.01

gen_pie = gen_shares[gen_shares >= others]

gen_pie['others'] = gen_shares[gen_shares < others].sum()

colors = ['tomato', 'lightskyblue', 'goldenrod', 'wheat', 'y','tomato', 'lightskyblue', 'goldenrod', 'wheat', 'y','tomato', 'lightskyblue', 'goldenrod', 'wheat', 'y','tomato', 'lightskyblue', 'goldenrod', 'wheat', 'y','lightskyblue']

# 設置分裂屬性

# 所占比例小於或等於%2時,增大每塊餅片邊緣偏離半徑的百分比

explode = (gen_pie <= 0.02)/10

 

# 繪制餅圖

gen_pie.plot(kind='pie',label='',colors=colors,explode=explode,startangle=0,

                    shadow=False,autopct='%3.1f%%',figsize=(8,8))

 

plt.title('不同電影類型所占百分比',fontsize=20)

plt.savefig("不同電影類型所占百分比-餅圖.png",dpi=300)

 

 

 

 

 

 

5.2.1.3 電影類型變化趨勢(繪制折線圖)

 

#電影類型隨時間變化的趨勢

gen_year_sum = genre_df.sort_index(ascending = False).groupby('release_year').sum()

gen_year_sum_sub = gen_year_sum[['Action','Adventure','Crime','Romance','Science Fiction','Drama','Comedy','Thriller']]

gen_year_sum_sub.plot(figsize=(12,9))

plt.legend(gen_year_sum_sub.columns)

plt.xticks(range(1915,2018,10))

plt.xlabel('年份', fontsize=16)

plt.ylabel('數量', fontsize=16)

plt.title('不同電影變化趨勢', fontsize=20)

 

plt.grid(False)

plt.savefig("不同電影類型數量-折線圖2.png",dpi=600)

plt.show()

 

 

 

 

5.2.1.4 不同電影類型預算/利潤(繪制組合圖)

 

 

# 計算不同電影類型的利潤

# Step1-創建profit_dataframe

df['profit'] = df['revenue']-df['budget']

profit_df = pd.DataFrame()

profit_df = pd.concat([genre_df.reset_index(), df['profit']], axis=1)

df.info()

# Step2-創建profit_series,橫坐標為genre

profit_s=pd.Series(index=genre_list)

# Step3-求出每種genre對應的利潤均值

for i in genre_list:

    profit_s.loc[i]=profit_df.loc[:,[i,'profit']].groupby(i, as_index=False).mean().loc[1,'profit']

profit_s = profit_s.sort_values(ascending = True)

profit_s

 

# 計算不同類型電影的budget

# Step1-創建profit_dataframe

budget_df = pd.DataFrame()

budget_df = pd.concat([genre_df.reset_index(), df['budget']], axis=1)

# Step2-創建budget_series,橫坐標為genre

budget_s=pd.Series(index=genre_list)

# Step3-求出每種genre對應的預算均值

for j in genre_list:

    budget_s.loc[j]=budget_df.loc[:,[j,'budget']].groupby(j, as_index=False).mean().loc[1,'budget']

budget_s

 

# 再接着,橫向合並 profit_s 和 budget_s

profit_budget = pd.concat([profit_s, budget_s], axis=1)

profit_budget.columns = ['profit', 'budget']

 

#添加利潤率列

profit_budget['rate'] = (profit_budget['profit']/profit_budget['budget'])*100

# 降序排序

profit_budget_sort=profit_budget.sort_values(by='budget',ascending = False)

profit_budget_sort.head(2)

 

# 繪制不同類型電影平均預算和利潤率(組合圖)

x = profit_budget_sort.index

y1 = profit_budget_sort.budget

y2 = profit_budget_sort.rate

# 返回profit_budget的行數

length = profit_budget_sort.shape[0]

 

fig = plt.figure(figsize=(12,9))

# 左軸

ax1 = fig.add_subplot(1,1,1)

plt.bar(range(0,length),y1,color='C4',label='平均預算')

plt.xticks(range(0,length),x,rotation=90, fontsize=12)  # 更改橫坐標軸名稱

ax1.set_xlabel('年份')                   # 設置x軸label ,y軸label

ax1.set_ylabel('平均預算',fontsize=16)

ax1.legend(loc=2,fontsize=12)

 

#右軸

# 共享x軸,生成次坐標軸

ax2 = ax1.twinx()

ax2.plot(range(0,length),y2,'ro-.')

ax2.set_ylabel('平均利潤率',fontsize=16)

ax2.legend(loc=1,fontsize=12)

 

# 將利潤率坐標軸以百分比格式顯示

import matplotlib.ticker as mtick

fmt='%.1f%%'

yticks = mtick.FormatStrFormatter(fmt)

ax2.yaxis.set_major_formatter(yticks)

 

# 設置圖片title

ax1.set_title('不同類型電影平均預算和利潤率',fontsize=20)

ax1.grid(False)

ax2.grid(False)

plt.savefig("不同電影平均預算+利潤率.png",dpi=300)

plt.show()

 

 

 

 

 

 

5.2.2 電影關鍵詞(keywords 關鍵詞分析,繪制詞雲圖)

 

from wordcloud import STOPWORDS

from wordcloud import WordCloud

keywords_list = []

for i in df['keywords']:

    keywords_list.append(i)

    keywords_list

#把字符串列表連接成一個長字符串

lis = ''.join(keywords_list)

lis.replace('\'s','')

#設置停用詞

stopwords = set(STOPWORDS)

stopwords.add('film')

wordcloud = WordCloud(

                background_color = 'black',

                random_state=3,

                stopwords = stopwords,

                max_words = 3000,

                scale=1).generate(lis)

plt.figure(figsize=(10,6))

plt.imshow(wordcloud)

plt.axis('off')

plt.savefig('詞雲圖.png',dpi=300)

plt.show()

 

 

 

 

5.3 when

 

查看 runtime 的類型,發現是 object 類型,也就是字符串,所以,先進行數據轉化。

 

1、先進行數據轉化

df.runtime.head(5)

 

 

 

 

 

 

2、

df.runtime = df.runtime.apply(pd.to_numeric, errors='coerce')

df.runtime.describe()

 

 

5.3.1 電影時長(繪制電影時長直方圖)

 

import seaborn as sns

 

sns.set_style('dark')

sns.distplot(df.runtime,bins = 30)

sns.despine(left = True) # 使用despine()方法來移除坐標軸,默認移除頂部和右側坐標軸

plt.xticks(range(50,360,20))

plt.savefig('電影時長直方圖.png',dpi=300)

plt.show()

 

 

 

 

5.3.2 發行時間(繪制每月電影數量和單片平均票房)

 

fig = plt.figure(figsize=(12,7))

x = list(range(1,13))

y1 = df.groupby('release_month').revenue.size()

y2 = df.groupby('release_month').revenue.mean()# 每月單片平均票房

 

# 左軸

ax1 = fig.add_subplot(1,1,1)

plt.bar(x,y1,color='C6',label='電影數量')

plt.grid(False)

ax1.set_xlabel('月份')                   # 設置x軸label ,y軸label

ax1.set_ylabel('電影數量',fontsize=16)

ax1.legend(loc=2,fontsize=12)

 

# 右軸

ax2 = ax1.twinx()

plt.plot(x,y2,'bo--',label='每月單片平均票房')

ax2.set_ylabel('每月單片平均票房',fontsize=16)

ax2.legend(loc=1,fontsize=12)

 

plt.savefig('每月電影數量和單片平均票房.png',dpi=300)

 

 

 

 

5.4 where

 

本數據集收集的是美國地區的電影數據,對於電影的制作公司以及制作國家,在本次的故事背景下不作分析。

5.5 who

 

5.5.1 分析票房分布及票房 Top10 的導演

director_df = pd.DataFrame()

director_df = df[['director','revenue','budget','profit','vote_average']]

director_df = director_df.groupby(by = 'director').mean().sort_values(by='revenue',ascending = False) # 取均值

director_df.info()

 

# 繪制票房分布直方圖

director_df['revenue'].plot.hist(bins=100, figsize=(12,12),color='C2')

plt.xlabel('票房')

plt.ylabel('頻數')

plt.title('不同導演執導的票房分布')

plt.savefig('不同導演執導的票房分布.png',dpi = 300)

plt.rcParams['font.sans-serif'] = ['SimHei']

plt.show()

# 票房均值Top10的導演

director_df.revenue.sort_values(ascending = True).tail(10).plot(kind='barh',figsize=(8,6),color='C5')

plt.xlabel('票房',fontsize = 16)

plt.ylabel('導演',fontsize = 16)

plt.title('票房排名Top10的導演',fontsize = 20)

plt.savefig('票房排名Top10的導演.png',dpi = 300)

plt.show()

 

 

 

 

 

 

 

 

5.5.2 分析評分分布及評分 Top10 的導演

#繪制導演評分直方圖

director_df['vote_average'].plot.hist(bins=18, figsize=(8,6),color='C8')

plt.xlabel('評分')

plt.ylabel('頻數')

plt.title('不同導演執導的評分分布')

plt.savefig('不同導演執導的評分分布.png',dpi = 300)

plt.show()

# 評分均值Top10的導演

director_df.vote_average.sort_values(ascending = True).tail(10).plot(kind='barh',figsize=(8,6),color='C4')

plt.xlabel('評分',fontsize = 16)

plt.ylabel('導演',fontsize = 16)

plt.title('評分排名Top10的導演',fontsize = 20)

plt.savefig('評分排名Top10的導演.png',dpi = 300)

plt.show()

 

 

 

 

 

 

 

5.6 how

 

5.6.1 原創 VS 改編占比(餅圖)

 

 

# 創建數據框

original_df = pd.DataFrame()

original_df['keywords'] = df['keywords'].str.contains('based on').map(lambda x: 1 if x else 0)

original_df['profit'] = df['profit']

original_df['budget'] = df['budget']

# 計算

novel_cnt = original_df['keywords'].sum() # 改編作品數量

original_cnt = original_df['keywords'].count() - original_df['keywords'].sum() # 原創作品數量

# 按照 是否原創 分組

original_df = original_df.groupby('keywords', as_index = False).mean() # 注意此處計算的是利潤和預算的平均值

# 增加計數列

original_df['count'] = [original_cnt, novel_cnt]

# 計算利潤率

original_df['profit_rate'] = (original_df['profit'] / original_df['budget'])*100

# 修改index

original_df.index = ['original', 'based_on_novel']

# 計算百分比

original_pie = original_df['count'] / original_df['count'].sum()

# 繪制餅圖

original_pie.plot(kind='pie',label='',startangle=90,shadow=False,autopct='%2.1f%%',figsize=(8,8),colors=['C6','C8'])

plt.title('改編 VS 原創',fontsize=20)

plt.legend(loc=2,fontsize=10)

plt.savefig('改編VS原創.png',dpi=300)

plt.show()

 

 

 

 

 

5.6.2 原創 VS 改編預算/利潤率(組合圖)

 

 

# 創建數據框

original_df = pd.DataFrame()

original_df['keywords'] = df['keywords'].str.contains('based on').map(lambda x: 1 if x else 0)

original_df['profit'] = df['profit']

original_df['budget'] = df['budget']

 

# 計算

novel_cnt = original_df['keywords'].sum() # 改編作品數量

original_cnt = original_df['keywords'].count() - original_df['keywords'].sum() # 原創作品數量

# 按照 是否原創 分組

original_df = original_df.groupby('keywords', as_index = False).mean() # 注意此處計算的是利潤和預算的平均值

# 增加計數列

original_df['count'] = [original_cnt, novel_cnt]

# 計算利潤率

original_df['profit_rate'] = (original_df['profit'] / original_df['budget'])*100

 

# 修改index

original_df.index = ['original', 'based_on_novel']

# 計算百分比

original_pie = original_df['count'] / original_df['count'].sum()

 

x = original_df.index

y1 = original_df.budget

y2 = original_df.profit_rate

 

fig= plt.figure(figsize = (8,6))

 

# 左軸

ax1 = fig.add_subplot(1,1,1)

plt.bar(x,y1,color='C9',label='平均預算',width=0.25)

plt.xticks(rotation=0, fontsize=12)  # 更改橫坐標軸名稱

ax1.set_xlabel('原創 VS 改編')                   # 設置x軸label ,y軸label

ax1.set_ylabel('平均預算',fontsize=16)

ax1.legend(loc=2,fontsize=10)

 

#右軸

# 共享x軸,生成次坐標軸

ax2 = ax1.twinx()

ax2.plot(x,y2,'bo-.',linewidth=5,label='平均利潤率')

ax2.set_ylabel('平均利潤率',fontsize=16)

ax2.legend(loc=1,fontsize=10) # loc=1,2,3,4分別表示四個角,和四象限順序一致

 

# 將利潤率坐標軸以百分比格式顯示

import matplotlib.ticker as mtick

fmt='%.1f%%'

yticks = mtick.FormatStrFormatter(fmt)

ax2.yaxis.set_major_formatter(yticks)

 

plt.savefig('改編VS原創的預算以及利潤率.png',dpi=300)

plt.show()

 

 

 

 

 

 

5.7 how much

 

5.7.1 計算相關系數(票房相關系數矩陣)

 

# 計算相關系數矩陣

revenue_corr = df[['runtime','popularity','vote_average','vote_count','budget','revenue']].corr()

 

sns.heatmap(

            revenue_corr,

            annot=True, # 在每個單元格內顯示標注

            cmap="BuGn_r", # 設置填充顏色:黃色,綠色,藍色

#             cmap="YlGnBu", # 設置填充顏色:黃色,綠色,藍色

#             cmap="coolwarm", # 設置填充顏色:冷暖色

            cbar=True,  # 顯示color bar

            linewidths=0.5, # 在單元格之間加入小間隔,方便數據閱讀

            # fmt='%.2f%%',  # 本來是確保顯示結果是整數(格式化輸出),此處有問題

           )

plt.savefig('票房相關系數矩陣.png',dpi=300)

plt.show()

 

 

 

 

 

 

5.7.2 票房影響因素散點圖

 

# 繪制散點圖

fig = plt.figure(figsize=(17,5))

 

# # 學習seaborn參考:https://www.jianshu.com/p/c26bc5ccf604

ax1 = plt.subplot(1,3,1)

ax1 = sns.regplot(x='budget', y='revenue', data=revenue_df, x_jitter=.1,color='r',marker='x')

# marker: 'x','o','v','^','<'

# jitter:抖動項,表示抖動程度

ax1.text(1.6e8,2.2e9,'r=0.7',fontsize=16)

plt.title('budget-revenue-scatter',fontsize=20)

plt.xlabel('budget',fontsize=16)

plt.ylabel('revenue',fontsize=16)

 

ax2 = plt.subplot(1,3,2)

ax2 = sns.regplot(x='popularity', y='revenue', data=revenue_df, x_jitter=.1,color='g',marker='o')

ax2.text(500,3e9,'r=0.59',fontsize=16)

plt.title('popularity-revenue-scatter',fontsize=18)

plt.xlabel('popularity',fontsize=16)

plt.ylabel('revenue',fontsize=16)

 

ax3 = plt.subplot(1,3,3)

ax3 = sns.regplot(x='vote_count', y='revenue', data=revenue_df, x_jitter=.1,color='b',marker='v')

ax3.text(7000,2e9,'r=0.75',fontsize=16)

plt.title('voteCount-revenue-scatter',fontsize=20)

plt.xlabel('vote_count',fontsize=16)

plt.ylabel('revenue',fontsize=16)

 

fig.savefig('revenue.png',dpi=300)

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM