1. 程式人生 > >零基礎Python資料分析實戰:豆瓣人的電影口味重嗎?

零基礎Python資料分析實戰:豆瓣人的電影口味重嗎?

在上一篇文章中,我們實戰使用urllibBeautifulSoup抓取了關於豆瓣電影TOP250的非常豐富的資訊,包括導演、編劇、演員、上映時間和地區、語言、別名、短評數、影評數、多少人想看、多少人看過等多達23個欄位。

接下來,我們要做的就是對這些資料進行分析、挖掘,得到儘可能多的資訊。畢竟有價值的不是資料,而是從資料中發掘而出的洞見。

一、資料清洗

我們先讀取我們的資料,觀察一下:

import pandas as pd
df = pd.read_csv('douban_top250.csv')
df.loc[0]

輸出為:

df.loc[0, 'director'
]

輸出為:

"['弗蘭克·德拉邦特']"

通過觀察我們迅速得知,這些資料並不能簡單地直接使用,因為有些資料的格式還不符合我們的要求,比如:

  • 評分人數部分為字串,且包含了漢字;
  • 多個欄位的列表實際上被讀取為字串;
  • 百分比以字串形式儲存等
  • ……

因此我們要先對這些資料進行一番清洗,再開始做後續的分析。

# 評分人數:x人評價清洗為x,並調整為int型別
df['score_cnt'] = df['score_cnt'].map(lambda x: int(x[:-3]))
df.describe()['score_cnt']

# 將字串改為列表
df_tmp = df[['director', 'writers', 'actors', 'types', 'dates', 'play_location', 'rating_per', 'betters', 'tags']] df[['director', 'writers', 'actors', 'types', 'dates', 'play_location', 'rating_per', 'betters', 'tags']] = df_tmp.applymap(lambda x: eval(x)) # 上映年份由字串轉換為int型別 df['dates'] = df['dates'
].map(lambda x: [int(i) for i in x]) df['year'] = df['dates'].map(lambda x: min(x)) # 五星比例/好評比例 df['five_star_rate'] = df['rating_per'].map(lambda x: float(x[0][:-1])/100) df['favor_rate'] = df['rating_per'].map(lambda x: (float(x[0][:-1]) + float(x[1][:-1]))/100) # 比百分之多少同類電影好 df['better_than'] = df['betters'].map(lambda x: sum([int(i.split('%')[0]) for i in x])/len(x))

好了,我們先簡單清洗下,後續分析過程中有什麼需要再額外新增。

可以看到,除了解決格式不符合要求的問題之外,我們還額外建立了一些欄位,比如五星比例、好於多少同類電影等,這些後續都會為我們的分析提供幫助。

二、簡單分析

1. 誰是豆瓣最佳導演?

我們先劃定一個標準,那就是在TOP250中有不少於3部電影的導演,才可以參與最佳導演評選。

我們先看一下一共有多少位導演:

from functools import reduce

# 消滅空格
df['director'] = df['director'].map(lambda x: [i.strip() for i in x])

# reduce迭代獲取所有導演的列表
director_list = reduce(lambda x, y: x + y, df.director)

print(len(director_list))

結果返回281,也就是說這250部影片有281位導演,存在聯合執導的情況。那我們接著看一下影片數量大於3部的有哪些導演:

from collections import Counter

dire_counter = Counter(director_list)
dire_counter = sorted(dire_counter.items(), key=lambda x: x[1], reverse=True)
top_directors = list(filter(lambda x: x[1] >= 3, dire_counter))
print(top_directors)

輸出為:

[('宮崎駿', 7),
 ('克里斯托弗·諾蘭', 7),
 ('史蒂文·斯皮爾伯格', 6),
 ('王家衛', 5),
 ('李安', 4),
 ('大衛·芬奇', 4),
 ('詹姆斯·卡梅隆', 3),
 ('朱塞佩·託納多雷', 3),
 ('劉鎮偉', 3),
 ('弗朗西斯·福特·科波拉', 3),
 ('姜文', 3),
 ('彼得·傑克遜', 3),
 ('彼特·道格特', 3),
 ('昆汀·塔倫蒂諾', 3),
 ('理查德·林克萊特', 3),
 ('李·昂克里奇', 3),
 ('理查德·柯蒂斯', 3),
 ('吳宇森', 3),
 ('是枝裕和', 3)]

哇,大神宮崎駿和諾蘭獨一檔,各有7部入榜;斯皮爾伯格、王家衛、李安、大衛芬奇分別以6、5、4、4的入榜數目分列3至6位,剩下的13位均有3部入榜電影,其中有劉鎮偉、姜文、吳宇森三位中國導演。

但這樣我們無法確定誰才是最佳導演,接下來我們用兩種方法對他們進行排序:

  • 以平均豆瓣評分來進行排序
  • 以平均榜單位置進行排序
from collections import defaultdict

top_dire_score = defaultdict(list)
top_dire_ind = defaultdict(list)
for name, cnt in top_directors:
    for index, row in df.iterrows():
        if name in row['director']:
            top_dire_score[name].append(row['score'])
            top_dire_ind[name].append(row['top_no'])
print(top_dire_score)
print(top_dire_ind)

看下結果:

# 評分
defaultdict(list,
            {'宮崎駿': [9.3, 9.1, 9.0, 8.9, 8.8, 8.8, 8.5],
             '克里斯托弗·諾蘭': [9.3, 9.2, 9.1, 8.8, 8.6, 8.6, 8.9],
             '史蒂文·斯皮爾伯格': [9.5, 8.9, 8.8, 8.7, 8.6, 8.5],
             '王家衛': [8.8, 8.7, 8.6, 8.6, 8.5],
             '李安': [9.0, 9.1, 8.7, 8.8],
             '大衛·芬奇': [9.0, 8.8, 8.8, 8.7],
             '詹姆斯·卡梅隆': [9.3, 8.6, 8.6],
             '朱塞佩·託納多雷': [9.2, 9.1, 8.8],
             '劉鎮偉': [9.2, 8.9, 8.7],
             '弗朗西斯·福特·科波拉': [9.2, 9.1, 8.8],
             '姜文': [9.2, 8.7, 8.8],
             '彼得·傑克遜': [9.1, 9.0, 8.9],
             '彼特·道格特': [8.9, 8.6, 8.7],
             '昆汀·塔倫蒂諾': [8.8, 8.6, 8.5],
             '理查德·林克萊特': [8.7, 8.8, 8.8],
             '李·昂克里奇': [8.6, 9.0, 8.8],
             '理查德·柯蒂斯': [8.5, 8.7, 8.6],
             '吳宇森': [8.6, 8.7, 8.4],
             '是枝裕和': [9.1, 8.7, 8.8]})

# 榜單位置           
defaultdict(list,
            {'宮崎駿': [7, 19, 36, 43, 88, 112, 191],
             '克里斯托弗·諾蘭': [9, 18, 27, 65, 137, 145, 192],
             '史蒂文·斯皮爾伯格': [8, 70, 83, 118, 171, 222],
             '王家衛': [80, 91, 132, 159, 181],
             '李安': [30, 54, 94, 131],
             '大衛·芬奇': [35, 62, 64, 104],
             '詹姆斯·卡梅隆': [6, 96, 210],
             '朱塞佩·託納多雷': [13, 29, 66],
             '劉鎮偉': [15, 38, 101],
             '弗朗西斯·福特·科波拉': [17, 50, 155],
             '姜文': [32, 69, 87],
             '彼得·傑克遜': [33, 51, 52],
             '彼特·道格特': [37, 127, 173],
             '昆汀·塔倫蒂諾': [73, 174, 218],
             '理查德·林克萊特': [105, 113, 217],
             '李·昂克里奇': [127, 129, 158],
             '理查德·柯蒂斯': [140, 154, 231],
             '吳宇森': [141, 151, 223],
             '是枝裕和': [153, 206, 208]})

接下來我們求一下均值,並將入榜電影數作為一個權重加進去:

from math import log2
from math import sqrt
rank_score = []
rank_ind = []

for name, scores in top_dire_score.items():
    rank_score.append([name, sum(scores) / len(scores) * sqrt(log2(len(scores)))])

for name, indexes in top_dire_ind.items():
    rank_ind.append([name, sum(indexes) / sqrt(log2(len(scores))) /len(indexes)])
    
rank_score = sorted(rank_score, key=lambda x: x[1], reverse=True)
rank_ind = sorted(rank_ind, key=lambda x: x[1])
print(rank_score[:10])
print(rank_ind[:10])

輸出為:

# 加權得分榜
[['克里斯托弗·諾蘭', 14.959967098817579],
 ['宮崎駿', 14.936031151459467],
 ['史蒂文·斯皮爾伯格', 14.202073072976324],
 ['王家衛', 13.165523290477429],
 ['李安', 12.586500705120548],
 ['大衛·芬奇', 12.480434687942564],
 ['朱塞佩·託納多雷', 11.372541542166006],
 ['弗朗西斯·福特·科波拉', 11.372541542166006],
 ['彼得·傑克遜', 11.330576444224434],
 ['劉鎮偉', 11.24664624834129]]
 
# 加權位置榜
[['朱塞佩·託納多雷', 28.59519121510834],
 ['彼得·傑克遜', 36.008759307914204],
 ['劉鎮偉', 40.774624510432254],
 ['姜文', 49.776814337410805],
 ['大衛·芬奇', 52.6230949444702],
 ['宮崎駿', 56.282598582118],
 ['弗朗西斯·福特·科波拉', 58.77900416438936],
 ['李安', 61.36051448241997],
 ['克里斯托弗·諾蘭', 67.28947774031447],
 ['詹姆斯·卡梅隆', 82.60833017697963]]

可以看到,在我們的加權得分演算法下,諾蘭以微弱優勢勝出,奪得豆瓣最佳導演獎。然而在我們的加權榜單位置演算法中,朱塞佩·託納多雷的電影平均能獲得更靠前的豆瓣排名,奪得桂冠,而宮崎駿和諾蘭的排名則分列6、9位。

具體哪個排名更靠譜呢?我傾向於第一個,因為這裡使用的是連續資料,而位置榜的資料是離散資料,本身有了一些資訊損耗,榜首和末尾的資料差了249倍,但他們的實際表現並沒有那麼大的差距。同時這一資料容易受到離群值的影響,比如某位導演絕大多數電影排名都很靠前,但某一部偏偏特別靠後,這時他的加權位置得分就會較大(位置越靠後),那他的總排名就會向後排。

因此我個人傾向於由拍出了數部經典大片的諾蘭獲得豆瓣最佳導演金獎(說得好像真的有這個獎似的……)。

2. 誰是豆瓣最佳演員?

我們將上述程式碼中的列名調整下就可以得到演員的榜單,這一部分大家自己嘗試,我來直接放出結果和作圖程式碼。

# 入榜電影數排行
import matplotlib.pyplot as plt
names = [i[0] for i in top_actors]
cnt = [i[1] for i in top_actors]

fig, ax = plt.subplots(figsize=(16, 8))
plt.bar(names, cnt, color='c')
ax.set_xticklabels(labels=names, 
                   fontdict={
                       'verticalalignment': 'bottom', 
                       'y': 0.1, 
                       'fontsize': 18, 
                       'rotation': 'vertical'
                   })
plt.ylabel('上榜電影數', fontsize=20)
plt.title('豆瓣電影TOP250入榜最多演員情況', fontsize=24)

# 加權得分榜
names = [i[0] for i in rank_score]
score = [i[1] for i in rank_score]
fig, ax = plt.subplots(figsize=(16, 8))
plt.bar(names, score, color='c')
ax.set_xticklabels(labels=names, 
                   fontdict={
                       'verticalalignment': 'bottom', 
                       'y': 0.1, 
                       'fontsize': 18, 
                       'rotation': 'vertical'
                   })
plt.ylim([10,16])
plt.ylabel('加權得分', fontsize=20)
plt.title('豆瓣電影TOP250演員加權得分榜', fontsize=24)

好,恭喜“哥哥”張國榮喜提豆瓣最佳演員獎!

3、TOP250分數及評分人數分佈

df.score.hist(color='green')
plt.title('豆瓣電影TOP250評分分佈')

9分以上的比例不算很高,在8.7、8.8附近有較多電影集中。

df.score_cnt.hist(bins=20, color='green')
plt.title('豆瓣電影TOP250評分人數分佈')

大多數電影的評分人數在40萬以下。

4、哪些型別的電影更受豆瓣使用者歡迎?

# 調整資料
type_list = reduce(lambda x, y: x + y, df.types)
type_counter = Counter(type_list)
type_counter = sorted(type_counter.items(), key=lambda x: x[1], reverse=True)
types = [i[0] for i in type_counter]
cnts = [i[1] for i in type_counter]

# 作圖
fig, ax = plt.subplots(figsize=(16, 8))
ax.bar(types[1:]