用Python分析豆瓣電影Top250
開場白:本文中使用的語言為 Python 3
,其中主要用了 BeautifulSoup、Numpy、Pandas、Matplotlib和WordCloud
等幾個資料分析常用庫,過程儘量寫的詳(luo)細(suo)些,希望能給和我一樣的資料分析初學者一些思路,如果文中有錯誤請告訴我。同時歡迎各種關於程式碼、分析思路、語言組織、排版等等的意見建議。非常感謝!文章很長,如果只看部分內容請戳目錄。
豆瓣電影Top250資料分析
不知道看什麼電影時,就會習慣性的看看豆瓣,但落伍的我直到最近才發現還有個神奇的豆瓣電影Top250榜單!它是根據每部影片看過的人數以及該影片所得的評論等綜合資料排名的,同時還考慮了人群的廣泛適應性和持續關注度。好高大上的演算法!
那麼得出的這個排行榜和電影評分及評論人數有怎樣的關係?
和上映時間關係大不大?
哪種型別的電影上榜最多呢?
哪些國家、導演、主演最受歡迎?
片長多長時間最合適?
帶著這些疑問,不妨進行一下資料分析。
資料收集
先來看一下頁面:
我們抓取排名、電影名、導演、主演、上映日期、製片國家/地區、型別,評分、評論數量、一句話評價以及電影連結,其中導演和主演分別取一位。
用開發者工具看一下原始碼:
大部分資訊比較好抓取,只有電影資訊在標籤 <p>
中都寫在了一起,可通過 strip()
函式去除兩側空格, split()
函式分裂字串來取得具體資訊。
選用Python 3,引入 url.request
BeautifulSoup
庫來抓取頁面資訊。程式碼如下:
import urllib.request as urlrequest
from bs4 import BeautifulSoup
top250_url = "https://movie.douban.com/top250?start={}&filter="
with open('top250_f1.csv','w',encoding='utf8') as outputfile:
outputfile.write("num#title#director#role#init_year#area#genre#rating_num#comment_num#comment#url\n" )
for i in range(10):
start = i*25
url_visit = top250_url.format(start)
crawl_content = urlrequest.urlopen(url_visit).read()
http_content = crawl_content.decode('utf8')
soup = BeautifulSoup(http_content,'html.parser')
all_item_divs = soup.find_all(class_='item')
for each_item_div in all_item_divs:
pic_div = each_item_div.find(class_='pic')
num = pic_div.find('em').get_text() #排名
href = pic_div.find('a')['href'] #電影連結
title = pic_div.find('img')['alt'] #電影名稱
bd_div = each_item_div.find(class_='bd')
infos = bd_div.find('p').get_text().strip().split('\n')
infos_1 = infos[0].split('\xa0\xa0\xa0')
director = infos_1[0][4:].rstrip('...').rstrip('/').split('/')[0] #導演
role = str(infos_1[1:])[6:].split('/')[0] #主演
infos_2 = infos[1].lstrip().split('\xa0/\xa0')
year = infos_2[0] #上映時間
area = infos_2[1] #國家/地區
genre = infos_2[2:] #電影型別
star_div = each_item_div.find(class_='star')
rating_num = star_div.find(class_='rating_num').get_text() #評分
comment_num = star_div.find_all('span')[3].get_text()[:-3] #評價數量
quote = each_item_div.find(class_='quote')
inq = quote.find(class_='inq').get_text() #一句話評價
outputfile.write('{}#{}#{}#{}#{}#{}#{}#{}#{}#{}#{}\n'.format(num,title,director,role,year,area,
genre,rating_num,comment_num,inq,href))
本來只打算抓這些資料,分析時發現有些主演名字顯示的不完全,看來還要再抓一下每個電影頁面。在上面抓取的url資訊中截取出id編號,用豆瓣API介面。
import urllib
import urllib.request as urlrequest
import json
import time
import random
import pandas as pd
df = pd.read_csv("top250_f1.csv",sep = "#", encoding = 'utf8')
urlsplit = df.url.str.split('/').apply(pd.Series)
id_list = list(urlsplit[4])
num=0
IP_list = [ ] #這裡寫幾個可用的IP地址和埠號,只抓250個頁面,有兩三個IP就夠了。
IP = random.chioce(IP_list)
with open('top250_f2.csv', 'w',encoding='utf8') as outputfile:
outputfile.write("num#rank#alt_title#title#pubdate#language#writer#director#cast#movie_duration#year#movie_type#tags#image\n")
proxy = urlrequest.ProxyHandler({'https': 'IP'})
opener = urlrequest.build_opener(proxy)
opener.addheaders = [('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4)
AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30')]
urlrequest.install_opener(opener)
for id in id_list:
url_visit = 'https://api.douban.com/v2/movie/{}'.format(id)
crawl_content = urlrequest.urlopen(url_visit).read()
json_content = json.loads(crawl_content.decode('utf-8'))
rank = json_content['rating']['average']
alt_title = json_content['alt_title']
image = json_content['image']
title = json_content['title']
pubdate = json_content['attrs']['pubdate']
language = json_content['attrs']['language']
try:
writer = json_content['attrs']['writer']
except:
writer = 'None'
director = json_content['attrs']['director']
cast = json_content['attrs']['cast']
movie_duration = json_content['attrs']['movie_duration']
year = json_content['attrs']['year']
movie_type = json_content['attrs']['movie_type']
tags = json_content['tags']
num = num +1
outputfile.write("{}#{}#{}#{}#{}#{}#{}#{}#{}#{}#{}#{}#{}#{}\n".format(num,rank,alt_title,title,pubdate,language,writer,
director,cast,movie_duration,year,movie_type,tags,image))
time.sleep(1)
OK,資料抓取完畢,看一下抓取結果。先來看爬取Top250頁面的資訊:
import numpy as ny
import pandas as pd
df_1 = pd.read_csv("top250_f1.csv",sep = "#", encoding = 'utf8')
df_1.head()
num | title | director | role | init_year | area | genre | rating_num | comment_num | comment | url | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 肖申克的救贖 | 弗蘭克·德拉邦特 Frank Darabont | 蒂姆·羅賓斯 Tim Robbins | 1994 | 美國 | [‘犯罪 劇情’] | 9.6 | 861343 | 希望讓人自由。 | https://movie.douban.com/subject/1292052/ |
1 | 2 | 霸王別姬 | 陳凱歌 Kaige Chen | 張國榮 Leslie Cheung | 1993 | 中國大陸 香港 | [‘劇情 愛情 同性’] | 9.5 | 618349 | 風華絕代。 | https://movie.douban.com/subject/1291546/ |
2 | 3 | 這個殺手不太冷 | 呂克·貝鬆 Luc Besson | 讓·雷諾 Jean Reno | 1994 | 法國 | [‘劇情 動作 犯罪’] | 9.4 | 824694 | 怪蜀黍和小蘿莉不得不說的故事。 | https://movie.douban.com/subject/1295644/ |
3 | 4 | 阿甘正傳 | Robert Zemeckis | Tom Hanks | 1994 | 美國 | [‘劇情 愛情’] | 9.4 | 703838 | 一部美國近現代史。 | https://movie.douban.com/subject/1292720/ |
4 | 5 | 美麗人生 | 羅伯託·貝尼尼 Roberto Benigni | 羅伯託·貝尼尼 Roberto Beni…’] | 1997 | 義大利 | [‘劇情 喜劇 愛情 戰爭’] | 9.5 | 410615 | 最美的謊言。 | https://movie.douban.com/subject/1292063/ |
再看一下抓取單個頁面的資訊:
df_2 = pd.read_csv("top250_f2.csv",sep = "#", encoding = 'utf8')
df_2.head()
num | rank | alt_title | title | pubdate | language | writer | director | cast | movie_duration | year | movie_type | tags | image | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 9.6 | 肖申克的救贖 / 月黑高飛(港) | The Shawshank Redemption | [‘1994-09-10(多倫多電影節)’, ‘1994-10-14(美國)’] | [‘英語’] | [‘弗蘭克·德拉邦特 Frank Darabont’, ‘斯蒂芬·金 Stephen King’] | [‘弗蘭克·德拉邦特 Frank Darabont’] | [‘蒂姆·羅賓斯 Tim Robbins’, ‘摩根·弗里曼 Morgan Freeman’… | [‘142 分鐘’] | [‘1994’] | [‘犯罪’, ‘劇情’] | [{‘count’: 178370, ‘name’: ‘經典’}, {‘count’: 15… | https://img3.doubanio.com/view/movie_poster_co… |
1 | 2 | 9.5 | 再見,我的妾 | 霸王別姬 | [‘1993-01-01(香港)’] | [‘漢語普通話’] | [‘蘆葦 Wei Lu’, ‘李碧華 Lillian Lee’] | [‘陳凱歌 Kaige Chen’] | [‘張國榮 Leslie Cheung’, ‘張豐毅 Fengyi Zhang’, ‘鞏俐 … | [‘171 分鐘’] | [‘1993’] | [‘劇情’, ‘愛情’, ‘同性’] | [{‘count’: 109302, ‘name’: ‘經典’}, {‘count’: 54… | https://img3.doubanio.com/view/movie_poster_co… |
2 | 3 | 9.4 | 這個殺手不太冷 / 殺手萊昂 | Léon | [‘1994-09-14(法國)’] | [‘英語’, ‘義大利語’, ‘法語’] | [‘呂克·貝鬆 Luc Besson’] | [‘呂克·貝鬆 Luc Besson’] | [‘讓·雷諾 Jean Reno’, ‘娜塔莉·波特曼 Natalie Portman’, … | [‘110分鐘(劇場版)’, ‘133分鐘(國際版)’] | [‘1994’] | [‘劇情’, ‘動作’, ‘犯罪’] | [{‘count’: 136989, ‘name’: ‘經典’}, {‘count’: 75… | https://img3.doubanio.com/view/movie_poster_co… |
3 | 4 | 9.4 | 阿甘正傳 / 福雷斯特·岡普 | Forrest Gump | [‘1994-06-23(洛杉磯首映)’, ‘1994-07-06(美國)’] | [‘英語’] | [‘Eric Roth’, ‘Winston Groom’] | [‘Robert Zemeckis’] | [‘Tom Hanks’, ‘Robin Wright Penn’, ‘Gary Sinis… | [‘142 分鐘’] | [‘1994’] | [‘劇情’, ‘愛情’] | [{‘count’: 165677, ‘name’: ‘勵志’}, {‘count’: 12… | https://img1.doubanio.com/view/movie_poster_co… |
4 | 5 | 9.5 | 美麗人生 / 一個快樂的傳說(港) | La vita è bella | [‘1997-12-20(義大利)’] | [‘義大利語’, ‘德語’, ‘英語’] | [‘文森佐·克拉米 Vincenzo Cerami’, ‘羅伯託·貝尼尼 Roberto B… | [‘羅伯託·貝尼尼 Roberto Benigni’] | [‘羅伯託·貝尼尼 Roberto Benigni’, ‘尼可萊塔·布拉斯基 Nicolet… | [‘116分鐘’] | [‘1997’] | [‘劇情’, ‘喜劇’, ‘愛情’, ‘戰爭’] | [{‘count’: 66790, ‘name’: ‘義大利’}, {‘count’: 61… | https://img3.doubanio.com/view/movie_poster_co… |
都是250行資訊。下面進行的資料清洗。
資料清洗
一般我們得到的資料是不可以直接使用的,裡面可能存在重複值、缺失值、空值、
無效值、異常值、錯誤值,以及邏輯、格式不正確等的資料不一致問題。網上抓取來的資料更容易有這些問題,我們需要處理這些髒資料,轉化成可供分析的資料。
資料分佈在兩個檔案中,我們選取 top250_f1.csv
檔案中的 num
(排名)、 title
(電影名)、 init_year
(上映時間)、 area
(國家/地區)、 genre
(型別)、 rating_num
(評分)、 comment_num
(評價人數),和 top250_f2.csv
檔案中的 language
(語言)、 director
(導演)、 cast
(主演)、 movie_duration
(時長)、 tags
(標籤)這些列進行分析,因此只對這些列中的髒資料做清洗工作。
先將這些列放到同一個DataFrame中:
df_1_cut = df_1[['num','title','init_year','area','genre','rating_num','comment_num']]
df_2_cut = df_2[['num','language','director','cast','movie_duration','tags']]
df = pd.merge(df_1_cut,df_2_cut,how = 'outer', on = 'num')
df.head()
num | title | init_year | area | genre | rating_num | comment_num | language | director | cast | movie_duration | tags | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 肖申克的救贖 | 1994 | 美國 | [‘犯罪 劇情’] | 9.6 | 861343 | [‘英語’] | [‘弗蘭克·德拉邦特 Frank Darabont’] | [‘蒂姆·羅賓斯 Tim Robbins’, ‘摩根·弗里曼 Morgan Freeman’… | [‘142 分鐘’] | [{‘count’: 178370, ‘name’: ‘經典’}, {‘count’: 15… |
1 | 2 | 霸王別姬 | 1993 | 中國大陸 香港 | [‘劇情 愛情 同性’] | 9.5 | 618349 | [‘漢語普通話’] | [‘陳凱歌 Kaige Chen’] | [‘張國榮 Leslie Cheung’, ‘張豐毅 Fengyi Zhang’, ‘鞏俐 … | [‘171 分鐘’] | [{‘count’: 109302, ‘name’: ‘經典’}, {‘count’: 54… |
2 | 3 | 這個殺手不太冷 | 1994 | 法國 | [‘劇情 動作 犯罪’] | 9.4 | 824694 | [‘英語’, ‘義大利語’, ‘法語’] | [‘呂克·貝鬆 Luc Besson’] | [‘讓·雷諾 Jean Reno’, ‘娜塔莉·波特曼 Natalie Portman’, … | [‘110分鐘(劇場版)’, ‘133分鐘(國際版)’] | [{‘count’: 136989, ‘name’: ‘經典’}, {‘count’: 75… |
3 | 4 | 阿甘正傳 | 1994 | 美國 | [‘劇情 愛情’] | 9.4 | 703838 | [‘英語’] | [‘Robert Zemeckis’] | [‘Tom Hanks’, ‘Robin Wright Penn’, ‘Gary Sinis… | [‘142 分鐘’] | [{‘count’: 165677, ‘name’: ‘勵志’}, {‘count’: 12… |
4 | 5 | 美麗人生 | 1997 | 義大利 | [‘劇情 喜劇 愛情 戰爭’] | 9.5 | 410615 | [‘義大利語’, ‘德語’, ‘英語’] | [‘羅伯託·貝尼尼 Roberto Benigni’] | [‘羅伯託·貝尼尼 Roberto Benigni’, ‘尼可萊塔·布拉斯基 Nicolet… | [‘116分鐘’] | [{‘count’: 66790, ‘name’: ‘義大利’}, {‘count’: 61… |
通過 pd.merge()
函式選出的 df_1_cut
和 df_2_cut
兩張表,取並集,連結鍵為num
。
看一下資料基本資訊:
df.info()
重複值檢查
檢查重複值可以用 duplicated()
函式,若返回值為“True
”,則含有重複項,返回值為“False
”,則不含重複項。 pd.Series.value_counts()
函式可以用來對series計數。
df.duplicated().value_counts()
False 250
dtype: int64
250個 `False` ,說明不含重複項。
檢查是否有重名電影:
len(df.title.unique())
250
250個唯一值,說明沒有重名電影。
檢查是否有並列排名:
len(df.num.unique())
250
同樣250個唯一值,沒有並列排名。
清洗資料格式、資料分列
粗略看一下,可以發現 genre
,language
,director
,cast
,movie_duration
,tags
列方括號、花括號和英文省略號等無效資訊,需要去掉。
對於兩側的 [' ']
或 {[' ']}
形式,可以用str分割字串。
df['genre'] = df['genre'].str[2:-2]
df['language'] = df['language'].str[2:-2]
df['director'] = df['director'].str[2:-2]
df['cast'] = df['cast'].str[2:-2]
df['movie_duration'] = df['movie_duration'].str[2:-2]
df['tags'] = df['tags'].str[3:-3]
df.head()
num | title | init_year | area | genre | rating_num | comment_num | language | director | cast | movie_duration | tags | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 肖申克的救贖 | 1994 | 美國 | 犯罪 劇情 | 9.6 | 861343 | 英語 | 弗蘭克·德拉邦特 Frank Darabont | 蒂姆·羅賓斯 Tim Robbins’, ‘摩根·弗里曼 Morgan Freeman’, … | 142 分鐘 | count’: 178370, ‘name’: ‘經典’}, {‘count’: 15001… |
1 | 2 | 霸王別姬 | 1993 | 中國大陸 香港 | 劇情 愛情 同性 | 9.5 | 618349 | 漢語普通話 | 陳凱歌 Kaige Chen | 張國榮 Leslie Cheung’, ‘張豐毅 Fengyi Zhang’, ‘鞏俐 Li… | 171 分鐘 | count’: 109302, ‘name’: ‘經典’}, {‘count’: 54458… |
2 | 3 | 這個殺手不太冷 | 1994 | 法國 | 劇情 動作 犯罪 | 9.4 | 824694 | 英語’, ‘義大利語’, ‘法語 | 呂克·貝鬆 Luc Besson | 讓·雷諾 Jean Reno’, ‘娜塔莉·波特曼 Natalie Portman’, ‘加… | 110分鐘(劇場版)’, ‘133分鐘(國際版) | count’: 136989, ‘name’: ‘經典’}, {‘count’: 75963… |
3 | 4 | 阿甘正傳 | 1994 | 美國 | 劇情 愛情 | 9.4 | 703838 | 英語 | Robert Zemeckis | Tom Hanks’, ‘Robin Wright Penn’, ‘Gary Sinise’… | 142 分鐘 | count’: 165677, ‘name’: ‘勵志’}, {‘count’: 12412… |
4 | 5 | 美麗人生 | 1997 | 義大利 | 劇情 喜劇 愛情 戰爭 | 9.5 | 410615 | 義大利語’, ‘德語’, ‘英語 | 羅伯託·貝尼尼 Roberto Benigni | 羅伯託·貝尼尼 Roberto Benigni’, ‘尼可萊塔·布拉斯基 Nicoletta… | 116分鐘 | count’: 66790, ‘name’: ‘義大利’}, {‘count’: 61289… |
對於 area
列,有些電影由多個國家或地區聯合制作,例如《霸王別姬》電影:
df['area'][1]
‘中國大陸 香港’
“中國大陸”和“香港”之間用空格隔開,可以用` str.split` 函式進行分列, `apply(pd.Series)` 使用到的函式作用在每一行或列。
area_split = df['area'].str.split(' ').apply(pd.Series)
area_split.head()
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 美國 | NaN | NaN | NaN | NaN |
1 | 中國大陸 | 香港 | NaN | NaN | NaN |
2 | 法國 | NaN | NaN | NaN | NaN |
3 | 美國 | NaN | NaN | NaN | NaN |
4 | 義大利 | NaN | NaN | NaN | NaN |
可以看到最多為5個製作國家/地區,當然,大多數只有一個製片國家/地區。要了解哪個國家/地區的電影最受歡迎,就需要對國家進行統計。
對於這麼多的空值,可以通過先按列計數,將空值 NaN
替換為“0”,再按行彙總。
a = area_split.apply(pd.value_counts).fillna('0')
a.columns = ['area_1','area_2','area_3','area_4','area_5']
a['area_1'] = a['area_1'].astype(int)
a['area_2'] = a['area_2'].astype(int)
a['area_3'] = a['area_3'].astype(int)
a['area_4'] = a['area_4'].astype(int)
a['area_5'] = a['area_5'].astype(int)
a = a.apply(lambda x: x.sum(),axis = 1)
area_c = pd.DataFrame(a, columns = ['counts'])
area_c.head()
counts | |
---|---|
中國大陸 | 15 |
丹麥 | 1 |
伊朗 | 2 |
加拿大 | 7 |
南非 | 2 |
以上過程也可以通過 unstack()
函式和 groupby()
函式來完成。 對 genre
列,我們使用這一方法。
genre_split = df['genre'].str.split(' ').apply(pd.Series)
genre_split.head()
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 犯罪 | 劇情 | NaN | NaN | NaN | NaN |
1 | 劇情 | 愛情 | 同性 | NaN | NaN | NaN |
2 | 劇情 | 動作 | 犯罪 | NaN | NaN | NaN |
3 | 劇情 | 愛情 | NaN | NaN | NaN | NaN |
4 | 劇情 | 喜劇 | 愛情 | 戰爭 | NaN | NaN |
g = genre_split.apply(pd.value_counts)
g.head()
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
傳記 | 2.0 | 9.0 | NaN | NaN | 1.0 | NaN |
兒童 | 1.0 | 3.0 | 2.0 | NaN | NaN | NaN |
冒險 | 7.0 | 6.0 | 13.0 | 11.0 | 4.0 | 1.0 |
劇情 | 163.0 | 26.0 | 5.0 | NaN | NaN | NaN |
動作 | 16.0 | 16.0 | 2.0 | NaN | NaN | NaN |
通過 unstack
函式將行“旋轉”為列,重排資料:
g.unstack().head()
0 傳記 2.0
兒童 1.0
冒險 7.0
劇情 163.0
動作 16.0
dtype: float64
此時資料為 `Series` ,去掉空值,並通過 `reset_index()` 轉化為 `Dataframe` :
g = g.unstack().dropna().reset_index()
g.head()
level_0 | level_1 | 0 | |
---|---|---|---|
0 | 0 | 傳記 | 2.0 |
1 | 0 | 兒童 | 1.0 |
2 | 0 | 冒險 | 7.0 |
3 | 0 | 劇情 | 163.0 |
4 | 0 | 動作 | 16.0 |
g.columns = ['level_0','level_1', 'counts']
genre_c = g.drop(['level_0'],axis = 1).groupby('level_1').sum()
genre_c.head()
counts | |
---|---|
level_1 | |
傳記 | 12.0 |
兒童 | 6.0 |
冒險 | 42.0 |
劇情 | 194.0 |
動作 | 34.0 |
此時 counts
列即為電影型別的統計計數。
類似的方法來處理以下幾項。
language
列:
language_split = df['language'].str.replace('\', \'',' ').str.split(' ').apply(pd.Series)
l = language_split.apply(pd.value_counts).stack().dropna().reset_index()
l.columns = ['level_0','level_1', 'counts']
language_c = l.groupby('level_0').sum()
language_c = language_c.drop(['level_1'],axis = 1)
language_c.head()
counts | |
---|---|
level_0 | |
Ungwatsi | 1.0 |
上海話 | 4.0 |
世界語 | 1.0 |
丹麥語 | 3.0 |
烏克蘭語 | 1.0 |
director
列:
director_split = df['director'].str.replace('\', \'','#').str.split('#').apply(pd.Series)
director_split.head()
0 | 1 | 2 | |
---|---|---|---|
0 | 弗蘭克·德拉邦特 Frank Darabont | NaN | NaN |
1 | 陳凱歌 Kaige Chen | NaN | NaN |
2 | 呂克·貝鬆 Luc Besson | NaN | NaN |
3 | Robert Zemeckis | NaN | NaN |
4 | 羅伯託·貝尼尼 Roberto Benigni | NaN | NaN |
選取第一位導演作為分析物件:
director = director_split[0].str.strip()
df['director'] = director
`cast` 列:
cast_split = df['cast'].str.replace('\', \'','#').str.split('#').apply(pd.Series) #[[0,1,2,3]].columns=['performer_1','performer_2','performer_3','performer_4']
cast_split.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | … | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 蒂姆·羅賓斯 Tim Robbins | 摩根·弗里曼 Morgan Freeman | 鮑勃·岡頓 Bob Gunton | 威廉姆·賽德勒 William Sadler | 克蘭西·布朗 Clancy Brown | 吉爾·貝羅斯 Gil Bellows | 馬克·羅斯頓 Mark Rolston | 詹姆斯·惠特摩 James Whitmore | 傑弗裡·德曼 Jeffrey DeMunn | 拉里·布蘭登伯格 Larry Brandenburg | … | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1 | 張國榮 Leslie Cheung | 張豐毅 Fengyi Zhang | 鞏俐 Li Gong | 葛優 You Ge | 英達 Da Ying | 蔣雯麗 Wenli Jiang | 吳大維 David Wu | 呂齊 Qi Lü | 雷漢 Han Lei | 尹治 Zhi Yin | … | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 | 讓·雷諾 Jean Reno | 娜塔莉·波特曼 Natalie Portman | 加里·奧德曼 Gary Oldman | 丹尼·愛羅 Danny Aiello | 彼得·阿佩爾 Peter Appel | 邁克爾·巴達魯科 Michael Badalucco | 艾倫·格里尼 Ellen Greene | 伊麗莎白·瑞根 Elizabeth Regen | 卡爾·馬圖斯維奇 Carl J. Matusovich | 弗蘭克·賽格 Frank Senger | … | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | Tom Hanks | Robin Wright Penn | Gary Sinise | Mykelti Williamson | Sally Field | Michael Conner Humphreys | Haley Joel Osment | NaN | NaN | NaN | … | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
4 | 羅伯託·貝尼尼 Roberto Benigni | 尼可萊塔·布拉斯基 Nicoletta Braschi | 喬治·坎塔里尼 Giorgio Cantarini | 朱斯蒂諾·杜拉諾 Giustino Durano | 塞爾吉奧·比尼·布斯特里克 Sergio Bini Bustric | 瑪麗莎·佩雷德斯 Marisa Paredes | 豪斯特·巴奇霍茲 Horst Buchholz | 利迪婭·阿方西 Lidia Alfonsi | 朱利亞娜·洛約迪切 Giuliana Lojodice | 亞美利哥·豐塔尼 Amerigo Fontani | … | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
5 rows × 44 columns
選取前六位演員作為分析物件。
c = cast_split[[0,1,2,3,4,5]] #.columns=['performer_1','performer_2','performer_3','performer_4','performer_5','performer_6']
c.columns=['performer_1','performer_2','performer_3','performer_4','performer_5','performer_6']
c = cast_split.unstack().dropna().reset_index()
c.head()
level_0 | level_1 | 0 | |
---|---|---|---|
0 | 0 | 0 | 蒂姆·羅賓斯 Tim Robbins |
1 | 0 | 1 | 張國榮 Leslie Cheung |
2 | 0 | 2 | 讓·雷諾 Jean Reno |
3 | 0 | 3 | Tom Hanks |
4 | 0 | 4 | 羅伯託·貝尼尼 Roberto Benigni |
c.columns=['level_0','level_1','performers']
c['performers'] = c['performers'].str.strip()
c.head()
level_0 | level_1 | performers | |
---|---|---|---|
0 | 0 | 0 | 蒂姆·羅賓斯 Tim Robbins |
1 | 0 | 1 | 張國榮 Leslie Cheung |
2 | 0 | 2 | 讓·雷諾 Jean Reno |
3 | 0 | 3 | Tom Hanks |
4 | 0 | 4 | 羅伯託·貝尼尼 Roberto Benigni |
演員表中有些人名中英文都標註了,有些只寫了中文或英文名,例如“Tom Hanks”和“湯姆·漢克斯 Tom Hanks”是指一個人。下面的步驟是找出單獨的中文或英文名,補全為中英兩種語言的名字。
for i in c['performers']:
for j in c[c['performers'].str.contains(i)]['performers']:
if (len(j) > len(i)):
c[c['performers']==i] = j
else:
continue
d:\ProgramData\Anaconda3\lib\site-packages\ipykernel\__main__.py:2: UserWarning: This pattern has match groups. To actually get the groups, use str.extract.
from ipykernel import kernelapp as app
c['performers'].head()
0 蒂姆·羅賓斯 Tim Robbins
1 張國榮 Leslie Cheung
2 讓·雷諾 Jean Reno
3 湯姆·漢克斯 Tom Hanks
4 羅伯託·貝尼尼 Roberto Benigni
Name: performers, dtype: object
c = c.groupby('performers').count()
此時 `level_0` 和 `level_1` 的資料是完全一樣的,都是表示演員出現的次數,刪除 `Level_0` 列。
c = c.drop(['level_0'], axis = 1)
c.columns = ['counts']
cast_c = c
cast_c.head()
counts | |
---|---|
performers | |
1326270 | 1 |
1976 (樂團) | 1 |
Agnese Nano | 1 |
Aldo Giuffrè | 1 |
Alexandre Rodrigues | 1 |
movie_duration
列:
movie_duration_split = df['movie_duration'].str.strip().str.replace('\', \'','#').str.split('#').apply(pd.Series)
movie_duration_split.head()
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | 142 分鐘 | NaN | NaN | NaN | NaN | NaN |
1 | 171 分鐘 | NaN | NaN | NaN | NaN | NaN |
2 | 110分鐘(劇場版) | 133分鐘(國際版) | NaN | NaN | NaN | NaN |
3 | 142 分鐘 | NaN | NaN | NaN | NaN | NaN |
4 | 116分鐘 | NaN | NaN | NaN | NaN | NaN |
有些電影時長存在多種版本,一般情況下第一個時長為國內最普通、觀看數量較多的版本,因此僅取第一個時長。
duration = movie_duration_split[0].str.split('分').apply(pd.Series)[0].str.strip()
duration.head()
0 142
1 171
2 110
3 142
4 116
Name: 0, dtype: object
型別為object,需改為int型別。此時若是直接用 `duration.astype(int)` ,會報錯: `ValueError: invalid literal for int() with base 10: ‘Australia: 80’` ,錯誤資訊顯示有一行為“Australia: 80”,混有其他字串,所以無法轉換成int型別。我們需要找出非數值型資料。
電影時長應該為兩位數或三位數,可以看一下數字長度:
duration.str.len().value_counts()
3 187
2 61
13 1
9 1
Name: 0, dtype: int64
字串長度有兩個不合理的值,找出來:
duration[duration.str.len() > 3]
74 Australia: 80
226 Japan: 75
Name: 0, dtype: object
duration[74] = duration[74].split(' ')[1]
duration[226] = duration[226].split(' ')[1]
現在更改資料型別:
duration = duration.astype(int)
duration.dtypes
dtype(‘int32’)
df['movie_duration'] = duration
df['movie_duration'].head()
0 142
1 171
2 110
3 142
4 116
Name: movie_duration, dtype: int32
對於 `tags` 列,先看一下第一項基本情況:
df.tags[0]
“count’: 178370, ‘name’: ‘經典’}, {‘count’: 150016, ‘name’: ‘勵志’}, {‘count’: 131943, ‘name’: ‘信念’}, {‘count’: 117510, ‘name’: ‘自由’}, {‘count’: 90200, ‘name’: ‘美國’}, {‘count’: 82546, ‘name’: ‘人性’}, {‘count’: 61162, ‘name’: ‘人生’}, {‘count’: 53244, ‘name’: ‘劇情”
其中的數值和漢字部分是需要提取的,其餘無關資訊可用 `str.replace()` 函式替換掉,用 `str.split()` 函式分列:
tags_split = df['tags'].str.replace('count\': ',' ').str.replace(', \'name\': \'',' ').str.replace('\'}, {\'','').str.split(' ').apply(pd.Series)
tags_split.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 178370 | 經典 | 150016 | 勵志 | 131943 | 信念 | 117510 | 自由 | 90200 | 美國 | 82546 | 人性 | 61162 | 人生 | 53244 | 劇情 |
1 | 109302 | 經典 | 54458 | 中國電影 | 53522 | 愛情 | 49358 | 文藝 | 46339 | 人性 | 45374 | 同志 | 37368 | 人生 | 28356 | 劇情 |
2 | 136989 | 經典 | 75963 | 愛情 | 73361 | 溫情 | 51532 | 人性 | 47454 | 劇情 | 36808 | 動作 | 31271 | 犯罪 | 19390 | 1994 |
3 | 165677 | 勵志 | 124126 | 經典 | 94060 | 美國 | 82929 | 人生 | 61445 | 信念 | 59325 | 成長 | 34048 | 劇情 | 24545 | 人性 |
4 | 66790 | 義大利 | 61289 | 經典 | 60683 | 二戰 | 58827 | 親情 | 36662 | 戰爭 | 21463 | 溫情 | 18881 | 愛情 | 18695 | 人性 |
刪除“0”列:
del tags_split[0]
tags_split.head(2)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 178370 | 經典 | 150016 | 勵志 | 131943 | 信念 | 117510 | 自由 | 90200 | 美國 | 82546 | 人性 | 61162 | 人生 | 53244 | 劇情 |
1 | 109302 | 經典 | 54458 | 中國電影 | 53522 | 愛情 | 49358 | 文藝 | 46339 | 人性 | 45374 | 同志 | 37368 | 人生 | 28356 | 劇情 |
一般閱讀習慣是先看標籤類別,再看標籤數量,調整一下位置比較便於閱讀:
tags_split = tags_split.reindex(columns = [2,1,4,3,6,5,8,7,10,9,12,11,14,13,16,15])
tags_split.head(2)
2 | 1 | 4 | 3 | 6 | 5 | 8 | 7 | 10 | 9 | 12 | 11 | 14 | 13 | 16 | 15 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 經典 | 178370 | 勵志 | 150016 | 信念 | 131943 | 自由 | 117510 | 美國 | 90200 | 人性 | 82546 | 人生 | 61162 | 劇情 | 53244 |
1 | 經典 | 109302 | 中國電影 | 54458 | 愛情 | 53522 | 文藝 | 49358 | 人性 | 46339 | 同志 | 45374 | 人生 | 37368 | 劇情 | 28356 |
更改列名:
tags_split.columns = ['tags_1','tags_counts_1','tags_2','tags_counts_2','tags_3','tags_counts_3','tags_4','tags_counts_4','tags_5','tags_counts_5','tags_6','tags_counts_6','tags_7','tags_counts_7','tags_8','tags_counts_8']
tags_split.head()
tags_1 | tags_counts_1 | tags_2 | tags_counts_2 | tags_3 | tags_counts_3 | tags_4 | tags_counts_4 | tags_5 | tags_counts_5 | tags_6 | tags_counts_6 | tags_7 | tags_counts_7 | tags_8 | tags_counts_8 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 經典 | 178370 | 勵志 | 150016 | 信念 | 131943 | 自由 | 117510 | 美國 | 90200 | 人性 | 82546 | 人生 | 61162 | 劇情 | 53244 |
1 | 經典 | 109302 | 中國電影 | 54458 | 愛情 | 53522 | 文藝 | 49358 | 人性 | 46339 | 同志 | 45374 | 人生 | 37368 | 劇情 | 28356 |
2 | 經典 | 136989 | 愛情 | 75963 | 溫情 | 73361 | 人性 | 51532 | 劇情 | 47454 | 動作 | 36808 | 犯罪 | 31271 | 1994 | 19390 |
3 | 勵志 | 165677 | 經典 | 124126 | 美國 | 94060 | 人生 | 82929 | 信念 | 61445 | 成長 | 59325 | 劇情 | 34048 | 人性 | 24545 |
4 | 義大利 | 66790 | 經典 | 61289 | 二戰 | 60683 | 親情 | 58827 | 戰爭 | 36662 | 溫情 | 21463 | 愛情 | 18881 | 人性 | 18695 |
有的電影給出了不同多家上映的時間,其中第一個最早,因此對於好幾個年份的情況取第一個值。
year_split = df['init_year'].str.split('/').apply(pd.Series)[0].str.strip()
year_split = pd.to_datetime(year_split).dt.year
df['init_year'] = year_split
df['init_year'].head()
0 1994
1 1993
2 1994
3 1994
4 1997
Name: init_year, dtype: int64
缺失值檢查
df[df.isnull().values == True]
num | title | init_year | area | genre | rating_num | comment_num | language | director | cast | movie_duration | tags |
---|