利用Python 进行数据分析 - 笔记(1)
本帖最后由 PeersLee 于 2016-4-28 13:52 编辑问题导读:
1.如何对类似电影评分数据进行切片切块用来满足实际需求?
2.如何通过对 1880-2010 年间全美婴儿的姓名来分析命名趋势?
static/image/hrline/3.gif
解决方案:
数据下载:http://github.com/pydata/pydata-book
使用python 对MovieLens 用户提供的电影评分数据进行切片切块:
该数据集含有来自6000名用户对4000部电影的100万条评分数据。分为3个表:评分、用户信息、电影信息。
分别读取3张表中的数据:
[*]我们可以用pandas.read_table()读入文本文件,返回一个DataFrame对象。read_table()会自动将第一行当作列名,对每行以制表符进行分割。
DataFrame对象是整个Pandas库中最核心、常用的类型,它与数据库或者Excel中的表格类似。由于数据很长,我们通过DataFrame.head()返回其头5行数据。在IPython Notebook中,DataFrame对象会以表格形式输出。
[*]参数解释:
(1)sep:分隔符。
(2)header:None 读取到的数据直接放在表中,第一行不作为列名, 0 读取到的第一行作为列名,默认为0。
(3)names:列名列表。
原始数据:
peerslee@peerslee-ubuntu:~/workspace/py/pydata-book-master/ch02/movielens$ cat users.dat | head -n 10
1::F::1::10::48067
2::M::56::16::70072
3::M::25::15::55117
4::M::45::7::02460
5::M::25::20::55455
6::F::50::9::55117
7::M::35::1::06810
8::M::25::12::11413
9::M::25::17::61614
10::F::35::1::95370
使用python 读取数据:
In : unames = ['user_id', 'gender', 'age', 'occupation', 'zip']
In : users = pd.read_table('users.dat', sep='::', header=None, names=unames)
In : users.head()
Out:
user_id genderageoccupation zip
0 1 F 1 1048067
1 2 M 56 1670072
2 3 M 25 1555117
3 4 M 45 702460
4 5 M 25 2055455
剩下的两张表使用一样的方式读取:
In : rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
In : ratings = pd.read_table('ratings.dat', sep='::', header=None, names=rnames)
In : ratings.head()Out:
user_idmovie_idratingtimestamp
0 1 1193 5978300760
1 1 661 3978302109
2 1 914 3978301968
3 1 3408 4978300275
4 1 2355 5978824291
In : mnames = ['movie_id', 'title', 'genres']
In : movies = pd.read_table('movies.dat', sep="::", header=None, names=mnames)
In : movies.head()
Out:
movie_id title genres
0 1 Toy Story (1995) Animation|Children's|Comedy
1 2 Jumanji (1995)Adventure|Children's|Fantasy
2 3 Grumpier Old Men (1995) Comedy|Romance
3 4 Waiting to Exhale (1995) Comedy|Drama
4 5Father of the Bride Part II (1995) Comedy
补充点知识:
[*]首先DataFrame的某列可以通过DataFrame的下标操作获取,它得到的是一个Series对象:
[*]Series是一个一维的类似的数组对象,包含一个数组的数据(任何NumPy的数据类型)和一个与数组关联的数据标签,被叫做 索引 。最简单的Series是由一个数组的数据构成:
[*]Series是一个定长的,有序的字典,因为它把索引和值映射起来了
In : users['age'].head()
Out:
0 1
1 56
2 25
3 45
4 25
Name: age, dtype: int64
将读取到的几张表合并在一起:
[*]pandas.merge 可根据一个或多个键将不同 DataFrame 中的行连接起来。
In : data01 = pd.merge(ratings,users)
In : data = pd.merge(data01,movies)
In : data.head()
Out:
user_idmovie_idratingtimestamp genderageoccupation zip\
0 1 1193 5978300760 F 1 1048067
1 2 1193 5978298413 M 56 1670072
2 12 1193 4978220179 M 25 1232793
3 15 1193 4978199279 M 25 722903
4 17 1193 5978158471 M 50 195350
title genres
0One Flew Over the Cuckoo's Nest (1975)Drama
1One Flew Over the Cuckoo's Nest (1975)Drama
2One Flew Over the Cuckoo's Nest (1975)Drama
3One Flew Over the Cuckoo's Nest (1975)Drama
4One Flew Over the Cuckoo's Nest (1975)Drama
使用pivot_table 按性别计算每一部电影的平均得分:
[*]Pandas透视表(pivot_table)详解
[*]numpy 中的几个函数:求和,平均值,方差:分别是np.sum(), np.mean(), np.var(), np.std()(这个是标准差)
In : arr
array([,
,
])
In : np.sum(arr)
Out: 18
In : np.sum(arr,axis=0)
Out: array()
In : np.sum(arr,axis=1)
Out: array()
In : np.sum(arr,axis=-1)
Out: array()
Out:
array([,
,
])
In : np.mean(arr)
Out: 2.0
In : np.mean(arr,axis=0)
Out: array([ 2.,2.,2.])
In : np.mean(arr,axis=1)
Out: array([ 1.,2.,3.])
In : np.var(arr)
Out: 0.66666666666666663
In : np.std(arr)
Out: 0.81649658092772603
电影平均得分的DataFrame,行标为电影名称,列标为性别:
In : mean_rating = pd.pivot_table(data,index="title",columns="gender", values="rating",aggfunc=np.mean)
In : mean_rating.head()
Out:
gender F M
title
$1,000,000 Duck (1971) 3.3750002.761905
'Night Mother (1986) 3.3888893.352941
'Til There Was You (1997) 2.6756762.733333
'burbs, The (1989) 2.7934782.962085
...And Justice for All (1979)3.8285713.689024
过滤掉评分数据不够300条的电影:
[*]对title进行分组,然后利用size()得到一个含有各电影分组大小的Series对象:
In : ratings_by_title = data.groupby('title').size()
In : ratings_by_title.head()
Out:
title
$1,000,000 Duck (1971) 37
'Night Mother (1986) 70
'Til There Was You (1997) 52
'burbs, The (1989) 303
...And Justice for All (1979) 199
dtype: int64
In : active_title = ratings_by_title.index
[*]另外一个特别之处在于 DataFrame 对象的索引方式,因为他有两个轴向(双重索引)。可以这么理解:DataFrame 对象的标准切片语法为:.ix[::,::]。ix 对象可以接受两套切片,分别为行(axis=0)和列(axis=1)的方向
In : mean_rating = mean_rating.ix
In : mean_rating.head()
Out:
gender F M
title
'burbs, The (1989) 2.7934782.962085
10 Things I Hate About You (1999)3.6465523.311966
101 Dalmatians (1961) 3.7914443.500000
101 Dalmatians (1996) 3.2400002.911215
12 Angry Men (1957) 4.1843974.328421
了解女性观众最喜欢的电影:
[*]Series 的 sort_index(ascending=True) 方法可以对 index 进行排序操作,ascending 参数用于控制升序或降序,默认为升序。
若要按值对 Series 进行排序,当使用 .order(na_last=True, ascending=True, kind='mergesort') 方法,任何缺失值默认都会被放到 Series 的末尾。
[*] DataFrame 的 sort_index(axis=0, by=None, ascending=True) 方法多了一个轴向的选择参数与一个 by 参数,by 参数的作用是针对某一(些)列进行排序(不能对行使用 by 参数)
In : top_female_rating = mean_rating.sort_index(by='F', ascending=False)
In : top_female_rating.head()
Out:
gender F M
title
Close Shave, A (1995) 4.6444444.473795
Wrong Trousers, The (1993) 4.5882354.478261
Sunset Blvd. (a.k.a. Sunset Boulevard) (1950) 4.5726504.464589
Wallace & Gromit: The Best of Aardman Animation (1996)4.5631074.385075
Schindler's List (1993) 4.5626024.491415
计算评分分歧:
[*]要求出男性与女性观众分歧最大的电影,可以在 mean_rating 加上一个用于存放平均得分之差的列,并对其进行排序:
In : mean_rating['diff'] = mean_rating['M'] - mean_rating['F']
In : sorted_by_diff = mean_rating.sort_index(by='diff')
In : sorted_by_diff.head()
Out:
gender F M diff
title
Dirty Dancing (1987) 3.7903782.959596 -0.830782
Jumpin' Jack Flash (1986)3.2547172.578358 -0.676359
Grease (1978) 3.9752653.367041 -0.608224
Steel Magnolias (1989) 3.9017343.365957 -0.535777
Anastasia (1997) 3.8000003.281609 -0.518391
[*]将排序结果反序得得到的是男性观众喜爱的电影:
In : sorted_by_diff[::-1].head()
Out:
gender F M diff
title
Good, The Bad and The Ugly, The (1966)3.4949494.2213000.726351
Kentucky Fried Movie, The (1977) 2.8787883.5551470.676359
Dumb & Dumber (1994) 2.6979873.3365950.638608
Longest Day, The (1962) 3.4117654.0314470.619682
Cable Guy, The (1996) 2.2500002.8637870.613787
[*]利用方差或标准差找出分歧最大的电影(不考虑性别因素):
(1)得到一个 根据电影名称分组的得分数据标准差 的Series
(2)根据active_titles 进行过滤
(3)根据值对Series 进行降序排列
In : rating_std_by_title = data.groupby('title')['rating'].std()
In : rating_std_by_title = rating_std_by_title.ix
In : rating_std_by_title.order(ascending=False).head()
Out:
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年间全美婴儿姓名来分析命名趋势:
[*]read_csv 就是 read_table 的 sep=','
In : import pandas as pd
In : names1880 = pd.read_csv('yob1880.txt', names=['name', 'sex', 'births'])
In : names1880.head()
Out:
name sexbirths
0 Mary F 7065
1 Anna F 2604
2 Emma F 2003
3Elizabeth F 1939
4 Minnie F 1746
[*]对names1880 中男孩和女孩分别求出 births总和
In : names1880.groupby('sex').births.sum()
Out:
sex
F 90993
M 110493
Name: births, dtype: int64
[*]该数据集按年度被分隔成了多个文件,我们要将这些文件都装导一个DataFrame中里面,并加上一个year字段。
[*]merge 算是一种整合的话,轴向连接 pd.concat() 就是单纯地把两个表拼在一起,这个过程也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。因此可以想见,这个函数的关键参数应该是 axis,用于指定连接的轴向。在默认的 axis=0 情况下,pd.concat() 函数的效果与 obj1.append(obj2) 是相同的;而在 axis=1 的情况下,pd.concat(,axis=1) 的效果与 pd.merge(df1,df2,left_index=True,right_index=True,how='outer') 是相同的。可以理解为 concat 函数使用索引作为“连接键”。
[*]merge 列数增加; append行数增加
#!/usr/bin/env python
# coding=utf-8
import pandas as pd
years = range(1880,2011)
pieces = []
columns = ['name', 'sex', 'births']
for year in years:
path = 'yob%d.txt' %year
frame = pd.read_csv(path, names = columns)
frame['year'] = year
pieces.append(frame)
names = pd.concat(pieces,ignore_index=True
In : run name02.py
In : names.head()
Out:
name sexbirthsyear
0 Mary F 70651880
1 Anna F 26041880
2 Emma F 20031880
3Elizabeth F 19391880
4 Minnie F 17461880
[*]利用pivot_table 在year 和 sex 级别上对其进行聚合
In : total_births = pd.pivot_table(names, index="year", columns="sex", values="births", aggfunc=sum)
In : total_births.tail()
Out:
sex F M
year
200618964682050234
200719168882069242
200818836452032310
200918276431973359
201017590101898382
In : total_births.plot(title="Total births by sex and year")
[*]向数据集中添加一列 指定名字的婴儿占出生婴儿总数的百分比:
[*](1)按照year 和 sex 分组
[*](2)将新列添加到各个分组上
#!/usr/bin/env python
# coding=utf-8
def add_prop(group):
births = group.births.astype(float)
group['prop'] = births / births.sum()
return group
In : names = names.groupby(['year', 'sex']).apply(add_prop)
In : names.head()
Out:
name sexbirthsyear prop
0 Mary F 706518800.077643
1 Anna F 260418800.028618
2 Emma F 200318800.022013
3Elizabeth F 193918800.021309
4 Minnie F 174618800.019188
[*]利用np.allclose 来检查这个分组的值是否接近 1
In : np.allclose(names.groupby(['year','sex']).prop.sum(),1)
Out: True
[*]取出该数据集的一个子集
#!/usr/bin/env python
# coding=utf-8
def get_top1000(group):
return group.sort_index(by='births', ascending=False)[:1000]
In : run name03.py
In : grouped = names.groupby(['year','sex'])
In : top1000 = grouped.apply(get_top1000)
In : top1000.head()
Out:
name sexbirthsyear prop
year sex
1880 F 0 Mary F 706518800.077643
1 Anna F 260418800.028618
2 Emma F 200318800.022013
3Elizabeth F 193918800.021309
4 Minnie F 174618800.019188
In :
分析命运趋势:
[*]将前1000个名字分为男女两个部分
In : boys = top1000
In : girls = top1000
[*]生成一张按照year 和 name 统计的总出生数透视图
In : total_births = pd.pivot_table(top1000, index='year', columns='name',values='births',aggfunc=sum)
In : total_births.head()
Out:
nameAadenAaliyahAaravAaronAarushAbagailAbbeyAbbieAbbigail\
year
1880 NaN NaN NaN 102 NaN NaN NaN 71 NaN
2010 448 4628 438 7374 226 277 295 324 585
nameAbby... ZionZoa ZoeZoeyZoieZolaZonaZoraZulaZuri
year ...
1880 6... NaN 8 23 NaN NaN 7 8 28 27 NaN
20101140... 1926NaN62005164 504 NaN NaN NaN NaN 258
[*]用DataFrame 的plot 方法绘制几个名字的曲线图:
(1)完整代码:
#!/usr/bin/env python
# coding=utf-8
import pandas as pd
import numpy as np
# 整合数据
years = range(1880,2011)
pieces = []
columns = ['name', 'sex', 'births']
for year in years:
path = 'yob%d.txt' %year
frame = pd.read_csv(path, names = columns)
frame['year'] = year
pieces.append(frame)
names = pd.concat(pieces,ignore_index=True)
# 在year 和 sex 级别上聚合数据
total_births = pd.pivot_table(names, index='year', columns='sex', values='births', aggfunc=np.sum)
def add_prop(group):
births = group.births.astype(float)
group['prop'] = births / births.sum()
return group
names = names.groupby(['year','sex']).apply(add_prop)
def get_top1000(group):
return group.sort_index(by='births', ascending=False)[:1000]
grouped = names.groupby(['year','sex'])
top1000 = grouped.apply(get_top1000)
boys = top1000
girls = top1000
total_births = pd.pivot_table(top1000, index='year', columns='name', values='births',aggfunc=np.sum)
subset = total_births[['John','Harry','Mary','Marilyn']]
(2)可视化:
In : subset.plot(subplots=True, figsize=(12,10), grid=False, title="Num of births per year")
评估命名多样性的增长:
[*]分性别 统计 起最流行的1000 个的名字的孩子占总人数中的比例:
table = pd.pivot_table(
top1000,
index='year',
columns='sex',
values='prop',
aggfunc=np.sum)
In : table.plot(yticks=np.linspace(0,1.2,13), xticks=range(1880,2020,10))
[*]从上图可以知道名字的多样性出现了增长
[*]使用另一个方法:
(1)计算在2010年,占总人数前50%的不同名字的数量
(2)利用numpy 先计算prop 的累计和cumsum
(3)通过searchsorted方法找出0.5 应该被插入在那个位置才能保证不破坏顺序
[*]塑层次化索引 - 层次化索引为 DataFrame 数据的重排任务提供了一种具有良好一致性的方式。重塑层次化索引通过以下两个方法完成:
[*].stack() 将列 “压缩” 为行的下级层次化索引
[*].unstack() stack 的逆操作——将层次化的行索引 “展开” 为列
def get_quantile_count(group, q=0.5):
group = group.sort_index(by='prop', ascending=False)
return group.prop.cumsum().searchsorted(q) + 1
diversity = top1000.groupby(['year', 'sex']).apply(get_quantile_count)
diversity = diversity.unstack('sex')
In : run names01.py
In : diversity.head()
Out:
sex F M
year
1880
1881
1882
1883
1884
[*]python 各种类型的转换:
int(x [,base ]) 将x转换为一个整数
long(x [,base ]) 将x转换为一个长整数
float(x ) 将x转换到一个浮点数
complex(real [,imag ])创建一个复数
str(x ) 将对象 x 转换为字符串
repr(x ) 将对象 x 转换为表达式字符串
eval(str ) 用来计算在字符串中的有效Python表达式,并返回一个对象
tuple(s ) 将序列 s 转换为一个元组
list(s ) 将序列 s 转换为一个列表
chr(x ) 将一个整数转换为一个字符
unichr(x ) 将一个整数转换为Unicode字符
ord(x ) 将一个字符转换为它的整数值
hex(x ) 将一个整数转换为一个十六进制字符串
oct(x ) 将一个整数转换为一个八进制字符串
In : run names01.py
In : diversity.plot()
Number of popular names in top 50%:
页:
[1]