Python数据科学指南

978-7-115-43510-1
作者: 【印度】Gopi Subramanian(萨伯拉曼尼安)
译者: 方延风刘丹
编辑: 胡俊英
分类: Python

图书目录:

详情

本书内容丰富、轻松易学,通过10章内容来介绍数据科学、Python环境、数据分析、数据挖掘、机器学习、集成方法等内容。本书不对读者做任何技术基础的要求,读者可以从前5章了解数据科学,从后面的5章了解Python如何在该领域发挥作用,从新手到专家不再是难题。

图书摘要

版权信息

书名:Python数据科学指南

ISBN:978-7-115-43510-1

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [印度] Gopi Subramanian

  译    方延风 刘 丹

  责任编辑 胡俊英

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright ©2015 Packt Publishing. First published in the English language under the title Python Data Science Cookbook.

All rights reserved.

本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


Python作为一种高级程序设计语言,凭借其简洁、易读及可扩展性日渐成为程序设计领域备受推崇的语言,并成为数据科学家的首选之一。

本书详细介绍了Python在数据科学中的应用,包括数据探索、数据分析与挖掘、机器学习、大规模机器学习等主题。每一章都为读者提供了足够的数学知识和代码示例来理解不同深度的算法功能,帮助读者更好地掌握各个知识点。

本书内容结构清晰,示例完整,无论是数据科学领域的新手,还是经验丰富的数据科学家都将从中获益。


方延风,高级工程师,现在福建省科学技术信息研究所任职,毕业于清华大学,获得计算机技术工程硕士学位,美国俄勒冈大学访问学者,曾出版过多本计算机图书,目前的研究方向是文本数据挖掘、自然语言处理(Natural Language Processing,NLP)、信息检索技术等。他主要翻译了第1章及第6~10章的内容。

刘丹,副教授,现任福州外语外贸学院物流系副主任。她主要翻译了第2~5章的内容,并对全书内容进行了校译。


Gopi Subramanian是一名数据科学家,他在数据挖掘与机器学习领域有着超过15年的经验。在过去的10年中,他设计、构思、开发并领导了数据挖掘、文本挖掘、自然语言处理、信息提取和检索等多个项目,涉及不同领域和商务垂直系统,包括工程基础设施、消费金融、医疗保健和材料等多个领域。在忠诚度分析领域,他构思并建立了创新的消费者忠诚度模型,设计了企业范围的个性化促销系统。他在美国和印度的专利局共计申请了10多项专利,并以自己的名义出版了许多书籍。目前,他在印度的班加罗尔生活和工作。


Bastiaan Sjardin是一位在人工智能、数学及机器学习方面有着雄厚实力的数据科学家和企业家。他从莱顿大学获得了认知科学及数理统计学的授课型硕士学位。在过去的5年中,他参与了大量数据科学方面的项目,经常在密歇根大学社会网络分析Coursera课程和约翰•霍普金斯大学的实用机器学习课程上担任助教。他常用的编程语言是R和Python。目前是Quandbee(www.quandbee.com)公司的联合创始人,该公司专门从事机器学习的应用程序开发。


如今,我们生活在一个万物互联的世界,每天都在产生海量数据,不可能依靠人力去分析产生的所有数据并做出决策。人类的决策越来越多地被计算机辅助决策所取代,这也得益于数据科学的发展。数据科学已经深入到我们互联世界中的每个角落,市场对那些十分了解数据科学算法并且有能力用这些算法进行编程的人才需求是不断增长的。数据科学是多领域交叉的,简单列举几个:数据挖掘、机器学习、统计学等。这对那些渴望成为数据科学家以及已经从事这一领域的人们在各方面都倍感压力。把算法当成黑盒子应用到决策系统里,可能会适得其反。面对着无数的算法和数不清的问题,我们需要充分掌握潜在的算法理论,这样才能给每个指定的问题选择最好的算法。

作为一门编程语言,Python演变至今,已经成为数据科学家的首选之一。在快速原型构建方面,它能充分发挥了脚本语言的能力,对于成熟软件的开发,它精巧的语言结构也十分适合,再加上它在数值计算方面神奇的库,这些都使得它被众多数据科学家和一般的科学编程群体所推崇。不仅如此,由于Django和Flaskweb等Web框架的出现,Python 在Web开发人员中也很受欢迎。

本书通过精心编写的内容和精选的主题来满足读者的需求,无论是新手还是经验丰富的数据科学家都将从中获益。本书的内容涉及数据科学的不同方面,包括数据探索、数据分析与挖掘、机器学习、大规模机器学习等。每一章都经过精心编写,带领读者探索相关领域。本书为读者提供了足够的数学知识来理解不同深度的算法功能。只要你有需求,我们都能为好学的读者提供充分的指导,各个主题都十分便于读者学习和理解。

本书给读者带来了数据科学的艺术力和Python编程的力量,并帮助他们掌握数据科学的概念。了解Python语言并不是死板地跟随本书学习,非Python程序员可以从第1章开始阅读,里面涵盖了Python数据结构及函数编程等概念。

前几章涵盖了数据科学的基础知识,后面的章节则致力于高级数据科学算法。目前最先进的算法已经引领数据科学家在不同的行业实践中进行探索,这些算法包括集成方法、随机森林、正则化回归等,书中将会详细介绍。一些在学术界流行而仍未广泛引入到主流应用中的算法,例如旋转森林等在文中也有详细介绍。

目前市场上有许多个人撰写的数据科学方面的书籍,但我认为它们在将隐藏在数据科学算法背后的数学原理和一些实施中的细节相结合方面仍存在很大空缺,本书志在填补这一空白。每一个主题,恰如其分的数学知识讲解能引导读者理解算法工作原理。我相信读者可以在他们的应用中充分感受这些方法带来的效益。

这里有一个忠告,虽然我们尽可能用客观的语言给读者解释这些主题,但它们并没有作为成品在极端的条件下进行过严格测试。成品的数据科学代码必须符合严格的工程规范。

本书可以作为学习数据科学方法的指南和快速参考书。这是一本独立的、介绍数据科学给新手和一些有一点算法基础的人的书,帮助他们成为这个行业的专家。

第1章,Python在数据科学中的应用,介绍了Python内置的数据结构及函数,为学习数据科学编程奠定了基础。

第2章,Python环境,介绍了Python的科学编程和绘图库,包括NumPy、matplotlib和scikit-learn等。

第3章,数据分析——探索与争鸣,覆盖了数据预处理、转换方法来探测性执行数据分析任务等内容,以便有效地构建数据科学算法。

第4章,数据分析——深入理解,引入降维概念来解决数据科学中的维数问题,详细讨论了从简单方法到最先进的降维技术。

第5章,数据挖掘——海底捞针,讨论了无监督数据挖掘技术,先精心探讨了基于距离方法、核方法等内容,接着对聚类与异常点检测技术进行详细讨论。

第6章,机器学习1,涵盖了有监督数据挖掘技术,包括最近邻算法、朴素贝叶斯算法及分类树算法,开始部分就重点强调了监督学习的数据准备工作。

第7章,机器学习2,介绍了回归问题和包括LASSO和岭回归在内的正则化主题。最后,讨论了运用交叉检验技术为这些方法选择超参数。

第8章,集成方法,介绍了各种集成方法,包括挂袋法、提升法及梯度提升法。本章展现了如何在数据科学领域创建强大的、最先进的方法,不是对给定的问题建立单一的模型,而是在集成中构建大量的模型。

第9章,生长树,介绍了更多的基于树的挂袋法,基于其对噪声的健壮性和对不同问题的通用性,它们在数据科学界非常流行。

第10章,大规模机器学习——在线学习,涵盖了大规模机器学习及解决如此大规模问题的合适算法,其中的算法使用数据流进行工作,使用的数据无法完全加载到内存中。

本书所有主题中的代码都在一台安装了64位Windows 7操作系统的计算机上进行开发和测试,其配置为Intel i7 CPU和8GB内存。

本书中使用的开发语言和库版本为:Python 2.7.5、NumPy 1.8.0、SciPy 0.13.2、Matplotlib 1.3.1、NLTK 3.0.2和scikit-learn 0.15.2。

这里的代码通过适当的库也能在Linux各种发行版和Macs上运行。另外一种方式是采用这些版本的库创建一个Python虚拟环境,这样就能运行所有主题中的代码。

本书适合于各个层次的数据科学专业人士,包括学生、业内人士,从新手到专家,各章节的不同主题契合了不同读者的需求。第1~5章,新手级读者可以花一些时间认识数据科学。专家级读者可以阅读后面的章节参考并理解如何用Pyhton实施一些先进的技术。本书涉及适当的数学内容以满足希望理解数据科学的程序员,给他们一些必要的参考。没有Python基础的人也可以有效地使用本书,本书的第1章介绍了基于Python编程语言的数据科学。如果你已有编程基础,这将对你很有帮助。本书的编写框架基本自成体系,能给入门级读者讲解数据科学,帮助他们成为这方面的专家。

在本书中,经常按:准备工作、操作方法、工作原理、更多内容、参考资料等主题进行讲解。

为了清楚提示如何能够完成这些主题,我们运用如下所示的各个部分。

这部分告诉你本主题要讲述的内容,并介绍如何安装软件及所需的初步设置。

本部分包含所需依照的操作步骤。

这部分通常是对之前内容的详细解释。

为了使读者知道更多相关主题的知识,这部分提供了一些附加信息。

这部分为主题提供了其他有用信息的配套链接。

在本书中,你会发现一些文本样式被用来区别不同种类的信息,以下是一些样式例子及其各自的含义。

文本中的代码,如函数名,如下所示。

我们调用get_iris_data()函数来获得输入数据,利用Scikit-learnd库的cross_validation模型的train_test_split函数将输入数据集一分为二。

代码块的格式设置如下。

# Shuffle the dataset
shuff_index = np.random.shuffle(range(len(y)))
x_train = x[shuff_index,:].reshape(x.shape)
y_train = np.ravel(y[shuff_index,:])

公式通常以图像形式提供,格式如下。

通常数学部分在每一节的开头部分被提出,某些章节中,各个主题通用的数学知识统一在简介部分进行介绍。

外部链接的格式如下。

http://scikit-learn.org/stable/modules/generated/sklearn.metrics.log_loss.html

第三方库中一些算法实现的细节的说明的规范如下。

“输入的样本被预测的分类被用来当作具有最高的平均预测概率,如果基准评估器没有实施predict_proba方法,则诉诸于投票。”

任何引用科技期刊或者论文作为参考文献的地方,格式规范如下。

你可以阅读Leo Breiman的论文来了解挂袋法的更多信息,请参见:

Leo Breiman著,《Bagging predictors.Mach. Learn》24, 2 (1996年8月),第123~140页,DOI=10.1023/A:1018054314350 http://dx.doi.org/10.1023/A:1018054314350。

程序的输出及图形通常以图像形式提供,例如。

任意命令行的输入/输出格式如下。

Counter({'Peter': 4, 'of': 4, 'Piper': 4, 'pickled': 4, 'picked': 4,'peppers': 4, 'peck': 4, 'a': 2, 'A': 1, 'the': 1, 'Wheres': 1, 'If': 1})

在Python shell中我们希望读者能够检查一些变量,指定的格式如下所示。

>>> print b_tuple[0]
1
>>> print b_tuple[-1]
c
>>>

这个格子里出现的是警告或者重要的注意点。

这个格子里出现的是提示和技巧。

我们永远欢迎来自读者的反馈。让我们知道你对于这本书的想法——哪些是你喜欢的或者不喜欢的。读者的反馈对我们来说十分重要,它可以帮助我们拓展书的内容,将会使你更加有效地使用本书。

读者可以用电子邮件发送反馈内容到邮箱feedback@packtpub.com,并在邮件主题中提及本书的标题/书名。

如果你在某个主题中有专业经验,并有兴趣编写或参与图书的出版,请访问 www.packtpub.com/authors中的作者指南。

现在,你荣幸地成为了Packt出版的图书的拥有者,我们将尽我们所能帮助你从产品中获得最完整的服务。

对于你所购买的任意Packt出版的图书,你可以在http://www.packtpub.com登录自己的账户下载示例代码文件。如果你是在别处购买的本书,也可以通过浏览http://www.packtpub.com/support网页并登记信息,我们将会通过电子邮件将文件发送给你。

我们还为你提供本书所包含的彩色截图/图像的PDF文件,彩图能帮你更好地了解输出结果。你可以从http://www.packtpub.com/sites/default/files/downloads/1234OT_ColorImages. pdf下载此文件。

虽然我们已尽力确保内容的准确性,但是难免会有错误发生。如果你在本书中发现文本或者代码错误,请告知我们,我们将感激不尽。这样一来,你可以帮助其他读者避免困惑,并帮助我们改进本书的后续版本。如果你发现任何错误,请访问http://www.packtpub.com/submit-errata进行举报,选择你的书,单击勘误表提交表单链接,然后填写你所发现的错误详情。一旦你的勘误通过验证,你所提交的内容将被接受,然后勘误将被上传到我们的网站并添加到该书的勘误列表中。

要查看之前提交的勘误信息,请访问https://www.packtpub.com/books/content/support,在检索框里输入书名,所需的信息就会出现在勘误表栏目中。

在互联网上以不同媒介对拥有版权的材料进行盗版是一直存在的问题,Packt非常重视版权和许可的保护。如果你遇到我们的作品在互联网上被以任何形式进行非法拷贝,请向我们提供网址或网站名称,使我们可以立即采取措施补救。请将涉嫌盗版材料的地址链接发送到copyright@packtpub.com,并与我们联系。

我们感谢你在保护作者方面提供的帮助,让我们能带给你更有价值的内容。

如果你对本书有任何方面的问题,可以通过发送邮件到questions@packtpub.com联系我们,我们将竭尽所能来解决问题。


在这一章里,我们将探讨以下主题。

Python语言提供了大量内置的数据结构和函数,十分便于数据科学的程序处理。在这一章里,我们先讨论那些最常用的部分。在后续章节中,你会看到它们在不同主题中的应用。熟练地掌握这些知识,有助于你在处理数据和开发算法的繁杂过程中快速地编写程序。

本章将对这些便捷的数据结构和方法做一个概述,当你成长为一个熟练的Python开发者,就能灵活地搭配并使用它们,同时找到自己的方式来实现目标。

各类数据结构都有其用途,在不同的环境下,可能要使用两类甚至更多来适应你的需求。在本书中,我们提供了大量的实例以供参考。

在Python语言中,容器是一种对象,它能够容纳任意数量、任意类型的对象。它可以对子对象进行操作,还可以迭代操作。字典、元组、列表还有集合都是容器对象。在collections模块中,Python提供了更多的容器类型。在这一节中,我们先来仔细了解字典。

我们先通过一个Python的脚本示例来理解字典是如何操作的,这段脚本用来统计词频,也就是每个词在给定的文本中出现的次数。

下面的示例演示了在Python中对字典对象如何操作。通过对一句简单的文本进行处理,我们仔细探究一下真正的字典创建过程。

# 1.  加载一个句子到变量中
sentence = "Peter Piper picked a peck of pickled peppers A peck of
pickled \
peppers Peter Piper picked If Peter Piper picked a peck of pickled \
peppers Wheres the peck of pickled peppers Peter Piper picked"

# 2.初始化一个字典对象
word_dict = {}
# 3. 执行对词频的统计
for word in sentence.split():
     if word not in word_dict:
          word_dict[word] =1
     else:
          word_dict[word]+=1
# 4. 打印输出词频结果
print word_dict

前面的代码创建了一个词频表,记录了每个词及其出现的频率。

最终的打印输出结果如下。

{'a': 2, 'A': 1, 'Peter': 4, 'of': 4, 'Piper': 4, 'pickled': 4,
'picked': 4, 'peppers': 4, 'the': 1, 'peck': 4, 'Wheres': 1, 'If': 1}

上面的结果是一个键值对,对于每个词(键),相对应的是频率(值)。字典数据结构是一个哈希映射,值对应于键。在上例中,我们是把字符串当作键,当然,我们也可以把其他不可变的数据类型当作键。

要看更多关于Python中的可变和不可变对象的详细讨论,请访问如下链接。

https://docs.python.org/2/reference/datamodel.html

同样地,值可以是任意数据类型,包括自定的类。在第2步中,我们初始化了一个字典对象,此时,它还是空的。当一个新键被添加到字典的时候,如果对字典进行的操作涉及这个新键,将抛出一个KeyError错误。在上例中的第3步里,我们可以在for循环里添加一个if语句来控制这个情形,我们也可以使用以下语句。

word_dict.setdefault(word,0)

如果我们要在循环中给一个字典添加元素,需要对字典的所有键进行操作,这个语句会被重复调用,我们可能并没有清楚地意识到这一点。

for word in sentence.split():
     word_dict.setdefault(word,0)
     word_dict[word]+=1

在Python 2.5及以上版本的collections模块中,有一个defaultdict类。它和setdefault方法有着对应关系,defaultdict类调用的实例如下所示。

from collections import defaultdict

sentence = "Peter Piper picked a peck of pickled peppers A peck of\
             pickled peppers Peter Piper picked If Peter Piper picked a peck of\
             pickled peppers Wheres the peck of pickled peppers Peter Piper picked"

word_dict = defaultdict(int)

for word in sentence.split():
     word_dict[word]+=1
print word_dict

你可能已经注意到了,我们在代码中包含了collections.defaultdict,初始化了字典。请注意defaultdict的参数int,采用了一个函数作为参数。在这个例子中,我们传递int()函数,当字典遇到一个之前没有遇到的键时,它将int()函数返回值用来初始化这个键,本例中这个值是0。在本书后续部分里,我们还会使用到defaultdict

标准的字典不会记住键被添加进来的顺序,在collections模块中,Python提供了一个能记住键被添加的顺序的容器,叫作OrderedDict。请阅读如下的Python文档了解更多细节。

https://docs.python.org/2/library/collections.html# collections.OrderedDict

遍历字典是很简单的,keys()函数可以遍历所有的键,values()函数可以遍历所有的值,items()函数则可以遍历所有的键值对,请看下面的例子。

For key, value in word_dict.items():
print key,value

在本例中,dict.items()函数迭代地遍历了字典中的所有键值对。

如下的Python文档非常详细地讲述了字典,是很方便的指南手册。详见:https://docs.python.org/2/tutorial/datastructures.html#dictionaries

字典是一种非常有用的媒介数据结构,如果你的程序使用JSON在模块间传送信息,字典就是最适合的数据类型。从JSON文件中装载数据到字典或者复制字典作为JSON字符串都很方便。

Python提供了高效的JSON操作库,详见:https://docs.python.org/2/library/json.html

Counter是一个字典子类,用来统计键值对类型的对象,我们的例子中的词频统计用Counter来完成是轻而易举的。

请看下面的示例。

from collections import Counter

sentence = "Peter Piper picked a peck of pickled peppers A peck of pickled \
             peppers Peter Piper picked If Peter Piper picked a peck of\
             pickled peppers Wheres the peck of pickled peppers Peter Piper \
             picked"

words = sentence.split()

word_count = Counter(words)

print word_count['Peter']
print word_dict

输出结果如下,你可以和前面的例子做一个比较。

Counter({'Peter': 4, 'of': 4, 'Piper': 4, 'pickled': 4, 'picked': 4,
'peppers': 4, 'peck': 4, 'a': 2, 'A': 1, 'the': 1, 'Wheres': 1, 'If':1})

访问以下链接,你能更好地理解Counter类。

https://docs.python.org/2/library/collections.html#collections.Counter

第1章“Python在数据科学中的应用”中1.3节“使用字典的字典”的相关内容。

我们之前提到,为了完成目标,你得创造性地应用各类数据结构,这样才能发挥它们的威力。接下来,我们通过一个实例来帮助理解“字典的字典”。

请看表1-1。

表1-1

用户/电影

LOR1

LOR2

LOR3

SW1

SW2

Alice

4

5

3

5

3

Huntsman

1

2

1

4

4

Snipe

3

4

4

2

1

第1列中列出了3个用户,其他列都是电影,单元格里是每个用户给电影的评分。我们要把这些数据放到内存中,这样大型程序的其他部分也能方便地访问,此时我们将使用“字典的字典”。

我们通过匿名函数来创建一个user_movie_rating的字典对象,以此展示“字典的字典”这一概念。

我们先将字典的字典填满数据。

from collections import defaultdict

user_movie_rating = defaultdict(lambda :defaultdict(int))

# 初始化艾丽丝的评分
user_movie_rating["Alice"]["LOR1"] = 4
user_movie_rating["Alice"]["LOR2"] = 5
user_movie_rating["Alice"]["LOR3"] = 3
user_movie_rating["Alice"]["SW1"] = 5
user_movie_rating["Alice"]["SW2"] = 3
print user_movie_rating

user_movie_rating就是一个字典的字典,在前一节中,defaultdict将一个函数作为参数,在本例中,我们传递了一个内置的匿名函数lambda,它返回一个字典。每当一个新的键被传递到user_movie_rating中,同时也有一个新的字典被创建。我们在后续章节中会经常提及lambda函数。

通过这种方式,我们可以快速地对每个用户和电影的组合体的评分进行操作,同样地,还有许多字典的字典的用例充分说明其便利性。

总之,熟练掌握字典数据结构,能帮助你简化数据科学的程序开发任务。以后我们将看到,在机器学习中,字典被大量用来存储特征值和标签。Python的NLTK库在文本挖掘的时候也会大量地使用字典来存储特征值,详见:http://www.nltk.org/book/ch05.html

上面链接中“使用字典映射词到特征”这一节展示了如何高效地使用字典。

第1章“Python在数据科学中的应用”中1.16节“使用lambda创建匿名函数”的相关内容。

在Python中,元组是一种顺序型的容器对象。元组是不可变的,元组中的元素由逗号分隔开,可以对不同类别构成的对象进行排序,不允许插入操作,支持以下操作。

我们讲解字典的时候,描述了完整的功能,对于元组,我们通过一些小段的代码来聚焦于元组的创建与维护操作。

先让我们看看一些元组创建和维护的示例代码。

# 1.创建一个元组
a_tuple = (1,2,'a')
b_tuple =1,2,'c'

# 2.利用索引访问元组的元素
print b_tuple[0]
print b_tuple[-1]
# 3.元组的元素值是无法修改的,如下的语句将返回一个错误
try:
     b_tuple[0] = 20
except:
     print "Cannot change value of tuple by index"

# 4.虽然元组是不可变的,但是元素的元组可以是一个可变的对象
# 在如下的代码中,元组的元组是一个列表
c_tuple =(1,2,[10,20,30])
c_tuple[2][0] = 100

# 5.元组一旦被创建,无法像列表那样进行扩展
# 不过,两个元组可以串联在一起

print a_tuple + b_tuple

# 6.对元组进行切片
a =(1,2,3,4,5,6,7,8,9,10)
print a[1:]
print a[1:3]
print a[1:6:2]
print a[:-1]

# 7.对元组求min和max值
print min(a),max(a)

# 8.包含于与非包含于
if 1 in a:
     print "Element 1 is available in tuple a"
else:
print "Element 1 is not available in tuple a"

在第1步中,我们创建了一个元组,严格来说,括号并不是必需的,不过它提高了代码的可读性。我们创建的是一个多种对象组成的元组,有数值、字符串等类型。第2步演示了通过索引来访问元组的细节,索引下标是从零开始。如果下标是负数,则从相反的方向访问元组的元素,print语句的输出结果如下。

>>> print b_tuple[0]
1
>>> print b_tuple[-1]
c
>>>

Python的元组下标从零开始,元组是不可变的。在第3步中,我们能看到元组最重要的属性:不可变。我们无法改变一个元组的元素的值,示例代码的第3步会导致编译器抛出一个错误。

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

这显得有点过于严格,然而这种特性从数据科学的观点来说有着重要的意义。

开发机器学习程序时,特别是从原始数据中生成特征值的时候,请创建特征值元组,这样数值就不会被下游的代码改变。

这些特征值保存在元组里,下游的代码就无法意外地修改特征值。

不过,我们需要指出,元组可以采用可变的对象作为它的成员,例如列表对象。像第4步中演示的元组,它的第3个元素就是一个列表对象,我们可以试试修改列表中的元素。

c_tuple[2][0] = 100

通过以下语句来打印输出元组。

print c_tuple

我们会得到如下的输出结果。

(1, 2, [100, 20, 30])

你能看到,列表的第1个元素的值已经被修改为100。

在第5步中,我们将两个元组串联起来,在机器学习程序开发中,常常有不同模块生成不同的特征值,此时使用元组串联是一个很好的选择。

例如:你有一个模块用于创建词袋模型的特征值,而另一个模块为了典型的文本分类而创建数值特征,这些模块都可以将输出结果保存到元组中,这样最终的模块可以连接所有的元组生成完整的特征值向量。

由于不可变的特性,元组不像列表那样可以在创建之后进行扩展,它不支持append函数。元组的不可变特性的另一个好处是它可以作为字典的键。

一般来说,当创建字典的键时,我们需要使用自定义的分隔符来连接不同的字符串,以此创建一个唯一的键。如果你使用元组,元组里的各个字符串可以对应作为字典的键。

这样可以大大提高程序输出结果的可读性,另外还能避免键在手动组合的时候产生难以觉察的错误。

在第6步中,我们讲述了元组的切片操作,一般地,切片参数需要3个冒号分隔的数字参数,第1个数是切片开始的位置,第2个是结束的位置,最后一个是步长。第6步的示例中也可以这样表示。

print a[1:]

打印输出的结果如下。

(2, 3, 4, 5, 6, 7, 8, 9, 10)

在这个示例中,我们只给出了开始位置(记住:索引起始于零),我们得到的是元组从索引值1开始的切片。再看另一个示例。

print a[1:3]

打印输出的结果如下。

(2, 3)

这样,我们指定了起始位置为1,结束位置为3。

切片操作是右侧结束。

虽然我们指定了结束位置为3,但输出的结果返回的值实际只到位置2,提前了一位。我们得到的是第2和第3位的部分。最后,我们来看看提供全部3个参数:起始位置、结束位置和步长。

print a[1:6:2]

打印输出的结果如下。

(2, 4,6)

除了起始和结束位置,我们设置了步长为2,上面的输出结果显示了每次输出都跳两个位置。

我们再来看看位置索引值为负数的情形。

print a[:-1]

打印输出的结果如下。

(1,2, 3, 4, 5, 6, 7, 8, 9)

除了最后一个元素,全部的元素都被打印显示了。

print a[::-1]

更深入地思考一下,上面这个语句的输出结果如下所示——拥有好奇心的读者应该能探索出为何得到这样的结果。

(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

在第7步中,我们通过min ()max ()函数来获取元组中的最小和最大值。

>>> print min(a), max(a)
1 10
>>>

在第8步中,我们展示了包含于、非包含于两种条件操作,这两种操作常常被用来检测某个元素是否存在于元组中。

if 1 in a:
     print "Element 1 is available in tuple a"
else:
     print "Element 1 is available in tuple a"

前一小节中,我们通过索引访问元组的元素。为了提高程序的可读性,我们会给元组的每个元素取一个名字,并通过名字来访问这些元素,这就是命名元组的来由,下面的URL提供了很好的命名元组文档。

https://docs.python.org/2/library/collections.html#collections.namedtuple

我们来看一个简单的命名元组应用示例。

from collections import namedtuple

vector = namedtuple("Dimension",'x y z')
vec_1 = vector(1,1,1)
vec_2 = vector(1,0,1)

manhattan_distance = abs(vec_1.x - vec_2.x) + abs(vec_1.y - vec_2.y) \
                                  + abs(vec_1.z - vec_2.z)

print "Manhattan distance between vectors = %d"%(manhattan_distance)

你可能已经注意到,我们采用了vec_1.xvec_1.y等对象符号来访问vec_1vec_2的元素。相较于使用元组的索引,这样的代码可读性更好。vec_1.xvec_1[0]是等价的。

第3章“数据分析——探索与争鸣”中3.16节“词袋模型表示文本”的相关内容。

除了不能存在重复值,集合和列表十分相似。集合是无序的同类元素的集合,通常情况下,集合被用来删除列表中的重复值。集合支持交集、并集、差集和对称差等操作,这些操作在许多用例中都十分便于使用。

在这节中,我们会写一小段代码来帮助理解集合数据结构的不同用途。在这个实例里,我们将使用Jaccard系数来计算两句话的相似度,并对Jaccard系数进行详细的讲述,在后续的章节里,我们还会介绍相似的其他度量方法。先给Jaccard系数来一个简要的介绍:它是介于0到1的数值,1表示高相似度,它的计算方法基于两个集合中存在的共同元素数量。

让我们来看看创建和维护集合的Python代码。

# 1.初始化两个句子
st_1 = "dogs chase cats"
st_2 = "dogs hate cats"

# 2.从字符串中创建词的集合
st_1_wrds = set(st_1.split())
st_2_wrds = set(st_2.split())

# 3.找出每个集合中不重复的词总数,即词表大小
no_wrds_st_1 = len(st_1_wrds)
no_wrds_st_2 = len(st_2_wrds)

# 4.找出两个集合中共有的词,保存到列表中,并统计总数
cmn_wrds = st_1_wrds.intersection(st_2_wrds)
no_cmn_wrds = len(st_1_wrds.intersection(st_2_wrds))

# 5.找出两个集合并集中不重复的词,保存到列表中,并统计总数
unq_wrds = st_1_wrds.union(st_2_wrds)
no_unq_wrds = len(st_1_wrds.union(st_2_wrds))

# 6.计算Jaccard相似度
similarity = no_cmn_wrds / (1.0 * no_unq_wrds)

# 7.打印输出
print "No words in sent_1 = %d"%(no_wrds_st_1)
print "Sentence 1 words =", st_1_wrds
print "No words in sent_2 = %d"%(no_wrds_st_2)
print "Sentence 2 words =", st_2_wrds
print "No words in common = %d"%(no_cmn_wrds)
print "Common words =", cmn_wrds
print "Total unique words = %d"%(no_unq_wrds)
print "Unique words=",unq_wrds
print "Similarity = No words in common/No unique words, %d/%d \
=%.2f"%(no_cmn_wrds,no_unq_wrds,similarity)

在第1步和第2步中,我们将两句话切分成多个词,并用set()函数创建了两个集合,set()函数可以将列表或者元组转为集合类型,请看下面的示例。

>>> a =(1,2,1)
>>> set(a)
set([1, 2])
>>> b =[1,2,1]
>>> set(b)
set([1, 2])

在这个示例中,a是一个元组,b是一个列表,通过set()函数,重复的元素被丢弃,并返回一个集合对象。st_1.split()st_2.split()方法返回一个列表,我们将它传递给set函数来获取集合对象。

现在我们用Jaccard系数计算两个句子之间的相似度,并对Jaccard系数进行详细的讲述,在后续的章节里,我们还会在“相似度计算”部分介绍其他的度量方法。我们使用union()intersection ()函数对集合进行操作来计算相似度。

我们在第4步执行了两个操作,第1个是intersection ()函数,由此我们找出了两个集合中的共有的词分别是“cats”和“dogs”,共有的词数量为2。接下来,我们用union()将两个集合进行并集,然后将不重复的那些词列出来,分别是:“cats”“hate”“dogs”和“chase”。这在自然语言处理中被称为词表。最后,我们在第6步中计算Jaccard系数,即两个集合共有的词数量与两个集合并集中不重复的词总数的比值。

输出的结果如下。

No words in sent_1 = 3
Sentence 1 words = set(['cats', 'dogs', 'chase'])
No words in sent_2 = 3
Sentence 2 words = set(['cats', 'hate', 'dogs'])
No words in common = 2
Common words = set(['cats', 'dogs'])
Total unique words = 4
Unique words= set(['cats', 'hate', 'dogs', 'chase'])
Similarity = No words in common/No unique words, 2/4 = 0.50

我们在上面的实例中演示了集合的函数的用法。此外,你也可以从scikit-learn之类的库中使用内置函数。我们将尽可能多地使用这些函数,而不必自己亲自写那些集合的应用函数。

# 加载库
from sklearn.metrics import jaccard_similarity_score

# 1.初始化两个句子
st_1 = "dogs chase cats"
st_2 = "dogs hate cats"

# 2.从字符串中创建词的集合
st_1_wrds = set(st_1.split())
st_2_wrds = set(st_2.split())

unq_wrds = st_1_wrds.union(st_2_wrds)

a =[ 1 if w in st_1_wrds else 0 for w in unq_wrds ]
b =[ 1 if w in st_2_wrds else 0 for w in unq_wrds]

print a
print b
print jaccard_similarity_score(a,b)

输出的结果如下。

[1, 0, 1, 1]
[1, 1, 1, 0]
0.5

列表是一种顺序型的容器对象,它和元组很相似,不过,它们是同构且是可变的。列表支持追加操作,它可以被用来当作栈或者队列。与元组不同,它可以扩展,你可以在创建一个列表之后使用append函数给它追加一个元素。

和介绍元组的小节相似,我们通过一些小段的代码来聚焦于列表的创建与维护操作,而不是像介绍字典那样采用完整的功能代码。

下面的Python代码演示列表的创建和维护等操作。

# 1.快速地创建一个列表
a = range(1,10)
print a
b = ["a","b","c"]
print b

# 2.列表可以通过索引来访问,索引起始于0
print a[0]

# 3.用负数作为索引,则对列表元素的访问从反方向开始
a[-1]

# 4.使用两个索引参数,切片操作可以访问列表的子集
print a[1:3] # prints [2, 3]
print a[1:] # prints [2, 3, 4, 5, 6, 7, 8, 9]
print a[-1:] # prints [9]
print a[:-1] # prints [1, 2, 3, 4, 5, 6, 7, 8]

# 5.列表串联
a = [1,2]
b = [3,4]
print a + b # prints [1, 2, 3, 4]

# 6.列表的最小值和最大值
print min(a),max(a)

# 7.包含于和非包含于
if 1 in a:
     print "Element 1 is available in list a"
else:
     print "Element 1 is available in tuple a"

# 8.追加和扩展列表
a = range(1,10)
print a
a.append(10)
print a

# 9.列表实现栈
a_stack = []

a_stack.append(1)
a_stack.append(2)
a_stack.append(3)

print a_stack.pop()
print a_stack.pop()
print a_stack.pop()

# 10.列表实现队列
a_queue = []

a_queue.append(1)
a_queue.append(2)
a_queue.append(3)

print a_queue.pop(0)
print a_queue.pop(0)
print a_queue.pop(0)

# 11.列表排序和反转
from random import shuffle
a = range(1,20)
shuffle(a)
print a
a.sort()
print a

a.reverse()
print a

第1步中,我们能看到创建列表的方式与其他的不同,请注意我们只有同类型的元素。和集合不一样,列表允许存在重复的元素。第2步到第7步和元组的相关步骤都是一样的,覆盖了索引、切片、串联、最小最大值、包含于和非包含于等操作,我们不再赘述。

第8步演示了追加和扩展操作,这也是列表区别于元组的地方(当然,请注意列表元素必须是同类型)。我们来看看代码第1部分的输出。

>>> a = range(1,10)
>>> print a

[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a.append(10)
>>> print a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>>

我们看到10被添加到列表a中。

下面的输出是第2部分中扩展函数的演示。

>>> b=range(11,15)
>>> a.extend(b)
>>> print a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>>

我们用另一个列表b扩展了原来的列表。

在第9步中,我们演示了用列表实现栈的功能,pop()函数用来取回追加到列表中的最后一个元素,输出结果如下。

3
2
1

最后一个被追加进来的元素被第一个取回,这就是栈的后进先出(Last In First Out,LIFO)。

在第10步,我们用列表来实现队列,pop()函数用0作为参数,表明要取出的元素的索引已经被传递了,输出结果如下。

1
2
3

输出结果遵循的是队列的FIFO类型,但这是一种低效的方法。由于列表底层实现的方法限制,弹出最初的元素不是一个好的选择。如果想要执行这个操作,一个更有效的方法是使用双端队列数据结构,我们将在下一章节中介绍。

最后一个步骤展示了列表的sortreverse操作。列表的内置函数sort()可以将列表的元素进行排序,默认是升序排序。本章后面有个专门的小节讲解排序。reverse()函数将列表中的元素进行反转。

我们先来创建一个列表,元素是从1到19。

a = range(1,20)

random模块中有一个shuffle()函数,我们先用它将列表中的元素搅乱,然后我们才能演示排序操作,搅乱后的输出结果如下。

[19, 14, 11, 12, 4, 13, 17, 5, 2, 3, 1, 16, 8, 15, 18, 6, 7, 9, 10]

现在,a.sort()执行了一个位置排序,我们得到如下的输出结果。

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

reverse()也是一个位置操作,产生如下输出结果。

[19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

栈或队列只能在一个方向上追加或弹出数据,而双端队列有两个端,可以在不同的端执行追加或弹出数据操作,请参见:

https://docs.python.org/2/library/collections.html#collections.deque

推导是从一个序列创建另一个序列的操作,例如,我们可以从列表或元组中创建一个列表。本节我们将讲述列表推导。一般地,列表推导具有以下特点。

我们先通过一个Python的脚本示例来理解列表推导中涉及的元素的不同之处。先输入一个列表,元素包含了一些正数和负数,我们希望得到的输出是由那些负数元素的平方值构成的列表。

下面的示例代码演示了列表推导操作。

# 1.定义一个由一些正整数和负整数构成的简单列表
a = [1,2,-1,-2,3,4,-3,-4]

# 2.现在让我们写出列表推导
# pow()是幂函数,需要两个输入参数,第1个参数是底数,第2个参数是指数,返回的输出为幂值
b = [pow(x,2) for x in a if x < 0]

# 3.最后我们看看保存在新建的列表b里的输出结果
print b

这个示例解释了列表推导的不同组成部分。我们来看第2步。

b = [pow(x,2) for x in a if x < 0]

这行代码可做如下解释。

对字典来说,推导的语法也是相同的,我们来看一个简单的示例。

a = {'a':1,'b':2,'c':3}
b = {x:pow(y,2) for x,y in a.items()}
print b

在上面的示例中,我们从输入的字典a中创建了一个新的字典b,输出结果如下。

{'a': 1, 'c': 9, 'b': 4}

我们保留了字典的键,但其新值是字典a中原来的值的平方。值得注意的一点是在推导过程中,花括号代替了括号。

我们也可以采用一点小技巧来给元组做推导,请看下面的示例。

def process(x):
     if isinstance(x,str):
          return x.lower()
     elif isinstance(x,int):
          return x*x
     else:
          return -9

a = (1,2,-1,-2,'D',3,4,-3,'A')
b = tuple(process(x) for x in a )

print b

我们编写了一个新的处理函数来替代pow()函数,我把它留给读者作为一个练习,以理解这个处理函数的作用。请注意我们遵循和推导列表一样的语法。不过,我们使用圆括号代替括号,这段代码的输出会返回如下错误信息。

<generator object <genexpr> at 0x05E87D00>

哎,我们想要一个元组,但是被一个迭代器终结了(后面的章节我们会介绍迭代器),正确的方式应该是下面这样的。

b = tuple(process(x) for x in a )

这样,“print b”语句将产生如下输出。

(1, 4, 1, 4, 'd', 9, 16, 9, 'a')

Python的推导是基于集迭代器符号的,请参见:

http://en.wikipedia.org/wiki/Set-builder_notation

关于Itertools.dropwhile,请参见:

https://docs.python.org/2/library/itertools.html#itertools.dropwhile

借助谓词和序列,dropwhile将只返回满足那些谓词表达式的序列中的项。

毫无疑问,对于数据科学的程序而言,数据是极其重要的输入。数据的大小是可变的,有些能装载到内存中,有些则不能。而记录访问架构也是随一种数据格式到另一种而变化。有趣的是,不同的算法处理数据时,需要的是可变长度的组块。例如,假如你在写一个随机梯度下降的算法,你希望在每个时间片传送5000条记录的数据块,如果你对如何访问数据、理解数据格式、依次传送数据、给调用者需要的数据等流程有着清晰的概念,那你才能成功。这样能让你写出清晰的代码。大多数时候,最有趣的部分是我们如何处理数据,而不是我们怎么访问这些数据。Python给我们提供了迭代器这种优雅的方式来处理所有这些需求。

Python中的迭代器实现了一种迭代器模式,它让我们可以一个接一个地处理一个序列,但不需要真正实现整个序列。

我们来创建一个简单的迭代器,叫作“简单计数器”,用一些代码演示了怎样高效地使用迭代器。

# 1.写一个简单的迭代器
class SimpleCounter(object):
    def __init__(self, start, end):
        self.current = start
        self.end = end
    def __iter__(self):
        'Returns itself as an iterator object'
        return self
    def next(self) :
        'Returns the next value till current is lower than end'
        if self.current > self.end:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1            
# 2.现在来访问这个迭代器
c = SimpleCounter(1,3)    
print c.next()
print c.next()
print c.next()
print c.next()

# 3.另外一种访问方式
for entry in iter(c):
    print entry

在第1步中,我们定义了一个名为Simple Counter的类,构造函数_init_有两个参数:起始和结束,来定义序列的开始和结束。请注意_iter_next这两个方法,在Python中想要成为一个迭代器的对象必须都支持这两个函数。_iter_返回整个类对象作为一个迭代器对象,next方法返回迭代器里的下一个值。

如第2步所示,我们可以使用next()函数访问迭代器中的连续元素。Python也提供了一个方便的函数iter(),它能用来在循环体中循序访问元素,如第3步所示。它在内部实现中使用了next函数。

请注意,一个迭代器对象只能被使用一次。运行上面的代码之后,如果我们仍要像下面这样访问迭代器。

print next(c)

系统会抛出一个StopIteration异常。在序列已经到尾部的时候再调用c.next() 会触发一个StopIteration异常。

    raise StopIteration
StopIteration
>>>

iter()函数会处理这个异常,当数据访问完成的时候退出循环。

再看另一个迭代器的示例,我们需要在程序中访问一个非常大的文件,不过,在代码里,我们每次只访问一行,直到读完整个文件。

f = open(some_file_of_interest)
for l in iter(f):
print l
f.close()

在Python里,一个文件对象就是一个迭代器,它支持iter()next()函数。因此,我们每次只处理一行数据,而不是将全部文件加载到内存中。

迭代器给了你自由,你可以让你的应用程序自己定义访问你的数据源的方式。

下面的链接提供了Python中多种多样的迭代器使用方法的信息,如无限迭代器itertools中的count()cycle()以及repeat()等。

https://docs.python.org/2/library/itertools.html#itertools.cycle

上一节,我们了解了什么是迭代器,这一节我们来讨论如何生成一个迭代器。

生成器提供了清晰的语法,能够依次访问一个序列,并不需要使用__iter__next()这两个函数。我们也不用写一个类了。请注意,生成器和可迭代这两者才能制造一个迭代器。

如果你理解了前面小节里的推导,我们下面的示例你也能明白,在这个示例中,我们有一个生成器推导。回忆一下,我们曾经用下面的方式来进行一个元组推导,并得到了一个生成器对象。

SimpleCounter  = (x**2 for x in range(1,10))

tot = 0
for val in SimpleCounter:
     tot+=val

print tot

很明显,上面的代码片段将算出给定范围的数的平方和,本例中的范围是从1到9(Python的range函数是右侧结束),使用生成器,我们创建了一个名为SimpleCounter的迭代器,我们用它在for循环中循序访问那些潜在的数据。请注意我们现在没有使用iter()函数,代码十分清晰,我们成功地用一种优雅的方式重建了我们的旧SimpleCounter类。

让我们看看如何使用yield语句来创建一个生成器。

def my_gen(low,high):
     for x in range(low,high):
          yield x**2

tot = 0     

for val in my_gen(1,10):
     tot+=val
print tot

在上面的代码中,my_gen()函数就是一个生成器,我们使用yield语句来返回一个序列输出。

在前面的小节中,我们提到过生成器和可迭代两者才能制造一个迭代器,下面我们通过使用iter函数调用生成器来验证一下。

gen = (x**2 for x in range(1,10))

for val in iter(gen):
     print val

在我们进入下一节“使用可迭代对象”之前,强调一下使用生成器的注意事项,当我们完成对序列的访问时,就该立刻结束,不要再试图获取更多的数据。

使用生成器对象时,我们只能访问序列一次。

可迭代对象和生成器十分相似,但是有一个重要的区别:我们可以重复地访问一个可迭代对象,即使我们已经访问完了序列中的所有元素,我们还可以从头重新访问它,这和生成器是完全不同的。

如果不保持任何状态,它们就是基于对象的生成器。所有带有iter方法的类,在用来产生数据时,都可以被作为无状态对象生成器来使用。

我们通过一个简单的示例来理解可迭代对象。如果理解了之前介绍的生成器和迭代器,你也能很容易地理解这个概念。

我们来创建一个简单的可迭代对象SimpleIterable,用代码来演示如何使用可迭代对象。

# 1.先创建一个简单的带有iter方法的类
class SimpleIterable(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end
    def __iter__(self):
        for x in range(self.start,self.end):
            yield x**2

# 2.现在调用这个类,并且迭代它的值两次
c = SimpleIterable(1,10)

# 3.第一次迭代
tot = 0
for val in iter(c):
    tot+=val
print tot

# 4.第二次迭代
tot =0
for val in iter(c):
    tot+=val

print tot

在第1步中,我们创建了一个简单的类作为我们的可迭代对象。Init构造函数有两个参数,起始和结束,这和我们之前的示例很相似。我们定义了一个iter函数,它将提供我们需要的序列,给定一个数值范围,返回这些数的平方值。

接下来,我们采用两个循环,在第1个循环中,我们迭代访问范围内的数值,从1到10。当我们进入第2个for循环时,我们会发现程序有迭代访问了那些序列,并且没有出现任何异常情况。

第1章“Python在数据科学中的应用”中1.8节“使用迭代器”的相关内容。

第1章“Python在数据科学中的应用”中1.9节“生成一个迭代器和生成器”的相关内容。

Python支持函数式编程,除了命令范式。在前面的章节中,我们已经接触到了一些函数式编程的概念,不过没有明确地说明,在这节里,我们再来回头看看。在Python中,函数是一等公民,它们拥有属性,可以被引用,并且可以被分配给一个变量。

这节里我们将研究函数作为变量传递的范例。

我们先定义一个简单的函数,然后看看如何将它当作变量来使用。

# 1.定义一个简单的函数
def square_input(x):
     return x*x
# 2.把它分配给一个变量
square_me = square_input

# 3.最后调用这个变量
print square_me(5)

我们在第1步中定义了一个简单的函数,给定一个输入值,这个函数返回输入值的平方值。我们将这个函数分配给了变量square_me。最终,我们可以通过输入一个合法的参数给变量square_me来调用这个函数。这演示了在Python里,我们可以将函数作为一个变量来对待,这是函数式编程的重要概念。

这一节将解释函数式编程里的另一个概念:在一个函数中定义另一个函数。

我们写一个简单的函数,它返回输入列表的数值的平方和。

我们定义一个简单的函数,用它演示在函数中嵌入函数。

# 1.定义一个函数,返回给定输入数值的平方和
def sum_square(x):
     def square_input(x):
          return x*x
     return sum([square_input(x1) for x1 in x])

# 2.输出结果来检查是否正确
print sum_square([2,4,5])

我们在第1步中在函数sum_square ()中定义了函数square_input (),父函数用它来执行平方值求和的操作。在第2步中,我们调用父函数打印输出结果。

输出的结果如下。

[4, 9, 16]

Python支持高阶函数功能:将一个函数作为另一个函数的参数传递。

我们将前面一个例子中的函数square_input ()重写,以此演示一个函数是如何被作为另一个函数的参数进行传递。

请看如何将一个函数作为另一个函数的参数进行传递。

from math import log

def square_input(x):
     return x*x

# 1.定义一个类函数,它将另外一个函数作为输入
# 并将它应用到给定的输入序列上。
def apply_func(func_x,input_x):
     return map(func_x,input_x)

# 2.这里使用apply_func()函数,并校验结果
a = [2,3,4]

print apply_func(square_input,a)
print apply_func(log,a)

我们在第1步中定义了函数apply_func,它有两个变量参数,第1个是一个函数,第2个是一个序列。我们使用了map函数(后续章节将介绍)将给定的函数应用到序列中的所有元素。

接着,我们在列表上调用apply_func,先是square_input函数,然后是log函数,输出的结果如下。

[4, 9, 16]

你会发现,所有的列表元素都被求出了平方值,map函数将square_input函数应用到序列里的所有元素上。

[0.69314718055994529, 1.0986122886681098, 1.3862943611198906]

同样地,log函数也被应用到序列里的所有元素上。

在这节里,我们讨论在一个函数里返回另一个函数。

我们举一个高中的例子来说明咱们使用返回一个函数的函数。我们要解决的问题是:给定半径,求出不同高度的圆柱体的容积。

请参见:http://www.mathopenref.com/cylindervolume.html

Volume = area * height = pi * r^2 * h

上面的公式可以准确地求出圆柱体的体积。

我们写一个简单的函数来演示在函数中返回函数的概念,此外还有一小段代码介绍如何使用。

# 1.定义一个函数来演示在函数中返回函数的概念
def cylinder_vol(r):
     pi = 3.141
     def get_vol(h):
          return pi * r**2 * h
     return get_vol

# 2.定义一个固定的半径值,在此给定半径和任意高度条件下,写一个函数来求出体积
radius = 10
find_volume = cylinder_vol(radius)

# 3.给出不同的高度,求解圆柱体的体积
height = 10
print "Volume of cylinder of radius %d and height %d = %.2f cubic\
units" %(radius,height,find_volume(height))
height = 20
print "Volume of cylinder of radius %d and height %d = %.2f cubic\
units" %(radius,height,find_volume(height))

在第1步中,我们定义了函数cylinder_vol(),它只有一个参数r,即半径。在这个函数中,我们定义了另一个函数get_vol(),这个函数获取rpi的值,并将高度作为参数。对于给定的半径r,也即cylinder_vol()的参数,不同高度值被作为参数传递给了get_vol()

在第2步中,我们定义了半径,在本例中具体值为10,调用并传递给了cylinder_vol()函数,这个函数返回了get_vol()函数,我们把它存在名为find_volume的变量中。

在第3步中,我们使用不同的高度值来调用find_volume,如10和20,请注意我们没有给出半径值。

输出结果如下。

Volume of cylinder of radius 10 and height 10 = 3141.00 cubic units
Volume of cylinder of radius 10 and height 20 = 6282.00 cubic units

Functools是高阶函数中的一个模块,请参考以下链接:

https://docs.python.org/2/library/functools.html

装饰器能封装一个函数,并改变它的行为,通过示例是理解它们的最好方式,本节中我们演示了实际应用中的一些示例。

还记得我们在前面章节中将函数作为另一个函数的参数、函数作为一个变量、函数中返回函数等介绍吗?最重要的是,你还记得那个圆柱体的例子吗?如果你掌握了这些,装饰器只是小菜一碟。在本节的示例中,我们将对给定的字符串建立清理操作管道:给定一个混合大小写并带有标点符号的字符串,我们使用装饰器对它进行清理,这些操作还很容易进行扩展。

我们写一个简单的装饰器来进行文本操作。

from string import punctuation

def pipeline_wrapper(func):
     def to_lower(x):
          return x.lower()

     def remove_punc(x):
          for p in punctuation:
               x = x.replace(p,'')
          return x

     def wrapper(*args,**kwargs):
          x = to_lower(*args,**kwargs)
          x = remove_punc(x)
          return func(x)
     return wrapper

@pipeline_wrapper
def tokenize_whitespace(inText):
     return inText.split()

s = "string. With. Punctuation?"
print tokenize_whitespace(s)

我们先从以下两行开始。

s = "string. With. Punctuation?"
print tokenize_whitespace(s)

我们声明了一个字符串变量,然后想对它进行清理,使之满足以下特性。

我们用字符串s作为参数,调用了tokenize_whitespace函数,我们来看看这个函数。

@pipeline_wrapper
def tokenize_whitespace(inText):
return inText.split()

这个函数很简单:输入一个字符串,函数采用空格作为分隔符将它进行分割,并返回一个词列表。接下来我们使用装饰器来改变这个函数的行为,这个装饰器就是@pipeline_wrapper,它是以简便的方式调用以下语句。

tokenize_whitespace = pipeline_wrapper (clean_tokens)

我们仔细看看这个装饰器函数。

def pipeline_wrapper(func):

    def to_lower(x):
             return x.lower()
    def remove_punc(x):
             for p in punctuation:
                         x = x.replace(p,'')
                         return x
             def wrapper(*args,**kwargs):
                         x = to_lower(*args,**kwargs)
                         x = remove_punc(x)
                         return func(x)
             return wrapper

pipeline_wrapper返回了wrapper函数,在后者中,最后的返回语句是返回func,这是我们传递给wrapper的原始函数,wrapper改变了我们原来的pipeline_wrapper函数的行为。pipeline_wrapper的输入先被to_lower()函数修改了,转成了小写。随后是remove_punc()函数,将标点符号清除。最后的输出如下。

['string', 'with', 'punctuation']

以上结果就是我们所要的:清除标点符号,转为小写字符,最后形式是词的列表。

匿名函数是由Python中的lambda语句产生的。一个没有被命名的函数就是匿名函数。

如果你掌握了将函数作为参数传递的内容,你会发现这节的示例和它非常相似。这节我们会传递一个预定义的函数,一个lambda函数。

我们写一个简单的操作小型数据集的示例,来解释Python中的匿名函数。

# 1.创建一个简单的列表,写一个类似于1.13节“将函数作为参数传递”中的函数
a =[10,20,30]

def do_list(a_list,func):
     total = 0
     for element in a_list:
          total+=func(element)
     return total

print do_list(a,lambda x:x**2)   
print do_list(a,lambda x:x**3)   

b =[lambda x: x%3 ==0  for x in a  ]

第1步中,do_list函数接受另一个函数作为参数。在输入的列表和函数的共同作用下,do_list函数应用输入的函数对给定的列表中的元素进行处理,对要转换的数值进行求和,并返回结果。

接着,对do_list函数进行调用,第1个参数是我们输入的列表a,第2个参数是我们的lambda函数,我们来解码它。

lambda x:x**2

通过关键字lambda,我们就声明了一个匿名函数,跟着是定义一个函数的参数,本例中,x就是被传递给这个匿名函数的参数名。表达式中跟在冒号符之后的是返回值,输入参数按照表达式进行运算,并给出返回值。本例中,输入值的平方值被返回作为输出。第2个print语句里,我们有另一个lambda函数,用来返回给定输入的立方值。

map是Python中的内置函数,它使用一个函数和一个可迭代对象作为参数,形式如下。

map(aFunction, iterable)

我们来看一个非常简单的使用map函数的示例。

我们看看如何使用map函数的示例。

#首先声明一个列表
a =[10,20,30]
#现在,在print语句中调用map函数
print map(lambda x:x**2,a)

这和上一节中的代码很相似,map函数有两个参数,第1个是一个函数,第2个是一个序列。本例中,我们使用了匿名函数。

lambda x:x**2

这个函数求出给定输入值的平方值。我们还传递了一个列表给map函数。

map函数对给定列表中的所有元素应用了求平方值函数,并以列表的形式返回结果。输出结果如下。

[100,400,900]

同样地,其他函数也可以被应用到列表上。

print map(lambda x:x**3,a)

使用map函数,我们可以把上一节中的代码段改写成单行的代码。

print sum(map(lambda x:x**2,a))
print sum(map(lambda x:x**3,a))

如果应用的函数需要N个参数,则map函数参数也需要N个序列,请看下面的示例以增进理解。

a =[10,20,30]
b = [1,2,3]

print map(pow,a,b)

我们传递了a、b两个序列给map函数,请注意传递的函数是power函数,它需要两个参数。上面示例的输出结果如下。

[10, 400, 27000]
>>>

列表a中的各个元素,被计算出以列表b中相同位置的值为指数的幂值。请注意,两个列表中必须是相同的大小,如果不满足这个条件,Python会自动将较小的那个列表补足空值。这个示例演示的是列表类型,其他任何可迭代对象也都能被传递给map函数。

顾名思义,过滤器就是按照给定的函数从一个序列中过滤出相应的元素。给定一个包含负数和正数的序列,我们可以使用过滤器函数将所有的负数过滤出来。过滤器filter是Python的内置函数,它使用一个函数和一个可迭代对象作为参数。

filter(aFunction, iterable)

函数被作为参数传递,返回一个测试结果的布尔值。

函数被应用到可迭代对象的所有元素,测试值为真的所有项以列表的形式作为返回值。lambda匿名函数最常被用来和filter函数配合。

请看一个简单的示例演示filter函数用法。

请看如何使用过滤器filter函数的示例。

# 先声明一个列表
a = [10,20,30,40,50]
# 应用filter函数到列表的所有元素上
print filter(lambda x:x>10,a)

我们使用的lambda函数很简单,当给定的值大于10时,它返回真值,否则返回假值。我们的print语句给出下面的结果。

[20, 30, 40, 50]

如你所见,只有大于10的元素才被返回。

zip函数将两个相同长度的集合合并成对,它是Python的内置函数。

我们通过一个简单示例来演示zip函数。

我们传递两个序列给zip函数,并打印输出。

print zip(range(1,5),range(1,5))

本例中zip函数的两个参数是两个列表,这两个列表都是由从1到5的数值组成。range函数有3个参数:起始数值、结束数值和步长,默认步长为1。本例中,我们分别把1和5作为列表的起始和结束值。记住,Python是右侧关闭的,所以range(1,5)将返回如下。

[1,2,3,4]

我们传递了两个序列给zip函数,输出结果如下。

[(1, 1), (2, 2), (3, 3), (4, 4)]

记住两个集合的大小必须一致,如果不满足,则输出结果会削减以匹配较小的集合大小。

现在请看下面的代码。

x,y = zip(*out)
print x,y

你能猜到输出结果是什么样的吗?

我们来看看*操作符是做什么的,它用来将集合中的每个元素作为位置参数进行传递。

a =(2,3)
print pow(*a)

power函数需要两个参数,a是一个元组,你会发现,*操作符将元组分为了两个独立的参数。它把元组分成了2和3,两者被作为参数传递,即pow(2,3),得到的结果是8。

**操作符可以用来将字典中的元素进行分解,我们看如下的代码段。

a_dict = {"x":10,"y":10,"z":10,"x1":10,"y1":10,"z1":10}

**操作符将字典中的元素变成命名参数进行传递。本例中,我们使用**操作符对字典进行操作,会得到6个参数。请看如下的函数,它需要6个参数。

def dist(x,y,z,x1,y1,z1):
    return abs((x-x1)+(y-y1)+(z-z1))

print dist(**a_dict)

print语句的输出结果是0。

使用这两种操作符,我们可以编写一些函数,可以接收的变量参数个数不再受限。

def any_sum(*args):
    tot = 0
    for arg in args:
          tot+=arg
    return tot
print any_sum(1,2)
print any_sum(1,2,3)

如你所见,上面代码中的any_sum函数可以使用任意数量的变量。严谨的读者可能会疑惑,为什么不使用列表作为any_sum函数的参数呢?确实本例可以使用列表来传递参数,但我们很快就会遇到一些情形,这些情形下,我们甚至不知道要传递什么类型的参数。

回到zip函数的应用上来,zip函数的一个缺点是它会立刻计算完一个列表,当我们使用两个超大的列表时,可能会出现一些问题。izip函数是用来解决此类状况的,它只在需要的时候计算相应的元素。izipitertools模块的一个组成部分,请参阅1.24节“使用itertools”中的相关内容。

第1章“Python在数据科学中的应用”中1.24节“使用itertools”的相关内容。

数据科学的应用程序要成功解决一个问题,必须先找到适当的处理数据的方法。例如在机器学习中对数据进行预测或分类,要么采用有监督的方法,要么采用无监督的方法。而在此之前,传输数据,把数据清洗到匹配算法,可能已经花费了很长的时间。

通常,有很多种方法对数据进行整理使之适合数据科学程序进行处理,数据科学程序开发者首先面对的挑战是如何访问数据,并用Python的数据结构让这些数据持续可用。掌握使用Python访问数据的诀窍是非常有用的,能让你避过纷扰,直接面对问题的核心内容。

一般数据是以文本的形式存放的,用逗号或者tab作为分隔符。我们可以采用Python的内置文件对象工具来进行处理。如前所述,文件对象实现了_iter_()next()方法,这让我们可以处理非常大的文件,这些文件无法一次全部装载到内存里,只能每次读取其中的一小部分。

Python的机器学习库(如scikit-learn)就是基于NumPy库,在这节中,我们将研究如何高效地读取外部数据,并将之转为NumPy的数组以便后续的数据处理。

NumPy提供了一个genfromtxt函数可以从表格数据中创建数组,数据存放到NumPy数组中以后,系统处理数据就轻松得多。我们通过一个NumPy 1.8.0编写的代码来看看如何使用genfrom text

我们先从导入必需的库开始,先定义输入的例子,然后演示如何处理表格数据。

# 1.我们先用StringIO来模拟一个小型的表格数据
import numpy as np
from StringIO import StringIO
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")

# 2.使用NumPy的genfromtxt来读取数据,并创建一个NumPy数组
data = np.genfromtxt(in_data,dtype=int,delimiter=",")

# 3.清除掉一些我们不需要的列
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",usecols=(0,1))

# 4.设定列名
in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names="a,b,c")

# 5.使用列名来处理数据
in_data = StringIO("a,b,c\n10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names=True)

第1步中,我们用StringIO来模拟表格数据,有3个行和3个列,行通过换行表示,列则通过逗号分隔。

第2步中,我们用NumPygenfromtxt导入数据到NumPy数组。genfromtxt的第一个参数是文件源和文件名,本例中是StringIO对象。输入由逗号分隔,分隔符参数允许我们自己定义分隔符。运行上面的代码后,数据格式如下。

>>> data
array([[10, 20, 30],
        [56, 89, 90],
        [33, 46, 89]])

如你所见,我们成功地将字符串数据加载到了NumPy数组中。

下面列出了genfromtxt函数的各个参数以及默认值。

genfromtxt(fname, dtype=<type 'float'>, comments='#', delimiter=None,
skiprows=0, skip_header=0, skip_footer=0, converters=None,
missing='', missing_values=None, filling_values=None, usecols=None,
names=None, excludelist=None, deletechars=None, replace_space='_',
autostrip=False, case_sensitive=True, defaultfmt='f%i', unpack=None,
usemask=False, loose=True, invalid_raise=True)

唯一必备的参数是数据源的名字,本例中是一个StringIO对象,它可以是一个文件名或者带有read方法的类似于文件的对象,也可以是一个远程文件的URL。

首先必须将给定的行分成列,当文件被打开进行读取时,genfromtxt将非空行切分成一个字符串序列。空行和注释行会被忽略,注释选项帮助gentext判断哪些行是注释行。我们指定的分隔符将字符串切分为列。我们的示例使用“,”作为分隔符。制表符“/t”也是一种常用的分隔符。gentext的默认分隔符是None,这意味着行被空格分成多个列。

一般而言,行被转换成字符串序列之后,列被萃取出来,每个独立的列并没有被清除前导或者后导的空格。在上面示例代码的后面部分,这种情况需要进行处理,特别是有些变量要被作为字典的键。例如,若是前导或后导的空格没有被处理完全,代码可能会出现bug或错误。设置参数autostrip=True有助于避免这类问题。

很多情况下,我们在读取文件的时候要跳过一些数据,比如跳过最前n行或者最后n行,这就需要使用headers和footers参数。设置skip_header=n会在读文件时跳过最开始的n行。类似地,设置skip_footer=n则跳过最后的n行。

和不需要的行类似,有时我们需要跳过一些列,usecols参数可以指定一个包含所需要的列的列表。

in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",usecols=(0,1))

在上面的示例中,我们只选择了两个列,第0和第1列。数据对象形式如下。

>>> data
array([[10, 20],
        [56, 89],
        [33, 46]])

使用names参数,我们可以自定义列名,由逗号分隔的列名字符串参数形式如下。

in_data = StringIO("10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names="a,b,c")
>>> data
array([(10, 20, 30), (56, 89, 90), (33, 46, 89)],
       dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<i4')])

设定names参数为真,输入文件的第1行会被当成列名。

in_data = StringIO("a,b,c\n10,20,30\n56,89,90\n33,46,89")
data = np.genfromtxt(in_data,dtype=int,delimiter=",",names=True)

>>> data
array([(10, 20, 30), (56, 89, 90), (33, 46, 89)],
       dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<i4')])

NumPy里还有个叫作loadtxt的方法可以方便地从文本文件中创建NumPy数组,请参阅:http://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html

这个函数比genfromtxt要简单一些,如果你不需要复杂的数据处理架构,比如处理丢失的数据等情况,你可以选用loadtxt

此外,如果你不需要装载数据到NumPy数组,只想把数据加载到列表中,Python默认提供了csv库,可以参考下面的URL。

https://docs.python.org/2/library/csv.html.

上面这个csv库里有一个有趣的方法叫作csv.Sniffer.sniff()。要处理一个很大的csv文件时,我们要理解它的结构,就可以使用sniff()函数,它返回一个具有csv文件大部分属性的子类。

我们获取的数据经常并不是我们能直接使用的格式。我们需要执行一系列在机器学习术语中称为数据预处理的数据处理过程。克服这个障碍的一条途径是采用字符串的形式获取所有数据,在后续的场景里再执行需要的数据格式转换。还有一种办法是在数据源阶段就完成这些转换工作。genfromtxt提供了一些函数,让我们可以在读取数据源的时候执行数据转换。

假定我们有如下的文本行。

30kg,inr2000,31.11,56.33,1
52kg,inr8000.35,12,16.7,2

这是一个我们获取到的生活中的常见数据样例,开头的两个列里,分别有字符串“kg”和“inr”在真正的数据的后面和前面。

我们来试试如下方法将数据放入NumPy数组中。

in_data = StringIO("30kg,inr2000,31.11,56.33,1\
n52kg,inr8000.35,12,16.7,2")
data = np.genfromtxt(in_data,delimiter=",")

输入结果如下。

>>> data
array([[ nan, nan, 31.11, 56.33, 1. ],
        [ nan, nan, 12. , 16.7 , 2. ]])

如你所见,开始的两个列的数据并没有被读取。

我们首先导入必需的库,然后定义一个输入样板,最后演示一下数据预处理。

import numpy as np
from StringIO import StringIO

# 定义一个数据集
in_data = StringIO("30kg,inr2000,31.11,56.33,1\
n52kg,inr8000.35,12,16.7,2")

# 1.使用lambda函数定义两个数据预处理函数
strip_func_1 = lambda x : float(x.rstrip("kg"))
strip_func_2 = lambda x : float(x.lstrip("inr"))

# 2.创建一个函数的字典
convert_funcs = {0:strip_func_1,1:strip_func_2}

# 3.将这个函数的字典传递给genfromtxt
data = np.genfromtxt(in_data,delimiter=",", converters=convert_funcs)

# 4.使用lambda函数来处理转换过程
in_data = StringIO("10,20,30\n56,,90\n33,46,89")
mss_func = lambda x : float(x.strip() or -999)
data = np.genfromtxt(in_data,delimiter=",", converters={1:mss_func})

第1步中,我们定义了两个lambda函数,一个将列1中的字符串“kg”从右面清除,另一个将列2中的字符串“inr”从左面清除。

第2步中,我们继续定义一个字典,它的键就是将被函数应用的列名,值就是函数。这个字典被作为参数converters传递给genfromtxt

现在印输出结果如下。

>>> data
array([[ 3.00000000e+01, 2.00000000e+03, 3.11100000e+01,
           5.63300000e+01, 1.00000000e+00],
        [ 5.20000000e+01, 8.00035000e+03, 1.20000000e+01,
           1.67000000e+01, 2.00000000e+00]])

请注意Nan值不见了,我们获取到了输入数据里的真实值。

converters还能用lambda函数来处理输入中丢失的记录。

in_data = StringIO("10,20,30\n56,,90\n33,46,89")
mss_func = lambda x : float(x.strip() or -999)
data = np.genfromtxt(in_data,delimiter=",", converters={1:mss_func})

lambda函数返回−999来替代丢失的数据。在我们的输入里,第2列第2行是一个空值,因而会被替换为−999,最终的输出如下所示。

>>> data
array([[ 10., 20., 30.],
        [ 56., -999., 90.],
        [ 33., 46., 89.]])

访问以下SciPy文档的链接,你能了解到更多的细节:

http://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.htmlhttp://docs.scipy.org/doc/numpy/reference/generated/numpy.genfromtxt.html

我们先讨论列表排序,然后扩展到对其他可迭代对象的排序。

排序有两种方法,第1种是使用列表里内置的sort函数。第2种是使用sorted函数。我们通过示例来进行说明。

我们来看看如何使用sortsorted函数。

# 先看一小段代码,对给定的列表进行排序
a = [8, 0, 3, 4, 5, 2, 9, 6, 7, 1]
b = [8, 0, 3, 4, 5, 2, 9, 6, 7, 1]

print a
a.sort()
print a

print b
b_s = sorted(b)
print b_s

我们声明了两个列表ab,它们的元素完全相同,打印输出列表a来进行检验。

[8, 0, 3, 4, 5, 2, 9, 6, 7, 1]

我们使用sort函数来处理列表数据类型,用a.sort()来执行位置排序,下面的print语句展示了被排序之后的列表。

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

现在,我们来试试sorted函数,这个函数对列表进行排序,返回一个新的排序后的列表。我们通过sorted(b)来调用,排序后的输出存在b_s中,print语句输出如下的结果。

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

sort函数只对列表数据类型有效,默认排序是按照升序进行的,可以通过reverse参数来控制sort函数的排序方式,默认情况下,reverse参数被设置为False

>>> a = [8, 0, 3, 4, 5, 2, 9, 6, 7, 1]
>>> print a
[8, 0, 3, 4, 5, 2, 9, 6, 7, 1]
>>> a.sort(reverse=True)
>>> print a
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>>

现在是降序排序。

其他可迭代对象只能采用sorted函数,我们看一个元组的示例。

>>> a = (8, 0, 3, 4, 5, 2, 9, 6, 7, 1)
>>> sorted(a)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

到目前为止,我们的示例都是采用元素对列表或其他序列进行排序,现在我们来试试对它们采用键排序。在前面的那些示例中,元素即是键。而在真实场景中,记录的复杂度要高得多,一条记录包含了多个列,我们有时需要对其中一个或多个列进行排序。我们通过对一个元组的列表进行排序来阐述,并将之推广到其他的序列类型。

本示例中,一个单独的元组表示一个人的个人记录,包括名字、ID、年龄等。我们来编写一段对不同的域进行排序的代码。

我们使用列表和元组来编写一个记录类的结构,并使用这些数据演示如何采用键进行排序。

# 1.首先创建一个元组组成的列表用来测试排序
employee_records = [ ('joe',1,53),('beck',2,26), \
                         ('ele',6,32),('neo',3,45), \
                         ('christ',5,33),('trinity',4,29), \
                         ]

# 2.使用雇员名字进行排序
print sorted(employee_records,key=lambda emp : emp[0])
"""
输出结果如下。
[('beck', 2, 26), ('christ', 5, 33), ('ele', 6, 32), ('joe', 1, 53),\
('neo', 3, 45), ('trinity', 4, 29)]
"""
# 3. 使用雇员ID进行排序
print sorted(employee_records,key=lambda emp : emp[1])
"""
输出结果如下。
[('joe', 1, 53), ('beck', 2, 26), ('neo', 3, 45), ('trinity', 4, 29),\
('christ', 5, 33), ('ele', 6, 32)]
"""
# 4. 使用雇员年龄进行排序
print sorted(employee_records,key=lambda emp : emp[2])
"""

输出结果如下。

[('beck', 2, 26), ('trinity', 4, 29), ('ele', 6, 32), ('christ', 5,\
33), ('neo', 3, 45), ('joe', 1, 53)]
"""

在我们的示例中,每条记录有3个域:姓名、ID和年龄。我们使用lambda函数来将我们需要排序的键进行传递。在第2步中,我们将姓名作为键来进行排序。类似地,在第2步和第3步中,都分别采用了ID和年龄作为键,这些不同步骤里的不同输出结果显示了我们想要的排序结果。

由于键排序十分重要,Python提供了快捷的函数来访问键,而不用自己写lambda函数。operator模块中提供了itemgetterattrgettermethodcaller等几个函数。前面排序示例我们可以使用itemgetter来重写,代码如下。

from operator import itemgetter
employee_records = [ ('joe',1,53),('beck',2,26), \
                         ('ele',6,32),('neo',3,45), \
                         ('christ',5,33),('trinity',4,29), \
                         ]
print sorted(employee_records,key=itemgetter(0))
"""
[('beck', 2, 26), ('christ', 5, 33), ('ele', 6, 32), ('joe', 1, 53),\
('neo', 3, 45), ('trinity', 4, 29)]
"""
print sorted(employee_records,key=itemgetter(1))
"""
[('joe', 1, 53), ('beck', 2, 26), ('neo', 3, 45), ('trinity', 4, 29),\
('christ', 5, 33), ('ele', 6, 32)]
"""
print sorted(employee_records,key=itemgetter(2))
"""
[('beck', 2, 26), ('trinity', 4, 29), ('ele', 6, 32), ('christ', 5,\
33), ('neo', 3, 45), ('joe', 1, 53)]
"""

请注意我们不再使用lambda函数,而是采用itemgetter来指定我们用来排序的键。如果需要多级排序,itemgetter可以接收多个用来排序的域。例如,我们先对名字,再对年龄进行排序,那代码如下。

>>> sorted(employee_records,key=itemgetter(0,1))
[('beck', 2, 26), ('christ', 5, 33), ('ele', 6, 32), ('joe', 1, 53),
('neo', 3, 45), ('trinity', 4, 29)]

如果可迭代对象里的元素是类对象,则可以用attrgettermethodcaller轻松搞定。请看如下示例。

# 将雇员记录封装为类对象
class employee(object):
     def __init__(self,name,id,age):
          self.name = name
          self.id = id
          self.age = age
     def pretty_print(self):
          print self.name,self.id,self.age

# 将这些类对象填入列表里
employee_records = []
emp1 = employee('joe',1,53)
emp2 = employee('beck',2,26)
emp3 = employee('ele',6,32)

employee_records.append(emp1)
employee_records.append(emp2)
employee_records.append(emp3)

# 打印输出记录
for emp in employee_records:
     emp.pretty_print()

from operator import attrgetter
employee_records_sorted = sorted(employee_
records,key=attrgetter('age'))
# 打印输出排序后的记录
for emp in employee_records_sorted:
     emp.pretty_print()

构造器使用nameageID等3个变量对类进行初始化,类还拥有一个pretty_print方法来输出类对象的各个值。

接着,把这些类对象填入一个列表。

employee_records = []
emp1 = employee('joe',1,53)
emp2 = employee('beck',2,26)
emp3 = employee('ele',6,32)
employee_records.append(emp1)
employee_records.append(emp2)
employee_records.append(emp3)

现在,我们有一个雇员对象的列表,每个对象中有3个变量:nameIDage。我们将列表打印输出来观察其顺序。

joe 1 53
beck 2 26
ele 6 32

如你所见,你的输入顺序被保留下来了。现在,我们使用attrgetter根据age域来对列表进行排序。

employee_records_sorted = sorted(employee_
records,key=attrgetter('age'))

打印输出排序后的列表,结果如下。

beck 2 26
ele 6 32
joe 1 53

记录已经被按照年龄进行了排序。

如果想用类里的某个方法来决定排序方式,我们得使用methodcaller。我们设计一个演示场景:添加一个随机方法,将年龄除以ID。

class employee(object):
     def __init__(self,name,id,age):
          self.name = name
          self.id = id
          self.age = age

     def pretty_print(self):
          print self.name,self.id,self.age

     def random_method(self):
          return self.age / self.id

# 填充数据
employee_records = []
emp1 = employee('joe',1,53)
emp2 = employee('beck',2,26)
emp3 = employee('ele',6,32)

employee_records.append(emp1)
employee_records.append(emp2)
employee_records.append(emp3)

from operator import methodcaller
employee_records_sorted = sorted(employee_records,key=methodcaller('ra\
ndom_method'))
for emp in employee_records_sorted:
     emp.pretty_print()

现在我们调用这个方法来进行排序。

sorted(employee_records,key=methodcaller('random_method'))

打印输出排序后的列表,结果如下。

ele 6 32
beck 2 26
joe 1 53

受一些函数式编程语言如Haskell等启发,itertools包含了一些处理可迭代对象的函数,它们能高效地使用内存,运行速度很快。

itertools包含了大量的函数,我们对其中的一部分进行演示来了解它们。本节最后部分提供了这些函数的全列表。

我们通过一些Python代码示例来演示itertools的使用方法。

# 加载库文件
from itertools import chain,compress,combinations,count,izip,islice

# 1.链的示例,不同的可迭代对象能被组合在一起
a = [1,2,3]
b = ['a','b','c']
print list(chain(a,b)) # prints [1, 2, 3, 'a', 'b', 'c']
# 2.压缩示例,一个数据筛选器,基于第2个对象对第1个对象中的数据进行筛选
a = [1,2,3]
b = [1,0,1]
print list(compress(a,b)) # prints [1, 3]

# 3.对给定的列表,返回长度为n的子序列
a = [1,2,3,4]
print list(combinations(a,2)) # prints [(1, 2), (1, 3), (1, 4), (2,\
3), (2, 4), (3, 4)]

# 4.给定一个起始整数,产生连续的整数
a = range(5)
b = izip(count(1),a)
for element in b:
     print element

# 5.从一个可迭代对象中根据索引参数筛选生成另一个可迭代对象,假定我们所需的是一个迭代器,它从输入的迭代器中返回间隔的各个元素
a = range(100)
b = islice(a,0,100,2)
print list(b)

第1步很直观,用chain()函数将两个可迭代对象组合在一起,值得注意的是,chain()函数并不会被真正实现直到被调用。请看下面的命令。

>>> chain(a,b)
<itertools.chain object at 0x060DD0D0>

调用chain(a,b)会返回一个链对象,当我们执行下面的命令时,真正的输出结果如下。

>>> list(chain(a,b))
[1, 2, 3, 'a', 'b', 'c']

第2步中描述了compress函数,在本例中,a里的元素是否被选中,依赖于b里对应位置的元素值。如你所见,b里的第2个值为0,因此a里的第2个值没有被选中。

第3步是简单的数学组合,输入列表a,产生了所有两个元素的组合。

第4步讲解了counter对象,给定一个起始值,它可以产生无限的连续数字序列。运行上面的代码,结果如下。

(1, 0)
(2, 1)
(3, 2)
(4, 3)
(5, 4)

这里我们还使用了izip函数(zipizip函数在前面章节已经介绍过了),输出结果是一个元组,第1个元素由counter提供,第2个则由输入的列表a提供。

第5步解释了islice操作的细节,islice和前面章节介绍过的slice(切片)相同,但是它使用内存更高效,在没有调用之前不会被实现。

关于itertools的函数全列表,请参见:https://docs.python.org/2/library/itertools.html


在这一章里,我们将探讨以下主题。

本章我们将介绍Python环境,本书的大部分内容都会涉及它。我们先从NumPy开始,这是一个用来高效地处理数组和矩阵的Python库,它也是本书会用到的大多数库的基础。然后我们会介绍名为matplotlib的绘图库。最后介绍机器学习库scikit-learn。

Python中,NumPy提供了一条高效处理超大数组的途径。大多数Python科学计算库中都在内部使用NumPy处理数组和矩阵操作。在本书中,NumPy被广泛应用,我们在本节介绍它。

我们先写一系列语句来操作数组和矩阵,学习如何使用NumPy。目的是让您习惯使用NumPy数组,它也是本书大多数内容的基础。

我们先创建一些简单的矩阵和数组。

# Recipe_1a.py
# 导入NumPy库,命名为np
import numpy as np
# 创建数组
a_list = [1,2,3]
an_array = np.array(a_list)
# 指定数据类型
an_array = np.array(a_list,dtype=float)

# 创建矩阵
a_listoflist = [[1,2,3],[5,6,7],[8,9,10]]
a_matrix = np.matrix(a_listoflist,dtype=float)

现在我们可以写一个简单方便的函数来处理NumPy对象。

# Recipe_1b.py
# 一个用来检测给定的NumPy对象的小函数
def display_shape(a):
     print
     print a
     print
     print "Nuber of elements in a = %d"%(a.size)
     print "Number of dimensions in a = %d"%(a.ndim)
     print "Rows and Columns in a ",a.shape
     print

display_shape(a_matrix)

换种方式来创建数组。

# Recipe_1c.py
# 换一种方式创建数组
# 1. 使用np.arange来创建NumPy数组
created_array = np.arange(1,10,dtype=float)
display_shape(created_array)

# 2. 使用np.linspace来创建NumPy数组
created_array = np.linspace(1,10)
display_shape(created_array)
# 3. 使用np.logspace来创建NumPy数组
created_array = np.logspace(1,10,base=10.0)
display_shape(created_array) 
# 4.在创建数组时指定arange的步长,这是它与np.linspace不同的地方
created_array = np.arange(1,10,2,dtype=int)
display_shape(created_array)

现在我们来看如何创建一些特殊的矩阵。

# Recipe_1d.py
# 创建一个所有元素都为1的矩阵
ones_matrix = np.ones((3,3))
display_shape(ones_matrix)
#创建一个所有元素都为0的矩阵
zeros_matrix = np.zeros((3,3))
display_shape(zeros_matrix)

# 鉴别矩阵
# k参数控制1的索引
# if k =0, (0,0),(1,1,),(2,2) cell values
# 被设置为1,在一个3×3的矩阵中
identity_matrix = np.eye(N=3,M=3,k=0)
display_shape(identity_matrix)
identity_matrix = np.eye(N=3,k=1)
display_shape(identity_matrix)

了解了创建数组和矩阵的知识,我们再看一些整形操作。

# Recipe_1e.py
# 数组的整形
a_matrix = np.arange(9).reshape(3,3)
display_shape(a_matrix)
.
.
.
display_shape(back_array)

接着来看一些矩阵的操作。

# Recipe_1f.py
# 矩阵的操作
a_matrix = np.arange(9).reshape(3,3)
b_matrix = np.arange(9).reshape(3,3)
.
.
.
print "f_matrix, row sum",f_matrix.sum(axis=1)

最后,我们来看一些转置、复制和网格操作。

#Recipe_1g.py
# 转置元素
display_shape(f_matrix[::-1])
.
.
.
zz = zz.flatten()

我们再看看NumPy库里的一些随机数生成例程。

#Recipe_1h.py
# 随机数
general_random_numbers = np.random.randint(1,100, size=10)
print general_random_numbers
.
.
.
uniform_rnd_numbers = np.random.normal(loc=0.2,scale=0.2,size=(3,3))

我们先从导入NumPy库开始。

# 导入NumPy库,命名为np
import numpy as np

来看看用NumPy生成数组的多种方式。

# 数组
a_list = [1,2,3]
an_array = np.array(a_list)
# 指定数据类型
an_array = np.array(a_list,dtype=float)

一个数组可以基于列表创建,在前面的示例中,我们声明了一个具有3个元素的列表,然后用np.array()将这个列表转为了NumPy一维数组。如上面代码的最后一行所示,我们也可以指定数据的类型。

了解完数组,再来看看矩阵。

# 矩阵
a_listoflist = [[1,2,3],[5,6,7],[8,9,10]]
a_matrix = np.matrix(a_listoflist,dtype=float)

我们从listoflist里创建了一个矩阵,同样地,我们指定了数据的类型。

在继续之前,我们得先定义display_shape函数,这个函数我们接下来会经常用到。

def display_shape(a):
     print
     print a
     print
     print "Nuber of elements in a = %d"%(a.size)
     print "Number of dimensions in a = %d"%(a.ndim)
     print "Rows and Columns in a ",a.shape
     print

所有的NumPy对象都有以下3个属性。

除了打印输出原始的元素,这个函数打印输出上述的3个属性,我们调用这个函数来处理我们之前创建的矩阵。

display_shape(a_matrix)

如图2-1所示,这个矩阵有9个元素,两个维度,最后,我们还能在shape参数里看到维数和每一维的元素个数。在本例中,矩阵有3行3列。

图2-1 

再看另一种创建数组的方法。

created_array = np.arange(1,10,dtype=float)
display_shape(created_array)

NumPy的arange函数返回指定间隔的均匀隔开的数值,本例中,我们所需的是从1到10均匀分布的数值。访问以下URL可以获取更多关于arange的资料。

http://docs.scipy.org/doc/numpy/reference/generated/numpy.arange.html

# 创建数组的另一种替代办法
created_array = np.linspace(1,10)
display_shape(created_array)

NumPy的linspace和arange类似,差别在于我们需要给出样例的数量值。使用linspace,我们知道在给定的范围里有多少个元素。默认情况下,它返回50个元素。而使用arange,我们还要指定步长。

created_array = np.logspace(1,10,base=10.0)
display_shape(created_array)

NumPy给你提供了一些创建特殊数组的函数。

ones_matrix = np.ones((3,3))
display_shape(ones_matrix)

# 创建一个所有元素均为0的矩阵
zeros_matrix = np.zeros((3,3))
display_shape(zeros_matrix)

ones()zero()函数分别用来创建全由1和0填充的矩阵,如图2-2所示。

单位矩阵的创建方式如下:

identity_matrix = np.eye(N=3,M=3,k=0)
display_shape(identity_matrix)

图2-2

参数k控制了1的起始索引值,输出结果如图2-3所示。

identity_matrix = np.eye(N=3,k=1)
display_shape(identity_matrix)

图2-3 

reshape函数可以控制数组的形态。

# 数组转换形态
a_matrix = np.arange(9).reshape(3,3)
display_shape(a_matrix)

通过传递参数−1,我们可以将数组转化为我们所需要的维数,输入结果如图2-4所示。

# 参数-1可以将矩阵转为所需的维数
back_to_array = a_matrix.reshape(-1)
display_shape(back_to_array)

图2-4

ravelflatten函数可以用来将矩阵转化为一维的数组,如图2-5所示。

a_matrix = np.arange(9).reshape(3,3)
back_array = np.ravel(a_matrix)
display_shape(back_array)

a_matrix = np.arange(9).reshape(3,3)
back_array = a_matrix.flatten()
display_shape(back_array)

图2-5 

我们再看一些矩阵操作,如两矩阵相加。

c_matrix = a_matrix + b_matrix

再看看矩阵对应元素相乘。

d_matrix = a_matrix * b_matrix

下面是矩阵的乘法操作。

e_matrix = np.dot(a_matrix,b_matrix)

最后,是矩阵的转置。

f_matrix = e_matrix.T

minmax函数可以用来找出矩阵中最小和最大的元素,sum函数则用来对矩阵的行或列进行求和,如图2-6所示。

print
print "f_matrix,minimum = %d"%(f_matrix.min())
print "f_matrix,maximum = %d"%(f_matrix.max())
print "f_matrix, col sum",f_matrix.sum(axis=0)
print "f_matrix, row sum",f_matrix.sum(axis=1)

图2-6 

采用下面的方法将矩阵的元素进行求逆运算。

# 对元素进行逆运算
display_shape(f_matrix[::-1])

copy函数可以复制一个矩阵,方法如下。

# Python中所有元素都能用来引用
# 如果需要复制,可以使用copy命令
f_copy = f_matrix.copy()

最后再看一下mgrid函数。

# Grid命令
xx,yy,zz = np.mgrid[0:3,0:3,0:3]
xx = xx.flatten()
yy = yy.flatten()
zz = zz.flatten()

mgrid函数用来查找m维矩阵中的坐标,在前面的示例中,矩阵是三维的,在每一维中,数值的范围从0到3。我们将xx、yy和zz打印输出出来以帮助理解,如图2-7所示。

我们来看每个数组的第1个元素,在本例的三维矩阵空间中,[0,0,0]是第1个坐标,所有3个数组中的第2个元素[0,0,1]是矩阵空间里的另一个点。据此,使用mgrid函数, 我们能占满三维坐标系统里的所有点。

图2-7 

NumPy提供了一个random模块给我们,可以用来定义产生随机数的规则。我们来看产生随机数的示例。

# 随机数
general_random_numbers = np.random.randint(1,100, size=10)
print general_random_numbers

使用random模块中的randint函数,我们可以生成随机整数。我们需要传递start、end和size等3个参数。本例中,我们的起始值为1,结束值为100,步长为10。我们需要介于1到100的10个随机整数,我们得到的返回结果如图2-8所示。

图2-8

我们也可以产生其他分布的随机数,来看看使用更好正态分布包产生10个随机数的示例。

uniform_rnd_numbers = np.random.normal(loc=0.2,scale=0.2,size=10)
print uniform_rnd_numbers

我们使用normal包的normal函数来生成随机数。normal包里的locscale参数分别指定了均值和标准差两个参数,最后,size参数决定了样本的数量。

通过传递一个行或列的元组,我们也可以产生一个随机的矩阵,示例如下。

uniform_rnd_numbers = np.random.normal(loc=0.2,scale=0.2,size=(3,3))

上面的示例产生了3×3的矩阵,输出结果如图2-9所示。

图2-9 

下面的链接提供了一些优秀的NumPy文档,请参见:http://www.numpy.org/

《Analyzing Data -Explore & Wrangle》的第3章中“使用matplotlib进行绘画的诀窍”部分有相关介绍。

《Analyzing Data -Explore & Wrangle》的第3章中“使用Scikit Learn进行机器学习的诀窍”部分有相关介绍。

Matplotlib是Python提供的一个二维绘图库,所有类型的平面图,包括直方图、散点图、折线图、点图、热图以及其他各种类型,都能由Python制作出来。在本书中,我们将采用matplotlib的pyplot接口实现所有的可视化需求。

本节中,我们会介绍使用pyplot的基础绘图框架,并且用它来完成本书中的所有可视化需求。

本书采用的是matplotlib1.3.1,你可以在命令行下调用_version_属性来检查版本,如图2-10所示。

图2-10 

我们通过示例来学习如何用matplotlib绘制一些简单的图形。

#Recipe_2a.py
import numpy as np
import matplotlib.pyplot as plt
def simple_line_plot(x,y,figure_no):
     plt.figure(figure_no)
     plt.plot(x,y)
     plt.xlabel('x values')
     plt.ylabel('y values')
     plt.title('Simple Line')

def simple_dots(x,y,figure_no):
     plt.figure(figure_no)
     plt.plot(x,y,'or')
     plt.xlabel('x values')
     plt.ylabel('y values')
     plt.title('Simple Dots')

def simple_scatter(x,y,figure_no):
     plt.figure(figure_no)
     plt.scatter(x,y)
     plt.xlabel('x values')
     plt.ylabel('y values')
     plt.title('Simple scatter')

def scatter_with_color(x,y,labels,figure_no):
     plt.figure(figure_no)
     plt.scatter(x,y,c=labels)
     plt.xlabel('x values')
     plt.ylabel('y values')
     plt.title('Scatter with color')

if __name__ == "__main__":
     plt.close('all')
     # x、y样例数据生成折线图和简单的点图
     x = np.arange(1,100,dtype=float)
     y = np.array([np.power(xx,2) for xx in x])

     figure_no=1
     simple_line_plot(x,y,figure_no)
     figure_no+=1
     simple_dots(x,y,figure_no)

     # x、y样例数据生成散点图
     x = np.random.uniform(size=100)
     y = np.random.uniform(size=100)

     figure_no+=1
     simple_scatter(x,y,figure_no)
     figure_no+=1
     label = np.random.randint(2,size=100)
     scatter_with_color(x,y,label,figure_no)
     plt.show()

接下来我们要探讨一些进阶的主题,包括生成热图以及给x和y轴添加标签。

# Recipe_2b.py
import numpy as np
import matplotlib.pyplot as plt
def x_y_axis_labeling(x,y,x_labels,y_labels,figure_no):
     plt.figure(figure_no)
     plt.plot(x,y,'+r')
     plt.margins(0.2)
     plt.xticks(x,x_labels,rotation='vertical') 
     plt.yticks(y,y_labels,)

def plot_heat_map(x,figure_no):
     plt.figure(figure_no)
     plt.pcolor(x)
     plt.colorbar()
if __name__ == "__main__":
     plt.close('all')
     x = np.array(range(1,6))
     y = np.array(range(100,600,100))
     x_label = ['element 1','element 2','element 3','element 
4','element 5']
     y_label = ['weight1','weight2','weight3','weight4','weight5']

     x_y_axis_labeling(x,y,x_label,y_label,1)

     x = np.random.normal(loc=0.5,scale=0.2,size=(10,10))
     plot_heat_map(x,2)

     plt.show()

我们先从导入需要的模块开始,使用pyplot前,必须先导入NumPy库。

import numpy as np
import matplotlib.pyplot as plt

我们从下面代码的main函数开始,之前运行的程序可能已经绘制了一些图形,先把它们全部关闭是一个好习惯。同时,我们的程序可能也需要更多的绘图资源。

plt.close('all')

接着,为了演示如何使用pyplot,我们得先用NumPy生成一些数据。

# x、y样例数据生成折线图和简单的点图   
x = np.arange(1,100,dtype=float)
y = np.array([np.power(xx,2) for xx in x])

我们在xy变量中生成了100个元素,yx变量的平方数,然后绘制一条简单的折线图。

figure_no=1
simple_line_plot(x,y,figure_no)

当程序中有多个图形的时候,最好用figure_no变量给每个图形设置一个编号。我们接着再看simple_line_plot函数。

def simple_line_plot(x,y,figure_no):
     plt.figure(figure_no)
     plt.plot(x,y)
     plt.xlabel('x values')
     plt.ylabel('y values')
     plt.title('Simple Line')

如你所见,我们开始用pyplot的figure函数编号标示图形,我们在main函数中把figure_no变量进行传递。然后,给定xy的值就可以轻松地调用plot函数。分别通过xlableylabel函数给x轴和y轴命名,可以让图形更直观易懂。最后,我们还可以给图形命名,这意味着我们的第1个折线图快要绘制完成了。但是图形不会自动显示,必须通过调用show()函数才能显示。在本例中,我们调用show()函数来将所有的图形一起显示,得到的图看起来应该是图2-11这样。

这里绘出的图形里,x轴是x的值,y轴是x的平方值。

我们绘制了一张简单的折线图,我们可以看到优美的弧线,因为y的值是x值的平方。

再看下一个图形。

figure_no+=1
simple_dots(x,y,figure_no)

图2-11 

我们增加了图形的编号并调用了simple_dots函数,希望将x和y的值用点而不是线的形式绘制出来,来看看simple_dots函数。

def simple_dots(x,y,figure_no):
     plt.figure(figure_no)
     plt.plot(x,y,'or')
     plt.xlabel('x values')
     plt.ylabel('y values')
     plt.title('Simple Dots')

除了下面这行,每行的代码和之前的函数都是相同的。

plt.plot(x,y,'or')

这个“or”参数说明我们需要的是点(o),这个点的颜色是红色(r)。上面的命令绘制的图形如图2-12所示。

再看下一个图形。

我们这次要绘制的是散点图,我们用NumPy生成一些数据。

# x、y样例数据生成散点图
x = np.random.uniform(size=100)
y = np.random.uniform(size=100)

我们采用均匀分布生成了100个样例数据点,现在我们调用simple_scatter函数来生成散点图。

图2-12 

figure_no+=1
simple_scatter(x,y,figure_no)

simple_scatter函数里的每一行都和前面的绘图方法里一样,除了以下这行。

plt.scatter(x,y)

我们调用了scatter函数而不是pyplot中的plot函数,绘制出来的图形如图2-13所示。

图2-13 

我们来看最终想要的散点图:每个点根据它所属的类标签被标上了颜色。

figure_no+=1
label = np.random.randint(2,size=100)
scatter_with_color(x,y,label,figure_no)

为了保持图表的可读性,我们继续增加图形的数量,接下来,我们要随机地给点加入一些标签,内容是0或者1。最后再用这些x、y和标签的变量作为参数来调用scatter_with_color函数。

这个函数和之前的scatter函数的区别如下所示。

plt.scatter(x,y,c=labels)

我们将标签的值传给c参数,就是颜色参数。每个标签对应着一个唯一的颜色。本例中,标签为0的点和标签为1的点颜色是不一样的,如图2-14所示。

图2-14 

接下来我们再看热图以及轴标签。

我们仍然从main函数开始。

     plt.close('all')
     x = np.array(range(1,6))
     y = np.array(range(100,600,100))
     x_label = ['element 1','element 2','element 3','element\
4','element 5']
     y_label = ['weight1','weight2','weight3','weight4','weight5']
     x_y_axis_labeling(x,y,x_label,y_label,1)

记得保持良好的习惯,调用close函数把之前的那些图先全部关闭。然后我们生成一些数据:x是一个有5个元素的数组,元素值从1到5;y也是一个5个元素的数组,元素值从100到500。我们定义两个列表x_labely_label作为图的标签。最后,我们调用x_y_axis_labeling函数来演示如何给x和y轴添上标签。

请看下面的函数。

def x_y_axis_labeling(x,y,x_labels,y_labels,figure_no):
     plt.figure(figure_no)
     plt.plot(x,y,'+r')
     plt.margins(0.2)
     plt.xticks(x,x_labels,rotation='vertical')
     plt.yticks(y,y_labels,)

我们将采用pyplot的dot函数来绘制一张简单的点图。这次的示例中,我们不再采用“o”,而是使用“+”来表示点。因此,我们指定的参数是“+r”,“r”代表红色。

在后面的两行里,我们还要指定x和y轴的标签类型。我们使用xticks函数传递x的值和它们的标签。此外,我们还要将文本进行垂直翻转以避免相互遮挡。y轴的处理过程也是完全类型的。请看图2-15。

图2-15 

我们来看如何用pyplot生成热图。

x = np.random.normal(loc=0.5,scale=0.2,size=(10,10))
plot_heat_map(x,2)

绘制热图需要准备一些数据。本例中,我们正态分布产生一个10×10矩阵的数据,设定loc变量为0.5作为均值,设定scale变量为0.2作为标准差,然后将矩阵传给plot_heat_map。第2个参数是图形的编号。

def plot_heat_map(x,figure_no):
     plt.figure(figure_no)
     plt.pcolor(x)
     plt.colorbar()

我们调用pcolor函数来创建热图,第2行里调用的colorbar函数用来控制渐变色的颜色范围,输出如图2-16所示。

图2-16 

要了解更多关于matplotlib的信息,以下地址提供了大量相关文档,请参见:

http://matplotlib.org/faq/usage_faq.html

以下地址是一个pyplot的优秀教程,请参见:

http://matplotlib.org/users/pyplot_tutorial.html

Matplotlib也提供了优秀的3D绘图能力,详情请参见:

http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html

matplotlib里的pylab模块联合使用了pyplot和NumPy命名空间,我们也可以使用pylab来绘制本节中的各种图形。

scikit-learn是Python中的一个全能的机器学习库,我们在本书中会大量使用它。我们使用的版本为0.15.2。你可以在命令行里调用_version_属性来检查版本,如图2-17所示。

图2-17 

本节里,我们会演示一些scikit-learn包的功能,学习它的一些API架构,为后续章节的学习打下基础。

scikit-learn提供了一个内置数据集,我们看看如何访问和使用它。

#Recipe_3a.py
from sklearn.datasets import load_iris,load_boston,make_classification
make_circles, make_moons

# Iris数据集
data = load_iris()
x = data['data']
y = data['target']
y_labels = data['target_names']
x_labels = data['feature_names']

print
print x.shape
print y.shape
print x_labels
print y_labels

# Boston数据集
data = load_boston()
x = data['data']
y = data['target']
x_labels = data['feature_names']
print
print x.shape
print y.shape
print x_labels

# 制作一些分类数据集
x,y = make_classification(n_samples=50,n_features=5, n_classes=2)

print
print x.shape
print y.shape

print x[1,:]
print y[1]

# 一些非线性数据集
x,y = make_circles()
import numpy as np
import matplotlib.pyplot as plt
plt.close('all')
plt.figure(1)
plt.scatter(x[:,0],x[:,1],c=y)

x,y = make_moons()
import numpy as np
import matplotlib.pyplot as plt
plt.figure(2)
plt.scatter(x[:,0],x[:,1],c=y)

plt.show()

我们来看看如何调用scikit-learn里的这些机器学习函数。

#Recipe_3b.py
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
# 数据预处理
x = np.asmatrix([[1,2],[2,4]])
poly = PolynomialFeatures(degree = 2)
poly.fit(x)
x_poly = poly.transform(x)

print "Original x variable shape",x.shape
print x
print
print "Transformed x variables",x_poly.shape
print x_poly

# 另一种写法
x_poly = poly.fit_transform(x)

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris

data = load_iris()
x = data['data']
y = data['target']

estimator = DecisionTreeClassifier()
estimator.fit(x,y)
predicted_y = estimator.predict(x)
predicted_y_prob = estimator.predict_proba(x)
predicted_y_lprob = estimator.predict_log_proba(x)

from sklearn.pipeline import Pipeline

poly = PolynomialFeatures(n=3)
tree_estimator = DecisionTreeClassifier()

steps = [('poly',poly),('tree',tree_estimator)]
estimator = Pipeline(steps=steps)
estimator.fit(x,y)
predicted_y = estimator.predict(x)

为了使用内置的数据集,我们得先加载scikit-learn库,库的模块里包含着各种各样的函数。

from sklearn.datasets import load_iris,load_boston,make_classification

第1个数据集是iris,请参见以下地址来获取更多细节信息。

https://en.wikipedia.org/wiki/Iris_flower_data_set

这是一个由Donald Fisher先生引入的分类问题的经典数据集。

data = load_iris()
x = data['data']
y = data['target']
y_labels = data['target_names']
x_labels = data['feature_names']

我们调用的load_iris函数返回一个字典。使用合适的键,可以从这个字典对象中查询获取到自变器x、因变量y、因变量名、各个特征属性名等信息。

我们将这些信息打印出来看看它们的值,结果如图2-18所示。

print
print x.shape
print y.shape
print x_labels
print y_labels

图2-18 

如你所见,预测器里有150个实例和4种属性,因变量有150个实例,每个预测集合里的记录都有一个类别标签。我们接着打印输出属性名:花瓣、花萼的宽度和长度,以及类别标签。在后续章节里,我们还会多次使用这个数据集。

我们接着要看的是另一个数据集:Boston住房数据集,它属于回归问题。

# Boston数据集
data = load_boston()
x = data['data']
y = data['target']
x_labels = data['feature_names']

这个数据集的加载过程和iris基本一样,从字典的各个键也可以查询到数据的各个组成部分,包括预测器和因变量。我们打印输出这些变量来看一下,如图2-19所示。

图2-19 

如你所见,预测器集合里有506个实例和13种属性,因变量有506个条目。最后,我们也打印输出属性名。

scikit-learn也给我们提供了一些函数来产生随机分类的数据集,并可以指定一些需要的属性。

# 产生一些分类数据集
x,y = make_classification(n_samples=50,n_features=5, n_classes=2)

make_classification函数用来产生分类数据集。本例中,我们指定n_samples参数生成50个实例,n_features参数生成5个属性,n_classes参数生成两个类集合。请看这个函数的输出,如图2-20所示。

print x.shape
print y.shape

print x[1,:]
print y[1]

图2-20 

如你所见,预测器里有150个实例和5种属性,因变量有150个实例,每个预测集合里的记录都有一个类别标签。

我们将预测器集合x里的第2条记录打印出来,你会看到这是一个五维的向量,与5个我们所需的特征相关联。最后,我们把因变量y也打印出来。预测器里的第2条记录的类别标签是1。

scikit-learn也给我们提供了一些函数来产生非线性关系。

# 一些非线性数据集
x,y = make_circles()
import numpy as np
import matplotlib.pyplot as plt
plt.close('all')
plt.figure(1)
plt.scatter(x[:,0],x[:,1],c=y)

你应该已经从前面的章节中了解了pyplot,现在通过它绘制的图来帮助我们理解非线性关系。

如图2-21所示,我们的分类结果产生了两个同心圆。x是两个变量的数据集,变量y是类标签。这两个同心圆说明了预测器里两个变量的关系是非线性的。

图2-21 

scikit-learn里还有一个有趣的函数make_moons也能产生非线性关系。

x,y = make_moons()
import numpy as np
import matplotlib.pyplot as plt
plt.figure(2)
plt.scatter(x[:,0],x[:,1],c=y)

我们看一下它生成的图2-22来理解非线性关系。

图2-22 

新月图形说明了预测器集合x里的属性之间的关系是非线性的。

接下来我们要来讨论scikit-learn的API架构,使用API架构的主要优势在于它十分简洁。所有源于BaseEstimator的数据模型必须严格实现fittransform函数。我们将从一些示例中详细了解。

我们先从scikit-learn的预处理模块开始。

import numpy as np
from sklearn.preprocessing import PolynomialFeatures

我们使用PolynomialFeatures类来演示使用scikit-learn的SDK的方便快捷之处。要了解PolynomialFeatures的更多信息,请参见:

https://en.wikipedia.org/wiki/Polynomial

有时我们需要往预测器变量集合中增加新的变量,以判断模型精度是否提高。我们可以将已有特征的多项式作为新特证,类PolynomialFeatures帮助我们实现这一目标。

# 数据预处理
x = np.asmatrix([[1,2],[2,4]])

首先,我们要创建一个数据集。本例中,数据集有两个实例和两个属性。

poly = PolynomialFeatures(degree = 2)

然后,我们将采用所需的多项式阶数来实例化PolynomialFeatures类。本例中,阶数为2。

poly.fit(x)
x_poly = poly.transform(x)

接着要介绍的是fit和transform函数。fit函数用来在数据转换时做必需的计算。本例中它是多余的,不过在本节后面部分我们会遇到一些如何使用它的示例。

transform函数接收输入数据,并基于fit函数的计算结果将输入数据进行转换。

# 换一种方式
x_poly = poly.fit_transform(x)

本例还有另外一种方式,在一个操作中调用fit和transform。我们来看看变量x初始和转换之后的数值和形态,如图2-23所示。

图2-23

scikit-learn中所有实现机器学习方法的类都来自BaseEstimator,请参见:http://scikit-learn.org/stable/modules/ generated/sklearn.base.BaseEstimator.html

BaseEstimator要求用以实现的类提供fit和transform两种方法,这样才能保持API简洁清晰。

我们再看另一个示例,从tree模块中引入DecisionTreeClassifier类,它实现了决策树算法。

from sklearn.tree import DecisionTreeClassifier

我们把这个类放到实践操作中。

from sklearn.datasets import load_iris

data = load_iris()
x = data['data']
y = data['target']

estimator = DecisionTreeClassifier()
estimator.fit(x,y)
predicted_y = estimator.predict(x)
predicted_y_prob = estimator.predict_proba(x)
predicted_y_lprob = estimator.predict_log_proba(x)

我们使用iris数据集来看来怎样使用树算法。先把iris数据集加载到变量x和y中,然后把DecisionTreeClassifier实例化,接着调用fit函数,传递预测器x和因变量y来建立模型。这样就建立了一个树模型,我们现在可以用它来进行预测。我们用predict函数对给定的输入预测其类标签。如你所见,和在PolynomialFeatures里一样,我们也使用了相同的fit和predict方法。还有另外两个方法:predict_proba和predict_log_proba。前者给出预测的概率,后者给出预测概率的对数。

现在来看另一个有趣的功能pipe lining,使用这个功能,不同的机器学习方法可以被链接在一起。

from sklearn.pipeline import Pipeline

poly = PolynomialFeatures(3)
tree_estimator = DecisionTreeClassifier()

我们从实例化PolynomialFeatures和DecisionTreeClassifier数据处理规范开始。

steps = [('poly',poly),('tree',tree_estimator)]

我们先定义一个元组列表来标示我们的链接顺序。运行多项式特征生成器之后,再执行决策树。

estimator = Pipeline(steps=steps)
estimator.fit(x,y)
predicted_y = estimator.predict(x)

我们通过steps变量声明的列表将Pipeline对象实例化。现在就能像以往那样调用fit和predict方法了。

我们可以调用named_steps属性来查看模型在pipeline里的不用阶段的情况,如图2-24所示。

图2-24 

scikit-learn里还有更多的数据集生成函数,请参见:

http://scikit-learn.org/stable/datasets/

在使用make_circle和make_moons函数的时候,我们曾经提到可以给数据集加入许多想要的属性,如果包含了不正确的类标签,数据可能会受到轻微的损坏。下面的链接列出了许多描述这些细微差别的选项,请参见:

http://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_circles.html

http://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_moons.html

第2章“Python环境”中“绘图技巧”的相关内容。


相关图书

深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
动手学自然语言处理
动手学自然语言处理
Python高性能编程(第2版)
Python高性能编程(第2版)
图像处理与计算机视觉实践——基于OpenCV和Python
图像处理与计算机视觉实践——基于OpenCV和Python
Web应用安全
Web应用安全
Python数据科学实战
Python数据科学实战

相关文章

相关课程