美文网首页
[译] 使用pandas构建IMDB Top 250

[译] 使用pandas构建IMDB Top 250

作者: phusFuNs | 来源:发表于2018-11-02 19:49 被阅读0次

    实际工作和生活中,不可避免地需要一些排序规则。这篇文章或多或少会有一些参考价值。

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

    互联网电影数据库(IMDB)维护着一份名为IMDB Top 250的表格,该表格是一份根据某种评分原则生成的排名前250的电影。表格中的电影都是非纪录片,且为剧场版本,影片时长至少45分钟,影评数超过250000条:



    这个表格可以看成最简单的推荐器。它没有考虑特定用户的喜好,也没有试图推断不同电影的相似度。它仅仅根据预定义的指标计算每部电影的评分,并以此输出一份排好序的电影列表。
    本文包括以下内容:

    简单的推荐器

    构建一个简单的推荐器的第一步是建立自己的工作目录。新建一个文件夹,命名为IMDB。建立一个名为Simple Recommender的Jupyter Notebook,然后在浏览器里打开。
    可用的数据集的地址: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()
    

    在运行单元格时,你应该能看到notebook中熟悉的类似表格的结构出现。
    构建简单的推荐器非常简单。步骤如下:

    1. 选择一个指标(或分数)来评价电影
    2. 确定要在表格上显示的电影的先决条件
    3. 计算满足条件的每部电影的分数
    4. 按照分数的降序输出电影列表

    衡量准则

    衡量准则是指对电影排名的定量标准。如果一部电影比另一部电影有更高的定量指标分,则认为该电影要优于另一部。因此,对于建立高质量的电影推荐器,一个鲁棒的可信赖的衡量准则非常重要。
    衡量准则的选择是任意的。一种最简单的指标是电影评分。然后,这种方式有各种的缺点。首先,影片评分没有考虑电影的欢迎度。因此,一部被100,000位用户评为9分电影的评分会低于另一部只有100位用户评为9.5分的电影。这是不可取的,因为很可能这类只有100人观看和评分的电影迎合了一个非常特定的群体,并不像前者一样,受大众喜爱,吸引普通观众。
    这也是一个事实,随着投票人数的增长,电影评分趋于正常化,并接近一个值,能反应电影质量和受欢迎度的价值。换而言之,只有少量评分的电影,其评分并不十分可信。一部只有5位用户评为10分的电影,并不意味着它是一部好电影。
    因此,需要定义一个指标,某种程度上,将影片评分及其参与的投票数(代表人气)都考虑进来。这将使得一部轰动一时的电影更受青睐,这部电影的评分为8,用户数为100,000,而另一部电影的评分为9,用户数只有100。
    幸运的是,您不必为指标集思广益。您可以使用IMDB的加权评级公式作为指标。在数学上,它可以表示如下:
    加权评分(WR)=
    (v/(v + m) * R)+ (m/(v+m) * C)
    参数解释如下:

    • v表示电影获得的票数
    • m表示电表格中电影所需的最小票数(先决条件)
    • R代指电影的平均评分
    • C表示数据集中所有电影的评分分
      v和R各自以电影的vote_count和vote_average的特征计算。计算C则非常简单。

    先决条件

    IMDB加权公式还有一个变量m,需要它计算得分。此变量用于确保仅考虑高于特定人气阈值的电影进行排名。因此,m的值确定有资格在表格中的电影,并且通过作为公式的一部分,确定得分的最终值。
    正如衡量准则,m值的选择是任意的。换言之,m没有一个正确的值。最好尝试不同的m值,然后选择你(以及你的观众)认为最好的推荐值。唯一需要记住的是,m的值越高,对电影受欢迎程度的重视程度越高,因此选择性越高。
    推荐而言,请使用第80百分位影片获得的投票数作为m的值。换句话说,对于要在排名中考虑的电影,它必须获得比数据集中至少80%的电影更多的选票。另外,在先前描述的加权公式中使用由第80百分位电影获得的投票数来得出分数的值。
    现在,计算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)
    

    数据集中45000部电影,大约9000(20%)部符合条件。

    计算分值

    在得到分值之前,最后需要计算的值就是C,数据集中所有电影的平均分:

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

    电影的平均得分为5.6/10。IMDB似乎对电影的评分要求特别严格。 现在已经有C的值,可以对每部电影打分了。
    首先,定义一个计算电影评分的函数,输入参数为电影的特征,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函数作用在Dataframe q_movie上,构建一个新的得分特征列。因为,计算是作用在每一行的,设置axis为1,表示基于行的操作。

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

    排序及输出

    只剩最后一步。现在需要基于计算出来的score,将Dataframe排序,输出一个top的电影列表:



    嗯,这样,推荐器就建好了。
    你可以看到宝莱坞电影Dilwale Dulhania Le Jayenge位居榜首。 它的票数明显少于其他前25部电影。 这有力地表明你应该探索更高的m值。 尝试不同的m值,观察图表中的电影如何变化。

    基于知识的推荐器

    接下来,你将构建一个基于知识的推荐器,方法类似于上面的IMDB Top 250。这将是一个简单函数,执行下面几个任务:

    • 询问用户他/她正在寻找的电影类型
    • 询问用户倾向的影片时长
    • 询问用户倾向的影片的年代
    • 使用收集的信息,向用户推荐具有高加权等级(根据IMDB公式)并满足上述条件的电影
      您拥有的数据包含有关影片时长,流派和时间线的信息,但它目前不是可直接使用的形式。 在将数据用于构建此推荐程序之前,您的数据需要进行处理。
      在您的IMDB文件夹中,创建一个名为Knowledge Recommender的新Jupyter Notebook。 此notebook将包含您在本节中编写的所有代码。
      将依赖包和数据加载到notebook中。 另外,请查看已有的特征,并确定对此任务有用的特征:
    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')
    

    结果来看,很清晰地看到哪些特征需要,哪些不需要。接下来,简化你的Dataframe,只包含你模型需要的特征:

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

    从release_date特征中提取发布年份:

    #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使用的空值。 将这些值转换为整数0,并将year特征的数据类型转换为int。
    为此,定义辅助函数convert_int,并将其应用于年份特征:

    #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()
    

    影片时长特征已经是可用的形式。 它不需要任何额外的处理。 现在,把你的注意力转向影片的类别。

    影片类别

    你也许发现类别信息是以一种类似于Json对象(或Python字典)的格式呈现。瞄一眼电影的类别对象:

    #Print genres of the first movie
    df.iloc[0]['genres']
    
    OUTPUT:
    "[{'id': 16, 'name': 'Animation'}, {'id': 35, 'name': 'Comedy'}, {'id': 10751, 'name': 'Family'}]"
    

    可以发现,输出的是一个字符串形式的字典。为了让该特征能用,有必要将其字符串转为原生的Python字典。幸运的是,Python中名为literal_eval的函数(在ast库里)可以准确地处理。literal_eval可以将任意的字符串转为相应的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'>
    

    现在已经有所有必要的工具,将类别特征转为Python字典格式。
    同时,每个字典代表一个类别,存在两个键:id和name。然而,对于这个任务,只需要name。因此,将字典列表转换为字符串列表,其中每个字符串都是一个类别名称

    #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列。换言之,如果一部电影有多个类别,生成这个电影的多个备份,每个备份对应一个类别。
    举例而言,假设一部名为Just Go With It的电影,有romance和comedy两个类别,将其explode成两行。一行是Just Go With I被标记为romance,另一行则是标记为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()
    

    你应该能看到三行Toy Story,分别对应着animation, family, 和 comedy。这个gen_df的DataFrame对构建知识base的推荐器很重要。

    build_chart函数

    现在终于可以写一个函数,作为推荐器了。现在不能像之前一样计算m和C的值,因为不是每部电影都符合要求。换句话说,分为以下三步:

    1. 获取用户的偏好
    2. 过滤出符合用户条件的电影
    3. 根据以上,计算m和C的值,然后按照上一节中的步骤构建图表
      因此,build_chart函数只接受两个输入:gen_df DataFrame和用于计算m值的百分位数。 默认情况下,百分位数设置为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小时之间的,发布时间在1990年到2005年。查看结果:



    您可以看到它输出的电影满足您作为输入传递的所有条件。 由于您应用了IMDB的指标,您还可以观察到您的电影同时受到高度评价和欢迎。

    相关文章

      网友评论

          本文标题:[译] 使用pandas构建IMDB Top 250

          本文链接:https://www.haomeiwen.com/subject/oypltqtx.html