PeersLee 发表于 2016-4-30 16:54:06

利用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]
查看完整版本: 利用Python 进行数据分析 - 笔记(1)