写在开头
怎么说呢,在看了第二章之后我也是有点小奇怪的,感觉这本书写的好像有些凌乱,不过姑且先看看吧。这一章讲的是Pandas的一个比较具体的用法,然后写和读本章的时候博主心情可能非常向吐槽,如果感觉里面吐槽太多了请不要介意。
这一章就会开始运用到上一章结束的时候让大家下的数据,当然,里面也有现成的代码供大家查阅,不过博主还是建议想要学好些的话还是自己实践一番。
关于.ipynb
你可能在面对文件夹里面的一堆.ipynb后缀结尾的文件一脸懵逼,然后博主在这里提醒你们一番,这个是用jupyter notebook下可查看的文件。
具体jupyter notebook怎么打开呢,首先你要先设置好jupyter notebook的环境,如果你用的是我之前推荐的方法下载的python的话,那现在就方便了,它会自动地帮你把jupyter notebook地环境给配好。这个时候你只需要把命令行的路径转化到数据(代码)所在的路径,再输入jupyter notebook,等待即可,你使用的默认浏览器会自动打开jupyter notebook了。
ok,提示到这里了,让我们开始吧!
温馨提示
(下面这一段是当天晚上补充的)
不!还有提示,提示就是作者写这一章或者说这一章的例子仅仅只是为了引起读者的兴趣,因此会在一个比较高的层次进行讲解,就是从来没有用过也没有关系,因为在后续章节会进行非常详细的讲解。
这一章看不懂很正常,作者都没有指望你们能看得懂,反正看的懂的我就很奇怪了,在此温馨提示大家不要失去对于学习数据分析的兴趣,好!啰嗦了那么多,让我们真正的开始吧!
第二章 引言
1.来自bit.ly的1.usa.gov数据
2.MovieLens 1M 数据集
3.1880–2010年全美婴儿姓名
4.小结及展望
本文将要向你介绍的是用于高效处理数据的Python工具,虽然读者各自工作的目的千差万别,但是基本需要完成一下几大类任务:
对外界进行交互:
读写各式各样的文件格式和数据
准备
对数据进行清理、修整、整合、规范化、重塑、切片切块、变形等处理以便进行分析。
转化
对数据集做一些数学和统计运算以产生新的数据集。比如说,根据分组变量对一个大表进行整合。
建模和计算
将数据跟统计模型、机器学习算法或者其他计算工具联系起来。
展示
创建交互式的或静态的图片或文字摘要
需要提示的是,在这里本来打算和大家一起敲代码的,但是博主还真没有找到数据。。。整的博主整个人极端郁闷。。。
1.来自bit.ly的1.usa.gov数据
我们开始我们的第一条代码。。。额。。。这个时候你会发现这本书上面的数据我们是没有的,如果你想使用同样的数据的话需要去找一份URL缩短服务bit.ly跟美国政府网站usa.gov合作之后提供的一份从.gov和从.mil短链接的用户那里收集来的匿名数据,除了实时数据之外它还有每小时快照版本的。书中所使用的就是每小时快照版本的了。
然后我找了一下,在没翻墙的情况下确实是找不到数据了,只能够把书本上的内容发上来了(网址是http://www.usa.gov/About/developer-resources/lusagov.shtml)
(In打头的是输入的内容,Out是运行之后的输出)。
(这里的操作是读取某文件的第一行)
In [15]: path = 'ch02/usagov_bitly_data2012-03-16-1331923249.txt'
#文件路径所在
In [16]: open(path).readline() #打开文件并读取一行(默认第一行开始读取),函数readline()读取文件一行
Out[16]: '{ "a": "Mozilla\\/5.0 (Windows NT 6.1; WOW64) AppleWebKit\\/535.11 (KHTML, like Gecko) Chrome\\/17.0.963.78 Safari\\/535.11", "c": "US", "nk": 1, "tz": "America\\/New_York", "gr": "MA", "g": "A6qOVH", "h": "wfLQtf", "l": "orofrog", "al": "en-US,en;q=0.8", "hh": "1.usa.gov", "r": "http:\\/\\/www.facebook.com\\/l\\/7AQEFzjSi\\/1.usa.gov\\/wfLQtf", "u": "http:\\/\\/www.ncbi.nlm.nih.gov\\/pubmed\\/22415991", "t": 1331923247, "hc":1331822918, "cy": "Danvers", "ll": [ 42.576698, -70.954903 ] }\n'
import json (导入模块json)
path = 'ch02/usagov_bitly_data2012-03-16-1331923249.txt'
records = [json.loads(line) for line in open(path)]
上面是课本原话,总感觉很违和的感觉,作者这样子给我的感觉就好像我知道你是菜鸟然后老司机故意忽然飙到220的样子,不知道为啥,总是给我种很什么的感觉,不过我想了一阵子……嗯,真的是一阵子,然后觉得反正、既然都不懂的话那也无所谓了,反正短时间也学不会。话说这本书之前不是假定读者是会Python来讲的嘛!怎么这里来句你可能没用过Python?!(不会的被叫去读最后的python语法了)
我简单的解释一下那句话,其实就是缩写在一起了而已,意思就是从open(path)拿出一个个line然后load之后形成新的数组赋值给records。
下面是输出
In [18]: records[0]
Out[18]:
{u'a': u'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 Safari/535.11', u'al': u'en-US,en;q=0.8',
u'c': u'US',
u'cy': u'Danvers',
u'g': u'A6qOVH',
u'gr': u'MA',
u'h': u'wfLQtf',
u'hc': 1331822918,
u'hh': u'1.usa.gov',
u'l': u'orofrog',
u'll': [42.576698, -70.954903],
u'nk': 1,
u'r': u'http://www.facebook.com/l/7AQEFzjSi/1.usa.gov/wfLQtf',
u't': 1331923247,
u'tz': u'America/New_York',
u'u': u'http://www.ncbi.nlm.nih.gov/pubmed/22415991'}
然后还需注意的是Python和很多语言一样是从0开始索检的。
In [19]: records[0]['tz']
Out[19]: u'America/New_York'
单引号前面的u表示unicode(一种标准的字符串编码格式)。注意,IPython在这里给出的是时区的字符串对象形式,而不是其打印形式:
然后这个格式没有什么特别的,以后如果你继续使用python的话肯定会遇到编码问题的,那个时候你会想疯的,相信博主!
In [20]: print records[0]['tz']
America/New_York
用纯Python代码对时区进行计数
假设我们想要知道该数据中集中最长出现的是哪个时区(也就是tz字段)。
time_zones = [rec['tz'] for rec in records]
但是直接这么做会报错,因为并不是所有的记录都有tz字段(可能是没有记录,也可能是数据丢失,也可能是作者自己删去了,反正种种原因)
将代码改成如下,加一个if条件语句进行判断即可
time_zones = [rec['tz'] for rec in records if 'tz' in rec]
输出为(正巧在pdf的换页之处。)
在这里使用两种方法进行计数,一种比较难(只使用python标准库),另一种比较简单(使用pandas)。计数的方法之一就是在遍历时区的过程中将技术值保留在字典之中:
from collection import defaultdict
def get_counts2(sequence):
counts = defaultdict(int)#所有值将会被初始化为0
for x in sequence:
counts[x] += 1
return counts
def语法这些是定义函数我也不具体讲了,这里又默认大家是老司机了,然后写成函数当然是为了复用性了。在此只要把time_zeros传入使用即可:
然后如果要得到前10位的话我就不贴代码,提示大家一句,因为字典是无序的,大家可以把字典的keys和values都取出来然后排序。
用pandas对时区进行计数
pandas中有三种数据结构,分别是Series、DataFrame、panel三种,最常用的是DataFrame,用来储存二维的数据,可以看作为一个table。
这里的frame的输出形式是摘要视图(summary view),主要用于比较大的DataFrame对象,frame[’tz‘]所返回的Series对象有一个value——counts的方法。
接下来呢,我们打算将其化成一张图出来,但是在此之前我们需要把未知或者缺失的时区填上一个代替值,在这里数值可以使用fillna函数来实现,而字符串就通过索引换去。
处理之后的数据就可以直接使用plot函数进行画图了。结果如下:
后面的地方作者硬是用这个例子展示了许多的操作,后面特意筛选了是否为windows用户,然后再进行画图,有兴趣的读者可以按照书本上面的代码敲一遍。
不过作者也显然是在搞事情。
MovieLens 1M数据集
这个数据在pydata-book-2nd-edition\datasets\movielens里面
GroupLens Research(http://www.grouplens.org/node/73)采集了一组从20世纪90年末到21世纪初由MovieLens用户提供的电影评分数据。这些数据中包括电影评分、电影元数据(风格类型和年代)以及关于用户的人口统计学数据(年龄、邮编、性别和职业等)。
MovieLens 1M数据集含有来自6000名用户对4000部电影的100万条评分数据。它分为三个表:评分、用户信息和电影信息。将该数据从zip文件中解压出来之后,可以通过pandas.read_table将各个表分别读到一个pandas DataFrame对象中:
import pandas as pd
unames = ['user_id', 'gender', 'age', 'occupation', 'zip']
users = pd.read_table('ml-1m/users.dat', sep='::', header=None, names=unames)
rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_table('ml-1m/ratings.dat', sep='::', header=None, names=rnames)
mnames = ['movie_id', 'title', 'genres']
movies = pd.read_table('ml-1m/movies.dat', sep='::', header=None, names=mnames)
利用Python的切片语法,通过查看每个DataFrame的前几行即可验证数据加载工作是否一切顺利:
In [334]: users[:5]
Out[334]:
user_id gender age occupation zip
0 1 F 1 10 48067
1 2 M 56 16 70072
2 3 M 25 15 55117
3 4 M 45 7 02460
4 5 M 25 20 55455
In [335]: ratings[:5]
Out[335]:
user_id movie_id rating timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291
In [336]: movies[:5]
Out[336]:
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 5 Father of the Bride Part II (1995) Comedy
In [337]: ratings
Out[337]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1000209 entries, 0 to 1000208
Data columns:
user_id 1000209 non-null values
movie_id 1000209 non-null values
rating 1000209 non-null values
timestamp 1000209 non-null values
dtypes: int64(4)
注意,其中的年龄和职业是以编码形式给出的,它们的具体含义请参考该数据集的README文件。分析散布在三个表中的数据可不是一件轻松的事情。假设我们想要根据性别和年龄计算某部电影的平均得分,如果将所有数据都合并到一个表中的话问题就简单多了。我们先用pandas的merge函数将ratings跟users合并到一起,然后再将movies也合并进去。pandas会根据列名的重叠情况推断出哪些列是合并(或连接)键:
In [338]: data = pd.merge(pd.merge(ratings, users), movies)
In [339]: data
Out[339]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1000209 entries, 0 to 1000208
Data columns:
user_id 1000209 non-null values
movie_id 1000209 non-null values
rating 1000209 non-null values
timestamp 1000209 non-null values
gender 1000209 non-null values
age 1000209 non-null values
occupation 1000209 non-null values
zip 1000209 non-null values
title 1000209 non-null values
genres 1000209 non-null values
dtypes: int64(6), object(4)
In [340]: data.ix[0]
Out[340]:
user_id 1
movie_id 1
rating 5
timestamp 978824268
gender F
age 1
occupation 10
zip 48067
title Toy Story (1995)
genres Animation|Children's|Comedy
Name: 0
现在,只要稍微熟悉一下pandas,就能轻松地根据任意个用户或电影属性对评分数据进行聚合操作了。为了按性别计算每部电影的平均得分,我们可以使用pivot_table方法:
In [341]: mean_ratings = data.pivot_table('rating', rows='title',
....: cols='gender', aggfunc='mean')
In [342]: mean_ratings[:5]
Out[342]:
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对象:
In [343]: ratings_by_title = data.groupby('title').size()
In [344]: ratings_by_title[:10]
Out[344]:
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
1-900 (1994) 2
10 Things I Hate About You (1999) 700
101 Dalmatians (1961) 565
101 Dalmatians (1996) 364
12 Angry Men (1957) 616
In [345]: active_titles = ratings_by_title.index[ratings_by_title >= 250]
In [346]: active_titles
Out[346]:
Index(['burbs, The (1989), 10 Things I Hate About You (1999),
101 Dalmatians (1961), ..., Young Sherlock Holmes (1985),
Zero Effect (1998), eXistenZ (1999)], dtype=object)
该索引中含有评分数据大于250条的电影名称,然后我们就可以据此从前面的mean_ratings中选取所需的行了:
In [347]: mean_ratings = mean_ratings.ix[active_titles]
In [348]: mean_ratings
Out[348]:
<class 'pandas.core.frame.DataFrame'>
Index: 1216 entries, 'burbs, The (1989) to eXistenZ (1999)
Data columns:
F 1216 non-null values
M 1216 non-null values
dtypes: float64(2)
为了了解女性观众最喜欢的电影,我们可以对F列降序排列:
In [350]: top_female_ratings = mean_ratings.sort_index(by='F', ascending=False)
In [351]: top_female_ratings[:10]
Out[351]:
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 (1996) 4.563107 4.385075
Schindler's List (1993) 4.562602 4.491415
Shawshank Redemption, The (1994) 4.539075 4.560625
Grand Day Out, A (1992) 4.537879 4.293255
To Kill a Mockingbird (1962) 4.536667 4.372611
Creature Comforts (1990) 4.513889 4.272277
Usual Suspects, The (1995) 4.513317 4.518248
计算评分分歧
在这里呢要找出男女分歧最大的电影,直接通过mean_ratings加上一个用于存放平均得分之差的列,并对其进行排序即可。
如果不考虑性别的因素的话,那么可以直接计算得分数据的方差或者标准差是个不错的选择。
1880-2010年间全美婴儿姓名
这里有一份从1880年到2010年的婴儿名字频率数据。Hadley Wickham(许多流行R包的作者)经常用这份数据来演示R的数据处理功能
这一份数据在pydata-book-2nd-edition\datasets\babynames里面
到了这里终于是可以在Ipython里面实践一波给大家看了,首先我们读取数据,以文件yob1880.txt为例。
也许你会问为什么我可以直接读取文件名,我可以直接告诉你,我把Ipython的路径切换到那里。
你可以用这个数据集做很多事,例如:
- 计算指定名字(可以是你自己的,也可以是别人的)的年度比例。
- 计算某个名字的相对排名。
- 计算各年度最流行的名字,以及增长或减少最快的名字。
- 分析名字趋势:元音、辅音、长度、总体多样性、拼写变化、首尾字母等。
- 分析外源性趋势:圣经中的名字、名人、人口结构变化等。
当然,上面所提到的都是之前用过的,可以模仿之前的进行操作一波,数据的链接在第一章中已经给出了。
数据在notepad++中的模样。
然后因为其是以逗号进行分隔的文件,所以使用pandas的read_csv()函数进行读取也没有什么问题(csv本质就是以逗号分隔的文件),当然,你直接使用read_table()也无不可,效果如图。
接下来呢,我们可以统计一下出生的男女的人数。
大家都能够看到文件中的一个个文件都是每一年的数据,那么现在有一个问题就是,如何把每年的数据合并起来呢?我们可以使用pandas.concat这个函数。
代码其实很好懂的,主要是concat里面的ignore_index = True的意思就是使用一开始的index就是说第一个index,然后大家都按照第一个的index来合并,不过这个只有在合并同样一个index数据的时候才可以比较好的使用。
在得到数据之后呢,我们就依据年份和性别进行聚合,如图:
如果按照书本的代码敲会发现,不存在rows和cols这个参数,可能是这本书成书太久后面函数已经修改了吧(毕竟我也没有见过这种用法),把它们分别改成index和columns即可。
下面我们来插入一个prop列,用于存放指定名字的婴儿数相对于总出生数的比例。prop值为0.02表示每100名婴儿中有2名取了当前这个名字。因此,我们先按year和sex分组,然后再将新列加到各个分组上:
因为是比例嘛,为了其正确我们可以简单的检查一下其和是否为1.
发现是正确的。
为了便于分析,我们取出数据的前1000个,最后得出这样子的结果(和书本里的稍有不同)
分析命名趋势
画图
评估命名多样性的增长
上图所反映的降低情况可能意味着父母愿意给小孩起常见的名字越来越少。这个假设可以从数据中得到验证。一个办法是计算最流行的1000个名字所占的比例,我按year和sex进行聚合并绘图:
结果如图所示。从图中可以看出,名字的多样性确实出现了增长(前1000项的比例降低)。另一个办法是计算占总出生人数前50%的不同名字的数量,这个数字不太好计算。我们只考虑2010年男孩的名字:
在对prop降序排列之后,我们想知道前面多少个名字的人数加起来才够50%。虽然编写一个for循环确实也能达到目的,但NumPy有一种更聪明的矢量方式。先计算prop的累计和cumsum,然后再通过searchsorted方法找出0.5应该被插入在哪个位置才能保证不破坏顺序:(这种做法确实是比较高级的)
因为索引是0开始的,所以排名为117,和1990年的25来说要小很多。
现在就可以对所有year/sex组合执行这个计算了。按这两个字段进行groupby处理,然后用一个函数计算各分组的这个值:
(这里书本中的代码是有问题的,但是具体怎么改我就懒得整了,直接贴书本的上来,因为博主想快点结束这一篇博客了)
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')
“最后一个字母”的变革
2007年,一名婴儿姓名研究人员Laura Wattenberg在她自己的网站上指(https://www.babynamewizard.com):近百年来,男孩名字在最后一个字母上的分布发生了显著的变化。为了了解具体的情况,我首先将全部出生数据在年度、性别以及末字母上进行了聚合:
接下来我们需要按总出生数对该表进行规范化处理,以便计算出各性别各末字母占总出生人数的比例:
可以看出,从20世纪60年代开始,以字母“n”结尾的男孩名字出现了显著的增长。回到之前创建的那个完整表,按年度和性别对其进行规范化处理,并在男孩名字中选取几个字母,最后进行转置以便将各个列做成一个时间序列:
变成女孩的男孩名字(以及相反的情况)
另一个有趣的趋势是,早年流行于男孩的名字近年来“变性了”,例如Lesley或Leslie。回到top1000数据集,找出其中以“lesl”开头的一组名字:
一口气把剩下的代码全部敲完了。
小结及展望
本章中的这些例子都非常简单,但它们可以让你大致了解后续章节的相关内容。本书关注的焦点是工具而不是那些复杂精妙的分析方法。掌握本书所介绍的技术将使你能够立马开展自己的分析工作(假设你已经知道要做什么了)。
终
说实话,在写到最后的时候整个人差不多炸了!博主在写博客的时候不是非常耐心的人,索性最后还是写完了,在歇息了一阵子之后,做出了一番总结。
首先,这里的例子也确实学到了一些,有些函数,我自己也从来没有想过能这么用(也可能是已经更新之后没有了这种用法),算是有些学到。
其次,如果大家也有敲代码学习或者比较细心的留意博主的代码的话,估计你会意识到,这本书虽然不算年代非常的久远,但是语言的发展日新月异,很多函数、语法之类的都已经和之前大不相同,只有一直保持着学习的心思才有可能不被时代所落下。
最后。。。预告一下下一篇,下一篇可能要有些久才出了。