[譯] 使用pandas構(gòu)建IMDB Top 250

實際工作和生活中,不可避免地需要一些排序規(guī)則。這篇文章或多或少會有一些參考價值。

原文地址:Building an IMDB Top 250 Clone with Pandas

互聯(lián)網(wǎng)電影數(shù)據(jù)庫(IMDB)維護(hù)著一份名為IMDB Top 250的表格,該表格是一份根據(jù)某種評分原則生成的排名前250的電影。表格中的電影都是非紀(jì)錄片,且為劇場版本,影片時長至少45分鐘,影評數(shù)超過250000條:



這個表格可以看成最簡單的推薦器。它沒有考慮特定用戶的喜好,也沒有試圖推斷不同電影的相似度。它僅僅根據(jù)預(yù)定義的指標(biāo)計算每部電影的評分,并以此輸出一份排好序的電影列表。
本文包括以下內(nèi)容:

  • 重構(gòu)一張IMDB Top 250的表格(后面代指簡單的推薦器)
  • 進(jìn)一步完善表格的功能,構(gòu)建一個基于知識的推薦器。該模型考慮了用戶的關(guān)于影片類型,年代,時長,語言的喜好,推薦滿足所有條件的電影。
    您需要在系統(tǒng)上安裝Python。最后,為了使用Git倉庫,你也需要安裝Git。這篇文章的代碼在Github的地址:https://github.com/PacktPublishing/Hands-On-Recommendation-Systems-with-Python/tree/master/Chapter3。你還可以在http://bit.ly/2v7SZD4上查看代碼視頻。

簡單的推薦器

構(gòu)建一個簡單的推薦器的第一步是建立自己的工作目錄。新建一個文件夾,命名為IMDB。建立一個名為Simple Recommender的Jupyter Notebook,然后在瀏覽器里打開。
可用的數(shù)據(jù)集的地址:https://www.kaggle.com/rounakbanik/the-movies-dataset/downloads/movies_metadata.csv/7

import pandas as pd
import numpy as np

#Load the dataset into a pandas dataframe
df = pd.read_csv('../data/movies_')

#Display the first five movies in the dataframe
df.head()

在運行單元格時,你應(yīng)該能看到notebook中熟悉的類似表格的結(jié)構(gòu)出現(xiàn)。
構(gòu)建簡單的推薦器非常簡單。步驟如下:

  1. 選擇一個指標(biāo)(或分?jǐn)?shù))來評價電影
  2. 確定要在表格上顯示的電影的先決條件
  3. 計算滿足條件的每部電影的分?jǐn)?shù)
  4. 按照分?jǐn)?shù)的降序輸出電影列表

衡量準(zhǔn)則

衡量準(zhǔn)則是指對電影排名的定量標(biāo)準(zhǔn)。如果一部電影比另一部電影有更高的定量指標(biāo)分,則認(rèn)為該電影要優(yōu)于另一部。因此,對于建立高質(zhì)量的電影推薦器,一個魯棒的可信賴的衡量準(zhǔn)則非常重要。
衡量準(zhǔn)則的選擇是任意的。一種最簡單的指標(biāo)是電影評分。然后,這種方式有各種的缺點。首先,影片評分沒有考慮電影的歡迎度。因此,一部被100,000位用戶評為9分電影的評分會低于另一部只有100位用戶評為9.5分的電影。這是不可取的,因為很可能這類只有100人觀看和評分的電影迎合了一個非常特定的群體,并不像前者一樣,受大眾喜愛,吸引普通觀眾。
這也是一個事實,隨著投票人數(shù)的增長,電影評分趨于正?;?,并接近一個值,能反應(yīng)電影質(zhì)量和受歡迎度的價值。換而言之,只有少量評分的電影,其評分并不十分可信。一部只有5位用戶評為10分的電影,并不意味著它是一部好電影。
因此,需要定義一個指標(biāo),某種程度上,將影片評分及其參與的投票數(shù)(代表人氣)都考慮進(jìn)來。這將使得一部轟動一時的電影更受青睞,這部電影的評分為8,用戶數(shù)為100,000,而另一部電影的評分為9,用戶數(shù)只有100。
幸運的是,您不必為指標(biāo)集思廣益。您可以使用IMDB的加權(quán)評級公式作為指標(biāo)。在數(shù)學(xué)上,它可以表示如下:
加權(quán)評分(WR)=
(v/(v + m) * R)+ (m/(v+m) * C)
參數(shù)解釋如下:

  • v表示電影獲得的票數(shù)
  • m表示電表格中電影所需的最小票數(shù)(先決條件)
  • R代指電影的平均評分
  • C表示數(shù)據(jù)集中所有電影的評分分
    v和R各自以電影的vote_count和vote_average的特征計算。計算C則非常簡單。

先決條件

IMDB加權(quán)公式還有一個變量m,需要它計算得分。此變量用于確保僅考慮高于特定人氣閾值的電影進(jìn)行排名。因此,m的值確定有資格在表格中的電影,并且通過作為公式的一部分,確定得分的最終值。
正如衡量準(zhǔn)則,m值的選擇是任意的。換言之,m沒有一個正確的值。最好嘗試不同的m值,然后選擇你(以及你的觀眾)認(rèn)為最好的推薦值。唯一需要記住的是,m的值越高,對電影受歡迎程度的重視程度越高,因此選擇性越高。
推薦而言,請使用第80百分位影片獲得的投票數(shù)作為m的值。換句話說,對于要在排名中考慮的電影,它必須獲得比數(shù)據(jù)集中至少80%的電影更多的選票。另外,在先前描述的加權(quán)公式中使用由第80百分位電影獲得的投票數(shù)來得出分?jǐn)?shù)的值。
現(xiàn)在,計算m的值:

#Calculate the number of votes garnered by the 80th percentile movie
m = df['vote_count'].quantile(0.80)
m

OUTPUT:
50.0

可以看到,只有百分之20的電影獲得了超過50個的評分。因此,m的值取50.
另一個考慮的先決條件是影片時長。僅僅考慮時長在45分鐘到300分鐘的電影。定義一個Dataframe,q_movies,包含符合條件的所有電影。

#Only consider movies longer than 45 minutes and shorter than 300 minutes
q_movies = df[(df['runtime'] >= 45) & (df['runtime'] <= 300)]

#Only consider movies that have garnered more than m votes
q_movies = q_movies[q_movies['vote_count'] >= m]

#Inspect the number of movies that made the cut
q_movies.shape

OUTPUT:
(8963, 24)

數(shù)據(jù)集中45000部電影,大約9000(20%)部符合條件。

計算分值

在得到分值之前,最后需要計算的值就是C,數(shù)據(jù)集中所有電影的平均分:

# Calculate C
C = df['vote_average'].mean()
C

OUTPUT:
5.6182072151341851

電影的平均得分為5.6/10。IMDB似乎對電影的評分要求特別嚴(yán)格。 現(xiàn)在已經(jīng)有C的值,可以對每部電影打分了。
首先,定義一個計算電影評分的函數(shù),輸入?yún)?shù)為電影的特征,m和C的值:

# Function to compute the IMDB weighted rating for each movie
def weighted_rating(x, m=m, C=C):
    v = x['vote_count']
    R = x['vote_average']
    # Compute the weighted score
    return (v/(v+m) * R) + (m/(m+v) * C)

然后,使用熟悉的apply函數(shù)作用在Dataframe q_movie上,構(gòu)建一個新的得分特征列。因為,計算是作用在每一行的,設(shè)置axis為1,表示基于行的操作。

# Compute the score using the weighted_rating function defined above
q_movies['score'] = q_movies.apply(weighted_rating, axis=1)

排序及輸出

只剩最后一步?,F(xiàn)在需要基于計算出來的score,將Dataframe排序,輸出一個top的電影列表:



嗯,這樣,推薦器就建好了。
你可以看到寶萊塢電影Dilwale Dulhania Le Jayenge位居榜首。 它的票數(shù)明顯少于其他前25部電影。 這有力地表明你應(yīng)該探索更高的m值。 嘗試不同的m值,觀察圖表中的電影如何變化。

基于知識的推薦器

接下來,你將構(gòu)建一個基于知識的推薦器,方法類似于上面的IMDB Top 250。這將是一個簡單函數(shù),執(zhí)行下面幾個任務(wù):

  • 詢問用戶他/她正在尋找的電影類型
  • 詢問用戶傾向的影片時長
  • 詢問用戶傾向的影片的年代
  • 使用收集的信息,向用戶推薦具有高加權(quán)等級(根據(jù)IMDB公式)并滿足上述條件的電影
    您擁有的數(shù)據(jù)包含有關(guān)影片時長,流派和時間線的信息,但它目前不是可直接使用的形式。 在將數(shù)據(jù)用于構(gòu)建此推薦程序之前,您的數(shù)據(jù)需要進(jìn)行處理。
    在您的IMDB文件夾中,創(chuàng)建一個名為Knowledge Recommender的新Jupyter Notebook。 此notebook將包含您在本節(jié)中編寫的所有代碼。
    將依賴包和數(shù)據(jù)加載到notebook中。 另外,請查看已有的特征,并確定對此任務(wù)有用的特征:
import pandas as pd
import numpy as np

df = pd.read_csv('../data/movies_metadata.csv')

#Print all the features (or columns) of the DataFrame
df.columns

OUTPUT:
Index(['adult', 'belongs_to_collection', 'budget', 'genres', 'homepage', 'id',
       'imdb_id', 'original_language', 'original_title', 'overview',
       'popularity', 'poster_path', 'production_companies',
       'production_countries', 'release_date', 'revenue', 'runtime',
       'spoken_languages', 'status', 'tagline', 'title', 'video',
       'vote_average', 'vote_count'],
      dtype='object')

結(jié)果來看,很清晰地看到哪些特征需要,哪些不需要。接下來,簡化你的Dataframe,只包含你模型需要的特征:

#Only keep those features that we require 
df = df[['title','genres', 'release_date', 'runtime', 'vote_average', 'vote_count']]

df.head()

從release_date特征中提取發(fā)布年份:

#Convert release_date into pandas datetime format
df['release_date'] = pd.to_datetime(df['release_date'], errors='coerce')

#Extract year from the datetime
df['year'] = df['release_date'].apply(lambda x: str(x).split('-')[0] if x != np.nan else np.nan)

年份特征仍然是一個對象,并且充滿了NaT值,這是一種由Pandas使用的空值。 將這些值轉(zhuǎn)換為整數(shù)0,并將year特征的數(shù)據(jù)類型轉(zhuǎn)換為int。
為此,定義輔助函數(shù)convert_int,并將其應(yīng)用于年份特征:

#Helper function to convert NaT to 0 and all other years to integers.
def convert_int(x):
    try:
        return int(x)
    except:
        return 0

#Apply convert_int to the year feature
df['year'] = df['year'].apply(convert_int)
You do not require the release_date feature anymore. So, go ahead and remove it:
#Drop the release_date column
df = df.drop('release_date', axis=1)

#Display the dataframe
df.head()

影片時長特征已經(jīng)是可用的形式。 它不需要任何額外的處理。 現(xiàn)在,把你的注意力轉(zhuǎn)向影片的類別。

影片類別

你也許發(fā)現(xiàn)類別信息是以一種類似于Json對象(或Python字典)的格式呈現(xiàn)。瞄一眼電影的類別對象:

#Print genres of the first movie
df.iloc[0]['genres']

OUTPUT:
"[{'id': 16, 'name': 'Animation'}, {'id': 35, 'name': 'Comedy'}, {'id': 10751, 'name': 'Family'}]"

可以發(fā)現(xiàn),輸出的是一個字符串形式的字典。為了讓該特征能用,有必要將其字符串轉(zhuǎn)為原生的Python字典。幸運的是,Python中名為literal_eval的函數(shù)(在ast庫里)可以準(zhǔn)確地處理。literal_eval可以將任意的字符串轉(zhuǎn)為相應(yīng)的Python對象:

#Import the literal_eval function from ast
from ast import literal_eval

#Define a stringified list and output its type
a = "[1,2,3]"
print(type(a))

#Apply literal_eval and output type
b = literal_eval(a)
print(type(b))

OUTPUT:
<class 'str'>
<class 'list'>

現(xiàn)在已經(jīng)有所有必要的工具,將類別特征轉(zhuǎn)為Python字典格式。
同時,每個字典代表一個類別,存在兩個鍵:id和name。然而,對于這個任務(wù),只需要name。因此,將字典列表轉(zhuǎn)換為字符串列表,其中每個字符串都是一個類別名稱

#Convert all NaN into stringified empty lists
df['genres'] = df['genres'].fillna('[]')

#Apply literal_eval to convert to the list object
df['genres'] = df['genres'].apply(literal_eval)

#Convert list of dictionaries to a list of strings
df['genres'] = df['genres'].apply(lambda x: [i['name'] for i in x] if isinstance(x, list) else [])

df.head()

打印Dataframe的頭部,將展示一個新的genres特征,其包含一個genre名字的列表。然而,事情還沒完成。最后一步是,explode這個genres列。換言之,如果一部電影有多個類別,生成這個電影的多個備份,每個備份對應(yīng)一個類別。
舉例而言,假設(shè)一部名為Just Go With It的電影,有romance和comedy兩個類別,將其explode成兩行。一行是Just Go With I被標(biāo)記為romance,另一行則是標(biāo)記為comedy:

#Create a new feature by exploding genres
s = df.apply(lambda x: pd.Series(x['genres']),axis=1).stack().reset_index(level=1, drop=True)

#Name the new feature as 'genre'
s.name = 'genre'

#Create a new dataframe gen_df which by dropping the old 'genres' feature and adding the new 'genre'.
gen_df = df.drop('genres', axis=1).join(s)

#Print the head of the new gen_df
gen_df.head()

你應(yīng)該能看到三行Toy Story,分別對應(yīng)著animation, family, 和 comedy。這個gen_df的DataFrame對構(gòu)建知識base的推薦器很重要。

build_chart函數(shù)

現(xiàn)在終于可以寫一個函數(shù),作為推薦器了?,F(xiàn)在不能像之前一樣計算m和C的值,因為不是每部電影都符合要求。換句話說,分為以下三步:

  1. 獲取用戶的偏好
  2. 過濾出符合用戶條件的電影
  3. 根據(jù)以上,計算m和C的值,然后按照上一節(jié)中的步驟構(gòu)建圖表
    因此,build_chart函數(shù)只接受兩個輸入:gen_df DataFrame和用于計算m值的百分位數(shù)。 默認(rèn)情況下,百分位數(shù)設(shè)置為80%或0.8:
def build_chart(gen_df, percentile=0.8):
    #Ask for preferred genres
    print("Input preferred genre")
    genre = input()

    #Ask for lower limit of duration
    print("Input shortest duration")
    low_time = int(input())

    #Ask for upper limit of duration
    print("Input longest duration")
    high_time = int(input())

    #Ask for lower limit of timeline
    print("Input earliest year")
    low_year = int(input())

    #Ask for upper limit of timeline
    print("Input latest year")
    high_year = int(input())

    #Define a new movies variable to store the preferred movies. Copy the contents of gen_df to movies
    movies = gen_df.copy()

    #Filter based on the condition
    movies = movies[(movies['genre'] == genre) & 
                    (movies['runtime'] >= low_time) & 
                    (movies['runtime'] <= high_time) & 
                    (movies['year'] >= low_year) & 
                    (movies['year'] <= high_year)]

    #Compute the values of C and m for the filtered movies
    C = movies['vote_average'].mean()
    m = movies['vote_count'].quantile(percentile)

    #Only consider movies that have higher than m votes. Save this in a new dataframe q_movies
    q_movies = movies.copy().loc[movies['vote_count'] >= m]

    #Calculate score using the IMDB formula
    q_movies['score'] = q_movies.apply(lambda x: (x['vote_count']/(x['vote_count']+m) * x['vote_average']) 
                                       + (m/(m+x['vote_count']) * C)
                                       ,axis=1)

    #Sort movies in descending order of their scores
    q_movies = q_movies.sort_values('score', ascending=False)

    return q_movies

是時候把你的模型付諸行動了!

您可能需要推薦動畫電影,并有以下要求:影片時長在30分鐘到2小時之間的,發(fā)布時間在1990年到2005年。查看結(jié)果:



您可以看到它輸出的電影滿足您作為輸入傳遞的所有條件。 由于您應(yīng)用了IMDB的指標(biāo),您還可以觀察到您的電影同時受到高度評價和歡迎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容