一个pandas套路

In [1]:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

In [2]:

# pass in column names for each CSV
u_cols = ['user_id', 'age', 'sex', 'occupation', 'zip_code']
users = pd.read_csv('ml-100k/u.user', sep='|', names=u_cols,
                    encoding='latin-1')

r_cols = ['user_id', 'movie_id', 'rating', 'unix_timestamp']
ratings = pd.read_csv('ml-100k/u.data', sep='\t', names=r_cols,
                      encoding='latin-1')

# the movies file contains columns indicating the movie's genres
# let's only load the first five columns of the file with usecols
m_cols = ['movie_id', 'title', 'release_date', 'video_release_date', 'imdb_url']
movies = pd.read_csv('ml-100k/u.item', sep='|', names=m_cols, usecols=range(5),
                     encoding='latin-1')

# create one merged DataFrame
movie_ratings = pd.merge(movies, ratings)
lens = pd.merge(movie_ratings, users)

评价最多的 25 部电影

In [3]:

most_rated = lens.groupby('title').size().sort_values(ascending=False)[:25]
most_rated

Out[3]:

title
Star Wars (1977)                             583
Contact (1997)                               509
Fargo (1996)                                 508
Return of the Jedi (1983)                    507
Liar Liar (1997)                             485
English Patient, The (1996)                  481
Scream (1996)                                478
Toy Story (1995)                             452
Air Force One (1997)                         431
Independence Day (ID4) (1996)                429
Raiders of the Lost Ark (1981)               420
Godfather, The (1972)                        413
Pulp Fiction (1994)                          394
Twelve Monkeys (1995)                        392
Silence of the Lambs, The (1991)             390
Jerry Maguire (1996)                         384
Chasing Amy (1997)                           379
Rock, The (1996)                             378
Empire Strikes Back, The (1980)              367
Star Trek: First Contact (1996)              365
Back to the Future (1985)                    350
Titanic (1997)                               350
Mission: Impossible (1996)                   344
Fugitive, The (1993)                         336
Indiana Jones and the Last Crusade (1989)    331
dtype: int64

上述代码的含义是先将 DataFrame 按电影标题分组,接下来利用 size 方法计算每组样本的个数,最后按降序方式输出前 25 条观测值。

在 SQL 中,等价的代码为:

SELECT title, count(1)
FROM lens
GROUP BY title
ORDER BY 2 DESC
LIMIT 25;

此外,在 Pandas 中有一个非常好用的替代函数—— value_counts

In [4]:

lens.title.value_counts()[:25]

Out[4]:

Star Wars (1977)                             583
Contact (1997)                               509
Fargo (1996)                                 508
Return of the Jedi (1983)                    507
Liar Liar (1997)                             485
English Patient, The (1996)                  481
Scream (1996)                                478
Toy Story (1995)                             452
Air Force One (1997)                         431
Independence Day (ID4) (1996)                429
Raiders of the Lost Ark (1981)               420
Godfather, The (1972)                        413
Pulp Fiction (1994)                          394
Twelve Monkeys (1995)                        392
Silence of the Lambs, The (1991)             390
Jerry Maguire (1996)                         384
Chasing Amy (1997)                           379
Rock, The (1996)                             378
Empire Strikes Back, The (1980)              367
Star Trek: First Contact (1996)              365
Titanic (1997)                               350
Back to the Future (1985)                    350
Mission: Impossible (1996)                   344
Fugitive, The (1993)                         336
Indiana Jones and the Last Crusade (1989)    331
Name: title, dtype: int64

评价最高的电影

In [6]:

movie_stats = lens.groupby('title').agg({'rating': [np.size, np.mean]})
movie_stats.head()

Out[6]:

 rating
 sizemean
title  
'Til There Was You (1997)92.333333
1-900 (1994)52.600000
101 Dalmatians (1996)1092.908257
12 Angry Men (1957)1254.344000
187 (1997)413.024390

我们可以利用 agg 方法来进行分组汇总计算,其参数包括键值和汇总方法。接下来我们对汇总结果进行排序即可得到评价最高的电影:

In [7]:

# sort by rating average
movie_stats.sort_values([('rating', 'mean')], ascending=False).head()

Out[7]:

 rating
 sizemean
title  
They Made Me a Criminal (1939)15
Marlene Dietrich: Shadow and Light (1996)15
Saint of Fort Washington, The (1993)25
Someone Else's America (1995)15
Star Kid (1997)35

由于 movie_stats 是一个 DataFrame,因此我们可以利用 sort 方法来排序——Series 对象则使用order 方法。此外,由于该数据集包含多层索引,所以我们需要传递一个元组数据来指定排序变量。

上表列出来的电影中评价数量都非常少,以致于我们无法从中得到一些有价值的信息。因此我们考虑对数据集进行筛选处理,只分析评价数量大于 100 的电影:

In [9]:

atleast_100 = movie_stats['rating']['size'] >= 100
movie_stats[atleast_100].sort_values([('rating', 'mean')], ascending=False)[:15]

Out[9]:

 rating
 sizemean
title  
Close Shave, A (1995)1124.491071
Schindler's List (1993)2984.466443
Wrong Trousers, The (1993)1184.466102
Casablanca (1942)2434.456790
Shawshank Redemption, The (1994)2834.445230
Rear Window (1954)2094.387560
Usual Suspects, The (1995)2674.385768
Star Wars (1977)5834.358491
12 Angry Men (1957)1254.344000
Citizen Kane (1941)1984.292929
To Kill a Mockingbird (1962)2194.292237
One Flew Over the Cuckoo's Nest (1975)2644.291667
Silence of the Lambs, The (1991)3904.289744
North by Northwest (1959)1794.284916
Godfather, The (1972)4134.283293

这个结果看起来比较靠谱,需要注意的是在这里我们利用布尔索引来筛选数据。

在SQL中,等价的代码为:

SELECT title, COUNT(1) size, AVG(rating) mean
FROM lens
GROUP BY title
HAVING COUNT(1) >= 100
ORDER BY 3 DESC
LIMIT 15;

筛选部分数据

为了便于进一步分析,我们从数据集中筛选出评价数最高的 50 部电影:

In [10]:

most_50 = lens.groupby('movie_id').size().sort_values(ascending=False)[:50]

SQL 中的等价代码为:

CREATE TABLE most_50 AS(
    SELECT movie_id, COUNT(1)
    FROM lens
    GROUP BY movie_id
    ORDER BY 2 DESC
    LIMIT 50
);

此外,我们也可以利用 EXISTS, IN 或者 JOIN 来过滤数据:

SELECT *
FROM lens
WHERE EXISTS (SELECT 1 FROM most_50 WHERE lens.movie_id = most_50.movie_id);

不同年龄段观众之间争议最大的电影

首先,我们来看下数据集中用户的年龄分布情况:

In [16]:

users.age.plot.hist(bins=30)
plt.title("Distribution of users' ages")
plt.ylabel("count of users")
plt.xlabel("age");

Pandas 整合了 matplotlib 的基础画图功能,我们只需要对列变量调用 hist 方法即可绘制直方图。当然了,我们也可以利用 matplotlib.pyplot 来自定义绘图。

对用户进行分箱处理

我认为直接对比不同年龄用户的行为无法得到有价值的信息,所以我们应该根据用户的年龄情况利用pandas.cut 将所有用户进行分箱处理。

In [17]:

labels = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79']
lens['age_group'] = pd.cut(lens.age, range(0, 81, 10), right=False, labels=labels)
lens[['age', 'age_group']].drop_duplicates()[:10]

Out[17]:

 ageage_group
06060-69
3972120-29
4593330-39
5243030-39
7822320-29
9952920-29
12292620-29
16643130-39
19422420-29
22703230-39

上述代码中,我们首先创建分组标签名,然后根据年龄变量将用户分成八组(0-9, 10-19, 20-29,...),其中参数 right=False 用于剔除掉区间上界数据,即30岁的用户对应的标签为 30-39。

现在我们可以比较不同年龄组之间的评分情况:

In [18]:

lens.groupby('age_group').agg({'rating': [np.size, np.mean]})

Out[18]:

 rating
 sizemean
age_group  
0-9433.767442
10-1981813.486126
20-29395353.467333
30-39256963.554444
40-49150213.591772
50-5987043.635800
60-6926233.648875
70-791973.649746

从上表中我们可以看出,年轻用户比其他年龄段的用户更加挑剔。接下来让我们看下这 50 部热评电影中不同年龄组用户的评价情况。

In [19]:

lens.set_index('movie_id', inplace=True)

In [21]:

by_age = lens.loc[most_50.index].groupby(['title', 'age_group'])
by_age.rating.mean().head()

Out[21]:

title                 age_group
Air Force One (1997)  10-19        3.647059
                      20-29        3.666667
                      30-39        3.570000
                      40-49        3.555556
                      50-59        3.750000
Name: rating, dtype: float64

需要注意的是,此处的电影标题和年龄组都是索引值,平均评分为 Series 对象。如果你觉得这个展示结果不直观的话,我们可以利用 unstack 方法将其转换成表格形式。

In [27]:

by_age.rating.mean().unstack(1).fillna(0)[10:20]

Out[27]:

age_group0-910-1920-2930-3940-4950-5960-6970-79
title        
E.T. the Extra-Terrestrial (1982)03.6800003.6090913.8068184.1600004.3684214.3750000.000000
Empire Strikes Back, The (1980)44.6428574.3116884.0520834.1000003.9090914.2500005.000000
English Patient, The (1996)53.7391303.5714293.6218493.6346153.7746483.9047624.500000
Fargo (1996)03.9375004.0104714.2307694.2941184.4423084.0000004.333333
Forrest Gump (1994)54.0476193.7857143.8617023.8478264.0000003.8000000.000000
Fugitive, The (1993)04.3200003.9699253.9814814.1904764.2400003.6666670.000000
Full Monty, The (1997)03.4210534.0568183.9333333.7142864.1463414.1666673.500000
Godfather, The (1972)04.4000004.3450704.4128443.9294124.4634154.1250000.000000
Groundhog Day (1993)03.4761903.7982463.7866673.8510643.5714293.5714294.000000
Independence Day (ID4) (1996)03.5952383.2914293.3893813.7187503.8888892.7500000.000000

unstack 方法主要用于拆分多层索引,此例中我们将移除第二层索引然后将其转换成列向量,并用 0 来填补缺失值。

男士与女士分歧最大的电影

首先思考下你会如何利用 SQL 来解决这个问题,你可能会利用判断语句和汇总函数来旋转你的数据集,你的查询语句大概会是这个样子:

SELECT title, AVG(IF(sex = 'F', rating, NULL)), AVG(IF(sex = 'M', rating, NULL))
FROM lens
GROUP BY title;

想象下,如果你必须处理多列数据的话,这样运算是多么的麻烦。DataFrame 提供了一个简便的方法—— pivot_table 。

In [29]:

lens.reset_index('movie_id', inplace=True)

In [30]:

pivoted = lens.pivot_table(index = ['movie_id', 'title'], columns = ['sex'],
                          values = 'rating', fill_value = 0)
pivoted.head()

Out[30]:

 sexFM
movie_idtitle  
1Toy Story (1995)3.7899163.909910
2GoldenEye (1995)3.3684213.178571
3Four Rooms (1995)2.6875003.108108
4Get Shorty (1995)3.4000003.591463
5Copycat (1995)3.7727273.140625

In [31]:

pivoted['diff'] = pivoted.M - pivoted.F
pivoted.head()

Out[31]:

 sexFMdiff
movie_idtitle   
1Toy Story (1995)3.7899163.9099100.119994
2GoldenEye (1995)3.3684213.178571-0.189850
3Four Rooms (1995)2.6875003.1081080.420608
4Get Shorty (1995)3.4000003.5914630.191463
5Copycat (1995)3.7727273.140625-0.632102

In [32]:

pivoted.reset_index('movie_id', inplace=True)

In [34]:

disagreements = pivoted[pivoted.movie_id.isin(most_50.index)]['diff']
disagreements.sort_values().plot(kind='barh', figsize=[9, 15])
plt.title('Male vs. Female Avg. Ratings\n(Difference > 0 = Favored by Men)')
plt.ylabel('Title')
plt.xlabel('Average Rating Difference');

从上图中我们可以看出,男性喜欢《终结者》的程度远高于女性,女性用户则更喜欢《独立日》。这个结果可靠吗?


原文链接:http://www.gregreda.com/2013/10/26/using-pandas-on-the-movielens-dataset/

原文作者:Greg Reda

译者:Fibears

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值