在之前写的一篇文中中,已经采用urllib和BeautifulSoup的方式抓取了豆瓣电影TOP250的导演、编剧、演员、上映时间和地区、语言、短评数、影评数、多少人想看、多少人看过等22个字段。
接下来,我们要对这些数据进行分析、挖掘,得到有价值的信息。
下面是整个分析过程的思维导图:
一、获取数据
先从csv文件中读取数据,观察一下:
import pandas as pd
df = pd.read_csv('exercise_douban_movie_Top250.csv')
print(df.loc[0])
输出为:
print(df.loc[0, 'directors'])
print(type(df.loc[0, 'directors']))
输出为:
['弗兰克·德拉邦特']
<class 'str'>
通过观察可以发现,这些数据不能直接使用,有些数据的格式不符合后续操作的要求。
比如:
- 评分人数部分为字符串,且包含了汉字;
- 多个字段的列表实际上被读取为字符串;
- 百分比以字符串形式存储等
- ……
所以我们要对数据进行清洗,使得格式满足后续分析的要求。
二、数据清洗
# 2. 数据清洗,把数据转化成需要的格式
# 评分人数:x人评价清洗为x,并调整为int类型
df['score_cnt'] = df['score_cnt'].map(lambda x: int(x[:-3]))
# 将字符串改为列表
df_tmp = df[['directors', 'writers', 'actors', 'types', 'dates', 'play_location', 'rating_per', 'betters', 'tags']]
df[['directors', '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[:4]) 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])/100+ float(x[1][:-1])/100))
# 比百分之多少同类电影好
df['better_than'] = df['betters'].map(lambda x: sum([int(i.split('%')[0]) for i in x])/len(x))
先进行一个简单的清洗,后续分析过程中有什么需要再进行添加。
除了解决格式不符合要求的问题外,我们还额外创建了一些字段,比如五星比例、好多多少同类电影等,这些都是为后续分析提供帮助。
三、进行分析
先划定一个标准,那就是在TOP250中有不少于3部电影的导演,才可以参与最佳导演评选。
先看一下一共有多少位导演:
from functools import reduce
# 消灭空格
df['directors'] = df['directors'].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)]
可以看到宫崎骏和诺兰的电影数最多。
但这样我们无法确定谁才是最佳导演,接下来我们用两种方法对他们进行排序:
- 以平均豆瓣评分来进行排序
- 以平均榜单位置进行排序
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,但是它们的实际表现并没有那么大的差距。
注意一点,这个排名数据可能会随着时间的推移而发生变化,因为电影的评分和排行榜会发生变化。
我们将上述代码中的列名调整下就可以得到演员的榜单,这一部分可以后续作为练习进行尝试。
总结
上面虽然是一个小的例子,但是它是一个完整的大数据分析过程,涉及到数据的读取,数据清洗和数据分析。可以把这个作为一个入门实战,然后在此基础上进行扩展,比如分析一下,哪一类电影更受豆瓣用户欢迎,可以做出词云图,可以在现有基础上为用户推荐电影。