MovieLens 1M数据集

GroupLens Research(http://www.grouplens.org/node/73)采集了一组从20世纪90年末到21世纪初由MovieLens用户提供的电影评分数据。这些数据中包括电影评分、电影元数据(风格类型和年代)以及关于用户的人口统计学数据(年龄、邮编、性别和职业等)。基于机器学习算法的推荐系统一般都会对此类数据感兴趣。虽然我不会在本书中详细介绍机器学习技术,但我会告诉你如何对这种数据进行切片切块以满足实际需求。

MovieLens 1M数据集 - 图1

图2-2:按Windows和非Windows用户统计的最常出现的时区

MovieLens 1M数据集 - 图2

图2-3:按Windows和非Windows用户比例统计的最常出现的时区

MovieLens 1M数据集含有来自6000名用户对4000部电影的100万条评分数据。它分为三个表:评分、用户信息和电影信息。将该数据从zip文件中解压出来之后,可以通过pandas.read_table将各个表分别读到一个pandas DataFrame对象中:

  1. import pandas as pd
  2.  
  3. unames = ['user_id', 'gender', 'age', 'occupation', 'zip']
  4. users = pd.read_table('ml-1m/users.dat', sep='::', header=None, names=unames)
  5.  
  6. rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
  7. ratings = pd.read_table('ml-1m/ratings.dat', sep='::', header=None, names=rnames)
  8.  
  9. mnames = ['movie_id', 'title', 'genres']
  10. movies = pd.read_table('ml-1m/movies.dat', sep='::', header=None, names=mnames)

利用Python的切片语法,通过查看每个DataFrame的前几行即可验证数据加载工作是否一切顺利:

  1. In [334]: users[:5]
  2. Out[334]:
  3. user_id gender age occupation zip
  4. 0 1 F 1 10 48067
  5. 1 2 M 56 16 70072
  6. 2 3 M 25 15 55117
  7. 3 4 M 45 7 02460
  8. 4 5 M 25 20 55455
  9.  
  10. In [335]: ratings[:5]
  11. Out[335]:
  12. user_id movie_id rating timestamp
  13. 0 1 1193 5 978300760
  14. 1 1 661 3 978302109
  15. 2 1 914 3 978301968
  16. 3 1 3408 4 978300275
  17. 4 1 2355 5 978824291
  18.  
  19. In [336]: movies[:5]
  20. Out[336]:
  21. movie_id title genres
  22. 0 1 Toy Story (1995) Animation|Children's|Comedy
  23. 1 2 Jumanji (1995) Adventure|Children's|Fantasy
  24. 2 3 Grumpier Old Men (1995) Comedy|Romance
  25. 3 4 Waiting to Exhale (1995) Comedy|Drama
  26. 4 5 Father of the Bride Part II (1995) Comedy
  27.  
  28. In [337]: ratings
  29. Out[337]:
  30. <class 'pandas.core.frame.DataFrame'>
  31. Int64Index: 1000209 entries, 0 to 1000208
  32. Data columns:
  33. user_id 1000209 non-null values
  34. movie_id 1000209 non-null values
  35. rating 1000209 non-null values
  36. timestamp 1000209 non-null values
  37. dtypes: int64(4)

注意,其中的年龄和职业是以编码形式给出的,它们的具体含义请参考该数据集的README文件。分析散布在三个表中的数据可不是一件轻松的事情。假设我们想要根据性别和年龄计算某部电影的平均得分,如果将所有数据都合并到一个表中的话问题就简单多了。我们先用pandas的merge函数将ratings跟users合并到一起,然后再将movies也合并进去。pandas会根据列名的重叠情况推断出哪些列是合并(或连接)键:

  1. In [338]: data = pd.merge(pd.merge(ratings, users), movies)
  2.  
  3. In [339]: data
  4. Out[339]:
  5. <class 'pandas.core.frame.DataFrame'>
  6. Int64Index: 1000209 entries, 0 to 1000208
  7. Data columns:
  8. user_id 1000209 non-null values
  9. movie_id 1000209 non-null values
  10. rating 1000209 non-null values
  11. timestamp 1000209 non-null values
  12. gender 1000209 non-null values
  13. age 1000209 non-null values
  14. occupation 1000209 non-null values
  15. zip 1000209 non-null values
  16. title 1000209 non-null values
  17. genres 1000209 non-null values
  18. dtypes: int64(6), object(4)
  19.  
  20. In [340]: data.ix[0]
  21. Out[340]:
  22. user_id 1
  23. movie_id 1
  24. rating 5
  25. timestamp 978824268
  26. gender F
  27. age 1
  28. occupation 10
  29. zip 48067
  30. title Toy Story (1995)
  31. genres Animation|Children's|Comedy
  32. Name: 0

现在,只要稍微熟悉一下pandas,就能轻松地根据任意个用户或电影属性对评分数据进行聚合操作了。为了按性别计算每部电影的平均得分,我们可以使用pivot_table方法:

  1. In [341]: mean_ratings = data.pivot_table('rating', rows='title',
  2. ....: cols='gender', aggfunc='mean')
  3.  
  4. In [342]: mean_ratings[:5]
  5. Out[342]:
  6. gender F M
  7. title
  8. $1,000,000 Duck (1971) 3.375000 2.761905
  9. 'Night Mother (1986) 3.388889 3.352941
  10. 'Til There Was You (1997) 2.675676 2.733333
  11. 'burbs, The (1989) 2.793478 2.962085
  12. ...And Justice for All (1979) 3.828571 3.689024

该操作产生了另一个DataFrame,其内容为电影平均得分,行标为电影名称,列标为性别。现在,我打算过滤掉评分数据不够250条的电影(随便选的一个数字)。为了达到这个目的,我先对title进行分组,然后利用size()得到一个含有各电影分组大小的Series对象:

  1. In [343]: ratings_by_title = data.groupby('title').size()
  2.  
  3. In [344]: ratings_by_title[:10]
  4. Out[344]:
  5. title
  6. $1,000,000 Duck (1971) 37
  7. 'Night Mother (1986) 70
  8. 'Til There Was You (1997) 52
  9. 'burbs, The (1989) 303
  10. ...And Justice for All (1979) 199
  11. 1-900 (1994) 2
  12. 10 Things I Hate About You (1999) 700
  13. 101 Dalmatians (1961) 565
  14. 101 Dalmatians (1996) 364
  15. 12 Angry Men (1957) 616
  16.  
  17. In [345]: active_titles = ratings_by_title.index[ratings_by_title >= 250]
  18.  
  19. In [346]: active_titles
  20. Out[346]:
  21. Index(['burbs, The (1989), 10 Things I Hate About You (1999),
  22. 101 Dalmatians (1961), ..., Young Sherlock Holmes (1985),
  23. Zero Effect (1998), eXistenZ (1999)], dtype=object)

该索引中含有评分数据大于250条的电影名称,然后我们就可以据此从前面的mean_ratings中选取所需的行了:

  1. In [347]: mean_ratings = mean_ratings.ix[active_titles]
  2.  
  3. In [348]: mean_ratings
  4. Out[348]:
  5. <class 'pandas.core.frame.DataFrame'>
  6. Index: 1216 entries, 'burbs, The (1989) to eXistenZ (1999)
  7. Data columns:
  8. F 1216 non-null values
  9. M 1216 non-null values
  10. dtypes: float64(2)

为了了解女性观众最喜欢的电影,我们可以对F列降序排列:

  1. In [350]: top_female_ratings = mean_ratings.sort_index(by='F', ascending=False)
  2.  
  3. In [351]: top_female_ratings[:10]
  4. Out[351]:
  5. gender F M
  6. title
  7. Close Shave, A (1995) 4.644444 4.473795
  8. Wrong Trousers, The (1993) 4.588235 4.478261
  9. Sunset Blvd. (a.k.a. Sunset Boulevard) (1950) 4.572650 4.464589
  10. Wallace & Gromit: The Best of Aardman Animation (1996) 4.563107 4.385075
  11. Schindler's List (1993) 4.562602 4.491415
  12. Shawshank Redemption, The (1994) 4.539075 4.560625
  13. Grand Day Out, A (1992) 4.537879 4.293255
  14. To Kill a Mockingbird (1962) 4.536667 4.372611
  15. Creature Comforts (1990) 4.513889 4.272277
  16. Usual Suspects, The (1995) 4.513317 4.518248

计算评分分歧

假设我们想要找出男性和女性观众分歧最大的电影。一个办法是给mean_ratings加上一个用于存放平均得分之差的列,并对其进行排序:

  1. In [352]: mean_ratings['diff'] = mean_ratings['M'] - mean_ratings['F']

按"diff"排序即可得到分歧最大且女性观众更喜欢的电影:

  1. In [353]: sorted_by_diff = mean_ratings.sort_index(by='diff')
  2.  
  3. In [354]: sorted_by_diff[:15]
  4. Out[354]:
  5. gender F M diff
  6. title
  7. Dirty Dancing (1987) 3.790378 2.959596 -0.830782
  8. Jumpin' Jack Flash (1986) 3.254717 2.578358 -0.676359
  9. Grease (1978) 3.975265 3.367041 -0.608224
  10. Little Women (1994) 3.870588 3.321739 -0.548849
  11. Steel Magnolias (1989) 3.901734 3.365957 -0.535777
  12. Anastasia (1997) 3.800000 3.281609 -0.518391
  13. Rocky Horror Picture Show, The (1975) 3.673016 3.160131 -0.512885
  14. Color Purple, The (1985) 4.158192 3.659341 -0.498851
  15. Age of Innocence, The (1993) 3.827068 3.339506 -0.487561
  16. Free Willy (1993) 2.921348 2.438776 -0.482573
  17. French Kiss (1995) 3.535714 3.056962 -0.478752
  18. Little Shop of Horrors, The (1960) 3.650000 3.179688 -0.470312
  19. Guys and Dolls (1955) 4.051724 3.583333 -0.468391
  20. Mary Poppins (1964) 4.197740 3.730594 -0.467147
  21. Patch Adams (1998) 3.473282 3.008746 -0.464536

对排序结果反序并取出前15行,得到的则是男性观众更喜欢的电影:

  1. # 对行反序,并取出前15行
  2. In [355]: sorted_by_diff[::-1][:15]
  3. Out[355]:
  4. gender F M diff
  5. title
  6. Good, The Bad and The Ugly, The (1966) 3.494949 4.221300 0.726351
  7. Kentucky Fried Movie, The (1977) 2.878788 3.555147 0.676359
  8. Dumb & Dumber (1994) 2.697987 3.336595 0.638608
  9. Longest Day, The (1962) 3.411765 4.031447 0.619682
  10. Cable Guy, The (1996) 2.250000 2.863787 0.613787
  11. Evil Dead II (Dead By Dawn) (1987) 3.297297 3.909283 0.611985
  12. Hidden, The (1987) 3.137931 3.745098 0.607167
  13. Rocky III (1982) 2.361702 2.943503 0.581801
  14. Caddyshack (1980) 3.396135 3.969737 0.573602
  15. For a Few Dollars More (1965) 3.409091 3.953795 0.544704
  16. Porky's (1981) 2.296875 2.836364 0.539489
  17. Animal House (1978) 3.628906 4.167192 0.538286
  18. Exorcist, The (1973) 3.537634 4.067239 0.529605
  19. Fright Night (1985) 2.973684 3.500000 0.526316
  20. Barb Wire (1996) 1.585366 2.100386 0.515020

如果只是想要找出分歧最大的电影(不考虑性别因素),则可以计算得分数据的方差或标准差:

  1. # 根据电影名称分组的得分数据的标准差
  2. In [356]: rating_std_by_title = data.groupby('title')['rating'].std()
  3.  
  4. # 根据active_titles进行过滤
  5. In [357]: rating_std_by_title = rating_std_by_title.ix[active_titles]
  6.  
  7. # 根据值对Series进行降序排列
  8. In [358]: rating_std_by_title.order(ascending=False)[:10]
  9. Out[358]:
  10. title
  11. Dumb & Dumber (1994) 1.321333
  12. Blair Witch Project, The (1999) 1.316368
  13. Natural Born Killers (1994) 1.307198
  14. Tank Girl (1995) 1.277695
  15. Rocky Horror Picture Show, The (1975) 1.260177
  16. Eyes Wide Shut (1999) 1.259624
  17. Evita (1996) 1.253631
  18. Billy Madison (1995) 1.249970
  19. Fear and Loathing in Las Vegas (1998) 1.246408
  20. Bicentennial Man (1999) 1.245533
  21. Name: rating

可能你已经注意到了,电影分类是以竖线(|)分隔的字符串形式给出的。如果想对电影分类进行分析的话,就需要先将其转换成更有用的形式才行。我将在本书后续章节中讲到这种转换处理,到时还会再用到这个数据。