หน้าหลัก 《从零开始学Python数据分析与挖掘》

《从零开始学Python数据分析与挖掘》

0 / 0
คุณชอบหนังสือเล่มนี้มากแค่ไหน
คุณภาพของไฟล์เป็นอย่างไรบ้าง
ดาวน์โหลดหนังสือเพื่อประเมินคุณภาพของไฟล์
คุณภาพของไฟล์ที่คุณดาวน์โหลดมาเป็นอย่างไรบ้าง

本书以Python 3版本作为数据分析与挖掘实战的应用工具,从Pyhton的基础语法开始,陆续介绍有关数值计算的Numpy、数据处理的Pandas、数据可视化的Matplotlib和数据挖掘的Sklearn等内容。全书共涵盖15种可视化图形以及10个常用的数据挖掘算法和实战项目,通过本书的学习,读者可以掌握数据分析与挖掘的理论知识和实战技能。


本书适于统计学、数学、经济学、金融学、管理学以及相关理工科专业的本科生、研究生使用,也能够提高从事数据咨询、研究或分析等人士的专业水平和技能。

ปี:
2018
ฉบับพิมพ์ครั้งที่:
1
สำนักพิมพ์:
清华大学出版社
ภาษา:
thai
จำนวนหน้า:
476
ISBN 13:
9787302509875
ไฟล์:
PDF, 68.01 MB

คุณอาจสนใจ Powered by Rec2Me

 

คำที่ถูกค้นหาบ่อยที่สุด

 
0 comments
 

To post a review, please sign in or sign up
คุณเขียนบทวิจารณ์หนังสือ และแบ่งปันประสบการณ์ของคุณได้ ผู้อ่านคนอื่น ๆ มักจะสนใจความคิดเห็นของคุณเกี่ยวกับหนังสือที่คุณอ่าน ไม่ว่าคุณจะชอบหนังสือเล่มนี้หรือไม่ก็ตาม หากคุณให้ความคิดเห็นที่ตรงไปตรงมาและมีรายละเอียดแล้ว ผู้คนจะได้พบกับหนังสือเล่มใหม่ที่เหมาะสมกับพวกเขา
1

Het zesde bedrijf

Tahun:
2007
Bahasa:
dutch
File:
PDF, 877 KB
0 / 0
2

Harry Potter En De Fakkel Met De Groene Vlammen

File:
PDF, 2,12 MB
0 / 0
本书封面贴有清华大学出版社防伪标签,无标签者不得销售。
版权所有,侵权必究。侵权举报电话:010-62782989 13701121933
图书在版编目(CIP)数据
从零开始学Python数据分析与挖掘/刘顺祥著.—北京:清华大学出版社,2018
ISBN 978-7-302-50987-5
Ⅰ.①从… Ⅱ.①刘… Ⅲ.①软件工具-程序设计 Ⅳ.①TP311.561
中国版本图书馆CIP数据核字(2018)第192204号
责任编辑:王金柱
封面设计:王 翔
责任校对:闫秀华
责任印制:刘海龙
出版发行:清华大学出版社
网

址:http://www.tup.com.cn,http://www.wqbook.com

地

址:北京清华大学学研大厦A座

邮

编:100084

社 总 机:010-62770175
邮

购:010-62786544

投稿与读者服务:010-62776969, c-service@tup.tsinghua.edu.cn
质量反馈:010-62772015,zhiliang@tup.tsinghua.edu.cn
印 装 者:三河市君旺印务有限公司
经

销:全国新华书店

开

本:190mm×260mm

印

张:23.5

字

数:602千字

版

次:2018年10月第1版

印

次:2018年10月第1次印刷

定

价:79.00元

产品编号:079655-01

作者简介
刘顺祥
统计学硕士,“数据分析1480”微信公众号运营者。曾就职于大数据咨询公司,为联想、亨
氏、美丽田园、网鱼网咖等企业项目提供服务;曾在唯品会大数据部担任数据分析师一职,负
责电商支付环节的数据分析业务。

内容简介
本书以Python 3版本作为数据分析与挖掘实战的应用工具,从Pyhton的基础语法开始,陆
续介绍有关数值计算的Numpy、数据处理的Pandas、数据可视化的Matplotlib和数据挖掘的
Sklearn等内容。全书共涵盖15种可视化图形以及10个常用的数据挖掘算法和实战项目,通过
本书的学习,读者可以掌握数据分析与挖掘的理论知识和实战技能。
本书适于统计学、数学、经济学、金融学、管理学以及相关理工科专业的本科生、研究生
使用,也能够提高从事数据咨询、研究或分析等人士的专业水平和技能。

前言
为什么写这本书
随着大数据时代的演进,越来越多的企业在搜集数据的同时,也开始关注并重视数据分
析与挖掘的价值,因为他们正尝到这项技术所带来的甜头。例如,通过该技术可以帮助企业很
好地认识其用户的画像特征,为用户提供个性化的优质服务,进而使用户的忠诚度不断提升;
通过该技术提前识别出不利于企业健康发展的“毒瘤”用户(如黄牛群体、欺诈群体等),进而
降低企业不必要的损失;通过该技术可以为企业实现某些核心指标的判断和预测,进而为企业
高层的决策提供参考依据等。企业对数据分析与挖掘技术的重视就意味着对人才的重视,这
就要求希望或正在从事数据相关岗位的人员具备该技术的理论知识和实战能力。
Python作为大数据相关岗位的应用利器,具有开源、简洁易读、快速上手、多场景应用以
及完善的生态和服务体系等优点,使其在数据分析与挖掘领域中的地位显得尤为突出。基于
Python可以对各种常见的脏数据完成清洗、绘制各式各样的统计图形,并实现各种有监督、无
监督和半监督的机器学习算法的落地,在数据面前做到游刃有余,所以说Python是数据分析与
挖掘工作的不二之选。根据多家招聘网站的统计,几乎所有的数据分析或挖掘岗位都要求应
聘者掌握至少一种编程语言,其中就包括Python。
纵观国内的图书市场,关于Python的书籍还是非常多的,它们主要偏向于工具本身的用
法,如关于Python的语法、参数、异常处理、调用以及开发类实例等。但是基于Python的数据分
析与挖掘书籍并不是特别多,关于这方面技术的书籍更多的是基于R语言等工具。本书将通过
具体的实例讲解数据的处理和可视化技术,同时也结合数据挖掘的理论知识和项目案例讲解
10种常用的挖掘算法。
2015年9月,笔者申请了微信公众号,取名为“数据分析1480”,目前已经陆续更新了近200
篇文章。一方面是为了将自己所学、所知记录下来,作为自己的知识沉淀;另一方面是希望尽
自己的微薄之力,将记录下来的内容分享给更多热爱或从事数据分析与挖掘事业的朋友。但
是公众号的内容并没有形成系统的知识框架,在王金柱老师的鼓励和支持下才开始了本书的
写作,希望读者能够从中获得所需的知识点。

本书的内容
本书一共分为三大部分,系统地介绍数据分析与挖掘过程中所涉及的数据清洗与整理、
数据可视化以及数据挖掘的落地。

第一部分(第1~3章)介绍有关数据分析与挖掘的概述以及Python的基础知识,并通过一
个有趣的案例引入本书内容的学习。本部分内容可以为; 初学Python的朋友奠定基础,进而为后
续章节的学习做准备。
第二部分(第4~6章)涉及numpy模块的数值计算、Pandas模块的数据清洗与整理以及
Matplotlib模块的可视化技术。本部分内容可以为数据预处理过程中的清洗、整理以及探索性
分析环节提供技术支撑。
第三部分(第7~16章)一共包含10种数据挖掘算法的应用,如线性回归、决策树、支持向
量机、GBDT等,使用通俗易懂的术语介绍每一个挖掘算法的理论知识,并借助于具体的数据
项目完成算法的实战。本部分内容可以提高热爱或从事数据分析相关岗位朋友的水平和技
能,也可以作为数据挖掘算法落地的模板。
本书每一章都有对应的数据源和完整代码,代码均包含具体的中文注释,读者可以在笔
者的github网站https://github.com/SnakeLiu/Python-Data-Aanalysis-and-Miner下载,或者在百度网
盘https://pan.baidu.com/s/18REQ_J057i7KL7ivBCX-cw(密码:xt4i)下载。

勘误和支持
由于笔者水平有限,书中难免会出现不当的地方,欢迎专家和读者朋友给予批评和指正。
可以通过下方的途径联系并反馈建议:
即时通信:添加个人微信(lsx19890717)或者QQ(1029776077),及时反馈问题。
公众号:添加个人微信公众号“数据分析1480”,可参与后台互动。
电子邮箱:发送邮件至lsxxx2011@163.com。

致谢
特别感谢清华大学出版社的王金柱老师,感谢他的热情相邀和宝贵建议,是他促成了本
书的完成,同时他专业而高效的审阅也使本书增色不少。感谢参与本书封面设计的王翔老师、
责任校对闫秀华老师,以及其他背后默默支持的出版工作者,在他们的努力和付出下,保证了
本书的顺利出版。
最后,感谢我的家人和朋友,尤其是我的妻子许欣女士,是她在我写书期间把家里的一切
整理得有条不紊,对我的照顾更是无微不至,才使我能够聚精会神地完成本书全部内容的撰
写。

刘顺祥(Sim Liu)
2018年8月于上海

目录
前言
第1章 数据分析与挖掘概述
1.1 什么是数据分析和挖掘
1.2 数据分析与挖掘的应用领域
1.2.1 电商领域——发现破坏规则的“害群之马”
1.2.2 交通出行领域——为打车平台进行私人订制
1.2.3 医疗健康领域——找到最佳医疗方案
1.3 数据分析与挖掘的区别
1.4 数据挖掘的流程
1.4.1 明确目标
1.4.2 数据搜集
1.4.3 数据清洗
1.4.4 构建模型
1.4.5 模型评估
1.4.6 应用部署
1.5 常用的数据分析与挖掘工具
1.6 本章小结
第2章 从收入的预测分析开始
2.1 下载与安装Anoconda
2.1.1 基于Windows系统安装
2.1.2 基于Mac系统安装
2.1.3 基于Linux系统安装
2.2 基于Python的案例实战
2.2.1 数据的预处理
2.2.2 数据的探索性分析
2.2.3 数据建模
2.3 本章小结
第3章 Python快速入门
3.1 数据结构及方法
3.1.1 列表
3.1.2 元组
3.1.3 字典
3.2 控制流
3.2.1 if分支
3.2.2 for循环

3.2.3 while循环
3.3 字符串处理方法
3.3.1 字符串的常用方法
3.3.2 正则表达式
3.4 自定义函数
3.4.1 自定义函数语法
3.4.2 自定义函数的几种参数
3.5 一个爬虫案例
3.6 本章小结
第4章 Python数值计算工具——Numpy
4.1 数组的创建与操作
4.1.1 数组的创建
4.1.2 数组元素的获取
4.1.3 数组的常用属性
4.1.4 数组的形状处理
4.2 数组的基本运算符
4.2.1 四则运算
4.2.2 比较运算
4.2.3 广播运算
4.3 常用的数学和统计函数
4.4 线性代数的相关计算
4.4.1 矩阵乘法
4.4.2 diag函数的使用
4.4.3 特征根与特征向量
4.4.4 多元线性回归模型的解
4.4.5 多元一次方程组的求解
4.4.6 范数的计算
4.5 伪随机数的生成
4.6 本章小结
第5章 Python数据处理工具——Pandas
5.1 序列与数据框的构造
5.1.1 构造序列
5.1.2 构造数据框
5.2 外部数据的读取
5.2.1 文本文件的读取
5.2.2 电子表格的读取
5.2.3 数据库数据的读取

5.3 数据类型转换及描述统计
5.4 字符与日期数据的处理
5.5 常用的数据清洗方法
5.5.1 重复观测处理
5.5.2 缺失值处理
5.5.3 异常值处理
5.6 数据子集的获取
5.7 透视表功能
5.8 表之间的合并与连接
5.9 分组聚合操作
5.10 本章小结
第6章 Python数据可视化
6.1 离散型变量的可视化
6.1.1 饼图
6.1.2 条形图
6.2 数值型变量的可视化
6.2.1 直方图与核密度曲线
6.2.2 箱线图
6.2.3 小提琴图
6.2.4 折线图
6.3 关系型数据的可视化
6.3.1 散点图
6.3.2 气泡图
6.3.3 热力图
6.4 多个图形的合并
6.5 本章小结
第7章 线性回归预测模型
7.1 一元线性回归模型
7.2 多元线性回归模型
7.2.1 回归模型的参数求解
7.2.2 回归模型的预测
7.3 回归模型的假设检验
7.3.1 模型的显著性检验——F检验
7.3.2 回归系数的显著性检验——t检验
7.4 回归模型的诊断
7.4.1 正态性检验
7.4.2 多重共线性检验

7.4.3 线性相关性检验
7.4.4 异常值检验
7.4.5 独立性检验
7.4.6 方差齐性检验
7.5 本章小结
第8章 岭回归与LASSO回归模型
8.1 岭回归模型
8.1.1 参数求解
8.1.2 系数求解的几何意义
8.2 岭回归模型的应用
8.2.1 可视化方法确定λ值
8.2.2 交叉验证法确定λ值
8.2.3 模型的预测
8.3 LASSO回归模型
8.3.1 参数求解
8.3.2 系数求解的几何意义
8.4 LASSO回归模型的应用
8.4.1 可视化方法确定λ值
8.4.2 交叉验证法确定λ值
8.4.3 模型的预测
8.5 本章小结
第9章 Logistic回归分类模型
9.1 Logistic模型的构建
9.1.1 Logistic模型的参数求解
9.1.2 Logistic模型的参数解释
9.2 分类模型的评估方法
9.2.1 混淆矩阵
9.2.2 ROC曲线
9.2.3 K-S曲线
9.3 Logistic回归模型的应用
9.3.1 模型的构建
9.3.2 模型的预测
9.3.3 模型的评估
9.4 本章小结
第10章 决策树与随机森林
10.1 节点字段的选择
10.1.1 信息增益

10.1.2 信息增益率
10.1.3 基尼指数
10.2 决策树的剪枝
10.2.1 误差降低剪枝法
10.2.2 悲观剪枝法
10.2.3 代价复杂度剪枝法
10.3 随机森林
10.4 决策树与随机森林的应用
10.4.1 分类问题的解决
10.4.2 预测问题的解决
10.5 本章小结
第11章 KNN模型的应用
11.1 KNN算法的思想
11.2 最佳k值的选择
11.3 相似度的度量方法
11.3.1 欧式距离
11.3.2 曼哈顿距离
11.3.3 余弦相似度
11.3.4 杰卡德相似系数
11.4 近邻样本的搜寻方法
11.4.1 KD树搜寻法
11.4.2 球树搜寻法
11.5 KNN模型的应用
11.5.1 分类问题的解决
11.5.2 预测问题的解决
11.6 本章小结
第12章 朴素贝叶斯模型
12.1 朴素贝叶斯理论基础
12.2 几种贝叶斯模型
12.2.1 高斯贝叶斯分类器
12.2.2 高斯贝叶斯分类器的应用
12.2.3 多项式贝叶斯分类器
12.2.4 多项式贝叶斯分类器的应用
12.2.5 伯努利贝叶斯分类器
12.2.6 伯努利贝叶斯分类器的应用
12.3 本章小结
第13章 SVM模型的应用

13.1 SVM简介
13.1.1 距离公式的介绍
13.1.2 SVM的实现思想
13.2 几种常见的SVM模型
13.2.1 线性可分的SVM
13.2.2 一个手动计算的案例
13.2.3 近似线性可分SVM
13.2.4 非线性可分SVM
13.2.5 几种常用的SVM核函数
13.2.6 SVM的回归预测
13.3 分类问题的解决
13.4 预测问题的解决
13.5 本章小结
第14章 GBDT模型的应用
14.1 提升树算法
14.1.1 AdaBoost算法的损失函数
14.1.2 AdaBoost算法的操作步骤
14.1.3 AdaBoost算法的简单例子
14.1.4 AdaBoost算法的应用
14.2 梯度提升树算法
14.2.1 GBDT算法的操作步骤
14.2.2 GBDT分类算法
14.2.3 GBDT回归算法
14.2.4 GBDT算法的应用
14.3 非平衡数据的处理
14.4 XGBoost算法
14.4.1 XGBoost算法的损失函数
14.4.2 损失函数的演变
14.4.3 XGBoost算法的应用
14.5 本章小结
第15章 Kmeans聚类分析
15.1 Kmeans聚类
15.1.1 Kmeans的思想
15.1.2 Kmeans的原理
15.2 最佳k值的确定
15.2.1 拐点法
15.2.2 轮廓系数法

15.2.3 间隔统计量法
15.3 Kmeans聚类的应用
15.3.1 iris数据集的聚类
15.3.2 NBA球员数据集的聚类
15.4 Kmeans聚类的注意事项
15.5 本章小结
第16章 DBSCAN与层次聚类分析
16.1 密度聚类简介
16.1.1 密度聚类相关的概念
16.1.2 密度聚类的步骤
16.2 密度聚类与Kmeans的比较
16.3 层次聚类
16.3.1 簇间的距离度量
16.3.2 层次聚类的步骤
16.3.3 三种层次聚类的比较
16.4 密度聚类与层次聚类的应用
16.5 本章小结

第1章
数据分析与挖掘概述
马云曾说“中国正迎来从IT时代到DT时代的变革”,DT就是大数据时代。随着移动互联网
的发展,人们越来越感受到技术所带来的便捷,同时企业也将搜集到越来越多与用户相关的
数据,包括用户的基本信息、交易记录、个人喜好、行为特征等。这些数据就相当于隐藏在地
球深处的宝贵资源,企业都想从数据红利中分得一杯羹,进而推进企业重视并善加利用数据
分析与挖掘相关的技术。
本章将以概述的形式介绍数据分析和挖掘相关的内容,通过本章的学习,你将了解如下
几方面的知识点:
数据分析与挖掘的认识;
数据分析与挖掘的几个应用案例;
数据分析与挖掘的几方面区别;
数据分析与挖掘的具体操作流程;
数据分析与挖掘的常用工具。

1.1 什么是数据分析和挖掘
随着数据时代的蓬勃发展,越来越多的企事业单位开始认识到数据的重要性,并通过各
种手段进行数据的搜集。例如,使用问卷调查法获取用户对产品的评价或改善意见;通过每一
次的实验获得产品性能的改良状况;基于各种设备记录空气质量状况、人体健康状态、机器运
行寿命等;通过网页或APP记录用户的每一次登录、浏览、交易、评论等操作;基于数据接口、
网络爬虫等手段获取万维网中的公开数据;甚至是企业间的合作实现多方数据的共享。企事业
单位花费人力、物力获取各种数据的主要目的就是通过数据分析和挖掘手段实现数据的变
现,否则囤积的数据就是资源的浪费。
数据分析和挖掘都是基于搜集来的数据,应用数学、统计、计算机等技术抽取出数据中的
有用信息,进而为决策提供依据和指导方向。例如,应用漏斗分析法挖掘出用户体验过程中的
不足之处,从而进一步改善产品的用户流程;利用AB测试法检验网页布局的变动对交易转化
率的影响,从而确定这种变动是否有利;基于RFM模型实现用户的价值分析,进而针对不同价
值等级的用户采用各自的营销方案,实现精准触达;运用预测分析法对历史的交通数据进行建
模,预测城市各路线的车流量,进而改善交通的拥堵状况;采用分类手段,对患者的体检指标

进行挖掘,判断其所属的病情状况;利用聚类分析法对交易的商品进行归类,可以实现商品的
捆绑销售、推荐销售等营销手段。应用数据分析和挖掘方法,让数据产生价值的案例还有很
多,这里就不一一枚举了,所以只有很好地利用数据,它才能产生价值,毫不夸张地说,大部
分功劳都要归功于数据分析和挖掘。

1.2 数据分析与挖掘的应用领域
也许读者也曾自我发问——学会了数据分析和挖掘技术,可以从事哪些行业的相关工作
呢?在笔者看来,有数据的地方就有用武之地。现在的数据充斥在各个领域,如庞大的互联网
行业,包含各种电商平台、游戏平台、社交平台、中介类平台等;金融行业,包含银行、P2P、互
联网金融等;影响国计民生的教育、医疗行业;各类乙方数据服务行业;传统行业,如房地产、
餐饮、美容等。这些行业都需要借助数据分析和挖掘技术来指导下一步的决策方向,以下仅举
3个行业应用的例子,进一步说明数据分析和挖掘的用武之地。

1.2.1 电商领域——发现破坏规则的“害群之马”
移动互联网时代下,电商平台之间的竞争都特别激烈,为了获得更多的新用户,往往会针
对新用户发放一些诱人的福利,如红包券、满减券、折扣券、限时抢购优惠券等,当用户产生
交易时,就能够使用这些券减免一部分交易金额。电商平台通过类似的营销手段一方面可以
促进新用户的获取,增添新鲜血液;另一方面也可以刺激商城的交易,增加用户的活跃度,可
谓各取所需的双赢效果。
然而,某些心念不正的用户为了从中牟取利益,破坏大环境下的游戏规则。某电商数据分
析人员在一次促销活动的复盘过程中发现交易记录存在异常,于是就对这批异常交易作更深
层次的分析和挖掘。最终发现这批异常交易都有两个共同特点,那就是一张银行卡对应数百
个甚至上千个用户id,同时,这些id自始至终就发生一笔交易。暗示了什么问题?这说明用户
很可能通过廉价的方式获得多个手机号,利用这些手机号去注册APP成为享受福利的多个新
用户,然后利用低价优势买入这些商品,最后再以更高的价格卖出这些商品,这种用户我们一
般称为“黄牛”。
这些“害群之马”的行为至少给电商平台造成两方面的影响,一是导致真正想买商品的新
用户买不到,因为有限的福利或商品都被这些用户抢走了;二是虚增了很多“薅羊毛”的假用
户,因为他们很可能利用完新用户的福利资格后就不会再交易了。如果没有数据分析与挖掘
技术在互联网行业的应用,就很难发现这些“害群之马”,企业针对“害群之马”对游戏规则做了
相应的调整,从而减少了不必要的损失,同时也挽回了真实用户的利益。

1.2.2 交通出行领域——为打车平台进行私人订制
打车工具的出现,改变了人们的出行习惯,也改善了乘车的便捷性,以前都是通过路边招
手才能搭乘出租车,现在坐在家里就可以完成一对一的打车服务。起初滴滴、快滴、优步、易
到等打车平台,为了抢占市场份额,不惜花费巨资补贴给司机端和乘客端,在一定程度上获得
了用户的青睐,甚至导致用户在短途出行中都依赖上了这些打车工具。然而随着时间的推移,
打车市场的格局基本定型,企业为了自身的利益和长远的发展,不再进行这种粗放式的“烧
钱”运营手段。
当司机端和乘客端不再享受以前的福利待遇时,在一定程度上影响了乘客端的乘车频率
和司机端的接单积极性。为了弥补这方面的影响,某打车平台利用用户的历史交易数据,为司
机端和乘客端的定价进行私人订制。
例如,针对乘客端,通过各种广告渠道将折扣券送到用户手中,一方面可以唤醒部分沉默
用户(此时的折扣力度会相对比较高),让他们再次回到应用中产生交易,另一方面继续刺激
活跃用户的使用频率(此时的折扣力度会相对比较低),进而提高用户的忠诚度。针对司机端,
根据司机在平台的历史数据,将其接单习惯、路线熟悉度、路线拥堵状况、距离乘客远近、天
气变化、乘客乘坐距离等信息输入到逻辑模型中,可以预测出司机接单的概率大小。这里的概
率在一定程度上可以理解为司机接单的意愿,概率越高,说明司机接单的意愿越强,否则意愿
就越弱。当模型发现司机接单的意愿比较低时,就会发放较高的补贴给司机端,否则司机就会
获得较少的补贴甚至没有补贴。如果不将数据分析与挖掘手段应用于大数据的交通领域,就
无法刺激司机端和乘客端的更多交易,同时,也会浪费更多的资金,造成运营成本居高不下,
影响企业的发展和股东的利益。

1.2.3 医疗健康领域——找到最佳医疗方案
众所周知,癌症的产生是由于体内某些细胞的DNA或RNA发生了病变,这种病变会导致
癌细胞不断地繁殖,进而扩散至全身,最终形成可怕的肿瘤。早在2003年,乔布斯在一次身体
检查时发现胰腺处有一块阴影,医生怀疑是一块肿瘤,建议乔布斯马上进行手术,但乔布斯选
择了药物治疗。遗憾的是,一年后,医生从乔布斯的身体检查中发现可怕的癌细胞已经扩散到
了全身,认为乔布斯的生命即将走到人生的终点。
乐观的乔布斯认为还可以有治疗的希望,于是花费几十万美元,让专业的医疗团队将自
己体内的DNA与历史肿瘤DNA样本进行比对,目的就是找到符合肿瘤病变的DNA。这样,对
于乔布斯体内的DNA来说就有了病变与正常的标签,然后基于这个标签构建分类算法。当正
常DNA出现病变特征时,该算法就能够准确地找出即将病变的DNA,从而指导医生及时地改
变医疗方案和寻找有效的药物。最终,使得原本即将走到终点的生命,延续了八年时间,正是

这短短的八年,让乔布斯一次次地创造了苹果的辉煌。如果没有数据分析与挖掘在医疗行业
的应用,也许就没有现在的苹果。

1.3 数据分析与挖掘的区别
从广义的角度来说,数据分析的范畴会更大一些,涵盖了数据分析和数据挖掘两个部分。
数据分析就是针对搜集来的数据运用基础探索、统计分析、深层挖掘等方法,发现数据中有用
的信息和未知的规律与模式,进而为下一步的业务决策提供理论与实践依据。所以广义的数
据分析就包含了数据挖掘的部分,正如读者在各招聘网站中所看见的,对于数据分析师的任
职资格中常常需要应聘者熟练使用数据挖掘技术解决工作中的问题。从狭义的角度来说,两
者存在一些不同之处,主要体现在两者的定义说明、侧重点、技能要求和最终的输出形式。接
下来阐述这几个方面的差异。
从定义说明出发:数据分析采用适当的统计学方法,对搜集来的数据进行描述性分析和
探索性分析,并从描述和探索的结果中发现数据背后存在的价值信息,用以评估现状
和修正当前的不足;数据挖掘则广泛交叉数据库知识、统计学、机器学习、人工智能等
方法,对搜集来的数据进行“采矿”,发现其中未知的规律和有用的知识,进一步应用于
数据化运营,让数据产生更大的价值。
从侧重点出发:数据分析更侧重于实际的业务知识,如果将数据和业务分开,往往会导
致数据的输出不是业务所需,业务的需求无法通过数据体现,故数据分析需要两者的
紧密结合,实现功效的最大化;数据挖掘更侧重于技术的实现,对业务知识的熟练度并
没有很高的要求,如何从海量的数据中发现未知的模式和规律,是数据挖掘的目的所
在,只有技术过硬,才能实现挖掘项目的落地。
从掌握的技能出发:数据分析一般要求具备基本的统计学知识、数据库操作技能、Excel
报表开发和常用可视化图表展现的能力,就可以解决工作中的分析任务;数据挖掘对数
学功底和编程能力有较高的要求,数学功底是数据挖掘、机器学习、人工智能等方面的
基础,没有好的数学功底,在数据挖掘领域是走不远的,编程能力是从数据中发现未知
模式和规律途径,没有编程技能,就无法实现算法的落地。
从输出的结果出发:数据分析更多的是统计描述结果的呈现,如平均水平、总体趋势、
差异对比、数据转化等,这些结果都必须结合业务知识进行解读,否则一组数据是没有
任何实际意义的;数据挖掘更多的是模型或规则的输出,通过模型或规则可对未知标签
的数据进行预测,如预测交通的畅通度(预测模型)、判别用户是否响应某种营销活动
(分类算法);通过模型或规则实现智能的商业决策,如推荐用户可能购买的商品(推荐
算法)、划分产品所属的群类(聚类算法)等。

为了读者更容易理解和区分两者之间的差异,这里将上面描述的四方面内容做一个简短
的对比和总结,如表1-1所示。
表1-1 数据分析与挖掘对比

1.4 数据挖掘的流程
本书将安排10个章节的内容来讲解具体的数据挖掘算法和应用案例,故需要对数据挖掘
的具体流程做一个详细的说明。这里的流程可以理解为数据挖掘过程中的规范,只有熟悉了
这些具体的规范,才可以在数据挖掘过程中做到游刃有余。首先通过图1-1中的金字塔了解数
据挖掘中具体的操作步骤。

图1-1 数据挖掘步骤

1.4.1 明确目标
前面讲了几个有关数据分析和数据挖掘在电商行业、交通领域和医疗健康方面的案例,

体现了数据分析与挖掘的重要性。你可能非常期待数据分析与挖掘在工作中的应用,先别急,
在实施数据挖掘之前必须明确自己需要解决的问题是什么,然后才可以有的放矢。
这里通过三个实际的案例来加以说明数据挖掘流程中的第一步,即明确目标:
在餐饮行业,可能都会存在这方面的痛点,即如何调整中餐或晚餐的当班人数,以及为
下一餐准备多少食材比较合理。如果解决了这个问题,那么对于餐厅来说既可以降低
人工成本,又可以避免食材的浪费。
当前互联网经济下的消费信贷和现金信贷都非常流行,对于企业来说可以达到“以钱赚
钱”的功效,对于用户来说短期内可以在一定程度上减轻经济压力,从而实现两端的双
赢。但是企业会面临给什么样的用户放发信贷的选择,如果选择正确了,可以赚取用户
的利息,如果选择错误了,就得赔上本金。所以风险控制(简称“风控”)尤其重要,如果
风控做得好,就能够降低损失,否则就会导致大批“坏账”甚至是面临倒闭。
对于任何一个企业来说,用户的价值高低决定了企业可从用户身上获得的利润空间。
用户越忠诚、价值越高,企业从用户身上获取的利润就越多,反之利润就越少。所以摆
在企业眼前的重大问题就是如何提升用户的生命价值。

1.4.2 数据搜集
当读者明确企业面临的痛点或工作中需要处理的问题后,下一步就得规划哪些数据可能
会影响到这些问题的答案,这一步就称为数据的搜集过程。数据搜集过程显得尤为重要,其决
定了后续工作进展的顺利程度。接下来继续第一步中的例子,说明这三个案例中都需要搜集
哪些相关的数据。

1.餐饮相关
食材数据:食材名称、食材品类、采购时间、采购数量、采购金额、当天剩余量等。
经营数据:经营时间、预定时间、预定台数、预定人数、上座台数、上座人数、上菜名称、
上菜价格、上菜数量、特价菜信息等。
其他数据:天气状况、交通便捷性、竞争对手动向、是否为节假日、用户口碑等。

2.金融授信
用户基本数据:姓名、性别、年龄、受教育水平、职业、工作年限、收入状况、婚姻状态、
借贷情况、房产、汽车等。
刷卡数据:是否有信用卡、刷卡消费频次、刷卡缴费规律、刷卡金额、是否分期、是否逾

期、逾期天数、未偿还金额、信用额度、额度使用率等。
其他数据:信用报告查询记录、电话核查记录、银行存款、社交人脉、其他APP数据等。

3.影响用户价值高低
会员数据:性别、年龄、教育水平、会员等级、会员积分、收入状况等。
交易数据:用户浏览记录、交易商品、交易数量、交易频次、交易金额、客单价、最后交
易时间、偏好、下单与结账时差等。
促销数据:用户活动参与度、优惠券领取率、优惠券使用率、购买数量、购买金额等。
客服数据:实时沟通渠道数量、用户沟通次数、用户疑问响应速度、疑问解答率、客户服
务满意度等。

1.4.3 数据清洗
为解决企业痛点或面临的问题,需要搜集相关的数据。即使数据搜集上来,也必须保证数
据“干净”,因为数据质量的高低将影响最终结果的准确性。通常都有哪些“不干净”的数据会影
响后面的建模呢?针对这些数据都有哪些解决方案呢?这里不妨做一个简要的概述。
缺失值:由于个人隐私或设备故障导致某些观测在维度上的漏缺,一般称为缺失值。缺
失值的存在可能会导致模型结果的错误,所以针对缺失值可以考虑删除法、替换法或
插值法解决。
异常值:异常值一般指远离正常样本的观测点,它们的存在同样会影响模型的准确性,
故可以考虑删除法或单独处理法。当然某些场景下,异常值是有益的,例如通过异常值
可以筛选出钓鱼网站。
数据的不一致性:主要是由于不同的数据源或系统并发不同步所导致的数据不一致性,
例如两个数据源中数据单位的不一致(一个以元为单位,另一个以万元为单位);系统并
发不同步导致一张电影票被多个用户购买。针对这种情况则需要不同数据源的数据更
新(SQL)或系统实现同步并发。
量纲的影响:由于某些模型容易受到不同量纲的影响,因此需要通过数据的标准化方法
将不同量纲的数据进行统一处理,如将数据都压缩至0~1的范围。
维度灾难:当采集来的数据包含上百乃至成千上万的变量时,往往会提高模型的复杂
度,进而影响模型的运行效率,故需要采用方差分析法、相关系数法、递归特征消除
法、主成分分析法等手段实现数据的特征提取或降维。

1.4.4 构建模型

“万事俱备,只欠建模”!据不完全统计,建模前的数据准备将占整个数据挖掘流程80%左
右的时间,可谓“地基不牢,地动山摇”。接下来,在数据准备充分的前提下,需要考虑企业面临
的痛点或难题可以通过什么类型的挖掘模型解决。
对于餐饮业需要预测下一餐将有多少消费者就餐的问题,可以归属于预测类型的挖掘
模型。如基于整理好的餐饮相关数据使用线性回归模型、决策树、支持向量机等实现预
测,进而为下一顿做好提前准备。
对于选择什么样的用户放发信贷问题,其实就是判断该用户是否具有良好信用的特
征,属于分类类型的挖掘模型。例如,基于Logistic模型、决策树、神经网络等完成用户
的分类,为选择优良用户提供决策支持。
对于用户的价值分析,不再具有现成的标签,故无法使用预测或分类类型的模型解决,
可以考虑无监督的聚类类型模型,因为“物以类聚,人以群分”。例如,使用K均值模型、
DBSCAN、最大期望EM等实现不同价值人群的划分。

1.4.5 模型评估
到此阶段,已经完成了数据挖掘流程中的绝大部分工作,并且通过数据得到解决问题的
多个方案(模型),接下来要做的就是从这些模型中挑选出最佳的模型,主要目的就是让这个
最佳的模型能够更好地反映数据的真实性。例如,对于预测或分类类型的模型,即使其在训练
集中的表现很好,但在测试集中结果一般,则说明该模型存在过拟合的现象,需要从数据或模
型角度做进一步修正。

1.4.6 应用部署
通常,模型构建和评估工作的完成,并不代表整个数据挖掘流程的结束,往往还需要最后
的应用部署。尽管模型构建和评估是数据分析师或挖掘工程师所擅长的,但是这些挖掘出来
的模式或规律是给真正的业务方或客户服务的,故需要将这些模式重新部署到系统中。
例如,疾控中心将网民在互联网上的搜索记录进行清洗和统计,并将整理好的数据输入
某个系统中,就可以预测某地区发生流感的概率;用户在申请贷款时,前端业务员通过输入贷
款者的信息,就可以知道其是否满足可贷款的结论;利用用户在电商平台留下的浏览、收藏、
交易等记录,就可以向用户推荐其感兴趣的商品。这些应用的背后,都将数据中的模式或规律
做了重新部署,进而便于使用方的操作。

1.5 常用的数据分析与挖掘工具

“欲先善其事,必先利其器!”这里的“器”含有两方面的意思,一方面是软实力,包含对企
业业务逻辑的理解、理论知识的掌握和施展工作的清醒大脑;另一方面是硬实力,即对数据挖
掘工具的掌握。接下来就针对数据分析和挖掘过程中所使用的几种常用工具做简单介绍。

1.R语言
R语言是由奥克兰大学统计系的Robert Gentleman和Ross Ihaka共同开发的,并在1993年首
次亮相。其具备灵活的数据操作、高效的向量化运算、优秀的数据可视化等优点,受到用户的
广泛欢迎。近年来,由于其易用性和可扩展性也大大提高了R语言的知名度。同时,它也是一
款优秀的数据挖掘工具,用户可以借助强大的第三方扩展包,实现各种数据挖掘算法的落地。

2.Python
Pyhton是由荷兰人Guido van Rossum于1989年发明的,并在1991年首次公开发行。它是一
款简单易学的编程类工具,同时,其编写的代码具有简洁性、易读性和易维护性等优点,也受
到广大用户的青睐。其原本主要应用于系统维护和网页开发,但随着大数据时代的到来,数据
挖掘、机器学习、人工智能等技术越发热门,进而促使了Python进入数据科学的领域。Python同
样拥有各种五花八门的第三方模块,用户可以利用这些模块完成数据科学中的工作任务。例
如,pandas、statsmodels、scipy等模块用于数据处理和统计分析;matplotlib、seaborn、bokeh等模
块实现数据的可视化功能;sklearn、PyML、keras、tensorflow等模块实现数据挖掘、深度学习等
操作。

3.Weka
Weka由新西兰怀卡托大学计算机系Ian Written博士于1992年末发起开发,并在1996年公开
发布Weka 2.1版本。它是一款公开的数据挖掘平台,包含数据预处理、数据可视化等功能,以
及各种常用的回归、分类、聚类、关联规则等算法。对于不擅长编程的用户,可以通过Weka的
图形化界面完成数据分析或挖掘的工作内容。

4.SAS
SAS是由美国北卡罗来纳州大学开发的统计分析软件,当时主要是为了解决生物统计方
面的数据分析。在1976年成立SAS软件研究所,经过多年的完善和发展,最终在国际上被誉为
统计分析的标准软件,进而受到各个领域的广泛应用。SAS由数十个模块构成,其中Base为核
心模块,主要用于数据的管理和清洗、GHAPH模块可以帮助用户实现数据的可视化、STAT模
块则涵盖了所有的实用统计分析方法、EM模块则是更加人性化的图形界面,通过托拉拽的方

式实现各种常规挖掘算法的应用。

5.SPSS
SPSS是世界上最早的统计分析软件,最初由斯坦福大学的三个研究生在1968年研发成
功,并成立SPSS公司,而且在1975年成立了SPSS芝加哥总部。用户可以通过SPSS的界面实现
数据的统计分析和建模、数据可视化及报表输出,简单的操作受到了众多用户的喜爱。除此之
外,SPSS还有一款Modeler工具,其前身是Clementine,2009年被IBM收购后,对其性能和功能
做了大幅的改进和提升。该工具充分体现了数据挖掘的各个流程,例如数据的导入、清洗、探
索性分析、模型选择、模型评估和结果输出,用户可基于界面化的操作完成数据挖掘的各个环
节。
上面向读者介绍了5款较为常用的数据分析与挖掘工具,其中R语言、Python和Weka都属
于开源工具,读者不需要支付任何费用就可以从官网下载并安装使用;而SAS和SPSS则为商业
软件,需要支付一定的费用方可使用。本书将基于开源的Python工具来讲解有关数据分析和挖
掘方面的应用和实战。

1.6 本章小结
本章主要站在读者的角度,回答了有关数据分析与挖掘的定义、应用的领域、两者的差
异、实际的操作流程和常用的落地工具,同时,通过一个个小案例来说明数据分析和挖掘在实
际应用中的价值体现,让读者对其拥有足够的重视。通过本章的学习,希望读者能够对数据分
析与挖掘有一个清晰的认识,进而为后续章节的学习做铺垫。

第2章
从收入的预测分析开始
在数据分析与挖掘过程中,预测性或分类性问题往往是企业需要解决的主要问题,例如
下一季度的营收可能会达到多少、什么样的用户可能会流失、一场营销活动中哪些用户的参
与度会比较高等。
本章将通过Python语言,以一个实战案例介绍分类性问题的解决步骤。通过本章的学习,
你将会了解到基于Python的数据处理和建模方法:
外部数据的读取;
数据的预处理;
数据的探索性分析;
数据建模;
模型预测与评估。

2.1 下载与安装Anoconda
本书中的所有代码都是基于Python 3实现的,所以必须确保你的电脑已经安装好了Python
工具。如果没有安装也不用担心,本节的主要内容就是引导读者如何下载并安装一款好用的
Python工具。
Anoconda是不错的选择,专门用于科学计算的Python发行版,支持Windows、Linux和Mac
系统,可以很方便地解决多版本Python并存、切换以及各种第三方模块安装的问题。更重要的
是,当你下载并安装好Anoconda后,它就已经集成了上百个科学计算的第三方模块,例如书中
将要使用的numpy、pandas、matplotlib、seaborn、statsmodels、sklearn等。用户需要使用这些模块
时,直接导入即可,不用再去下载。
接下来将针对Windows、Linux和Mac系统,分别介绍各自的安装方法,以便读者按步操
作。首先你需要到Anoconda官网(https://www.anaconda.com/download/)下载对应系统的
Anoconda工具。注意,本书是基于Python 3的应用,所以你需要下载Python 3.X的Anoconda。

2.1.1 基于Windows系统安装
步骤01 从官网中下载好Windows版本的Anoconda后,双击该软件并进入安装向导,并单

击“Next”按钮,如图2-1所示。

图2-1 安装引导页

步骤02 进入阅读“License Agreement”窗口,单击“I Agree”按钮。
步骤03 推荐选择“Just Me (recommended)”,如果选择的是“All Users”,就需要Windows的
管理员权限。
步骤04 选择目标路径用于Anodonda工具的安装,并单击“Next”按钮,如图2-2所示。

图2-2 选择安装路径

步骤05 建议不添加Anoconda到环境变量中,因为它可能会影响到其他软件的正常运行,
故选择将Python3.×作为Anoconda的默认版本。单击“Install”按钮,进入安装环节,如图2-3所
示。

图2-3 设置环境变量页

步骤06 大概5分钟就可以完成安装,单击“Finish”按钮即可,如图2-4所示。

图2-4 安装成功页

2.1.2 基于Mac系统安装
步骤01 从官网中下载好Mac版本的Anoconda后,双击该软件,进入Anoconda的安装向
导,单击“Continue”按钮。

步骤02 进入“Read Me”窗口,继续单击“Continue”按钮。
步骤03 进入阅读“License”窗口,勾选“I Agree”,并单击“Continue”按钮。
步骤04 进入“Destination Select”窗口,推荐选择“Install for me only”,并单击“Continue”按
钮,如图2-5所示。

图2-5 目标选择页

步骤05 进入“Installation Type”窗口,推荐默认设置(将Anoconda安装在主目录下),无须
改动安装路径,单击“Install”按钮,进入安装环节,如图2-6所示。

图2-6 安装类型页

步骤06 经过几分钟,即完成整个安装流程,如图2-7所示。

图2-7 安装成功页

当然,如果你不习惯在Mac系统中使用图形化的安装方式,也可以通过命令行的方式完成
Anoconda的安装(以Anaconda3-5.0.1版本为例),具体步骤如下:
步骤01 同样需要通过官网下载好Mac版本的Anoconda,并将其放在桌面。
步骤02 打开终端,输入“bash Anaconda3-5.0.1-MacOSX-x86_64.sh”。
步骤03 接下来会提示阅读“条款协议”,只需按一下回车键即可。
步骤04 滑动滚动条到协议底部,输入“Yes”。
步骤05 提示“按下回车键”接受默认路径的安装,接下来继续输入“Yes”,进入安装环节。
步骤06 最终完成安装,并提示“Thank you for installing Anaconda!”。
注意,关闭终端,重启后安装才有效。

2.1.3 基于Linux系统安装
步骤01 从官网中下载好Linux版本的Anoconda,并将其放在桌面。
步骤02 打开终端,输入“bash Anaconda3-5.0.1-Linux-x86_64.sh”。
步骤03 接下来会提示阅读“条款协议”,只需按一下回车键即可。
步骤04 滑动滚动条到协议底部,输入“Yes”。
步骤05 提示“按下回车键”接受默认路径的安装,接下来继续输入“Yes”。
步骤06 最终完成安装,并提示“Thank you for installing Anaconda3!”。

注意,关闭终端,重启后安装才有效。

2.2 基于Python的案例实战
2.2.1 数据的预处理
1994年Ronny Kohavi和Barry Becker针对美国某区域的居民做了一次人口普查,经过筛
选,一共得到32 561条样本数据。数据中主要包含了关于居民的基本信息以及对应的年收入,
其中年收入就是本章中需要预测的变量,具体数据指标和含义见表2-1。
表2-1 美国某区域居民基本数据集

基于上面的数据集,需要预测居民的年收入是否会超过5万美元,从表2-1的变量描述信息
可知,有许多变量都是离散型的,如受教育程度、婚姻状态、职业、性别等。通常数据拿到手
后,都需要对其进行清洗,例如检查数据中是否存在重复观测、缺失值、异常值等,而且,如果
建模的话,还需要对字符型的离散变量做相应的重编码。首先将上面的数据集读入Python的工
作环境中:
# 导入第三方包
import pandas as pd
import numpy as np

import seaborn as sns
# 数据读取
income = pd.read_excel(r'C:\Users\Administrator\Desktop\income.xlsx')
# 查看数据集是否存在缺失值
income.apply(lambda x:np.sum(x.isnull()))

见表2-2。
表2-2 变量缺失概览

表2-2显示,居民的收入数据集中有3个变量存在数值缺失,分别是居民的工作类型、职业
和国籍。缺失值的存在一般都会影响分析或建模的结果,所以需要对缺失数值做相应的处理。
缺失值的处理一般采用三种方法:一是删除法,即将存在缺失的观测进行删除,如果缺失
比例非常小,则删除法是比较合理的,反之,删除比例比较大的缺失值将会丢失一些有用的信
息;二是替换法,即使用一个常数对某个变量的缺失值进行替换,如果缺失的变量是离散型,
则可以考虑用众数替换缺失值,如果缺失的变量是数值型,则可以考虑使用均值或中位数替
换缺失值;三是插补法,即运用模型方法,基于未缺失的变量预测缺失变量的值,如常见的回
归插补法、多重插补法、拉格朗日插补法等。
由于收入数据集中的3个缺失变量都是离散型变量,这里不妨使用各自的众数来替换缺失
值:

2.2.2 数据的探索性分析
在上面的数据清洗过程中,对缺失值采用了替换处理的方法,接下来对居民收入数据集
做简单的探索性分析,目的是了解数据背后的特征,如数据的集中趋势、离散趋势、数据形状
和变量间的关系等。
首先,需要知道每个变量的基本统计值,如均值、中位数、众数等,只有了解了所需处理
的数据特征,才能做到“心中有数”:
# 数值型变量的统计描述,参见表2-3。
income.describe()

表2-3 数值变量的统计描述

如表2-3所示,描述了有关数值型变量的简单统计值,包括非缺失观测的个数(count)、平
均值(mean)、标准差(std)、最小值(min)、下四分位数(25%)、中位数(50%)、上四分位数
(75%)和最大值(max)。以3万多居民的年龄为例,他们的平均年龄为38.6岁;最小年龄为17岁;
最大年龄为90岁;四分之一的居民年龄不超过28岁;一半的居民年龄不超过37岁;四分之三的
居民年龄不超过48岁;并且年龄的标准差为13.6岁。同理,读者也可以类似地解释其他数值变
量的统计值。
接下来,再来看看数据集中离散型变量的描述性统计值:
# 离散型变量的统计描述,见表2-4。
income.describe(include =[ 'object'])

表2-4 离散变量的统计描述

如表2-4所示,得到的是关于离散变量的统计值,包含每个变量非缺失观测的数量
(count)、不同离散值的个数(unique)、出现频次最高的离散值(top)和最高频次数(freq)。以受
教育水平变量为例,一共有16种不同的教育水平;3万多居民中,高中毕业的学历是出现最多
的;并且一共有10 501名。
数据的分布形状(如偏度、峰度等)可以通过可视化的方法进行展现,这里仅以被调查居
民的年龄和每周工作小时数为例,绘制各自的分布形状图:

见图2-8。

图2-8 核密度曲线

如图2-8所示,第一幅图展现的是,在不同收入水平下,年龄的核密度分布图,对于年收入
超过5万美元的居民来说,他们的年龄几乎呈现正态分布,而收入低于5万美元的居民,年龄呈
现右偏特征,即年龄偏大的居民人数要比年龄偏小的人数多;第二幅图展现了不同收入水平
下,周工作小时数的核密度图,很明显,两者的分布趋势非常相似,并且出现局部峰值。如果
读者需要研究其他数值型变量的分布形状,按照上面的代码稍做修改即可。
同理,也可以针对离散型变量,对比居民的收入水平高低在性别、种族状态、家庭关系等
方面的差异,进而可以发现这些离散变量是否影响收入水平:

见图2-9。

图2-9 收入水平的对比条形图

左图反映的是相同的种族下,居民年收入水平高低的人数差异;右图反映的是相同的家庭
成员关系下,居民年收入水平高低的人数差异。但无论怎么比较,都发现一个规律,即在某一
个相同的水平下(如白种人或未结婚人群中),年收入低于5万美元的人数都要比年收入高于5
万美元的人数多,这个应该是抽样导致的差异(数据集中年收入低于5万和高于5万的居民比例
大致在75%:25%)。如果读者需要研究其他离散型变量与年收入水平的关系,可以稍稍修改上
面的代码,实现可视化的绘制。

2.2.3 数据建模
1.对离散变量重编码
前面提到,由于收入数据集中有很多离散型变量,这样的字符变量是不能直接用于建模
的,需要对这些变量进行重编码,关于重编码的方法有多种,如将字符型的值转换为整数型的
值、哑变量处理(0-1变量)、One-Hot热编码(类似于哑变量)等。在本案例中,将采用“字符转数
值”的方法对离散型变量进行重编码,具体可以通过下面的代码实现:

表2-5 离散变量的数值化编码

表2-5中的结果就是对字符型离散变量的重编码效果,所有的字符型变量都变成了整数型
变量,如workclass、education、marital-status等,接下来就基于这个处理好的数据集对收入水平
income进行预测。
在原本的居民收入数据集中,关于受教育程度的有两个变量,一个是education(教育水
平),另一个是education-num(受教育时长),而且这两个变量的值都是一一对应的,只不过一
个是字符型,另一个是对应的数值型,如果将这两个变量都包含在模型中的话,就会产生信息
的冗余;fnlwgt变量代表的是一种序号,其对收入水平的高低并没有实际意义。故为了避免冗
余信息和无意义变量对模型的影响,考虑将education变量和fnlwgt变量从数据集中删除:
# 删除变量
income.drop(['education','fnlwgt'], axis = 1, inplace = True)
income.head()

见表2-6。
表2-6 数据集的前5行预览

表2-6中呈现的就是经处理“干净”的数据集,所要预测的变量就是income,该变量是二元
变量,对其预测的实质就是对年收入水平的分类(一个新样本进来,通过分类模型,可以将该
样本分为哪一种收入水平)。
关于分类模型有很多种,如Logistic模型、决策树、K近邻、朴素贝叶斯模型、支持向量机、
随机森林、梯度提升树GBDT模型等。本案例将对比使用K近邻和GBDT两种分类器,因为通常
情况下,都会选用多个模型作为备选,通过对比才能得知哪种模型可以更好地拟合数据。接下
来就进一步说明如何针对分类问题,从零开始完成建模的步骤。

2.拆分数据集
基于上面的“干净”数据集,需要将其拆分为两个部分,一部分用于分类器模型的构建,另
一部分用于分类器模型的评估,这样做的目的是避免分类器模型过拟合或欠拟合。如果模型
在训练集上表现很好,而在测试集中表现很差,则说明分类器模型属于过拟合状态;如果模型
在训练过程中都不能很好地拟合数据,那说明模型属于欠拟合状态。通常情况下,会把训练集
和测试集的比例分配为75%和25%:

结果显示,运用随机抽样的方法,将数据集拆分为两部分,其中训练数据集包含24 420条
样本,测试数据集包含8 141条样本,下面将运用拆分好的训练数据集开始构建K近邻和GBDT
两种分类器。

3.默认参数的模型构建

首先,针对K近邻模型,这里直接调用sklearn子模块neighbors中的KNeighborsClassifier类,
并且使用模型的默认参数,即让K近邻模型自动挑选最佳的搜寻近邻算法(algorithm='auto')、
使用欧氏距离公式计算样本间的距离(p=2)、指定未知分类样本的近邻个数为
5(n_neighbors=5)而且所有近邻样本的权重都相等(weights='uniform')。如果读者想了解更多有
关K近邻算法的理论可以翻阅第11章。

其次,针对GBDT模型,可以调用sklearn子模块ensemble中的GradientBoostingClassifier类,
同样先尝试使用该模型的默认参数,即让模型的学习率(迭代步长)为0.1(learning_rate=0.1)、
损失函数使用的是对数损失函数(loss='deviance')、生成100棵基础决策树(n_estimators=100),
并且每棵基础决策树的最大深度为3(max_depth=3),中间节点(非叶节点)的最小样本量为
2(min_samples_split=2),叶节点的最小样本量为1(min_samples_leaf=1),每一棵树的训练都不
会基于上一棵树的结果(warm_start=False)。如果读者想继续了解更多GBDT相关的理论知识
点,可以参考第14章。

如上K近邻模型和GBDT模型都是直接调用第三方模块,并且都是基于默认参数的模型构
建,虽然这个方法可行,但是往往有时默认参数并不能得到最佳的拟合效果。所以,需要不停
地调整模型参数,例如K近邻模型设置不同的K值、GBDT模型中设置不同的学习率、基础决策
树的数量、基础决策树的最大深度等。然后基于这些不同的参数值,验证哪种组合的参数会得
到效果最佳的模型,看似可以通过for循环依次迭代来完成,但是效率会比较慢。一个好消息
是,读者可以不用手写for循环,找到最佳的参数,在Python的sklearn模块中提供了网格搜索
法,目的就是找到上面提到的最佳参数。接下来,就带着大家使用Python中的网格搜索法来完
成模型的参数选择。

4.模型网格搜索
同样,先对K近邻模型的参数进行网格搜索,这里仅考虑模型中n_neighbors参数的不同选
择。执行脚本如下:

简单解释一下GridSearchCV函数中的几个参数含义,estimator参数接受一个指定的模型,
这里为K近邻模型的类;param_grid用来指定模型需要搜索的参数列表对象,这里是K近邻模型

中n_neighbors参数的11种可能值;cv是指网格搜索需要经过10重交叉验证;scoring指定模型评
估的度量值,这里选用的是模型预测的准确率。
通过网格搜索的计算,得到三部分的结果,第一部分包含了11种K值下的平均准确率(因
为做了10重交叉验证);第二部分选择出了最佳的K值,K值为6;第三部分是当K值为6时模型
的最佳平均准确率,且准确率为84.78%。
接下来,对GBDT模型的参数进行网格搜索,搜索的参数包含三个,分别是模型的学习速
率、生成的基础决策树个数和每个基础决策树的最大深度。具体执行代码如下:

输出的结果与K近邻结构相似,仍然包含三个部分。限于篇幅的影响,上面的结果中并没
有显示所有参数的组合,从第二部分的结果可知,最佳的模型学习率为0.05,生成的基础决策
树个数为300棵,并且每棵基础决策树的最大深度为5。这样的组合可以使GBDT模型的平均准
确率达到87.51%。

5.模型预测与评估

上文中,我们花了一部分的篇幅来介绍基于“干净”数据集的模型构建,下一步要做的就
是使用得到的分类器对测试数据集进行预测,进而验证模型在样本外的表现能力,同时,也可
以从横向的角度来比较模型之间的好坏。
通常,验证模型好坏的方法有多种。例如,对于预测的连续变量来说,常用的衡量指标有
均方误差(MSE)和均方根误差(RMSE);对于预测的分类变量来说,常用的衡量指标有混淆矩
阵中的准确率、ROC曲线下的面积AUC、K-S值等。接下来,依次对上文中构建的四种模型进
行预测和评估。

6.默认的K近邻模型
# K近邻模型在测试集上的预测
kn_pred = kn.predict(X_test)
print(pd.crosstab(kn_pred, y_test))
# 模型得分
print('模型在训练集上的准确率%f' %kn.score(X_train,y_train))
print('模型在测试集上的准确率%f' %kn.score(X_test,y_test))

见表2-7。
表2-7 KNN算法的混淆矩阵

模型在训练集上的准确率0.890500
模型在测试集上的准确率0.838840

如上结果所示,第一部分是混淆矩阵,矩阵中的行是模型的预测值,矩阵中的列是测试集
的实际值,主对角线就是模型预测正确的数量(5637和1192),589和723就是模型预测错误的
数量。经过计算,得到第二部分的结论,即模型在训练集中的准确率为89.1%,但在测试集上
的错误率超过16%(1-0.839),说明默认参数下的KNN模型可能存在过拟合的风险。
模型的准确率就是基于混淆矩阵计算的,但是该方法存在一定的弊端,即如果数据本身
存在一定的不平衡时(正负样本的比例差异较大),一定会导致准确率很高,但并不一定说明
模型就是理想的。这里再介绍一种常用的方法,就是绘制ROC曲线,并计算曲线下的面积AUC
值:

见图2-10。

图2-10 KNN算法的ROC曲线

图2-10中绘制了模型的ROC曲线,经计算得知,该曲线下的面积AUC为0.865。如果读者使
用AUC来评估模型的好坏,那应该希望AUC越大越好。一般而言,当AUC的值超过0.8时,基本
上就可以认为模型比较合理。所以,基于默认参数的K近邻模型在居民收入数据集上的表现还
算理想。

7.网格搜索的K近邻模型

见表2-8。
表2-8 网格搜索KNN算法的混淆矩阵

模型在训练集上的准确率0.882473
模型在测试集上的准确率0.845351

见图2-11。

图2-11 网格搜索KNN算法的ROC曲线

相比于默认参数的K近邻模型来说,经过网格搜索后的模型在训练数据集上的准确率下
降了,但在测试数据集上的准确率提高了,这也是我们所期望的,说明优化后的模型在预测效
果上更加优秀,并且两者差异的缩小也能够降低模型过拟合的可能。再来看看ROC曲线下的
面积,网格搜索后的K近邻模型所对应的AUC为0.87,相比于原先的KNN模型提高了一点。所
以,从模型的稳定性来看,网格搜索后的K近邻模型比原始的K近邻模型更加优秀。

8.默认的GBDT模型

见表2-9。
表2-9 GBDT算法的混淆矩阵

模型在训练集上的准确率0.869451
模型在测试集上的准确率0.858985

见图2-12。

图2-12 GBDT算法的ROC曲线

如上结果所示,集成算法GBDT在测试集上的表现明显要比K近邻算法优秀,这就是基于
多棵决策树进行投票的优点。该模型在训练集和测试集上的表现都非常好,准确率均超过
85%,而且AUC值也是前面两种模型中最高的,达到了0.913。

9.网络搜索的GBDT模型

见表2-10。
表2-10 网格搜索GBDT算法的混淆矩阵

模型在训练集上的准确率0.890336
模型在测试集上的准确率0.870900

见图2-13。

图2-13 网格搜索GBDT算法的ROC曲线

如上展示的是基于网格搜索后的GBDT模型的表现,从准确率来看,是4个模型中表现最
佳的,该模型在训练集上的准确率接近90%,同时,在测试集上的准确率也超过87%;从绘制的
ROC曲线来看,AUC的值也是最高的,超过0.92。
不论是K近邻模型,还是梯度提升树GBDT模型,都可以通过网格搜索法找到各自的最佳
模型参数,而且这些最佳参数的组合一般都会使模型比较优秀和健壮。所以,纵向比较默认参
数的模型和网格搜索后的最佳参数模型,后者可能是比较好的选择(尽管后者可能会花费更多
的运行时间);横向比较单一模型和集成模型,集成模型一般会比单一模型表现优秀。

2.3 本章小结
本章解决的是一个分类问题的预测,通过实际的案例介绍了有关数据挖掘的重要流程,
包括数据的清洗、数据的探索性分析、模型构建和模型的评估。通过本章的学习,进一步加强

对数据挖掘流程的理解,以便读者在实际的学习和工作中能够按部就班地完成数据挖掘任
务。

第3章
Python快速入门
本章重点介绍有关Python的基础知识,这是每一个Python用户所要走过的必经之路,因为
任何一段Python代码中都会包含一些基础知识。对于读者来说,只有基础夯实牢了,在之后的
代码编程中才会轻松自如。如果你是从零开始的Python用户,希望能够认真学完本章的Python
入门基础知识,相信本章内容对你将有很大的帮助;如果你是Python的中级或高级用户,通过
本章内容的阅读,也许多少会有一点查缺补漏的功效,当然读者也可以直接跳过本章内容,进
入下一章节的学习。
通过本章内容的学习,读者将会掌握如下的Python常用基础知识以及一个简单的爬虫案
例:
常用的数据结构及对应方法;
三种控制流的使用;
字符串的常用处理方法;
正则表达式的使用;
自定义函数的编写;
上海历史天气数据的抓取。

3.1 数据结构及方法
本节所介绍的Python数据结构,并非等同于数据库中的数据结构,而是指列表、元组和字
典,它们都属于存储数据的容器。如何构建和灵活使用这三种数据结构将是本节的主要内容。

3.1.1 列表
关于列表,需要对其说明如下三点:
列表的构造是通过英文状态下的方括号完成的,即[]。可以将每一个元素存放在中括号
中,而且列表中的元素是不受任何限制的,可以存放数值、字符串及其他数据结构的内
容。
列表是一种序列,即每个列表元素是按照顺序存入的,这些元素都有一个属于自己的
位置(或下标)。

列表是一种可变类型的数据结构,即可以实现对列表的修改,包括增加、删除和修改列
表中的元素值。
“列表是一种序列”指的是可以通过索引(或下标)的方式实现列表元素的获取,Python中
的索引都是用英文状态下的方括号表示,而且,对于位置索引来说,都是从0开始。接下来通过
具体的例子来解释四种常见的索引方式。

1.正向单索引
正向单索引指的是只获取列表中的某一个元素,并且是从左到右的方向数元素所在的位
置,可以用[n]表示,例如:

如上结果显示,变量list1是一个含有7个元素的列表,包含字符串(注意,字符串必须用引
号引起来)、数值和列表。由于位置索引是从0开始,所以索引号与实际的位置正好差1,最后使
用print函数将取回的元素打印出来。列表中最后一个元素正好又是一个列表(一般称为嵌套列
表),所以要取出嵌套列表中的元素就需要两层索引实现。

2.负向单索引
负向单索引是指在正向单索引的基础上添加一个负号“-”,所表达的含义是从右向左的方
向获取元素,可以用[-n]表示,例如:

如果列表元素特别多,而需要获取的数据恰好又是最后几个,那么负向单索引就显得尤
为方便和简单,否则从头开始数下去,就显得非常麻烦。注意,最后一个列表元素可以用[-1]表
示,千万不要写成[-0],这是初学者容易犯错的地方。

3.切片索引
切片索引指的是按照固定的步长,连续取出多个元素,可以用[start:end:step]表示。start指
索取元素的起始位置;end指索取元素的终止位置(注意,end位置的元素是取不到的!);step指
索取元素的步长,默认为1,表示逐个取出一连串的列表元素;切片,你可以把它理解成高中所
学的值域范围,属于左闭右开的效果。例如:

如上结果显示,第一个切片是逐个获取元素;第二个切片是隔元素返回;第三个切片并没
有获得所有的最后三个元素,不管你把-1换成0还是换成别的值,返回的结果中都无法得到“湖
北”这个元素。为解决这个末尾元素取不到的难题,我们下面介绍无限索引。

4.无限索引
无限索引是指在切片过程中不限定起始元素的位置或终止元素的位置,甚至起始和终止

元素的位置都不限定,可以用[::step]表示。第一个冒号是指从列表的第一个元素开始获取;第
二个冒号是指到最后一个元素结束(包含最后一个元素值)。例如:

如上结果显示,如果需要从头开始返回元素,可以将切片中的start设置为冒号(:);如果需
要返回至结尾的元素,可以将切片中的end设置为冒号;当然,start和end都设置为冒号的话,返
回的是整个列表元素(等同于复制的功效),再通过step控制步长,实现有规律地跳格取数。
“列表是可变类型的数据结构”指的是可以通过列表特有的“方法”,实现列表元素的增加、
删除和修改,一旦通过这些方法完成列表的改操作,列表本身就发生变化了。注意,这里说的
是特有的“方法”,而不是函数,对于初学者来说,不易分清Python中的“方法”和函数。
为了让读者容易理解两者的区别,这里举个形象的例子加以说明。“方法”可以理解为“婴
幼儿专用商品”,写成Python的语法就是object.method,这里的object就是婴幼儿,method就是专
用商品,例如儿童玩具、奶嘴、尿不湿等商品就是给婴幼儿使用的,这些商品是限定用户的,
就像“方法”是限定特有对象一样;函数可以理解为普通商品,写成Python的语法就是
function(object),这里的object就是普通大众,function就是大众商品,例如雨伞、自行车、米饭等
商品是不限定任何人群的,就像函数可以接受任何一类参数对象一样(如所有可迭代对象)。
上面是从狭义的角度简单理解两者的区别,如果从广义的角度来看“方法”和函数,它们都属
于对象的处理函数。

5.列表元素的增加
如果需要往列表中增加元素,可使用Python提供的三种方法,即append、extend和insert。下
面通过例子来解释三者的区别:
list3 = [1,10,100,1000,10000]
# 在列表末尾添加数字2
list3.append(2)

print(list3)

out:
[1, 10, 100, 1000, 10000, 2]

append是列表所特有的方法,其他常见对象是没有这个方法的,该方法是往列表的尾部增
加元素,而且每次只能增加一个元素。如果需要一次增加多个元素,该方法无法实现,只能使
用列表的extend方法。
# 在列表末尾添加20,200,2000,20000四个值
list3.extend([20,200,2000,20000])
print(list3)

out:
[1, 10, 100, 1000, 10000, 2, 20, 200, 2000, 20000]

使用extend方法往列表尾部增加多个元素时,一定要将多个元素捆绑为列表传递给该方
法,即使只有一个元素,也需要以列表的形式传递。

insert方法可以在列表的指定位置插入新值,该方法需要传递两个参数:一个是索引(或下
标)参数,如上面的2,是指在列表元素的第三个位置插入;另一个参数是具体插入的值,既可
以是一个常量,也可以是一个列表,如果是列表,就是以嵌套列表的形式插入。

6.列表元素的删除
能往列表中增加元素,就能从列表中删除元素。关于列表元素的删除有三种方法,分别是
pop、remove和clear,下面举例说明:
# 删除list3中20000这个元素
list3.pop()
print(list3)

# 删除list3中11这个元素
list3.pop(2)
print(list3)
out:
[1, 10, 11, 100, 1000, 10000, ['a', 'b', 'c'], 2, 20, 200, 2000]
[1, 10, 100, 1000, 10000, ['a', 'b', 'c'], 2, 20, 200, 2000]

如上结果所示,通过pop方法,可以完成列表元素两种风格的删除,一种是默认删除列表
的末尾元素,另一种是删除指定位置的列表元素,而且都只能删除一个元素。
# 删除list3中的['a', 'b', 'c']
list3.remove(['a', 'b', 'c'])
print(list3)
out:
[1, 10, 100, 1000, 10000, 2, 20, 200, 2000]

remove方法提供了删除指定值的功能,该功能非常棒,但是它只能删除首次出现的指定
值。如果你的列表元素特别多,通过pop方法删除指定位置的元素就显得非常笨拙,因为你需
要数出删除值的具体位置,而使用remove方法就很方便。
# 删除list3中所有元素
list3.clear()
print(list3)

out:
[]

clear从字面理解就是清空的意思,确实,该方法就是将列表中的所有元素全部删除。如上
结果所示,通过clear方法返回的是一个空列表。

7.列表元素的修改
如果列表元素值存在错误该如何修改呢?不幸的是对于列表来说,没有具体的方法可
言,但可以使用“取而改之”的思想实现元素的修改。下面通过具体的例子来加以说明:
list4 = ['洗衣机','冰响','电视机','电脑','空调']
# 将“冰响”修改为“冰箱”
print(list4[1])
list4[1] = '冰箱'
print(list4)

out:
冰响

['洗衣机', '冰箱', '电视机', '电脑', '空调']

“取而改之”是指先通过错误元素的获取(通过索引的方法),再使用正确的值重新替换即
可。正如上面的结果所示,就是用新值替换旧值,完成列表元素的修改。
当然,除了上面介绍的列表元素增加和删除所涉及的“方法”外,还有其他“方法”,如排
序、计数、查询位置、逆转,接下来仍然通过具体的例子来说明它们的用法:

count方法是用来对列表中的某个元素进行计数,每次只能往count方法中传递一个值;
index方法则返回指定值在列表中的位置,遗憾的是只返回首次出现该值的位置;reverse方法
是将列表元素全部翻转,最后一个元素重新排到第一个位置,倒数第二个元素排到第二个位
置,以此类推;sort方法可以实现列表元素的排序,默认是升序,可以将reverse参数设置为
True,进而调整为降序。需要注意的是,sort方法只能对同质数据进行排序,即列表元素统一都
是数值型或字符型,不可以混合多种数据类型或数据结构。

3.1.2 元组
元组与列表类似,关于元组同样需要做如下三点说明:
元组通过英文状态下的圆括号构成,即()。其存放的元素与列表一样,可以是不同的数
值类型,也可以是不同的数据结构。
元组仍然是一种序列,所以几种获取列表元素的索引方法同样可以使用到元组对象
中。

与列表最大的区别是,元组不再是一种可变类型的数据结构。
由于元组只是存储数据的不可变容器,因此其只有两种可用的“方法”,分别是count和
index。它们的功能与列表中的count和index方法完全一样,这里就简单举例,不再详细赘述:
t = ('a','d','z','a','d','c','a')
# 计数
print(t.count('a'))
# 元素位置
print(t.index('c'))

out:
3
5

3.1.3 字典
字典是非常常用的一种数据结构,它与json格式的数据非常相似,核心就是以键值对的形
式存储数据,关于Python中的字典做如下四点说明:
构造字典对象需要使用大括号表示,即{},每一个字典元素都是以键值对的形式存在,
并且键值对之间用英文状态下的冒号隔开,即key:value。
键在字典中是唯一的,不能有重复,对于字符型的键需要用引号引起来。值可以是单个
值,也可以是多个值构成的列表、元组或字典。
字典不再是序列,无法通过位置索引完成元素值的获取,只能通过键索引实现。
字典与列表一样,都是可变类型的数据结构。
首先介绍字典的键索引如何实现元素值的获取,举例如下:

对于字典来说,它不再是序列,通过第一条输出结果可知,构造时的字典元素与输出时的
字典元素顺序已经发生了变化,要想获取元素值,只能在索引里面写入具体的键;在字典dict1
中,键“子女”对应的值是另一个字典,属于dict1的嵌套字典,所以需要通过双层键索引获取张
三儿子的姓名;键“兴趣”对应的值是列表,所以“游泳”这个值只能通过先锁定字典的键再锁定
列表元素的位置才能获得。
接下来介绍字典的可变性。关于可变性,仍然是对字典元素进行增加、删除和修改的操
作,这些操作都可以通过字典的“方法”实现,下面将依次介绍字典的各个操作。

1.字典元素的增加
针对字典元素的增加,可以使用如下三种方式实现,分别是setdefault方法、update方法和
键索引方法:

如上结果所示,setdefault方法接受两个参数,第一个参数为字典的键,第二个参数是键对
应的值;update从字面理解是对字典的更新,关于update方法完成字典元素的修改可参见后面
的内容,除此,它还可以增加元素,与setdefault不同的是该方法接受的是一个字典对象;第三
种方法是通过键索引实现的,如果原字典中没有指定的键,就往字典中增加元素,否则,起到
修改字典元素的功能。

2.字典元素的删除
关于字典元素的删除可以使用pop、popitem和clear三种“方法”实现,具体操作如下:

如上结果显示,pop方法在列表中同样起到删除元素的作用,如果不传递任何值给pop方
法,则表示删除列表末尾的一个元素,否则就是删除指定下标的一个元素,但是在字典中pop
方法必须指定需要删除的键,否则就会引起语法错误;如果需要删除嵌套字典中的某个键,就
必须先通过键索引取出对应的字典,然后使用pop方法完成嵌套字典元素的删除;popitem方法
不需要传递任何值,它的功能就是任意删除字典中的某个元素;clear方法则可以干净利落地清
空字典中的所有元素。

3.字典元素的修改
最后来看一下字典元素的修改,关于修改部分,可以使用如下两种方法:

正如“字典元素的增加”部分所提到的,也可以使用update方法和键索引方法完成字典元素
的修改,具体如上面的例子所示。需要注意的是,如果字典中的值是另一个字典或列表,需要
先通过键索引实现字典元素的查询,然后在查询的基础上应用对应的修改方法即可(如update
方法或“取而改之”的方法)。
列表还有一些其他“方法”,这里列出几个比较重要的方法并通过例子来解释:

get方法的功能与键索引已知,可以从字典中取出键对应的值。所不同的是,如果某个键在
字典中不存在,应用键索引的方法会产生“键错误”的信息;而get方法则不会报错,也就不会影
响其他脚本的正常执行。keys、values和items方法分别取出字典中的所有键、值和键值对。

3.2 控制流
Python中的控制流语句和其他编程软件控制流相似,主要包含if分支、for循环和while循
环,而且控制流的使用非常频繁。例如,分不同情况执行不同的内容就可以使用if分支完成;对
每一个对象进行相同的操作可以使用for循环实现;当无法确定循环的对象是什么时,还可以
使用while循环完成重复性的操作。下面就详细介绍if分支、for循环和while循环的具体使用说
明。

3.2.1 if分支
if分支是用来判别某个条件是否满足时所对应的执行内容,常见的分支类型有二分支类
型和多分支类型。二分支是指条件只有两种情况,例如年龄是否大于18周岁,收入是否超过
15000元等。多分支是指条件个数超过两种,例如将考试成绩分成合格、良好和优秀三种等级,
将年龄分为少年、青年、中年和老年4个段。可以将if分支形象地表示成图3-1。

图3-1 if分支流程图

如图3-1所示,菱形代表条件,矩形代表不同条件下执行的语句块。左图展示的就是二分
支的情况,右图为三分支的判断风格。在Python中二分支和三分支的语法可以写成表3-1的形
式。
表3-1 if分支的二分支和三分支的语法

关于上面的语法,有如下4点需要注意:
对于多分支的情况,else if在Python缩写为elif。
不论是关键词if、elif还是else,其所在的行末尾都必须加上英文状态的冒号。
在条件之后的执行语句(expression部分)都需要缩进,而且在整个语句块中,保持缩进
风格一致。
else关键词后面千万不要再加上具体的条件。
针对上面的语法,通过简单的例子(见表3-2)来加以说明,希望读者能够比较好地理解if
分支的语法和注意事项。
表3-2 if语句二分支和多分支的例子

3.2.2 for循环

循环的目的一般都是为了解决重复性的工作,如果你需要对数据集中的每一行做相同的
处理,但不使用循环的话,就会导致代码量剧增,而且都是无意义的重复代码。如果使用循环
的语法解决类似上面的问题,也许只要10行左右的代码即可,既保证代码的简洁性,又保证问
题得到解决。为了使读者形象地理解for循环的操作流程,可将其表示为如图3-2所示的效果。

图3-2 for循环的操作流程

如图3-2所示,对于for循环来说,就是把可迭代对象中的元素(如列表中的每一个元素)通
过漏斗的小口依次倒入之后的执行语句中。在图3-2中,漏斗代表可迭代对象,小球代表可迭
代对象中的元素,黑框是对每一个元素的具体计算过程,菱形是需要对每一个元素做条件判
断,圆柱体则存放了计算后的结果。
对于左图来说,直接将漏斗中的每一个元素进行某种计算,最终把计算结果存储起来;右
图相对复杂一些,多了一步计算前的判断,这个就需要if分支和for循环搭配完成,然后将各分
支的结果进行存储。接下来,分别对如上两种for循环用法加以案例说明。

如上展示的就是对列表list6中每个元素做平方加1的结果,在for循环之前先构造了空列表
result,用于最终计算结果的存储;Python中的指数运算可以使用两个星号表示,如3的5次方可
以写成3**5;最后通过列表的append方法将每个元素的计算结果依次存入result变量中。下面再
看一个有判断条件的for循环用法。

如上结果所示,通过for循环可以非常方便地计算出所有1到100之间的偶数和。对于上面
Python语句有如下5点说明:
在进入循环之前必须定义一个变量,并将0赋给它,目的是用于和的累加。
虽然可以通过方括号[]实现列表的构建,但是手工写入1至100的数字很麻烦,如果使用
Python提供的range函数就可以非常方便地生成有规律的可迭代对象,但是该函数取不
到上限,所以range函数的第二个参数写入的是101。
判断一个数值是否为偶数,就将该数值与2相除求其余数,如果余数等于0则为偶数,否
则为奇数,所以用%表示计算两个数商的余数,判断余数是否等于0,用双等号“==”表
示。
由于计算的是偶数和,所以if分支属于二分支类型,这里只关心偶数的和,对于else部分
就直接使用关键词pass表示忽略,当然读者也可以省略掉else:和pass两行。
最后的print输出部分使用了格式化的输出方法,如代码中的%d代表一个整数型的坑,
%s1_100就是往坑中填入的值,如果有多个待填入坑,就得指定多个填入的值,这样的
格式化输出可以写成%(值1,值2,值3)。除了有整数型的坑,还有%s、%f和.2f%等,分别
代表字符型的坑、浮点型(小数型)的坑和保留两位小数点的浮点型坑等。
如果是对可迭代对象中的每一个元素做相同处理的话,正如上面的例子中对列表list6的
每个元素做平方加1的计算,不仅可以使用for循环,还可以通过更简单的列表表达式完成。对
于列表表达式,可以写成如下语法:
[expression for i in iterable if condition]

在上面的列表表达式中,expression就是对每一个元素的具体操作表达式;iterable是某个
可迭代对象,如列表、元组或字符串等;if condition是对每一个元素做分支判断,如果条件符
合,则expression操作对应的元素。为了更好地说明列表表达式,下面举一个示例:

# 对列表中的偶数做三次方减10的处理
list7 = [3,1,18,13,22,17,23,14,19,28,16]
result = [i ** 3 - 10 for i in list7 if i % 2 == 0]
print(result)

out:
[5822, 10638, 2734, 21942, 4086]

如上结果所示,在原列表list7中通过余数判断获得5个偶数,分别是18、22、14、28和16,再
对这些数做三次方减10的操作就得到了最终的输出结果,而且结果还是列表型的数据结构。
Python中除了有列表表达式,还有元组表达式和字典表达式,它们的语法跟列表表达式类似,
由于它们在实际工作中的使用并不是很频繁,所以就不对它们做详细说明了。
如果读者在学习或工作中需要解决的问题既可以用for循环实现也可以通过列表表达式完
成,建议优先选择列表表达式的方法,因为其语法简洁,而且在计算的效率上也比多行的for循
环高得多。关于for循环的内容就讲解这么多,最后介绍控制流中的while循环。

3.2.3 while循环
while循环与for循环有一些相似之处,有时for循环的操作和while循环的操作是可以互换
的,但while循环更适合无具体迭代对象的重复性操作。这句话理解起来可能比较吃力,下面
通过一个比较形象的例子来说明两者的差异。
当你登录某手机银行APP账号时,一旦输入错误,就会告知用户还剩几次输入机会,很明
显,其背后的循环就是限定用户只能在N次范围内完成正确的输入,否则当天就无法再进行用
户名和密码的输入,对于for循环来说,就有了具体的迭代对象,如从1到N;当你在登录某邮箱
账号时,输入错误的用户名或密码,只会告知“您的用户名或密码错误”,并不会限定还有几次
剩余的输入机会,所以对于这种重复性的输入操作,对方服务器不确定用户将会输入多少次
才会正确,对于while循环来说,就相当于一个无限次的循环,除非用户输入正确。
首先来了解一下while循环在Python中的语法表达:
while condition:
if condition1:
expression1
elif condition2:
expression2
else:
expression3

当while关键词后面的条件满足时,将会重复执行第二行开始的所有语句块。一般情况

下,while循环都会与if分支搭配使用,就像for循环与if分支搭配一样,如上面的while循环语法
中就内嵌了三分支的if判断,读者可以根据具体的情况调整分支的个数。针对上文提到的两种
账号登录模式,进一步通过实例(见表3-3)代码来比较for循环和while循环的操作差异。
表3-3 for循环和while的例子

对如上呈现的代码做几点解释:
input函数可以实现人机交互式的输入,一旦运行,用户填入的任何内容都会以字符型
的值赋值给user变量和password变量,由于实际的密码为数字123,因此必须将input函
数的结果套在int函数内,将其转换为整数型。
如果有多个条件,条件之间的逻辑关系不管是“且”(用&表示)还是“或”(用|表示),所有
的条件都必须用圆括号括起来,否则可能会得到诡异的结果。
在while循环中,while关键词后面直接跟上True值,就表示循环将无限次执行,正如用
户无限次输入错误的用户名和密码一般,直到输入正确并碰见break关键词时才会退出
循环。
break关键字在Python的循环过程中会比较常见,其功能是退出离它最近的循环系统(可
能是for循环或while循环)。如代码所示,当正确填入用户名和密码时,就会执行break关
键字,此时就会退出整个循环系统。与break类似的另一个关键字是continue,不同的是,

continue只是结束循环系统中的当前循环,还得继续下一轮的循环操作,并不会退出整
个循环。

3.3 字符串处理方法
3.3.1 字符串的常用方法
在平时的工作中,也会碰见字符串型数据的处理,例如如何截取字符串中的某一段内容、
如何将字符串按照某个指定的分隔符将其切割开、如何对字符串中的某些值进行替换等。本
节内容重点讲述有关字符串的几种常见处理“方法”,首先介绍一下Python中的字符串有哪些
构造方法:

构造字符串可以使用三种形式的引号,如果字符串的内容不包含任何引号,那么单引号、
双引号和三引号都可以使用;如果字符串的内容仅包含双引号,类似变量string1的形式,那么
只能使用单引号或三引号构造字符串;如果字符串的内容仅包含单引号,类似变量string2的形
式,那么只能使用双引号或三引号完成字符串的创建;如果字符串的内容既包含单引号,又包
含双引号,类似变量string3所示,那只能使用三引号构建字符串。所以,三引号是适用情况最
多的字符串构造方法,而且三引号允许长字符串的换行,这是其他两种引号无法实现的,如变
量string4所示。
接下来将字符串的常用“方法”汇总到表3-4中,以便读者学习和查阅。
表3-4 字符串的常用方法

为了使读者很好地理解表3-4中的字符串“方法”,下面通过一些小例子作为字符串常
用“方法”的解释:

需要说明的是,字符串的index和find方法都是只能返回首次发现子串的位置,如果子串在
原字符串中没有找到,对于index方法来说,则返回报错信息,对于find方法,则返回值-1。所
以,推荐使用find方法寻找子串的位置,因为即使找不到子串也不会因为错误而影响其他程序
的正常执行。
有时,光靠字符串的这些“方法”无法实现字符串的其他处理功能,例如,怎样在字符串中

找到有规律的目标值、怎样替换那些不是固定值的目标内容、怎样按照多个分隔符将字符串
进行切割等。关于这方面问题的解决,需要用到字符串的正则表达式,接下来我们就进入正则
表达式的学习。

3.3.2 正则表达式
正则表达式就是从字符串中发现规律,并通过“抽象”的符号表达出来。打个比方,对于
2,5,10,17,26,37这样的数字序列,如何计算第7个值,肯定要先找该序列的规律,然后用n2+1这
个表达式来描述其规律,进而得到第7个值为50。对于需要匹配的字符串来说,同样把发现规
律作为第一步,本节主要使用正则表达式完成字符串的查询匹配、替换匹配和分割匹配。在进
入字符串的匹配之前,先来了解一下都有哪些常用的正则符号,见表3-5。
表3-5 常用的正则符号

如果读者能够比较熟练地掌握表3-5中的内容,相信在字符串处理过程中将会游刃有余。
如前文所说,本节将基于正则表达式完成字符串的查询、替换和分割操作,这些操作都需要导
入re模块,并使用如下几个函数。

1.匹配查询函数
findall(pattern, string, flags=0)
findall函数可以对指定的字符串进行遍历匹配,获取字符串中所有匹配的子串,并返回一
个列表结果。该函数的参数含义如下:
pattern:指定需要匹配的正则表达式。

string:指定待处理的字符串。
flags:指定匹配模式,常用的值可以是re.I、re.M、re.S和re.X。re.I的模式是让正则表达式
对大小写不敏感;re.M的模式是让正则表达式可以多行匹配;re.S的模式指明正则符号.
可以匹配任意字符,包括换行符\n;re.X模式允许正则表达式可以写得更加详细,如多
行表示、忽略空白字符、加入注释等。

2.匹配替换函数
sub(pattern, repl, string, count=0, flags=0)
sub函数的功能是替换,类似于字符串的replace方法,该函数根据正则表达式把满足匹配
的内容替换为repl。该函数的参数含义如下:
pattern:同findall函数中的pattern。
repl:指定替换成的新值。
string:同findall函数中的string。
count:用于指定最多替换的次数,默认为全部替换。
flags:同findall函数中的flags。

3.匹配分割函数
split(pattern, string, maxsplit=0, flags=0)
split函数是将字符串按照指定的正则表达式分隔开,类似于字符串的split方法。该函数的
具体参数含义如下:
pattern:同findall函数中的pattern。
maxsplit:用于指定最大分割次数,默认为全部分割。
string:同findall函数中的string。
flags:同findall函数中的flags。
如果上面的函数和参数含义都已经掌握了,还需要进一步通过案例加强理解,接下来举
例说明上面的三个函数:

如上结果所示,在第一个例子中通过正则表达式"tianqi:'(.*?)'"实现目标数据的获取,如果
不使用括号的话,就会产生类似"tianqi:'晴'", "tianqi:'阴~小雨'"这样的值,所以,加上括号就是
为了分组,且仅返回组中的内容;第二个例子并没有将正则表达式写入圆括号,如果写上圆括
号也是返回一样的结果,所以findall就是用来返回满足匹配条件的列表值,如果有括号,就仅
返回括号内的匹配值;第三个例子使用替换的方法,将所有的标点符号换为空字符,进而实现
删除的效果;第四个例子是对字符串的分割,如果直接按照正则'[,。、a-zA-Z0-9()]'分割的话,
返回的结果中包含空字符,如'2室2厅'后面就有一个空字符。为了删除列表中每个元素的首尾
空字符,使用了列表表达式,并且结合字符串的strip方法完成空字符的压缩。

3.4 自定义函数

3.4.1 自定义函数语法
虽然Python的标准库中自带了很多“方法”或函数,并且第三方模块也提供了更多的现
成“方法”与函数,但有时还是不能满足学习或工作中的需求,这时就需要自定义函数了。另
外,为了避免重复代码的编写,也可以将常用的代码块封装为函数,在需要时调用函数即可,
这样也会使代码简洁易读。
在Python中有一种自定义函数叫匿名函数,可以用lambda关键字定义。通过lambda构造的
函数可以没有名称,最大特点是“一气呵成”,即在自定义匿名函数时,所有代码只能在一行内
完成,语法如下:
lambda parameters : function_expression

如上语法中,lambda为匿名函数的关键起始词;parameters是函数可能涉及的形参,如果有
多个参数,需要用英文状态的逗号隔开;function_expression为具体的函数体。需要再次强调的
是,如果需要构造的函数不是很复杂,可以使用lambda匿名函数一气呵成地表达完,否则就只
能使用def关键字构造有名称的自定义函数了。下面举一个实例来描述lambda匿名函数的使
用:

本案例的目的是统计列表中的元素频次,并根据频次从高到低排序。首先在统计元素频
次时使用了for循环,其中set函数是构造集合对象,可以实现列表元素的去重;然后直接对存储
键值对的列表直接排序,发现默认是按照字母排序,见第三行输出,并不是以实际的频次排
序;最后通过构建匿名函数,对列表元素(每一个键值对元组)的第二个元素降序排序,进而实
现输出结果中的最后一行效果。
虽然匿名函数用起来很灵活,会在很多代码中遇到,但是它的最大特点也是它的短板,即
无法通过lambda函数构造一个多行而复杂的函数。为了弥补其缺陷,Python提供了另一个关键
字def构造复杂的自定义函数,其语法如下:
def function_name(parameters):
function_expression
return(result)

如上语法中,def是define单词的缩写,表示自定义;function_name为自定义的函数名称;
parameters为自定义函数的形参,需要放在圆括号内;第一行的结束必须要加上英文状态的冒
号,这是很多初学者容易忽略的细节;function_expression是具体的函数体(注意,第二行开始

需要缩进),根据自定义的需求,可以很简单也可以很复杂;return用于返回函数的计算结果,
如果有多个值需要返回,可以全部写在return的括号内,并以逗号隔开。首先,编写一段猜数字
游戏的自定义函数,用于说明自定义函数的语法:

如上的猜数字游戏代码,大家可能见过,这里在《Python简明教程》的基础上做了一定的修
改,进而可以更加“智能”地告知参与游戏的用户可以在什么范围内猜数。代码中用到的知识
点都是前面介绍过的基础内容,这里就不对代码做详细解释了。

3.4.2 自定义函数的几种参数
通过构造自定义函数,可以避免冗余代码的出现。关于Python中的自定义函数,还有四类
重要的参数需要跟读者一一解释,即必选参数、默认参数、可变参数和关键字参数。

1.必选参数
必选参数,顾名思义就是当你在调用一个自定义函数时必须给函数中的必选参数赋值,
否则程序将会报错,并提醒用户“缺少一些必选的位置参数”。就以上面的猜数字函数为例,如
果不给该函数的max参数传递一个值,结果就是这样的:

如上所示,返回“类型错误”的提示,再具体查看最后一行的反馈信息,结论为“game函数
缺少一个必要的位置参数max”表明game函数需要给max参数传值。

2.默认参数
默认参数是指在构造自定义函数的时候就已经给某些参数赋予了各自的初值,当调用函
数时,这样的参数可以不用传值。例如计算1到n的p次方和:

如上构造的自定义函数中,n为必选参数,p为默认参数。根据结果显示,在第一次调用函
数时,并没有给p参数传递任何值,函数正常运行,而且默认计算平方和;在第二次调用函数
时,给p传递了新值3,此时p参数由原来的2换成了3,进而可以计算立方和。

3.可变参数
上面讲解的必选参数和默认参数都是在已知这个自定义函数需要多少个形参的情况下构
建的,如果不确定该给自定义函数传入多少个参数值时,该如何自定义函数呢?这么说可能
有点抽象,接下来通过对比的例子来说明。

例如,小明的弟弟小亮刚读一年级,老师布置了一些关于两个数的求和运算,针对这个问
题,我们可以构建如下的自定义函数:

如果只是两个数求和的问题可以很简单地利用自定义函数add解决,但如果不是两个数之
和,而是三个数或四个数或五个数之和,也就是说不确定接下来会计算几个数的和,这时再使
用上面的add函数似乎就不合理了。好在Python给自定义函数提供了可变参数,目的就是解决
这类问题。举例如下:

如上自定义函数中,参数args前面加了一个星号*,这样的参数就称为可变参数,该参数是
可以接纳任意多个实参的。之所以能够接纳任意多个实参,是因为该类型的参数将这些输入
的实参进行了捆绑,并且组装到元组中,正如输出结果中的第一行和第三行,就是自定义函数
中print(args)语句的效果。

4.关键字参数
虽然一个可变参数可以接受多个实参,但是这些实参都被捆绑为元组了,而且无法将具
体的实参指定给具体的形参,那有没有一种参数既可以接受多个实参,又可以把多个实参指
定给各自的实参名呢?答案是关键字参数,而且这种参数会把带参数名的参数值组装到一个

字典中,键就是具体的实参名,值就是传入的参数值。为了帮助读者理解关键字参数的含义,
下面举一个例子来解释关键字参数。
例如某电商平台,在用户注册时,用户的手机号及出生日期为必填项,其他信息为选填
项。对于选填项来说,电商平台并不知道用户会不会填,以及可能填多少个信息,而且这些信
息都是有对应含义的。为了搜集信息,可以创建一个含关键字参数的自定义函数:

如上结果所示,在自定义函数info_collection中,tel和birthday都是必选参数,kwargs为关键
字参数。当调用函数时,tel和birthday两个参数必须要传入对应的值,而其他的参数都是用户
任意填写的,并且关键字参数会把这些任意填写的信息组装为字典,如输出中的第一行信息;
为了把必选参数的值和关键字参数的值都汇总起来,在自定义函数时初设了空字典user_info,
并通过字典元素增加的方法完成用户信息的搜集,如输出的第二个结果。

3.5 一个爬虫案例
虽然前面的基础知识点都通过一些小例子加以解释和说明,但毕竟都是零散的。为了能
够将前文的基础知识点串起来,下面给出一个简单的爬虫案例,希望读者在学习该案例的同
时,更进一步地认识到基础知识的重要性。

该案例主要是为了获取某城市的历史天气数据,字段包含日期、最低气温、最高气温、风
向、风力、天气状况、空气质量指标值、空气质量等级和空气质量说明,所有数据一共包含
2544天的记录。下面就详细写出整个爬虫的代码:

代码说明:如上所示的爬虫代码中,绝大多数都添加了相应的注释性语言,另外再解释两
点,一个是爬虫中添加字典类型的请求头headers,这样做的目的是为了将Python伪装成一个真
实的浏览器,进而促使被访问的网站(或者称服务器)将Python当作一个正常的访问用户;另一
个是在爬虫的循环中随机停顿几秒,这样做的目的是为了减轻被访问网站的流量压力,否则
单机在一秒内访问对方十几次甚至上百次,会消耗对方很多资源。之所以在代码中添加这两
方面的内容,都是为了防止被访问的网站对爬虫代码实施反爬举措,如访问需要输入验证码、
重新登录甚至是封闭IP。
最终运行完上面的Python爬虫代码,就可以获得如下结构的数据表:

3.6 本章小结
本章主要向读者介绍了有关Python的基础知识,包含三种基本的数据结构及对应的常用
方法、三类控制流语法、字符串的常用处理方法、正则表达式的灵活使用、如何编写自定义函
数以及如何基于这些知识点完成一个小的爬虫案例。通过本章内容的学习,希望读者能够牢
牢掌握基础,为后续章节的学习做好充分的准备。
最后,回顾一下本章中学到的Python“方法”和函数,以便读者查询和记忆:

第4章
Python数值计算工具——Numpy
尽管在第3章中介绍了有关存储数据的列表对象,但是其无法直接参与数值运算(虽然可
以使用加法和乘法,但分别代表列表元素的增加和重复)。本章将介绍另一种非常有用的数据
结构,那就是数组,通过数组可以实现各种常见的数学运算,而且基于数组的运算,也是非常
高效的。
本章的重点是讲解有关Python数值运算的numpy模块,通过numpy模块的学习,你将掌握如
下几方面的内容,进而为后面章节的统计运算和机器学习打下基础:
数组的创建与操作;
数组的基本数学运算;
常用数学和统计函数;
线性代数的求解;
伪随机数的创建。

4.1 数组的创建与操作
通过numpy模块中的array函数实现数组的创建,如果向函数中传入一个列表或元组,将构
造简单的一维数组;如果传入多个嵌套的列表或元组,则可以构造一个二维数组。构成数组的
元素都是同质的,即数组中的每一个值都具有相同的数据类型,下面分别构造一个一维数组
和二维数组。

4.1.1 数组的创建

如上结果所示,可以将列表或元组转换为一个数组,在第二个数组中,输入的元素含有整
数型和浮点型两种数据类型,但输出的数组元素全都是浮点型(原来的整型会被强制转换为浮
点型,从而保证数组元素的同质性)。
使用位置索引可以实现数组元素的获取,虽然在列表中讲解过如何通过正向单索引、负
向单索引、切片索引和无限索引获取元素,但都无法完成不规律元素的获取,如果把列表转换
为数组,这个问题就可以解决了,下面介绍具体的操作。

4.1.2 数组元素的获取
先来看一下一维数组元素与二维数组元素获取的例子,代码如下:

如上结果是通过位置索引获取一维和二维数组中的元素,在一维数组中,列表的所有索
引方法都可以使用在数组上,而且还可以将任意位置的索引组装为列表,用作对应元素的获
取;在二维数组中,位置索引必须写成[rows,cols]的形式,方括号的前半部分用于控制二维数
组的行索引,后半部分用于控制数组的列索引。如果需要获取所有的行或列元素,那么,对应
的行索引或列索引需要用英文状态的冒号表示。但是,要是从数组中取出某几行和某几列,通
过[rows,cols]的索引方法就不太有效了,例如:

如上结果所示,第一个打印结果并不是2×2的数组,而是含两个元素的一维数组,这是因

为numpy将[[0,-1],[1,3]]组合的理解为了[0,1]和[-1,3];同样,在第二个元素索引中,numpy仍然
将[[0,-1],[1,2,3]]组合理解为拆分单独的[rows,cols]形式,最终导致结果中的错误信息。实际
上,numpy的理解是错误的,第二个输出应该是一个2×3的数组。为了克服[rows,cols]索引方法
的弊端,建议读者使用ix_函数,具体操作如下:

4.1.3 数组的常用属性
如果不是手工写入的数组,而是从外部读入的数据,此时也许对数据就是一无所知,如该
数据的维数、行列数、数据类型等信息,下面通过简短的代码来了解数组的几个常用属性,进
而跨出了解数据的第一步。
在numpy模块中,可以通过genfromtxt函数读取外部文本文件的数据,这里的文本文件主要
为csv文件和txt文件。关于该函数的语法和重要参数含义如下:
np.genfromtxt(fname, dtype=<class ‘float’>, comments=’#’, delimiter=None, s
skip_footer=0, converters=None, missing_values=None, filling_values=None, usecol
names=None,)

fname:指定需要读入数据的文件路径。
dtype:指定读入数据的数据类型,默认为浮点型,如果原数据集中含有字符型数据,必
须指定数据类型为“str”。
comments:指定注释符,默认为“#”,如果原数据的行首有“#”,将忽略这些行的读入。
delimiter:指定数据集的列分割符。
skip_header:是否跳过数据集的首行,默认不跳过。
skip_footer:是否跳过数据集的脚注,默认不跳过。
converters:将指定列的数据转换成其他数值。
miss_values:指定缺失值的标记,如果原数据集含指定的标记,读入后这样的数据就为
缺失值。

filling_values:指定缺失值的填充值。
usecols:指定需要读入哪些列。
names:为读入数据的列设置列名称。
接下来通过上面介绍的数据读入函数,读取学生成绩表数据,然后使用数组的几个属性,
进一步掌握数据的结构情况。

如上结果所示,读入的学生成绩表是一个二维的数组(type函数和ndim方法),一共包含
1380行观测和5个变量(shape方法),形成6900个元素(size方法),并且这些元素都属于浮点型
(dtype方法)。通过上面的几个数组属性,就可以大致了解数组的规模。

4.1.4 数组的形状处理
数组形状处理的手段主要有reshape、resize、ravel、flatten、vstack、hstack、row_stack和
colum_stack,下面通过简单的案例来解释这些“方法”或函数的区别。

如上结果所示,虽然reshape和resize都是用来改变数组形状的“方法”,但是reshape方法只
是返回改变形状后的预览,但并未真正改变数组arr3的形状;而resize方法则不会返回预览,而
是会直接改变数组arr3的形状,从前后两次打印的arr3形状就可以发现两者的区别。如果需要
将多维数组降为一维数组,利用ravel、flatten和reshape三种方法均可以轻松解决:

如上结果所示,在默认情况下,优先按照数组的行顺序,逐个将元素降至一维(见数组降
维的前三行打印结果);如果按原始数组的列顺序,将数组降为一维的话,需要设置order参数
为“F”(见数组降维的后三行打印结果)。尽管这三者的功能一致,但之间是否存在差异呢?接
下来对降维后的数组进行元素修改,看是否会影响到原数组arr4的变化:

如上结果所示,通过flatten方法实现的降维返回的是复制,因为对降维后的元素做修改,
并没有影响到原数组arr4的结果;相反,ravel方法与reshape方法返回的则是视图,通过对视图
的改变,是会影响到原数组arr4的。
vstack用于垂直方向(纵向)的数组堆叠,其功能与row_stack函数一致,而hstack则用于水
平方向(横向)的数组合并,其功能与colum_stack函数一致,下面通过具体的例子对这四种函
数的用法和差异加以说明。

如上结果所示,前两个输出是纵向堆叠的效果,后两个则是横向合并的效果。如果是多个
数组的纵向堆叠,必须保证每个数组的列数相同;如果将多个数组按横向合并的话,则必须保
证每个数组的行数相同。

4.2 数组的基本运算符
本章开头就提到列表是无法直接进行数学运算的,一旦将列表转换为数组后,就可以实
现各种常见的数学运算,如四则运算、比较运算、广播运算等。

4.2.1 四则运算
在numpy模块中,实现四则运算的计算既可以使用运算符号,也可以使用函数,具体如下
例所示:

四则运算中的符号分别是“+-*/”,对应的numpy模块函数分别是np.add、np.
subtract、
np.multiply和np.divide。需要注意的是,函数只能接受两个对象的运算,如果需要多个对象的运
算,就得使用嵌套方法,如上所示的符号加法和符号除法。不管是符号方法还是函数方法,都
必须保证操作的数组具有相同的形状,除了数组与标量之间的运算(如除法中的身高与100的
商)。另外,还有三个数学运算符,分别是余数、整除和指数:

可以使用“%、//、**”计算数组元素之间商的余数、整除部分以及数组元素之间的指数。当
然,如果读者比较喜欢使用函数实现这三种运算的话,可以使用np.fmod、np.modf和np.power,
但是整除的函数应用会稍微复杂一点,需要写成np.modf(arr7/arr8)[1],因为modf可以返回数值
的小数部分和整数部分,而整数部分就是要取的整除值。

4.2.2 比较运算
除了数组的元素之间可以实现上面提到的数学运算,还可以做元素间的比较运算。关于
比较运算符有表4-1所示的六种情况。
表4-1 比较运算符及其含义

运用比较运算符可以返回bool类型的值,即True和False。在笔者看来,有两种情况会普遍
使用到比较运算符,一个是从数组中查询满足条件的元素,另一个是根据判断的结果执行不
同的操作。例如:

运用bool索引,将满足条件的元素从数组中挑选出来,但不管是一维数组还是多维数组,
通过bool索引返回的都是一维数组;np.where函数与Excel中的if函数一样,就是根据判定条件
执行不同的分支语句。

4.2.3 广播运算

前面所介绍的各种数学运算符都是基于相同形状的数组,当数组形状不同时,也能够进
行数学运算的功能称为数组的广播。但是数组的广播功能是有规则的,如果不满足这些规则,
运算时就会出错。数组的广播规则是:
各输入数组的维度可以不相等,但必须确保从右到左的对应维度值相等。
如果对应维度值不相等,就必须保证其中一个为1。
各输入数组都向其shape最长的数组看齐,shape中不足的部分都通过在前面加1补齐。
从字面上理解这三条规则可能比较困难,下面通过几个例子对每条规则加以说明,希望
能够帮助读者理解它们的含义:

如上结果所示,第一个打印结果其实并没有用到数组的广播,因为这两个数组具有同形

状;第二个打印结果是三维数组和两维数组的和,虽然维数不一样,但末尾的两个维度值是一
样的,都是4和3,最终得到5×4×3的数组;第三个打印中的两个数组维数和维度值均不一样,但
末尾的两个维度值中必须含一个1,且另一个必须相同,都为4,相加之后得到5×4×3的数组;第
四个打印结果反映的是4×3的二维数组和(3,)的一维数组的和,两个数组维度不一致,为了能
够运算,广播功能会自动将(3,)的一维数组补齐为(1,3)的二维数组,进而得到4×3的数组。通过
对上面例子的解释,希望读者能够掌握数组广播功能的操作规则,以防数组运算时发生错误。

4.3 常用的数学和统计函数
numpy模块的核心就是基于数组的运算,相比于列表或其他数据结构,数组的运算效率是
最高的。在统计分析和挖掘过程中,经常会使用到numpy模块的函数,接下来将常用的数学函
数和统计函数汇总到表4-2中,以便读者查询和使用。
表4-2 数学函数与统计函数

根据上面的表格,需要对统计函数重点介绍,这些统计函数都有axis参数,该参数的目的
就是在统计数组元素时需要按照不同的轴方向计算,如果axis=1,则表示按水平方向计算统计
值,即计算每一行的统计值;如果axis=0,则表示按垂直方向计算统计值,即计算每一列的统计
值。为了简单起见,这里做一组对比测试,以便读者明白轴的方向具体指什么:

如上结果所示,垂直方向就是对数组中的每一列计算总和,而水平方向就是对数组中的
每一行计算总和。同理,如果读者想小试牛刀的话,就以4.1.3节中读取的学生考试成绩为例,
计算每一个学生(水平方向)的总成绩和每一门科目(垂直方向)的平均分。

4.4 线性代数的相关计算
数据挖掘的理论背后几乎离不开有关线性代数的计算问题,如矩阵乘法、矩阵分解、行列
式求解等。本章介绍的numpy模块同样可以解决各种线性代数相关的计算,只不过需要调用
Numpy的子模块linalg(线性代数的缩写),该模块几乎提供了线性代数所需的所有功能。
表4-3给出了一些numpy模块中有关线性代数的重要函数,以便读者快速查阅和掌握函数
用法。
表4-3 numpy模块中有关线性代数的重要函数

4.4.1 矩阵乘法

点积函数dot,使用在两个一维数组中,实际上是计算两个向量的乘积,返回一个标量;使
用在两个二维数组中,即矩阵的乘法,矩阵乘法要求第一个矩阵的列数等于第二个矩阵的行
数,否则会报错。

4.4.2 diag函数的使用

如上结果所示,如果给diag函数传入的是二维数组,则返回由主对角元素构成的一维数
组;如果向diag函数传入一个一维数组,则返回方阵,且方阵的主对角线就是一维数组的值,方
阵的非主对角元素均为0。

4.4.3 特征根与特征向量
我们知道,假设A为n阶方阵,如果存在数λ和非零向量,使得Ax=λx(x≠0),则称λ为A的特
征根,x为特征根λ对应的特征向量。如果需要计算方阵的特征根和特征向量,可以使用子模块
linalg中的eig函数:

如上结果所示,特征根和特征向量的结果存储在元组中,元组的第一个元素就是特征根,
每个特征根对应的特征向量存储在元组的第二个元素中。

4.4.4 多元线性回归模型的解
多元线性回归模型一般用来预测连续的因变量,如根据天气状况预测游客数量、根据网
站的活动页面预测支付转化率、根据城市人口的收入、教育水平、寿命等预测犯罪率等。该模
型可以写成Y=Xβ+ε,其中Y为因变量,X为自变量,ε为误差项。要想根据已知的X来预测Y的话,
必须得知道偏回归系数β的值。对于熟悉多元线性回归模型的读者来说,一定知道偏回归系数
的求解方程,即β=(X'X)-1X’Y)。如果读者并不是很熟悉多元线性回归模型的相关知识,可以查
看第7章的内容。

如上所示,X数组中,第一列全都是1,代表了这是线性回归模型中的截距项,剩下的三列
代表自变量,根据β的求解公式,得到模型的偏回归系数,从而可以将多元线性回归模型表示
为Y=1.781+0.247x1+0.158x2+0.133x3。

4.4.5 多元一次方程组的求解
在中学的时候就学过有关多元一次方程组的知识,例如《九章算术》中有一题是这样描述
的:今有上禾三秉,中禾二秉,下禾一秉,实三十九斗;上禾二秉,中禾三秉,下禾一秉,实三十
四斗;上禾一秉,中禾二秉,下禾三秉,实二十六斗;问上、中、下禾实秉各几何?解答这个问
题就需要应用三元一次方程组,该方程组可以表示为:

在线性代数中,这个方程组就可以表示成AX=b, A代表等号左边数字构成的矩阵,X代表
三个未知数,b代表等号右边数字构成的向量。如需求解未知数X,可以直接使用linalg子模块

中的solve函数,具体代码如下:
# 多元线性方程组
A = np.array([[3,2,1],[2,3,1],[1,2,3]])
b = np.array([39,34,26])
X = np.linalg.solve(A,b)
print('三元一次方程组的解:\n',X)

out:
三元一次方程组的解:
[ 9.25 4.25 2.75]

如上结果所示,得到方程组x、y、z的解分别是9.25、4.25和2.75。

4.4.6 范数的计算
范数常常用来度量某个向量空间(或矩阵)中的每个向量的长度或大小,它具有三方面的
约束条件,分别是非负性、齐次性和三角不等性。最常用的范数就是p范数,其公式可以表示成
ǁxǁp =(|x1|p+|x2|p+…+|xn|p)1/p 。关于范数的计算,可以使用linalg子模块中的norm函数,举例如下:

如上结果所示,向量的无穷范数是指从向量中挑选出绝对值最大的元素。

4.5 伪随机数的生成

虽然在Python内置的random模块中可以生成随机数,但是每次只能随机生成一个数字,而
且随机数的种类也不够丰富。如果读者想一次生成多个随机数,或者在内置的random模块中
无法找到所需的分布函数,作者推荐使用numpy模块中的子模块random。关于各种常见的随机
数生成函数,可见表4-4,以供读者查阅。
表4-4 常见随机数生成函数

读者可能熟悉上面的部分分布函数,但并不一定了解它们的概率密度曲线。为了直观展
现分布函数的概率密度曲线,这里以连续数值的正态分布和指数分布为例进行介绍。如果读
者想绘制更多其他连续变量的分布概率密度曲线,可以对如下代码稍做修改。

见图4-1。

图4-1 各形态的正态分布密度曲线

如图4-1所示,呈现的是不同均值和标准差下的正态分布概率密度曲线。当均值相同时,

标准差越大,密度曲线越矮胖;当标准差相同时,均值越大,密度曲线越往右移。

见图4-2。

图4-2 各形态的指数分布密度曲线

图4-2展现的是指数分布的概率密度曲线,通过图形可知,指数分布的概率密度曲线呈现
在y=0的右半边,而且随着lambda参数的增加,概率密度曲线表现得越矮,同时右边的“尾
巴”会更长而厚。

4.6 本章小结
本章介绍了有关数值计算的numpy模块,包括数组的创建、基本操作、数学运算、常用的数
学和统计函数、线性代数以及随机数的生成。通过本章内容的学习,希望能够为读者在之后的
数据分析和挖掘方面的学习打下基础。
下面对本章中涉及的Python函数进行汇总,主要是正文中没有写入表格的函数,以便读者
查询和记忆。

第5章
Python数据处理工具——Pandas
上一章向读者介绍了有关数值计算的numpy模块,通过numpy模块可以非常方便地调用各
种常用的数学和统计函数。本章将介绍强大的数据处理模块Pandas,该模块可以帮助数据分析
师轻松地解决数据的预处理问题,如数据类型的转换、缺失值的处理、描述性统计分析、数据
的汇总等。
通过本章内容的学习,读者将会掌握如下知识点,进而在数据处理过程中做到游刃有余,
为后续的数据分析或机器学习做准备:
两种重要的数据结构,即序列和数据框;
如何读取外部数据(如文本文件、电子表格或数据库中的数据);
数据类型转换及描述性统计分析;
字符型与日期型数据的处理;
常见的数据清洗方法;
如何应用iloc、loc、与ix完成数据子集的生成;
实现Excel中的透视表操作;
多表之间的合并与连接;
数据集的分组聚合操作。

5.1 序列与数据框的构造
Pandas模块的核心操作对象就是序列(Series)和数据框(DataFrame)。序列可以理解为数
据集中的一个字段,数据框是指含有至少两个字段(或序列)的数据集。首先需要向读者说明
哪些方式可以构造序列和数据框,之后才能实现基于序列和数据框的处理和操作。

5.1.1 构造序列
构造一个序列可以使用如下方式实现:
通过同质的列表或元组构建。
通过字典构建。
通过Numpy中的一维数组构建。

通过数据框DataFrame中的某一列构建。
为了使读者能够理解上面所提到的四种构造方法,这里通过具体的代码案例加以解释和
说明:

由于数据框的知识点还没有介绍到,上面的代码展示的是通过Series函数将列表、字典和
一维数组转换为序列的过程。不管是列表、元组还是一维数组,构造的序列结果都是第一个打
印的样式。该样式会产生两列,第一列属于序列的行索引(可以理解为行号),自动从0开始,第
二列才是序列的实际值。通过字典构造的序列就是第二个打印样式,仍然包含两列,所不同的
是第一列不再是行号,而是具体的行名称(label),对应到字典中的键,第二列是序列的实际
值,对应到字典中的值。
序列与一维数组有极高的相似性,获取一维数组元素的所有索引方法都可以应用在序列
上,而且数组的数学和统计函数也同样可以应用到序列对象上,不同的是,序列会有更多的其
他处理方法。下面通过几个具体的例子来加以测试:

针对上面的代码需要说明几点,如果序列是行名称风格,既可以使用位置(行号)索引,又
可以使用标签(行名称)索引;如果需要对序列进行数学函数的运算,一般首选numpy模块,因
为Pandas模块在这方面比较缺乏;如果是对序列做统计运算,既可以使用numpy模块中的函数,
也可以使用序列的“方法”,作者一般首选序列的“方法”,因为序列的“方法”更加丰富,如计算
序列的偏度、峰度等,而Numpy是没有这样的函数的。

5.1.2 构造数据框

前面提到,数据框实质上就是一个数据集,数据集的行代表每一条观测,数据集的列则代
表各个变量。在一个数据框中可以存放不同数据类型的序列,如整数型、浮点型、字符型和日
期时间型,而数组和序列则没有这样的优势,因为它们只能存放同质数据。构造一个数据库可
以应用如下方式:
通过嵌套的列表或元组构造。
通过字典构造。
通过二维数组构造。
通过外部数据的读取构造。
接下来通过几个简单的例子来说明数据框的构造:

构造数据框需要使用到Pandas模块中的DataFrame函数,如果通过嵌套列表或元组构造数
据框,则需要将数据框中的每一行观测作为嵌套列表或元组的元素;如果通过二维数组构造数
据框,则需要将数据框的每一行写入到数组的行中;如果通过字典构造数据框,则字典的键构
成数据框的变量名,对应的值构成数据框的观测。尽管上面的代码都可以构造数据框,但是将
嵌套列表、元组或二维数组转换为数据框时,数据框是没有具体的变量名的,只有从0到N的列
号。所以,如果需要手工构造数据框的话,一般首选字典方法。剩下一种构造数据框的方法并

没有在代码中体现,那就是外部数据的读取,这个内容将在下一节中重点介绍。

5.2 外部数据的读取
很显然,每次通过手工构造数据框是不现实的,在实际工作中,更多的情况则是通过
Python读取外部数据集,这些数据集可能包含在本地的文本文件(如csv、txt等)、电子表格Excel
和数据库中(如MySQL、SQL Server等)。本节内容就是重点介绍如何基于Pandas模块实现文本
文件、电子表格和数据库数据的读取。

5.2.1 文本文件的读取
如果读者需要使用Python读取txt或csv格式中的数据,可以使用Pandas模块中的read_table
函数或read_csv函数。这里的“或”并不是指每个函数只能读取一种格式的数据,而是这两种函
数均可以读取文本文件的数据。由于这两个函数在功能和参数使用上类似,因此这里仅以
read_table函数为例,介绍该函数的用法和几个重要参数的含义。

filepath_or_buffer:指定txt文件或csv文件所在的具体路径。
sep:指定原数据集中各字段之间的分隔符,默认为Tab制表符。
header:是否需要将原数据集中的第一行作为表头,默认将第一行用作字段名称。
names:如果原数据集中没有字段,可以通过该参数在数据读取时给数据框添加具体的
表头。
index_col:指定原数据集中的某些列作为数据框的行索引(标签)。
usecols:指定需要读取原数据集中的哪些变量名。
dtype:读取数据时,可以为原数据集的每个字段设置不同的数据类型。
converters:通过字典格式,为数据集中的某些字段设置转换函数。
skiprows:数据读取时,指定需要跳过原数据集开头的行数。
skipfooter:数据读取时,指定需要跳过原数据集末尾的行数。
nrows:指定读取数据的行数。
na_values:指定原数据集中哪些特征的值作为缺失值。
skip_blank_lines:读取数据时是否需要跳过原数据集中的空白行,默认为True。
parse_dates:如果参数值为True,则尝试解析数据框的行索引;如果参数为列表,则尝试

解析对应的日期列;如果参数为嵌套列表,则将某些列合并为日期列;如果参数为字
典,则解析对应的列(字典中的值),并生成新的字段名(字典中的键)。
thousands:指定原始数据集中的千分位符。
comment:指定注释符,在读取数据时,如果碰到行首指定的注释符,则跳过改行。
encoding:如果文件中含有中文,有时需要指定字符编码。
为了说明read_table函数中一些参数所起到的作用,这里构造一个稍微复杂点的数据集用
于测试,数据存放在txt中,具体如图5-1所示。

图5-1 待读取的txt数据

图5-1所呈现的txt格式数据集存在一些常见的问题,具体如下:
数据集并不是从第一行开始,前面几行实际上是数据集的来源说明,读取数据时需要
注意什么问题。
数据集的末尾3行仍然不是需要读入的数据,如何避免后3行数据的读入。
中间部分的数据,第四行前加了#号,表示不需要读取该行,该如何处理。
数据集中的收入一列,千分位符是&,如何将该字段读入为正常的数值型数据。
如果需要将year、month和day三个字段解析为新的birthday字段,该如何做到。
数据集中含有中文,一般在读取含中文的文本文件时都会出现编码错误,该如何解决。
针对这样一个复杂的数据集,该如何通过read_table函数将数据正常读入到Python内存中,
并构成一个合格的数据框呢?这里给出具体的数据读入代码,希望读者能够理解其中每一个
参数所起到的作用:

见表5-1。
表5-1 txt数据的读取结果

读取的数据如表5-1所示。代码说明:由于read_table函数在读取数据时,默认将字段分隔
符sep设置为Tab制表符,而原始数据集是用逗号分割每一列,所以需要改变sep参数;
parse_dates参数通过字典实现前三列的日期解析,并合并为新字段birthday;skiprows和
skipfooter参数分别实现原数据集开头几行和末尾几行数据的跳过;由于数据部分的第四行前
面加了#号,因此通过comment参数指定跳过的特殊行;这里仅改变字符编码参数encoding是不
够的,还需要将原始的txt文件另存为UTF-8格式;最后,对于收入一列,由于千分位符为&,因
此为了保证数值型数据的正常读入,需要设置thousands参数为&。

5.2.2 电子表格的读取
还有一种常见的本地数据格式,那就是Excel电子表格,如果读者在学习或工作中需要使
用Python分析某个Excel表格数据,该如何完成第一步的数据读取工作呢?本节将运用Pandas
模块中的read_excel函数,教读者完美地读取电子表格数据。首先,介绍该函数的用法及几个
重要参数的含义:

io:指定电子表格的具体路径。
sheetname:指定需要读取电子表格中的第几个Sheet,既可以传递整数也可以传递具体
的Sheet名称。
header:是否需要将数据集的第一行用作表头,默认为是需要的。

skiprows:读取数据时,指定跳过的开始行数。
skip_footer:读取数据时,指定跳过的末尾行数。
index_col:指定哪些列用作数据框的行索引(标签)。
names:如果原数据集中没有字段,可以通过该参数在数据读取时给数据框添加具体的
表头。
parse_cols:指定需要解析的字段。
parse_dates:如果参数值为True,则尝试解析数据框的行索引;如果参数为列表,则尝试
解析对应的日期列;如果参数为嵌套列表,则将某些列合并为日期列;如果参数为字
典,则解析对应的列(字典中的值),并生成新的字段名(字典中的键)。
na_values:指定原始数据中哪些特殊值代表了缺失值。
thousands:指定原始数据集中的千分位符。
convert_float:默认将所有的数值型字段转换为浮点型字段。
converters:通过字典的形式,指定某些列需要转换的形式。
如图5-2所示,该数据集反映的是儿童类服装的产品信息。在读取数据时需要注意两点:一
点是该表没有表头,如何读数据的同时就设置好具体的表头;另一点是数据集的第一列实际上
是字符型的字段,如何避免数据读入时自动变成数值型字段。

图5-2 待读取的Excel数据

见表5-2。
表5-2 Excel数据的读取结果

这里需要重点说明的是converters参数,通过该参数可以指定某些变量需要转换的函数。
很显然,原始数据集中的商品ID是字符型的,如果不将该参数设置为{0:str},读入的数据与原
始的数据集就不一致了。

5.2.3 数据库数据的读取
绝大多数公司都会选择将数据存入数据库中,因为数据库既可以存放海量数据,又可以
非常便捷地实现数据的查询。本节将以MySQL和SQL Server为例,教会读者如何使用Pandas模
块和对应的数据库模块(分别是pymysql模块和pymssql模块,如果读者的Python没有安装这两
个模块,需要通过cmd命令输入pip install pymysql和pip install pysmsql)实现数据的连接与读
取。
首先需要介绍pymysql模块和pymssql模块中的连接函数connect,虽然两个模块中的连接
函数名称一致,但函数的参数并不完全相同,所以需要分别介绍函数用法和几个重要参数的
含义:
(1)pymysql中的connect
pymysql.connect(host=None, user=None, password='', database=None, port=0,

host:指定需要访问的MySQL服务器。
user:指定访问MySQL数据库的用户名。
password:指定访问MySQL数据库的密码。
database:指定访问MySQL数据库的具体库名。
port:指定访问MySQL数据库的端口号。
charset:指定读取MySQL数据库的字符集,如果数据库表中含有中文,一般可以尝试将
该参数设置为“utf8”或“gbk”。

c

(2)pymssql中的connect
pymssql.connect(server = None, user = None, password = None, database = Non
= None)

从两个模块的connect函数看,两者几乎没有差异,而且参数含义也是一致的,所不同的是
pymysql模块中connect函数的host参数表示需要访问的服务器,而pymssql函数中对应的参数是
server。为了简单起见,以本地电脑中的MySQL和SQL Server为例,演示一遍如何使用Python连
接数据库的操作(如果读者需要在自己电脑上操作,必须确保你的电脑中已经安装了这两种数
据库)。图5-3、图5-4所示分别是MySQL和SQL Server数据库中的数据表。

图5-3 待读取的MySQL数据

图5-4 待读取的SQL Server数据

见表5-3。

表5-3 MySQL数据的读取结果

如上结果所示,将数据库中的数据读入到了Python中。由于MySQL的原数据集中含有中
文,为了避免乱码的现象,将connect函数中的chartset参数设置为utf8。读取数据时,需要用到
Pandas模块中的read_sql函数,该函数至少传入两个参数,一个是读取数据的查询语句(sql),
另一个是连接桥梁(con);在读取完数据之后,请务必关闭连接conn,因为它会一直占用电脑的
资源,影响电脑的运行效率。

见表5-4。
表5-4 SQL Server数据的读取结果

如上所示,连接SQL Server的代码与MySQL的代码基本相同,由于访问SQL Server不需要

填入用户名和密码,因此user参数和password参数需要设置为空字符;在读取数据时,可以写
入更加灵活的SQL代码,如上代码中的SQL语句附加了数据的筛选功能,即所有朝南的二手
房;同样,数据导入后,仍然需要关闭连接。

5.3 数据类型转换及描述统计
也许读者通过5.2节的学习掌握了如何将常用的外部数据读入到Python中的技能,但是你
可能并不了解该数据,所以需要进一步学习Pandas模块中的其他知识点。本节内容主要介绍如
何了解数据,例如读入数据的规模如何、各个变量都属于什么数据类型、一些重要的统计指标
对应的值是多少、离散变量各唯一值的频次该如何统计等。下面以某平台二手车信息为例:
# 数据读取
sec_cars = pd.read_table(r'C:\Users\Administrator\Desktop\sec_cars.csv', sep
# 预览数据的前五行
sec_cars.head()

见表5-5。
表5-5 二手车数据的前5行预览

表5-5所示就是读入的二手车信息,如果读者只需要预览数据的几行信息,可以使用head
方法和tail方法。如上代码中,head方法可以返回数据集的开头5行;如果读者需要查看数据集
的末尾5行,可以使用tail方法。进一步,如果还想知道数据集有多少观测和多少变量,以及每
个变量都是什么数据类型,可以按如下代码得知:

结果如上,该数据集一共包含了10 948条记录和7个变量,除二手车价格Sec_price和行驶
里程数Km(W)为浮点型数据之外,其他变量均为字符型变量。但是,从表5-5来看,二手车的上
牌时间Boarding_time应该为日期型,新车价格New_price应该为浮点型,为了后面的数据分
析,需要对这两个变量进行类型的转换,具体操作如下:

如上结果所示,经过两行代码的处理,上牌时间Boarding_time更改为了日期型数据,新车
价格New_price更改为了浮点型数据。需要说明的是,Pandas模块中的to_datetime函数可以通过
format参数灵活地将各种格式的字符型日期转换成真正的日期数据;由于二手车新车价格含
有“万”字,因此不能直接转换数据类型,为达到目的,需要三步走,首先通过str方法将该字段
转换成字符串,然后通过切片手段,将“万”字剔除,最后运用astype方法,实现数据类型的转
换。

接下来,需要对数据做到心中有数,即通过基本的统计量(如最小值、均值、中位数、最大
值等)描述出数据的特征。关于数据的描述性分析可以使用describe方法:
# 数据的描述性统计
sec_cars.describe()

见表5-6。
表5-6 数值型数据的统计描述

如上结果所示,通过describe方法,直接运算了数据框中所有数值型变量的统计值,包括
非缺失个数、平均值、标准差、最小值、下四分位数、中位数、上四分位数和最大值。以二手车
的售价Sec_price为例,平均价格为25.7万(很明显会受到极端值的影响)、中位数价格为10.2万
(即一半的二手车价格不超过10.2万)、最高售价为808万、最低售价为0.65万、绝大多数二手车
价格不超过23.8万(上四分位数75%对应的值)。
以上都是有关数据的统计描述,但并不能清晰地知道数据的形状分布,如数据是否有偏
以及是否属于“尖峰厚尾”的特征,为了一次性统计数值型变量的偏度和峰度,读者可以参考
如下代码:

见表5-7。

表5-7 数值型数据的偏度和峰度

如上结果所示正是每个数值型变量的偏度和峰度,这三个变量都属于右偏(因为偏度值均
大于0),而且三个变量也是尖峰的(因为峰度值也都大于0)。代码说明:columns方法用于返回
数据集的所有变量名,通过布尔索引和切片方法获得所有的数值型变量;在自定义函数中,运
用到了计算偏度的skew方法和计算峰度的kurt方法,然后将计算结果组合到序列中;最后使用
apply方法,该方法的目的就是对指定轴(axis=0,即垂直方向的各列)进行统计运算(运算函数
即自定义函数)。
以上的统计分析全都是针对数值型变量的,对于数据框中的字符型变量(如二手车品牌
Brand、排放量Discharge等)该如何做统计描述呢?仍然可以使用describe方法,所不同的是,
需要设置该方法中的include参数,具体代码如下:
# 离散型变量的统计描述
sec_cars.describe(include = ['object'])

见表5-8。
表5-8 离散型数据的统计描述

如上结果包含离散变量的四个统计值,分别是非缺失观测数、唯一水平数、频次最高的离
散值和具体的频次。以二手车品牌为例,一共有10 984辆二手车,包含104种品牌,其中别克品
牌最多,高达1 346辆。需要注意的是,如果对离散型变量作统计分析,需要将“object”以列表
的形式传递给include参数。
对于离散型变量,运用describe方法只能得知哪个离散水平属于“明星”值。如果读者需要

统计的是各个离散值的频次,甚至是对应的频率,该如何计算呢?这里直接给出如下代码(以
二手车品的标准排量Discharge为例):
# 离散变量频次统计
Freq = sec_cars.Discharge.value_counts()
Freq_ratio = Freq/sec_cars.shape[0]
Freq_df = pd.DataFrame({'Freq':Freq,'Freq_ratio':Freq_ratio})
Freq_df.head()

见表5-9。
表5-9 变量值的频次统计

如上结果所示,构成的数据框包含两列,分别是二手车各种标准排量对应的频次和频率,
数据框的行索引(标签)就是二手车不同的标准排量。如果读者需要把行标签设置为数据框中
的列,可以使用reset_index方法,具体操作如下:
# 将行索引重设为变量
Freq_df.reset_index(inplace = True)
Freq_df.head()

见表5-10。
表5-10 将行索引转为字段

reset_index方法的使用还是比较频繁的,它可以非常方便地将行标签转换为数据框的变
量。在如上代码中,将reset_index方法中的inplace参数设置为True,表示直接对原始数据集进
行操作,影响到原数据集的变化,否则返回的只是变化预览,并不会改变原数据集。

5.4 字符与日期数据的处理
在本书第3章的Python基础知识讲解中就已经介绍到有关字符串的处理和正则表达式,但
那都是基于单个字符串或字符串列表的操作,在本节中将会向读者介绍如何基于数据框操作
字符型变量,希望对读者在后期的学习和工作中处理字符串时有所帮助。同时,本节也会介绍
有关日期型数据的处理,比方说,如何从日期型变量中取出年份、月份、星期几等,如何计算
两个日期间的时间差。
为了简单起见,这里就以自己手工编的数据为例,展示如何通过Pandas模块中的知识点完
成字符串和日期数据的处理。表5-11所示就是即将处理的数据。
表5-11 待处理的数据表

针对如上数据,读者可以在不看下方代码的情况下尝试着回答这些关于字符型及日期型
的问题:
如何更改出生日期birthday和手机号tel两个字段的数据类型。
如何根据出生日期birthday和开始工作日期start_work两个字段新增年龄和工龄两个字
段。
如何将手机号tel的中间四位隐藏起来。
如何根据邮箱信息新增邮箱域名字段。
如何基于other字段取出每个人员的专业信息。

见表5-12。
表5-12 问题的解答结果

如上结果所示,回答了上面提到的5个问题。为了使读者理解上面的代码,接下来对代码
做详细的解释:
通过dtypes方法返回数据框中每个变量的数据类型,由于出生日期birthday为字符型、手
机号tel为整型,不便于第二问和第三问的回答,所以需要进行变量的类型转换。这里通

过Pandas模块中的to_datetime函数将birthday转换为日期型(必须按照原始的birthday格
式设置format参数);使用astype方法将tel转换为字符型。
对于年龄和工龄的计算,需要将当前日期与出生日期和开始工作日期进行减法运算,
而当前日期的获得,则使用了Pandas子模块datetime中的today函数。由于计算的是相隔
的年数,所以还需进一步取出日期中的年份(year方法)。需要注意的是,对于birthday和
start_work变量,使用year方法之前,还需使用dt方法,否则会出错。
隐藏手机号的中间四位和衍生出邮箱域名变量,都是属于字符串的处理范畴,两个问
题的解决所使用的方法分布是字符串中的替换法(replace)和分割法(split)。由于替换
法和分割法所处理的对象都是变量中的每一个观测,属于重复性工作,所以考虑使用
序列的apply方法。需要注意的是,apply方法中的func参数都是使用匿名函数,对于隐藏
手机号中间四位的思路就是用星号替换手机号的中间四位;对于邮箱域名的获取,其思
路就是按照邮箱中的@符风格,然后取出第二个元素(列表索引为1)。
从other变量中获取人员的专业信息,该问题的解决使用了字符串的正则表达式,不管
是字符串“方法”还是字符串正则,在使用前都需要对变量使用一次str方法。由于findall
返回的是列表值,因此衍生出的email_domain字段值都是列表类型,如果读者不想要这
个中括号,可以参考第三问或第四问的解决方案,这里就不再赘述了。
如果需要删除数据集中的某些变量,可以使用数据框的drop方法。该方法接受的第一个
参数,就是被删除的变量列表,尤其要注意的是,需要将axis参数设置为1,因为默然
drop方法是用来删除数据框中的行记录。
关于更多数据框中字符型变量的处理“方法”可以参考第3章,最后,再针对日期型数据罗
列一些常用的“方法”,见表5-13,希望对读者的学习和记忆有所帮助。
表5-13 常用的日期时间处理“方法”

接下来,挑选几个日期处理“方法”用以举例说明:

5.5 常用的数据清洗方法
在数据处理过程中,一般都需要进行数据的清洗工作,如数据集是否存在重复、是否存在
缺失、数据是否具有完整性和一致性、数据中是否存在异常值等。当发现数据中存在如上可能
的问题时,都需要有针对性地处理,本节将重点介绍如何识别和处理重复观测、缺失值和异常

值。

5.5.1 重复观测处理
重复观测,顾名思义是指观测行存在重复的现象,重复观测的存在会影响数据分析和挖
掘结果的准确性,所以在数据分析和建模之前需要进行观测的重复性检验,如果存在重复观
测,还需要进行重复项的删除。
在搜集数据过程中,可能会存在重复观测的出现,例如通过网络爬虫,就比较容易产生重
复数据。如表5-14所示,就是通过爬虫获得某APP市场中电商类APP的下载量数据(部分),通
过肉眼,是能够发现这10行数据中的重复项的,例如,唯品会出现了两次、当当出现了三次。
如果搜集上来的数据不是10行,而是10万行,甚至更多时,就无法通过肉眼的方式检测数据是
否存在重复项了。下面将介绍如何运用Python对读入的数据进行重复项检查,以及如何删除数
据中的重复项。
表5-14 待清洗数据

# 数据读入
df = pd.read_excel(r'C:\Users\Administrator\Desktop\data_test04.xlsx')
# 重复观测的检测
print('数据集中是否存在重复观测:\n',any(df.duplicated()))

out:
数据集中是否存在重复观测:
True

检测数据集的记录是否存在重复,可以使用duplicated方法进行验证,但是该方法返回的
是数据集每一行的检验结果,即10行数据会返回10个bool值。很显然,这样也不能直接得知数
据集的观测是否重复,为了能够得到最直接的结果,可以使用any函数。该函数表示的是在多
个条件判断中,只要有一个条件为True,则any函数的结果就为True。正如结果所示,any函数的
运用返回True值,说明该数据集是存在重复观测的。接下来,删除数据集中的重复观测:

# 删除重复项
df.drop_duplicates(inplace = True)
df

见表5-15。
表5-15 重复观测的删除结果

如表5-15所示,原先的10行观测在排重后得到7行,被删除的行号为3、8和9。同样,该方法
中也有inplace参数,设置为True就表示直接在原始数据集上做操作。

5.5.2 缺失值处理
缺失值是指数据集中的某些观测存在遗漏的指标值,缺失值的存在同样会影响到数据分
析和挖掘的结果。导致观测的缺失可能有两方面原因,一方面是人为原因(如记录过程中的遗
漏、个人隐私而不愿透露等),另一方面是机器或设备的故障所导致(如断电或设备老化等原
因)。
一般而言,当遇到缺失值(Python中用NaN表示)时,可以采用三种方法处置,分别是删除
法、替换法和插补法。删除法是指当缺失的观测比例非常低时(如5%以内),直接删除存在缺
失的观测,或者当某些变量的缺失比例非常高时(如85%以上),直接删除这些缺失的变量;替
换法是指用某种常数直接替换那些缺失值,例如,对连续变量而言,可以使用均值或中位数替
换,对于离散变量,可以使用众数替换;插补法是指根据其他非缺失的变量或观测来预测缺失
值,常见的插补法有回归插补法、K近邻插补法、拉格朗日插补法等。
为了简单起见,本节就重点介绍删除法和替换法,采用的数据来自于某游戏公司的用户
注册信息(仅以10行记录为例),见表5-16。
表5-16 待处理的缺失值数据

从表5-16展现的数据可知,该数据集存在4条缺失观测,行号分别是4、5、7和9,表中的缺
失值用NaN表示。接下来要做的是如何判断数据集是否存在缺失值(尽管记录数少的时候可以
清楚地发现):
# 数据读入
df = pd.read_excel(r'C:\Users\Administrator\Desktop\data_test05.xlsx')
# 缺失观测的检测
print('数据集中是否存在缺失值:\n',any(df.isnull()))

out:
数据集中是否存在缺失值:
True

检测数据集是否存在重复观测使用的是isnull方法,该方法仍然是基于每一行的检测,所
以仍然需要使用any函数,返回整个数据集中是否存在缺失的结果。从代码返回的结果看,该
数据集确实是存在缺失值的。接下来分别使用两种方法实现数据集中缺失值的处理:
# 删除法之记录删除
df.dropna()
# 删除法之变量删除
df.drop('age', axis = 1)

见表5-17。
表5-17 观测删除与变量删除

如表5-17所示,左表为行删除法,即将所有含缺失值的行记录全部删除,使用dropna方法;
右表为变量删除法,由于原数据集中age变量的缺失值最多,所以使用drop方法将age变量删
除。
# 替换法之前向替换
df.fillna(method = 'ffill')
# 替换法之后向替换
df.fillna(method = 'bfill')

见表5-18。
表5-18 缺失观测的前向填充与后向填充

缺失值的替换需要借助于fillna方法,该方法中的method参数可以接受'ffill'和'bfill'两种
值,分别代表前向填充和后向填充。前向填充是指用缺失值的前一个值替换(如左表所示),而

后向填充则表示用缺失值的后一个值替换(如右表所示)。右表中的最后一个记录仍包含缺失
值,是因为后向填充法找不到该缺失值的后一个值用于替换。缺失值的前向填充或后向填充
一般适用于时间序列型的数据集,因为这样的数据前后具有连贯性,而一般的独立性样本并
不适用该方法。

见表5-19。
表5-19 缺失观测的值填充

另一种替换手段仍然是使用fillna方法,只不过不再使用method参数,而是使用value参数。
左表是使用一个常数0替换所有的缺失值(有些情况是有用的,例如某人确实没有工作,故收
入为0),但是该方法就是典型的“以点概面”,非常容易导致错误,例如结果中的性别莫名多出
异样的0值;右表则是采用了更加灵活的替换方法,即分别对各缺失变量使用不同的替换值(需
要采用字典的方式传递给value参数),性别使用众数替换,年龄使用均值替换,收入使用中位
数替换。
需要说明的是,如上代码并没有实际改变df数据框的结果,因为dropna、drop和fillna方法
并没有使inplace参数设置为True。读者可以在实际的学习和工作中挑选一个适当的缺失值处
理方法,然后将该方法中的inplace参数设置为True,进而可以真正地改变你所处理的数据集。

5.5.3 异常值处理
异常值是指那些远离正常值的观测,即“不合群”观测。导致异常值的出现一般是人为的

记录错误或者是设备的故障等,异常值的出现会对模型的创建和预测产生严重的后果。当然
异常值也不一定都是坏事,有些情况下,通过寻找异常值就能够给业务带来良好的发展,如销
毁“钓鱼”网站、关闭“薅羊毛”用户的权限等。
对于异常值的检测,一般采用两种方法,一种是n个标准差法,另一种是箱线图判别法。标
准差法的判断公式是
,其中 为样本均值,σ为样本标准差,当n=2时,满
足条件的观测就是异常值,当n=3时,满足条件的观测就是极端异常值;箱线图的判断公式是
outlinear>Q3+nIQR或者outlinear<Q1-nIQR,其中Q1为下四分位数(25%),Q3为上四位数
(75%),IQR为四分位差(上四分位数与下四分位数的差),当n=1.5s时,满足条件的观测为异常
值,当n=3时,满足条件的观测即为极端异常值。为了方便读者理解异常值(图中的红色点)的
两种判别方法,可以参见图5-5。

图5-5 异常值判断的两种方法

这两种方法的选择标准如下,如果数据近似服从正态分布时,优先选择n个标准差法,因
为数据的分布相对比较对称;否则优先选择箱线图法,因为分位数并不会受到极端值的影响。
当数据存在异常时,一般可以使用删除法将异常值删除(前提是异常观测的比例不能太大)、
替换法(可以考虑使用低于判别上限的最大值或高于判别下限的最小值替换、使用均值或中位
数替换等)。下面将以年为单位的太阳黑子个数为例(时间范围:1700—1988),识别并处理异
常值:

如上结果所示,不管是标准差检验法还是箱线图检验法,都发现太阳黑子数据中存在异
常值,而且异常值都是超过上限临界值的。接下来,通过绘制太阳黑子数量的直方图和核密度
曲线图,用于检验数据是否近似服从正态分布,进而选择一个最终的异常值判别方法:
# 导入绘图模块
import matplotlib.pyplot as plt
# 设置绘图风格
plt.style.use('ggplot')
# 绘制直方图
sunspots.counts.plot(kind = 'hist', bins = 30, normed = True)
# 绘制核密度图
sunspots.counts.plot(kind = 'kde')
# 图形展现
plt.show()

见图5-6。

图5-6 太阳黑子直方图和核密度曲线

如图5-6所示,不管是直方图还是核密度曲线,所呈现的数据分布形状都是有偏的,并且
属于右偏。基于此,这里选择箱线图法来判定太阳黑子数据中的那些异常值。接下来要做的就
是选用删除法或替换法来处理这些异常值,由于删除法的Python代码已经在5.5.2节的缺失值
处理中介绍过,这里就使用替换法来处理异常值,即使用低于判别上限的最大值或高于判别
下限的最小值替换,代码如下:

如果使用箱线图法判别异常值,则认定太阳黑子数目一年内超过148.85时即为异常值年
份,对于这些年份的异常值使用141.7替换。为了比较替换前后的差异,将太阳黑子数量的统
计值汇总到表5-20中。
表5-20 异常值处理前后的统计描述对比

由表5-20可知,对于异常值的替换,改变了原始数据的均值、标准差和最大值,并且这些
值改变后都降低了,这是显而易见的,因为是将所有超过148.85的异常值改为了较低的141.7。

5.6 数据子集的获取
有时数据读入后并不是对整体数据进行分析,而是数据中的部分子集,例如,对于地铁乘
客量可能只关心某些时间段的流量、对于商品的交易可能只需要分析某些颜色的价格变动、
对于医疗诊断数据可能只对某个年龄段的人群感兴趣等。所以,该如何根据特定的条件实现
数据子集的获取将是本节的主要内容。
通常,在Pandas模块中实现数据框子集的获取可以使用iloc、loc和ix三种“方法”,这三种
方法既可以对数据行进行筛选,也可以实现变量的挑选,它们的语法可以表示成
[rows_select,cols_select]。
iloc只能通过行号和列号进行数据的筛选,读者可以将iloc中的“i”理解为“integer”,即只
能向[rows_select, cols_select]指定整数列表。该索引方式与数组的索引方式类似,都是从0开
始,可以间隔取号,对于切片仍然无法取到上限。
loc要比iloc灵活一些,读者可以将loc中的“l”理解为“label”,即可以向[rows_select,
cols_select]指定具体的行标签(行名称)和列标签(字段名)。注意,这里是标签不再是索引。而
且,还可以将rows_select指定为具体的筛选条件,在iloc中是无法做到的。
ix是iloc和loc的混合,读者可以将ix理解为“mix”,该“方法”吸收了iloc和loc的优点,使数
据框子集的获取更加灵活。为了使读者理解这三种方法的使用和差异,接下来通过具体的代
码加以说明:

见表5-21。

表5-21 数据子集的获取结果

如上结果所示,如果原始数据的行号与行标签(名称)一致,iloc、loc和ix三种方法都可以
取出满足条件的数据子集。所不同的是,iloc运用了索引的思想,故中间三行的表示必须用
1:4,因为切片索引取不到上限,同时,姓名和年龄两列也必须用数值索引表示;loc是指获取行
或列的标签(名称),由于该数据集的行标签与行号一致,所以1:3就表示对应的3个行名称,而
姓名和年龄两列的获取就不能使用数值索引了,只能写入具体的变量名称;ix则混合了iloc与
loc的优点,如果数据集的行标签与行号一致,则ix对观测行的筛选与loc的效果一样,但是ix对
变量名的筛选既可以使用对应的列号(如代码所示),也可以使用具体的变量名称。
假如数据集没有行号,而是具体的行名称,该如何使用这三种方法实现中间三行数据的
获取?代码如下:
# 将员工的姓名用作行标签
df2 = df1.set_index('name')
df2
# 取出数据集的中间三行
df2.iloc[1:4,:]
df2.loc[['李四','王二','丁一'],:]
df2.ix[1:4,:]

见表5-22。
注意,这时的数据集是以员工姓名作为行名称,不再是之前的行号,对于目标数据的返回
同样可以使用iloc、loc和ix三种方法。对于iloc来说,不管什么形式的数据集都可以使用,始终
表示行索引,即取哪些行下标的观测;loc就不能使用数值表示行标签了,因为此时数据集的行
标签是姓名,所以需要写入中间三行对应的姓名;通过ix方法,既可以用行索引(如代码所示)
表示,也可以用行标签表示,可根据读者的喜好选择。由于并没有对数据集的变量做任何限
制,所以cols_select用英文冒号表示,代表取出数据集的所有变量。
表5-22 数据子集的获取结果

很显然,在实际的学习和工作中,观测行的筛选很少是通过写入具体的行索引或行标签,
而是对某些列做条件筛选,进而获得目标数据。例如,在上面的df1数据集中,如何返回所有男
性的姓名和年龄,代码如下:
# 使用筛选条件,取出所有男性的姓名和年龄
# df1.iloc[df1.gender == '男',]
df1.loc[df1.gender == '男',['name','age']]
df1.ix[df1.gender == '男',['name','age']]

见表5-23。
表5-23 数据子集的获取结果

如果是基于条件的记录筛选,只能使用loc和ix两种方法。正如代码所示,对iloc方法的那
行代码做注释,是因为iloc不允许使用条件筛选,这行代码是无法运行成功的。对变量名的筛
选,loc必须指定具体的变量名,而ix既可以使用变量名,也可以使用字段的数值索引。
综上所述,ix方法几乎可以实现所有情况中数据子集的获取,是iloc和loc两种方法的优点
合成体,而且对于行号与行名称一致的数据集来说(如df1数据集),名称索引的优先级在位置
索引之前(如本节第一段代码中的df1.ix[1:3,[0,2]])。

5.7 透视表功能

相信读者在平时的学习或工作中经常会使用到Excel的透视表功能,该功能的主要目的就
是实现数据的汇总统计。例如,按照某个分组变量统计商品的平均价格、销售数量、最大利润
等,或者按照某两个分组变量构成统计学中的列联表(计数统计),甚至是基于多个分组变量
统计各组合下的均值、中位数、总和等。如果你使用Excel,只需要简单的托拉拽就可以迅速地
形成一张统计表,如图5-7所示(数据是关于珠宝的重量、颜色、纯度、价格、面积等)。

图5-7 Excel中的透视表

图5-7所呈现的就是基于单个分组变量实现的均值统计,读者只需将分组变量color拖
入“行标签”框中、数值变量price拖入到“数值”框中,然后下拉“数值”单击“值字段设置”选
择“平均值”的计算类型就可以实现均值的分组统计(因为默认是统计总和)。如果需要构造列
联表(如图5-8所示),可以按照下方的步骤实现。

图5-8 Excel中的透视表

图5-8是关于频次的列联表,将分组变量clarity和cut分别拖至“行标签”框和“列标签”框,然
后将其他任意一个变量拖入“数值”框中,接下来就是选择“计数”的计算类型。同理,如果需要
生成多个分组变量的汇总表,只需将这些分组变量根据实际情况分散到“行标签”和“列标
签”框中。
如果这样的汇总过程不是在Excel中,而是在Python中,该如何实现呢?Pandas模块提供了
实现透视表功能的pivot_table函数,该函数简单易用,与Excel的操作思想完全一致,相信读者
一定可以快速掌握函数的用法及参数含义。接下来,向读者介绍一下有关该函数的参数含义:

data:指定需要构造透视表的数据集。
values:指定需要拉入“数值”框的字段列表。
index:指定需要拉入“行标签”框的字段列表。
columns:指定需要拉入“列标签”框的字段列表。
aggfunc:指定数值的统计函数,默认为统计均值,也可以指定numpy模块中的其他统计
函数。
fill_value:指定一个标量,用于填充缺失值。
margins:bool类型参数,是否需要显示行或列的总计值,默认为False。
dropna:bool类型参数,是否需要删除整列为缺失的字段,默认为True。
margins_name:指定行或列的总计名称,默认为All。
为了说明该函数的灵活功能,这里以上面的珠宝数据为例,重现Excel制作成的透视表。
首先来尝试一下单个分组变量的均值统计,具体代码如下:
# 数据读取
diamonds = pd.read_table(r'C:\Users\Administrator\Desktop\diamonds.csv', se
# 单个分组变量的均值统计
pd.pivot_table(data = diamonds, index = 'color', values = 'price', margins
= '总计')

见图5-9。

图5-9 Python的透视表结果

如上结果所示就是基于单个分组变量color的汇总统计(price的均值),返回结果属于
Pandas模块中的序列类型,该结果与Excel形成的透视表完全一致。接下来看看如何构造两个
分组变量的列联表,代码如下所示:

见表5-24。
表5-24 Python的透视表结果

如表5-24所示,对于列联表来说,行和列都需要指定某个分组变量,所以index参数和
columns参数都需要指定一个分组变量,并且统计的不再是某个变量的均值,而是观测个数,
所以aggfunc参数需要指定numpy模块中的size函数。通过这样的参数设置,返回的是一个数据
框对象,结果与Excel透视表完全一样。

5.8 表之间的合并与连接
在学习或工作中可能会涉及多张表的操作,例如将表结构相同的多张表纵向合并到大表
中,或者将多张表的字段水平扩展到一张宽表中。如果你对数据库SQL语言比较熟悉的话,那
表之间的合并和连接就非常简单了。对于多张表的合并,只需要使用UNION或UNION ALL关
键词;对于多张表之间的连接,只需要使用INNER JOIN或者LEFT JOIN即可。
如果读者对表的合并和连接并不是很熟悉的话,可以查看图5-10。上图为两表之间的纵向
合并,下图为两表之间的水平扩展并且为左连接操作。

图5-10 数据合并与连接的预览效果

需要注意的是,对于多表之间的纵向合并,必须确保多表的列数和数据类型一致;对于多
表之间的水平扩展,必须保证多表要有共同的匹配字段(如图5-10中的ID变量)。图5-10中的
NaN代表缺失,表示3号用户没有对应的考试科目和成绩。
Pandas模块同样提供了关于多表之间的合并和连接操作函数,分别是concat函数和merge
函数,首先介绍一下这两个函数的用法和重要参数含义。
(1)合并函数concat
pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False, ke

objs:指定需要合并的对象,可以是序列、数据框或面板数据构成的列表。
axis:指定数据合并的轴,默认为0,表示合并多个数据的行,如果为1,就表示合并多个
数据的列。
join:指定合并的方式,默认为outer,表示合并所有数据,如果改为inner,表示合并公共
部分的数据。
join_axes:合并数据后,指定保留的数据轴。
ignore_index:bool类型的参数,表示是否忽略原数据集的索引,默认为False,如果设为
True,就表示忽略原索引并生成新索引。
keys:为合并后的数据添加新索引,用于区分各个数据部分。
针对合并函数concat,需要强调两点。一点是,如果纵向合并多个数据集,即使这些数据集
都含有“姓名”变量,但变量名称不一致,如Name和name,通过合并后,将会得到错误的结果。

另一点是join_axes参数的使用,例如纵向合并两个数据集df1和df2,可以写成
pd.concat([df1,df2]),如果该参数等于[df1.index],就表示保留与df1行标签一样的数据,但需要
配合axis=1一起使用;如果等于[df1.columns],就保留与df1列标签一样的数据,但不需要添加
axis=1的约束。下面举例说明concat函数的使用:

见表5-25。
表5-25 数据的合并结果

如上结果所示,为了区分合并后的df1数据集和df2数据集,代码中的concat函数使用了
keys参数,如果再设置参数ignore_index为True,此时keys参数将不再有效。如上右表所示,就是
由两个数据集的变量名称不一致(name和Name)所致,最终产生错误的结果。
(2)连接函数merge
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=False, suffixes=('_x', '_

left:指定需要连接的主表。
right:指定需要连接的辅表。
how:指定连接方式,默认为inner内连,还有其他选项,如左连left、右连right和外连
outer。

on:指定连接两张表的共同字段。
left_on:指定主表中需要连接的共同字段。
right_on:指定辅表中需要连接的共同字段。
left_index:bool类型参数,是否将主表中的行索引用作表连接的共同字段,默认为
False。
right_index:bool类型参数,是否将辅表中的行索引用作表连接的共同字段,默认为
False。
sort:bool类型参数,是否对连接后的数据按照共同字段排序,默认为False。
suffixes:如果数据连接的结果中存在重叠的变量名,则使用各自的前缀进行区分。
该函数的最大缺点是,每次只能操作两张数据表的连接,如果有n张表需要连接,则必须
经过n-1次的merge函数使用。接下来,为了读者更好地理解merge函数的使用,这里举例说明:

如表5-26所示,就是构造的三个数据集,虽然df3和df4都用共同的字段“编号”,但是一个
为id,另一个为Id,所以在后面的表连接时需要留意共同字段的写法。
表5-26 数据的连接结果

如果需要将这三张表横向扩展到一张宽表中,需要经过两次merge操作。如上代码所示,
第一次merge连接了df3和df4,由于两张表的共同字段不一致,所以需要分别指定left_on和
right_on的参数值;第二次merge连接了首次的结果和df5,此时并不需要指定left_on和right_on参
数,是因为第一次的merge结果就包含了id变量,所以merge时会自动挑选完全一致的变量用于
表连接。如表5-27所示,就是经过两次merge之后的结果,结果中的NaN为缺失值,表示无法匹
配的值。
表5-27 数据的连接结果

5.9 分组聚合操作
在数据库中还有一种非常常见的操作就是分组聚合,即根据某些分组变量,对数值型变
量进行分组统计。以珠宝数据为例,统计各颜色和刀工组合下的珠宝数量、最小重量、平均价
格和最大面宽。如果读者对SQL比较熟悉的话,可以写成下方的SQL代码,实现数据的统计:

见图5-11。

图5-11 SQL Server查询结果

如上结果所示,就是通过SQL Server完成的统计,在每一种颜色和刀工的组合下,都会对
应4种统计值。读者如果对SQL并不是很熟悉,该如何运用Python实现数据的分组统计呢?其
实也很简单,只需结合使用Pandas模块中的groupby“方法”和aggregate“方法”,就可以完美地得
到统计结果。详细的Python代码如下所示:

见表5-28。
表5-28 Python的聚合操作结果

如上结果所示,与SQL Server形成的结果完全一致,使用Pandas实现分组聚合需要分两步
走,第一步是指定分组变量,可以通过数据框的groupby“方法”完成;第二步是对不同的数值变
量计算各自的统计值。在第二步中,需要跟读者说明的是,必须以字典的形式控制变量名称和
统计函数(如上代码所示)。
通过这样的方式可以实现数值变量的聚合统计,但是最终的统计结果(如代码中的第一次
返回result)可能并不是你所预期的,例如数据框的变量顺序发生了改动,变量名应该是统计后
的别名;为了保证与SQL Server的结果一致,需要更改结果的变量名顺序(如代码中的第二次
返回result)和变量名的名称(如代码中的第三次返回result)。
通过几次的修改就可以得到如上结果中的左半部分。细心的读者一定会发现,分组变量
color和cut成了数据框的行索引。如果需要将这两个行索引转换为数据框的变量名,可以使用
数据框的reset_index方法(如倒数第二行代码所示),这样就可以得到右半部分的最终结果。

5.10 本章小结
本章重点介绍了有关数据处理过程中应用到的Pandas模块,内容涉及序列与数据框的创
建、外部数据的读取、变量类型的转换与描述性分析、字符型和日期型数据的处理、常用的数
据清洗方法、数据子集的生成、如何制作透视表、多表之间的合并与连接以及数据集的分组聚
合。通过本章的学习,读者可以掌握数据预处理过程中的绝大部分知识点,进而为之后的数据
分析和挖掘做铺垫。
由于内容比较多,为了使读者清晰地掌握本章所涉及的函数和“方法”,这里将这些函数
和“方法”重新梳理一下,以便读者查阅和记忆。

第6章
Python数据可视化
“文不如字,字不如表,表不如图”,说的就是可视化的重要性。从事与数据相关的工作者
经常会作一些总结或展望性的报告,如果报告中密密麻麻都是文字,相信听众或者老板一定
会厌烦;如果报告中呈现的是大量的图形化结果,就会受到众人的喜爱,因为图形更加直观、
醒目。
本章内容的重点就是利用Python绘制常见的统计图形,例如条形图、饼图、直方图、折线
图、散点图等,通过这些常用图形的展现,将复杂的数据简单化。这些图形的绘制可以通过
matplotlib模块、pandas模块或者seaborn模块实现。通过本章内容的学习,读者将会掌握以下几
个方面的知识点:
离散型数据都有哪些可用的可视化方法;
数值型的单变量可用哪些图形展现;
多维数值之间的关系表达;
如何将多个图形绘制到一个画框内。

6.1 离散型变量的可视化
如果你需要使用数据可视化的方法来表达离散型变量的分布特征,例如统计某APP用户
的性别比例、某产品在各区域的销售量分布、各年龄段内男女消费者的消费能力差异等。对于
类似这些离散型变量的统计描述,可以使用饼图或者条形图对其进行展现。接下来,通过具体
的案例来学习饼图和条形图的绘制,进而掌握Python的绘图技能。

6.1.1 饼图
饼图属于最传统的统计图形之一(1801年由William Playfair首次发布使用),它几乎随处
可见,例如大型公司的屏幕墙、各种年度论坛的演示稿以及各大媒体发布的数据统计报告等。
首先,需要读者了解有关饼图的原理。饼图是将一个圆分割成不同大小的楔形,而圆中的
每一个楔形代表了不同的类别值,通常会根据楔形的面积大小来判断类别值的差异。如图6-1
所示,就是一个由不同大小的楔形组成的饼图。

图6-1 饼图示意图

对于这样的饼图,该如何通过Python完成图形的绘制呢?其实很简单,通过matplotlib模块
和pandas模块都可以非常方便地得到一个漂亮的饼图。下面举例说明如何利用Python实现饼图
的绘制。

1.matplotlib模块
如果你选择matplotlib模块绘制饼图的话,首先需要导入该模块的子模块pyplot,然后调用
模块中的pie函数。关于该函数的语法和参数含义如下:
pie(x, explode=None, labels=None, colors=None,
autopct=None, pctdistance=0.6, shadow=False,
labeldistance=1.1, startangle=None,
radius=None, counterclock=True, wedgeprops=None,
textprops=None, center=(0, 0), frame=False)

x:指定绘图的数据。
explode:指定饼图某些部分的突出显示,即呈现爆炸式。
labels:为饼图添加标签说明,类似于图例说明。
colors:指定饼图的填充色。
autopct:自动添加百分比显示,可以采用格式化的方法显示。
pctdistance:设置百分比标签与圆心的距离。
shadow:是否添加饼图的阴影效果。
labeldistance:设置各扇形标签(图例)与圆心的距离。
startangle:设置饼图的初始摆放角度。
radius:设置饼图的半径大小。
counterclock:是否让饼图按逆时针顺序呈现。
wedgeprops:设置饼图内外边界的属性,如边界线的粗细、颜色等。

textprops:设置饼图中文本的属性,如字体大小、颜色等。
center:指定饼图的中心点位置,默认为原点。
frame:是否要显示饼图背后的图框,如果设置为True的话,需要同时控制图框x轴、y轴
的范围和饼图的中心位置。
该函数的参数虽然比较多,但是应用起来非常灵活,而且绘制的饼图也比较好看。下面
以“芝麻信用”失信用户数据为例(数据来源于财新网),分析近300万失信人群的学历分布,pie
函数绘制饼图的详细代码如下:

见图6-2。

图6-2 Matplotlib库绘制的饼图

图6-2所示就是一个不加任何修饰的饼图。这里只给pie函数传递了三个核心参数,即绘图
的数据、每个数据代表的含义(学历标签)以及给饼图添加数值标签。很显然,这样的饼图并不
是很完美,例如饼图看上去并不成正圆、饼图没有对应的标题、没有突出显示饼图中的某个部
分等。下面进一步对该饼图做一些修饰,尽可能让饼图看起来更加舒服,代码如下:

见图6-3。

图6-3 matplotlib库绘制的饼图

如上呈现的饼图,直观上要比之前的饼图好看很多,这些都是基于pie函数的灵活参数所
实现的。饼图中突出显示大专学历的人群,是因为在这300万失信人群中,大专学历的人数比

例最高,该功能就是通过explode参数完成的。另外,还需要对如上饼图的绘制说明几点:
如果绘制的图形中涉及中文及数字中的负号,都需要通过rcParams进行控制。
由于不加修饰的饼图更像是一个椭圆,所以需要pyplot模块中的axes函数将椭圆强制为
正圆。
自定义颜色的设置,既可以使用十六进制的颜色,也可以使用具体的颜色名称,如red、
black等。
如果需要添加图形的标题,需要调用pyplot模块中的title函数。
代码plt.show()用来呈现最终的图形,无论是使用Jupyter或Pycharm编辑器,都需要使用
这行代码呈现图形。

2.pandas模块
细心的读者一定会发现,在前面的几个章节中或多或少地应用到pandas模块的绘图“方
法”plot,该方法可以针对序列和数据框绘制常见的统计图形,例如折线图、条形图、直方图、
箱线图、核密度图等。同样,plot也可以绘制饼图,接下来简单介绍一下该方法针对序列的应用
和参数含义:
Series.plot(kind='line', ax=None, figsize=None, use_index=True, title=None,
grid=None, legend=False, style=None, logx=False, logy=False,
loglog=False, xticks=None, yticks=None, xlim=None, ylim=None,
rot=None, fontsize=None, colormap=None, table=False, yerr=None,
xerr=None, label=None, secondary_y=False, **kwds)

kind:指定一个字符串值,用于绘制图形的类型,默认为折线图line。还可以绘制垂直条
形图bar、水平条形图hbar、直方图hist、箱线图box、核密度图kde、面积图area和饼图pie。
ax:控制当前子图在组图中的位置。例如,在一个2×2的图形矩阵中,通过该参数控制当
前图形在矩阵中的位置。
figsize:控制图形的宽度和高度,以元组形式传递,即(width,hright)。
use_index:bool类型的参数,是否将序列的行索引用作x轴的刻度,默认为True。
title:用以添加图形的标题。
grid:bool类型的参数,是否给图形添加网格线,默认为False。
legend:bool类型的参数,是否添加子图的图例,默认为False。
style:如果kind为line,该参数可以控制折线图的线条类型。
logx:bool类型的参数,是否对x轴做对数变换,默认为False。
logy:bool类型的参数,是否对y轴做对数变换,默认为False。

loglog:bool类型的参数,是否同时对x轴和y轴做对数变换,默认为False。
xticks:用于设置x轴的刻度值。
yticks:用于设置y轴的刻度值。
xlim:以元组或列表的形式,设置x轴的取值范围,如(0,3)表示x轴落在0~3的范围之内。
ylim:以元组或列表的形式,设置y轴的取值范围。
rot:接受一个整数值,用于旋转刻度值的角度。
fontsize:接受一个整数,用于控制x轴与y轴刻度值的字体大小。
colormap:接受一个表示颜色含义的字符串,或者Python的色彩映射对象,该参数用于
设置图形的区域颜色。
table:该参数如果为True,表示在绘制图形的基础上再添加数据表;如果传递的是序列
或数据框,则根据数据添加数据表。
yerr:如果kind为bar或hbar,该参数表示在条形图的基础上添加误差棒。
xerr:含义同yerr参数。
label:用于添加图形的标签。
secondary_y:bool类型的参数,是否添加第二个y轴,默认为False。
**kwds:关键字参数,该参数可以根据不同的kind值,为图形添加更多的修饰性参数(依
赖于pyplot中的绘图函数)。
pandas模块中的plot“方法”可以根据kind参数绘制不同的统计图形,而且也包含了其他各
种灵活的参数。除此,根据不同的kind参数值,可以调用更多对应的关键字参数**kwds,这些
关键字参数都源于pyplot中的绘图函数。
为了帮助读者更好地理解plot方法绘制的统计图形,这里仍然以失信用户数据为例,绘制
学历的分布饼图,详细代码如下:

见图6-4。

图6-4 pandas库绘制的饼图

如图6-4所示,应用pandas模块中的plot方法,也可以得到一个比较好看的饼图。该方法中
除了kind参数和title参数属于plot方法,其他参数都是pyplot模块中pie函数的参数,并且以关键
字参数的形式调用。

6.1.2 条形图

虽然饼图可以很好地表达离散型变量在各水平上的差异(如会员的性别比例、学历差异、
等级高低等),但是其不擅长对比差异不大或水平值过多的离散型变量,因为饼图是通过各楔
形面积的大小来表示数值的高低,而人类对扇形面积的比较并不是特别敏感。如果读者手中
的数据恰好不适合用饼图展现,可以选择另一种常用的可视化方法,即条形图。
以垂直条形图为例,离散型变量在各水平上的差异就是比较柱形的高低,柱体越高,代表
的数值越大,反之亦然。在Python中,可以借助matplotlib、pandas和seaborn模块完成条形图的绘
制。下面将采用这三个模块绘制条形图。

1.matplotlib模块
应用matplotlib模块绘制条形图,需要调用bar函数,关于该函数的语法和参数含义如下:
bar(left, height, width=0.8, bottom=None, color=None, edgecolor=None,
linewidth=None, tick_label=None, xerr=None, yerr=None,
label = None, ecolor=None, align, log=False, **kwargs)

left:传递数值序列,指定条形图中x轴上的刻度值。
height:传递数值序列,指定条形图y轴上的高度。
width:指定条形图的宽度,默认为0.8。
bottom:用于绘制堆叠条形图。
color:指定条形图的填充色。
edgecolor:指定条形图的边框色。
linewidth:指定条形图边框的宽度,如果指定为0,表示不绘制边框。
tick_label:指定条形图的刻度标签。
xerr:如果参数不为None,表示在条形图的基础上添加误差棒。
yerr:参数含义同xerr。
label:指定条形图的标签,一般用以添加图例。
ecolor:指定条形图误差棒的颜色。
align:指定x轴刻度标签的对齐方式,默认为center,表示刻度标签居中对齐,如果设置
为edge,则表示在每个条形的左下角呈现刻度标签。
log:bool类型参数,是否对坐标轴进行log变换,默认为False。
**kwargs:关键字参数,用于对条形图进行其他设置,如透明度等。
bar函数的参数同样很多,希望读者能够认真地掌握每个参数的含义,以便使用时得心应
手。下面将基于该函数绘制三类条形图,分别是单变量的垂直或水平条形图、堆叠条形图和水

平交错条形图。
(1)垂直或水平条形图
首先来绘制单个离散变量的垂直或水平条形图,数据来源于互联网,反映的是2017年中
国六大省份的GDP,绘图代码如下:

见图6-5。

图6-5 matplotlib库绘制的垂直条形图

如图6-5所示,该条形图比较清晰地反映了6个省份GDP的差异。针对如上代码需要做几点

解释:
条形图中灰色网格的背景是通过代码plt.style.use('ggplot')实现的,如果不添加该行代
码,则条形图为白底背景。
如果添加图形的x轴或y轴标签,需要调用pyplot子模块中的xlab和ylab函数。
由于bar函数没有添加数值标签的参数,因此使用for循环对每一个柱体添加数值标签,
使用的核心函数是pyplot子模块中的text。该函数的参数很简单,前两个参数用于定位
字符在图形中的位置,第三个参数表示呈现的具体字符值,第四个参数为ha,表示字符
的水平对齐方式为居中对齐。
站在阅读者的角度来看,该条形图可能并不是很理想,因为不能快速地发现哪个省份
GDP最高或最低。如果将该条形图进行降序或升序处理,可能会更直观一些。这里就以水平条
形图为例,代码如下:

见图6-6。

图6-6 matplotlib库绘制的水平条形图

图6-6所示就是经过排序的水平条形图(实际上是垂直条形图的轴转置)。需要注意的是,
水平条形图不再是bar函数,而是barh函数。读者可能疑惑,为什么对原始数据做升序排序,但
是图形看上去是降序(从上往下)?那是因为水平条形图的y轴刻度值是从下往上布置的,所以
条形图从下往上是满足升序的。
(2)堆叠条形图
正如前文所介绍的,不管是垂直条形图还是水平条形图,都只是反映单个离散变量的统
计图形,如果想通过条形图传递两个离散变量的信息该如何做到?相信读者一定见过堆叠条
形图,该类型条形图的横坐标代表一个维度的离散变量,堆叠起来的“块”代表了另一个维度
的离散变量。这样的条形图,最大的优点是可以方便比较累积和,那这种条形图该如何通过
Python绘制呢?这里以2017年四个季度的产业值为例(数据来源于中国统计局),绘制堆叠条
形图,详细代码如下:

见图6-7。

图6-7 matplotlib库绘制的堆叠条形图

如上就是一个典型的堆叠条形图,虽然绘图的代码有些偏长,但是其思想还是比较简单
的,就是分别针对三种产业的产值绘制三次条形图。需要注意的是,第二产业的条形图是在第
一产业的基础上做了叠加,故需要将bottom参数设置为Industry1;而第三产业的条形图又是叠
加在第一和第二产业之上,所以需要将bottom参数设置为Industry1+ Industry2。
读者可能疑惑,通过条件判断将三种产业的值(Industry1、Industry2、Industry3)分别取出来
后,为什么还要重新设置行索引?那是因为各季度下每一种产业值前的行索引都不相同,这
就导致无法进行Industry1+ Industry2的和计算(读者不妨试试不改变序列Industry1和Industry2的
行索引的后果)。
(3)水平交错条形图
堆叠条形图可以包含两个离散变量的信息,而且可以比较各季度整体产值的高低水平,
但是其缺点是不易区分“块”之间的差异,例如二、三季度的第三产业值差异就不是很明显,区
分高低就相对困难。而交错条形图恰好就可以解决这个问题,该类型的条形图就是将堆叠
的“块”水平排开,如想绘制这样的条形图,可以参考下方代码(数据来源于胡润财富榜,反映
的是5个城市亿万资产超高净值家庭数):

见图6-8。

图6-8 matplotlib库绘制的水平交错条形图

图6-8反映的是2016年和2017年5大城市亿万资产家庭数的条形图,可以很好地比较不同
年份下的差异。例如,这5个城市中,2017年的亿万资产家庭数较2016年都有所增加。
但是对于这种数据,就不适合使用堆叠条形图,因为堆叠条形图可以反映总计的概念。如
果将2016年和2017年亿万资产家庭数堆叠计总,就会出现问题,因为大部分家庭数在这两年
内都被重复统计在胡润财富榜中,计算出来的总和会被扩大。
另外,再对如上的代码做三点解释,希望能够帮助读者解去疑惑:
如上的水平交错条形图,其实质就是使用两次bar函数,所不同的是,第二次bar函数使
得条形图往右偏了0.4个单位(left=np.arange(len(Cities))+bar_width),进而形成水平交错
条形图的效果。
每一个bar函数,都必须控制条形图的宽度(width=bar_width),否则会导致条形图的重
叠。
如果利用bar函数的tick_label参数添加条形图x轴上的刻度标签,会发现标签并不是居
中对齐在两个条形图之间,为了克服这个问题,使用了pyplot子模块中的xticks函数,并
且使刻度标签的位置向右移0.2个单位。

2.pandas模块
通过pandas模块绘制条形图仍然使用plot方法,该“方法”的语法和参数含义在前文已经详
细介绍过,但是plot方法存在一点瑕疵,那就是无法绘制堆叠条形图。下面通过该模块的plot方
法绘制单个离散变量的垂直条形图或水平条形图以及两个离散变量的水平交错条形图,代码
如下:

见图6-9。

图6-9 pandas库绘制的垂直条形图

只要掌握matplotlib模块绘制单个离散变量的条形图方法,就可以套用到pandas模块中的
plot方法,两者是相通的。读者可以尝试plot方法绘制水平条形图,这里就不再给出参考代码
了。接下来使用plot方法绘制含两个离散变量的水平交错条形图,具体代码如下:

见图6-10。

图6-10 长形表转宽形表

如上代码所示,应用plot方法绘制水平交错条形图,必须更改原始数据集的形状,即将两
个离散型变量的水平值分别布置到行与列中(代码中采用透视表的方法实现),最终形成的表
格变换如图6-10所示。
针对变换后的数据,可以使用plot方法实现水平交错条形图的绘制,从代码量来看,要比
使用matplotlib模块简短一些,得到的条形图如图6-11所示。

图6-11 pandas库绘制的水平交错条形图

3.seaborn模块绘制条形图
seaborn模块是一款专门用于绘制统计图形的利器,通过该模块写出来的代码也是非常通
俗易懂的。该模块并不在Anoconda集成工具中,故需要读者另行下载。下面就简单介绍一下如
何通过该模块完成条形图的绘制(同样无法绘制堆叠条形图)。

见图6-12。

图6-12 seaborn库绘制的水平条形图

如上代码就是通过seaborn模块中的barplot函数实现单个离散变量的条形图。除此之外,
seaborn模块中的barplot函数还可以绘制两个离散变量的水平交错条形图,所以有必要介绍一
下该函数的用法及重要参数含义:
sns.barplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None,
ci=95, n_boot=1000, orient=None, color=None, palette=None,
saturation=0.75, errcolor='.26', errwidth=None, dodge=True, ax=Non

x:指定条形图的x轴数据。
y:指定条形图的y轴数据。
hue:指定用于分组的另一个离散变量。
data:指定用于绘图的数据集。
order:传递一个字符串列表,用于分类变量的排序。
hur_order:传递一个字符串列表,用于分类变量hue值的排序。
ci:用于绘制条形图的误差棒(置信区间)。
n_boot:当指定ci参数时,可以通过n_boot参数控制自助抽样的迭代次数。
orient:指定水平或垂直条形图。
color:指定所有条形图所属的一种填充色。
palette:指定hue变量中各水平的颜色。
saturation:指定颜色的透明度。
errcolor:指定误差棒的颜色。
errwidth:指定误差棒的线宽。
capsize:指定误差棒两端线条的长度。
dodge:bool类型参数,当使用hue参数时,是否绘制水平交错条形图,默认为True。

ax:用于控制子图的位置。
**kwagrs:关键字参数,可以调用plt.bar函数中的其他参数。
为了说明如上函数中的参数,这里以泰坦尼克号数据集为例,绘制水平交错条形图,代码
如下:

见图6-13。

图6-13 seaborn库绘制的水平交错条形图

如图6-13所示,绘制的每一个条形图中都含有一条竖线,该竖线就是条形图的误差棒,即
各组别下年龄的标准差大小。从图6-13可知,三等舱的男性乘客年龄是最为接近的,因为标准

差最小。
需要注意的是,数据集Titanic并非汇总好的数据,是不可以直接应用到matplotlib模块中的
bar函数与pandas模块中的plot方法。如需使用,必须先对数据集进行分组聚合,关于分组聚合
的内容已经在第5章中介绍过,读者可以前去了解。

6.2 数值型变量的可视化
很多时候,我们拿到手的数据都包含大量的数值型变量,在对数值型变量进行探索和分
析时,一般都会应用到可视化方法。而本节的重点就是介绍如何使用Python实现数值型变量的
可视化,通过本节内容的学习,读者将会掌握如何使用matplotlib模块、pandas模块和seaborn模
块绘制直方图、核密度图、箱线图、小提琴图、折线图以及面积图。

6.2.1 直方图与核密度曲线
直方图一般用来观察数据的分布形态,横坐标代表数值的均匀分段,纵坐标代表每个段
内的观测数量(频数)。一般直方图都会与核密度图搭配使用,目的是更加清晰地掌握数据的
分布特征,下面将详细介绍该类型图形的绘制。

1.matplotlib模块
matplotlib模块中的hist函数就是用来绘制直方图的。关于该函数的语法及参数含义如下:
plt.hist(x, bins=10, range=None, normed=False,
weights=None, cumulative=False, bottom=None,
histtype='bar', align='mid', orientation='vertical',
rwidth=None, log=False, color=None,
label=None, stacked=False)

x:指定要绘制直方图的数据。
bins:指定直方图条形的个数。
range:指定直方图数据的上下界,默认包含绘图数据的最大值和最小值。
normed:是否将直方图的频数转换成频率。
weights:该参数可为每一个数据点设置权重。
cumulative:是否需要计算累计频数或频率。
bottom:可以为直方图的每个条形添加基准线,默认为0。
histtype:指定直方图的类型,默认为bar,除此之外,还有barstacked、step和stepfilled。

align:设置条形边界值的对齐方式,默认为mid,另外还有left和right。
orientation:设置直方图的摆放方向,默认为垂直方向。
rwidth:设置直方图条形的宽度。
log:是否需要对绘图数据进行log变换。
color:设置直方图的填充色。
edgecolor:设置直方图边框色。
label:设置直方图的标签,可通过legend展示其图例。
stacked:当有多个数据时,是否需要将直方图呈堆叠摆放,默认水平摆放。
这里不妨以Titanic数据集为例绘制乘客的年龄直方图,具体代码如下:

见图6-14。

图6-14 matplotlib库绘制的直方图

如图6-14所示,就是关于乘客年龄的直方图分布。需要注意的是,如果原始数据集中存在
缺失值,一定要对缺失观测进行删除或替换,否则无法绘制成功。如果在直方图的基础上再添
加核密度图,通过matplotlib模块就比较吃力了,因为首先得计算出每一个年龄对应的核密度
值。为了简单起见,下面利用pandas模块中的plot方法将直方图和核密度图绘制到一起。

2.pandas模块

见图6-15。

图6-15 matplotlib库绘制的直方图+核密度曲线

如图6-15所示,Python的核心代码就两行,分别是利用plot方法绘制直方图和核密度图。需
要注意的是,在直方图的基础上添加核密度图,必须将直方图的频数更改为频率,即normed参
数设置为True。

3.seaborn模块
尽管这幅图满足了两种图形的合成,但其表达的是所有乘客的年龄分布,如果按性别分
组,研究不同性别下年龄分布的差异,该如何实现?针对这个问题,使用matplotlib模块或
pandas模块都会稍微复杂一些,推荐使用seaborn模块中的distplot函数,因为该函数的代码简洁
而易懂。关于该函数的语法和参数含义如下:
sns.distplot(a, bins=None, hist=True, kde=True, rug=False, fit=None,
hist_kws=None, kde_kws=None, rug_kws=None, fit_kws=None,
color=None, vertical=False, norm_hist=False, axlabel=None,
label=None, ax=None)

a:指定绘图数据,可以是序列、一维数组或列表。
bins:指定直方图条形的个数。
hist:bool类型的参数,是否绘制直方图,默认为True。
kde:bool类型的参数,是否绘制核密度图,默认为True。
rug:bool类型的参数,是否绘制须图(如果数据比较密集,该参数比较有用),默认为
False。
fit:指定一个随机分布对象(需调用scipy模块中的随机分布函数),用于绘制随机分布的
概率密度曲线。

hist_kws:以字典形式传递直方图的其他修饰属性,如填充色、边框色、宽度等。
kde_kws:以字典形式传递核密度图的其他修饰属性,如线的颜色、线的类型等。
rug_kws:以字典形式传递须图的其他修饰属性,如线的颜色、线的宽度等。
fit_kws:以字典形式传递概率密度曲线的其他修饰属性,如线条颜色、形状、宽度等。
color:指定图形的颜色,除了随机分布曲线的颜色。
vertical:bool类型的参数,是否将图形垂直显示,默认为True。
norm_hist:bool类型的参数,是否将频数更改为频率,默认为False。
axlabel:用于显示轴标签。
label:指定图形的图例,需结合plt.legend()一起使用。
ax:指定子图的位置。
从函数的参数可知,通过该函数,可以实现三种图形的合成,分别是直方图(hist参数)、核
密度曲线(kde参数)以及指定的理论分布密度曲线(fit参数)。接下来,针对如上介绍的distplot
函数,绘制不同性别下乘客的年龄分布图,具体代码如下:

见图6-16。

图6-16 seaborn库绘制的直方图+核密度曲线

如图6-16所示,为了避免四个图形混在一起不易发现数据背后的特征,将直方图与核密度
图分开绘制。从直方图来看,女性年龄的分布明显比男性矮,说明在各年龄段下,男性乘客要
比女性乘客多;再看核密度图,男女性别的年龄分布趋势比较接近,说明各年龄段下的男女乘
客人数同步增加或减少。

6.2.2 箱线图
箱线图是另一种体现数据分布的图形,通过该图可以得知数据的下须值(Q1-1.5IQR)、下
四分位数(Q1)、中位数(Q2)、均值、上四分位(Q3)数和上须值(Q3+1.5IQR),更重要的是,箱
线图还可以发现数据中的异常点。
箱线图的绘制仍然可以通过matplotlib模块、pandas模块和seaborn模块完成,下面将一一
介绍各模块绘制条形图的过程。

1.matplotlib模块
首先介绍一下matplotlib模块中绘制箱线图的boxplot函数,有关该函数的语法和参数含义
如下:
plt.boxplot(x, notch=None, sym=None, vert=None,
whis=None, positions=None, widths=None,
patch_artist=None, meanline=None, showmeans=None,
showcaps=None, showbox=None, showfliers=None,
boxprops=None, labels=None, flierprops=None,
medianprops=None, meanprops=None,
capprops=None, whiskerprops=None)

x:指定要绘制箱线图的数据。
notch:是否以凹口的形式展现箱线图,默认非凹口。
sym:指定异常点的形状,默认为+号显示。
vert:是否需要将箱线图垂直摆放,默认垂直摆放。
whis:指定上下须与上下四分位的距离,默认为1.5倍的四分位差。
positions:指定箱线图的位置,默认为[0,1,2…]。
widths:指定箱线图的宽度,默认为0.5。
patch_artist:bool类型参数,是否填充箱体的颜色;默认为False。
meanline:bool类型参数,是否用线的形式表示均值,默认为False。
showmeans:bool类型参数,是否显示均值,默认为False。
showcaps:bool类型参数,是否显示箱线图顶端和末端的两条线(即上下须),默认为
True。
showbox:bool类型参数,是否显示箱线图的箱体,默认为True。
showfliers:是否显示异常值,默认为True。
boxprops:设置箱体的属性,如边框色,填充色等。
labels:为箱线图添加标签,类似于图例的作用。
filerprops:设置异常值的属性,如异常点的形状、大小、填充色等。
medianprops:设置中位数的属性,如线的类型、粗细等。
meanprops:设置均值的属性,如点的大小、颜色等。
capprops:设置箱线图顶端和末端线条的属性,如颜色、粗细等。
whiskerprops:设置须的属性,如颜色、粗细、线的类型等。
为读者方便理解boxplot函数的用法,这里以某平台二手房数据为例,运用箱线图探究其
二手房单价的分布情况,具体代码如下:

见图6-17。

图6-17 matplotlib库绘制的箱线图

如图6-17所示,图中的上下两条横线代表上下须、箱体的上下两条横线代表上下四分位
数、箱体中的虚线代表中位数、箱体中的点则为均值、上下须两端的点代表异常值。通过图中
均值和中位数的对比就可以得知数据微微右偏(判断标准:如果数据近似正态分布,则众数=
中位数=均值;如果数据右偏,则众数<中位数<均值;如果数值左偏,则众数>中位数>均值)。
如上绘制的是二手房整体单价的箱线图,这样的箱线图可能并不常见,更多的是分组箱
线图,即二手房的单价按照其他分组变量(如行政区域、楼层、朝向等)进行对比分析。下面继
续使用matplotlib模块对二手房的单价绘制分组箱线图,代码如下:

见图6-18。

图6-18 matplotlib库绘制的分组箱线图

应用matplotlib模块绘制如上所示的分组箱线图会相对烦琐一些,由于boxplot函数每次只
能绘制一个箱线图,为了能够实现多个箱线图的绘制,对数据稍微做了一些变动,即将每个行
政区域下的二手房单价汇总到一个列表中,然后基于这个大列表应用boxplot函数。在绘图过
程中,首先做了一个“手脚”,那就是统计各行政区域二手房的平均单价,并降序排序,这样做

的目的就是让分组箱线图能够降序呈现。
虽然pandas模块中的plot方法可以绘制分组箱线图,但是该方法是基于数据框执行的,并
且数据框的每一列对应一个箱线图。对于二手房数据集来说,应用plot方法绘制分组箱线图不
太合适,因为每一个行政区的二手房数量不一致,将导致无法重构一个新的数据框用于绘图。

2.seaborn模块
如果读者觉得matplotlib模块绘制分组箱线图比较麻烦,可以使用seaborn模块中的boxplot
函数。下面不妨先了解一下该函数的参数含义:
sns.boxplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None,
orient=None, color=None, palette=None, saturation=0.75, width=0.8,
dodge=True, fliersize=5, linewidth=None, whis=1.5, notch=False, ax=N

x:指定箱线图的x轴数据。
y:指定箱线图的y轴数据。
hue:指定分组变量。
data:指定用于绘图的数据集。
order:传递一个字符串列表,用于分类变量的排序。
hue_order:传递一个字符串列表,用于分类变量hue值的排序。
orient:指定箱线图的呈现方向,默认为垂直方向。
color:指定所有箱线图的填充色。
palette:指定hue变量的区分色。
saturation:指定颜色的透明度。
width:指定箱线图的宽度。
dodge:bool类型的参数,当使用hue参数时,是否绘制水平交错的箱线图,默认为True。
fliersize:指定异常值点的大小。
linewidth:指定箱体边框的宽度。
whis:指定上下须与上下四分位的距离,默认为1.5倍的四分位差。
notch:bool类型的参数,是否绘制凹口箱线图,默认为False。
ax:指定子图的位置。
**kwargs:关键字参数,可以调用plt.boxplot函数中的其他参数。
这里仍以上海二手房数据为例,应用seaborn模块中的boxplot函数绘制分组箱线图,详细
代码如下:

通过如上代码,同样可以得到完全一致的分组箱线图。这里建议读者不要直接学习和使
用pandas模块和seaborn模块绘制统计图形,而是先把matplotlib模块摸透,因为Python的核心绘
图模块是matplotlib。

6.2.3 小提琴图
小提琴图是比较有意思的统计图形,它将数值型数据的核密度图与箱线图融合在一起,
进而得到一个形似小提琴的图形。尽管matplotlib模块也提供了绘制小提琴图的函数
violinplot,但是绘制出来的图形中并不包含一个完整的箱线图,所以本节将直接使用seaborn
模块中的violinplot函数绘制小提琴图。首先,带领读者了解一下有关violinplot函数的语法和参
数含义:
sns.violinplot(x=None, y=None, hue=None, data=None, order=None, hue_order=No
bw='scott', cut=2, scale='area', scale_hue=True, gridsize=100,
width=0.8, inner='box', split=False, dodge=True, orient=None,
linewidth=None, color=None, palette=None, saturation=0.75, ax=None)

x:指定小提琴图的x轴数据。
y:指定小提琴图的y轴数据。
hue:指定一个分组变量。
data:指定绘制小提琴图的数据集。
order:传递一个字符串列表,用于分类变量的排序。
hue_order:传递一个字符串列表,用于分类变量hue值的排序。
bw:指定核密度估计的带宽,带宽越大,密度曲线越光滑。
scale:用于调整小提琴图左右的宽度,如果为area,则表示每个小提琴图左右部分拥有
相同的面积;如果为count,则表示根据样本数量来调节宽度;如果为width,则表示每个

小提琴图左右两部分拥有相同的宽度。
scale_hue:bool类型参数,当使用hue参数时,是否对hue变量的每个水平做标准化处理,
默认为True。
width:使用hue参数时,用于控制小提琴图的宽度。
inner:指定小提琴图内部数据点的形态,如果为box,则表示绘制微型的箱线图;如果为
quartiles,则表示绘制四分位的分布图;如果为point或stick,则表示绘制点或小竖条。
split:bool类型参数,使用hue参数时,将小提琴图从中间分为两个不同的部分,默认为
False。
dodge:bool类型的参数,当使用hue参数时,是否绘制水平交错的小提琴图,默认为
True。
orient:指定小提琴图的呈现方向,默认为垂直方向。
linewidth:指定小提琴图的所有线条宽度。
color:指定小提琴图的颜色,该参数与palette参数一起使用时无效。
palette:指定hue变量的区分色。
saturation:指定颜色的透明度。
ax:指定子图的位置。
接下来,