MovieLens 1M数据集
GroupLens Research采集了一组从20世纪90年末到21世纪初由MovieLens用户提供的电影评分数据。MovieLens 1M数据集含有来自6000名用户对4000部电影的100万条评分数据。它分为三个表:评分、用户信息和电影信息。
我们来试着读取数据,按性别计算每部电影的平均分:
import pandas as pd encoding = 'latin1' unames = ['user_id', 'gender', 'age', 'occupation', 'zip'] rnames = ['user_id', 'movie_id', 'rating', 'timestamp'] mnames = ['movie_id', 'title', 'genres'] users = pd.read_csv('ch02/movielens/users.dat', sep='::', header=None, names=unames, encoding=encoding, engine = 'python') ratings = pd.read_csv('ch02/movielens/ratings.dat', sep='::', header=None, names=rnames, encoding=encoding, engine = 'python') movies = pd.read_csv('ch02/movielens/movies.dat', sep='::', header=None, names=mnames, encoding=encoding, engine = 'python') data=pd.merge(pd.merge(ratings,users),movies) mean_ratings= data.pivot_table('rating',index='title',columns='gender',aggfunc='mean') print(mean_ratings[:5])
输出结果:
gender F M title $1,000,000 Duck (1971) 3.375000 2.761905 'Night Mother (1986) 3.388889 3.352941 'Til There Was You (1997) 2.675676 2.733333 'burbs, The (1989) 2.793478 2.962085 ...And Justice for All (1979) 3.828571 3.689024
该操作产生了另一个DataFrame,其内容为电影平均得分,行标为电影名称,列标为性别。现在,我打算过滤掉评分数据不够250条的电影。为了达到这个目的,我先对title进行分组,然后利用size()得到一个含有各电影分组大小的Series对象:
ratings_by_title=data.groupby('title').size() active_title=ratings_by_title.index[ratings_by_title>=250] mean_ratings=mean_ratings.ix[active_title] print(mean_ratings[:5])
输出结果:
gender F M title 'burbs, The (1989) 2.793478 2.962085 10 Things I Hate About You (1999) 3.646552 3.311966 101 Dalmatians (1961) 3.791444 3.500000 101 Dalmatians (1996) 3.240000 2.911215 12 Angry Men (1957) 4.184397 4.328421
为了了解女性观众最喜欢的电影,我们可以对F列降序排列:
top_female_ratings = mean_ratings.sort_values(by='F', ascending=False) print(top_female_ratings[:5])
输出结果:
gender F M title Close Shave, A (1995) 4.644444 4.473795 Wrong Trousers, The (1993) 4.588235 4.478261 Sunset Blvd. (a.k.a. Sunset Boulevard) (1950) 4.572650 4.464589 Wallace & Gromit: The Best of Aardman Animation... 4.563107 4.385075 Schindler's List (1993) 4.562602 4.491415
假设我们想要找出男性和女性观众分歧最大的电影。一个办法是给mean_ratings加上一个用于存放平均得分之差的列diff,并对其进行排序,按“diff”升序排序即可得到分歧最大且为女性观众更喜欢的电影:
mean_ratings['diff'] = mean_ratings['M'] - mean_ratings['F'] sorted_by_diff = mean_ratings.sort_values(by='diff') print(sorted_by_diff[:5])
输出结果:
gender F M diff title Dirty Dancing (1987) 3.790378 2.959596 -0.830782 Jumpin' Jack Flash (1986) 3.254717 2.578358 -0.676359 Grease (1978) 3.975265 3.367041 -0.608224 Little Women (1994) 3.870588 3.321739 -0.548849 Steel Magnolias (1989) 3.901734 3.365957 -0.535777
如果只是想要找出分歧最大的电影(不考虑性别因素),则可以计算得分数据的方差或标准差:
#根据电影名称分组的得分数据的标准差 rating_std_by_title = data.groupby('title')['rating'].std() #根据active_titles进行过滤 rating_std_by_title = rating_std_by_title.ix[active_titles] #根据值对Series进行降序排列 print(rating_std_by_title.sort_values(ascending=False)[:5])
输出结果:
title Dumb & Dumber (1994) 1.321333 Blair Witch Project, The (1999) 1.316368 Natural Born Killers (1994) 1.307198 Tank Girl (1995) 1.277695 Rocky Horror Picture Show, The (1975) 1.260177 Name: rating, dtype: float64
可能你已经注意到了,电影分类是以竖线(|)分隔的字符串形式给出的。如果想对电影分类进行分析的话,就需要先将其转换成更有用的形式才行。我将在本书后续章节中讲到这种转换处理,到时还会再用到这个数据。
1880-2010年间全美婴儿姓名
美国社会保障总署(SSA)提供了一份从1880年到2010年的婴儿名字频率数据。
import pandas as pd names1880 = pd.read_csv('ch02/names/yob1880.txt', names=['name', 'sex', 'births']) print(names1880[:5])
输出结果:
name sex births 0 Mary F 7065 1 Anna F 2604 2 Emma F 2003 3 Elizabeth F 1939 4 Minnie F 1746
这些文件中仅含有当年出现超过5次的名字。为了简单起见,我们可以用births列的sex分组小计表示该年度的births总计:
print(names1880.groupby('sex').births.sum())
输出结果:
sex F 90993 M 110493 Name: births, dtype: int64
由于该数据集按年度被分隔成了多个文件,所以第一件事情就是要将所有数据都组装到一个DataFrame里面,并加上一个year字段。使用pandas.concat即可达到这个目的:
# 2010是目前最后一个有效统计年度 years = range(1880, 2011) pieces = [] columns = ['name', 'sex', 'births'] for year in years: path = 'ch02/names/yob%d.txt' % year frame = pd.read_csv(path, names=columns) frame['year'] = year pieces.append(frame) # 将所有数据整合到单个DataFrame中 names = pd.concat(pieces, ignore_index=True)
输出结果:
name sex births year 0 Isabella F 22731 2010 1 Sophia F 20477 2010 2 Emma F 17179 2010 3 Olivia F 16860 2010 4 Ava F 15300 2010
有了这些数据之后,我们就可以利用groupby或pivot_table在year和sex级别上对其进行聚合了
total_births = names.pivot_table('births', index='year',columns='sex', aggfunc=sum) print(total_births.tail())
输出结果:
sex F M year 2006 1896468 2050234 2007 1916888 2069242 2008 1883645 2032310 2009 1827643 1973359 2010 1759010 1898382
还可以画出图像:
total_births.plot(title='Total births by sex and year') plt.show()
下面我们来插入一个prop列,用于存放指定名字的婴儿数相对于总出生数的比例。prop值为0.02表示每100名婴儿中有2名取了当前这个名字。因此,我们先按year和sex分组,然后再将新列加到各个分组上:
def add_prop(group): births = group.births.astype(float) group['prop'] = births / births.sum() return group names = names.groupby(['year', 'sex']).apply(add_prop)
输出结果:
name sex births year prop 0 Mary F 7065 1880 0.077643 1 Anna F 2604 1880 0.028618 2 Emma F 2003 1880 0.022013 3 Elizabeth F 1939 1880 0.021309 4 Minnie F 1746 1880 0.019188