From 33e38b51b3c1353f0d0d950b84db69a03e961010 Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Fri, 1 Jan 2021 11:53:51 +0800 Subject: [PATCH] 2021-01-01 11:53:51 --- docs/1.md | 188 ++++++++++++++++---------------- docs/10.md | 188 ++++++++++++++++---------------- docs/11.md | 276 +++++++++++++++++++++++------------------------ docs/12.md | 106 +++++++++--------- docs/13.md | 130 +++++++++++----------- docs/14.md | 220 ++++++++++++++++++------------------- docs/2.md | 282 ++++++++++++++++++++++++------------------------ docs/3.md | 200 +++++++++++++++++----------------- docs/4.md | 274 +++++++++++++++++++++++----------------------- docs/5.md | 310 ++++++++++++++++++++++++++--------------------------- docs/6.md | 192 ++++++++++++++++----------------- docs/7.md | 198 +++++++++++++++++----------------- docs/8.md | 166 ++++++++++++++-------------- docs/9.md | 260 ++++++++++++++++++++++---------------------- docs/a.md | 270 +++++++++++++++++++++++----------------------- docs/b.md | 192 ++++++++++++++++----------------- 16 files changed, 1726 insertions(+), 1726 deletions(-) diff --git a/docs/1.md b/docs/1.md index b121d93..40e8916 100644 --- a/docs/1.md +++ b/docs/1.md @@ -2,7 +2,7 @@ ## 1.1 本书的内容 -本书讲的是利用Python进行数据控制、处理、整理、分析等方面的具体细节和基本要点。我的目标是介绍Python编程和用于数据处理的库和工具环境,掌握这些,可以让你成为一个数据分析专家。虽然本书的标题是“数据分析”,重点却是Python编程、库,以及用于数据分析的工具。这就是数据分析要用到的Python编程。 +本书讲的是利用 Python 进行数据控制、处理、整理、分析等方面的具体细节和基本要点。我的目标是介绍 Python 编程和用于数据处理的库和工具环境,掌握这些,可以让你成为一个数据分析专家。虽然本书的标题是“数据分析”,重点却是 Python 编程、库,以及用于数据分析的工具。这就是数据分析要用到的 Python 编程。 ### 什么样的数据? @@ -10,110 +10,110 @@ * 表格型数据,其中各列可能是不同的类型(字符串、数值、日期等)。比如保存在关系型数据库中或以制表符/逗号为分隔符的文本文件中的那些数据。 * 多维数组(矩阵)。 -* 通过关键列(对于SQL用户而言,就是主键和外键)相互联系的多个表。 +* 通过关键列(对于 SQL 用户而言,就是主键和外键)相互联系的多个表。 * 间隔平均或不平均的时间序列。 这绝不是一个完整的列表。大部分数据集都能被转化为更加适合分析和建模的结构化形式,虽然有时这并不是很明显。如果不行的话,也可以将数据集的特征提取为某种结构化形式。例如,一组新闻文章可以被处理为一张词频表,而这张词频表就可以用于情感分析。 -大部分电子表格软件(比如Microsoft Excel,它可能是世界上使用最广泛的数据分析工具了)的用户不会对此类数据感到陌生。 +大部分电子表格软件(比如 Microsoft Excel,它可能是世界上使用最广泛的数据分析工具了)的用户不会对此类数据感到陌生。 -## 1.2 为什么要使用Python进行数据分析 +## 1.2 为什么要使用 Python 进行数据分析 -许许多多的人(包括我自己)都很容易爱上Python这门语言。自从1991年诞生以来,Python现在已经成为最受欢迎的动态编程语言之一,其他还有Perl、Ruby等。由于拥有大量的Web框架(比如Rails(Ruby)和Django(Python)),自从2005年,使用Python和Ruby进行网站建设工作非常流行。这些语言常被称作脚本(scripting)语言,因为它们可以用于编写简短而粗糙的小程序(也就是脚本)。我个人并不喜欢“脚本语言”这个术语,因为它好像在说这些语言无法用于构建严谨的软件。在众多解释型语言中,由于各种历史和文化的原因,Python发展出了一个巨大而活跃的科学计算(scientific computing)社区。在过去的10年,Python从一个边缘或“自担风险”的科学计算语言,成为了数据科学、机器学习、学界和工业界软件开发最重要的语言之一。 +许许多多的人(包括我自己)都很容易爱上 Python 这门语言。自从 1991 年诞生以来,Python 现在已经成为最受欢迎的动态编程语言之一,其他还有 Perl、Ruby 等。由于拥有大量的 Web 框架(比如 Rails(Ruby)和 Django(Python)),自从 2005 年,使用 Python 和 Ruby 进行网站建设工作非常流行。这些语言常被称作脚本(scripting)语言,因为它们可以用于编写简短而粗糙的小程序(也就是脚本)。我个人并不喜欢“脚本语言”这个术语,因为它好像在说这些语言无法用于构建严谨的软件。在众多解释型语言中,由于各种历史和文化的原因,Python 发展出了一个巨大而活跃的科学计算(scientific computing)社区。在过去的 10 年,Python 从一个边缘或“自担风险”的科学计算语言,成为了数据科学、机器学习、学界和工业界软件开发最重要的语言之一。 -在数据分析、交互式计算以及数据可视化方面,Python将不可避免地与其他开源和商业的领域特定编程语言/工具进行对比,如R、MATLAB、SAS、Stata等。近年来,由于Python的库(例如pandas和scikit-learn)不断改良,使其成为数据分析任务的一个优选方案。结合其在通用编程方面的强大实力,我们完全可以只使用Python这一种语言构建以数据为中心的应用。 +在数据分析、交互式计算以及数据可视化方面,Python 将不可避免地与其他开源和商业的领域特定编程语言/工具进行对比,如 R、MATLAB、SAS、Stata 等。近年来,由于 Python 的库(例如 pandas 和 scikit-learn)不断改良,使其成为数据分析任务的一个优选方案。结合其在通用编程方面的强大实力,我们完全可以只使用 Python 这一种语言构建以数据为中心的应用。 -### Python作为胶水语言 +### Python 作为胶水语言 -Python成为成功的科学计算工具的部分原因是,它能够轻松地集成C、C++以及Fortran代码。大部分现代计算环境都利用了一些Fortran和C库来实现线性代数、优选、积分、快速傅里叶变换以及其他诸如此类的算法。许多企业和国家实验室也利用Python来“粘合”那些已经用了多年的遗留软件系统。 +Python 成为成功的科学计算工具的部分原因是,它能够轻松地集成 C、C++以及 Fortran 代码。大部分现代计算环境都利用了一些 Fortran 和 C 库来实现线性代数、优选、积分、快速傅里叶变换以及其他诸如此类的算法。许多企业和国家实验室也利用 Python 来“粘合”那些已经用了多年的遗留软件系统。 -大多数软件都是由两部分代码组成的:少量需要占用大部分执行时间的代码,以及大量不经常执行的“胶水代码”。大部分情况下,胶水代码的执行时间是微不足道的。开发人员的精力几乎都是花在优化计算瓶颈上面,有时更是直接转用更低级的语言(比如C)。 +大多数软件都是由两部分代码组成的:少量需要占用大部分执行时间的代码,以及大量不经常执行的“胶水代码”。大部分情况下,胶水代码的执行时间是微不足道的。开发人员的精力几乎都是花在优化计算瓶颈上面,有时更是直接转用更低级的语言(比如 C)。 ### 解决“两种语言”问题 -很多组织通常都会用一种类似于领域特定的计算语言(如SAS和R)对新想法做研究、原型构建和测试,然后再将这些想法移植到某个更大的生产系统中去(可能是用Java、C\#或C++编写的)。人们逐渐意识到,Python不仅适用于研究和原型构建,同时也适用于构建生产系统。为什么一种语言就够了,却要使用两个语言的开发环境呢?我相信越来越多的企业也会这样看,因为研究人员和工程技术人员使用同一种编程工具将会给企业带来非常显著的组织效益。 +很多组织通常都会用一种类似于领域特定的计算语言(如 SAS 和 R)对新想法做研究、原型构建和测试,然后再将这些想法移植到某个更大的生产系统中去(可能是用 Java、C\#或 C++编写的)。人们逐渐意识到,Python 不仅适用于研究和原型构建,同时也适用于构建生产系统。为什么一种语言就够了,却要使用两个语言的开发环境呢?我相信越来越多的企业也会这样看,因为研究人员和工程技术人员使用同一种编程工具将会给企业带来非常显著的组织效益。 -### 为什么不选Python +### 为什么不选 Python -虽然Python非常适合构建分析应用以及通用系统,但它对不少应用场景适用性较差。 +虽然 Python 非常适合构建分析应用以及通用系统,但它对不少应用场景适用性较差。 -由于Python是一种解释型编程语言,因此大部分Python代码都要比用编译型语言(比如Java和C++)编写的代码运行慢得多。由于程序员的时间通常都比CPU时间值钱,因此许多人也愿意对此做一些取舍。但是,在那些延迟要求非常小或高资源利用率的应用中(例如高频交易系统),耗费时间使用诸如C++这样更低级、更低生产率的语言进行编程也是值得的。 +由于 Python 是一种解释型编程语言,因此大部分 Python 代码都要比用编译型语言(比如 Java 和 C++)编写的代码运行慢得多。由于程序员的时间通常都比 CPU 时间值钱,因此许多人也愿意对此做一些取舍。但是,在那些延迟要求非常小或高资源利用率的应用中(例如高频交易系统),耗费时间使用诸如 C++这样更低级、更低生产率的语言进行编程也是值得的。 -对于高并发、多线程的应用程序而言(尤其是拥有许多计算密集型线程的应用程序),Python并不是一种理想的编程语言。这是因为Python有一个叫做全局解释器锁(Global Interpreter Lock,GIL)的组件,这是一种防止解释器同时执行多条Python字节码指令的机制。有关“为什么会存在GIL”的技术性原因超出了本书的范围。虽然很多大数据处理应用程序为了能在较短的时间内完成数据集的处理工作都需要运行在计算机集群上,但是仍然有一些情况需要用单进程多线程系统来解决。 +对于高并发、多线程的应用程序而言(尤其是拥有许多计算密集型线程的应用程序),Python 并不是一种理想的编程语言。这是因为 Python 有一个叫做全局解释器锁(Global Interpreter Lock,GIL)的组件,这是一种防止解释器同时执行多条 Python 字节码指令的机制。有关“为什么会存在 GIL”的技术性原因超出了本书的范围。虽然很多大数据处理应用程序为了能在较短的时间内完成数据集的处理工作都需要运行在计算机集群上,但是仍然有一些情况需要用单进程多线程系统来解决。 -这并不是说Python不能执行真正的多线程并行代码。例如,Python的C插件使用原生的C或C++的多线程,可以并行运行而不被GIL影响,只要它们不频繁地与Python对象交互。 +这并不是说 Python 不能执行真正的多线程并行代码。例如,Python 的 C 插件使用原生的 C 或 C++的多线程,可以并行运行而不被 GIL 影响,只要它们不频繁地与 Python 对象交互。 -## 1.3 重要的Python库 +## 1.3 重要的 Python 库 -考虑到那些还不太了解Python科学计算生态系统和库的读者,下面我先对各个库做一个简单的介绍。 +考虑到那些还不太了解 Python 科学计算生态系统和库的读者,下面我先对各个库做一个简单的介绍。 -NumPy NumPy(Numerical Python的简称)是Python科学计算的基础包。本书大部分内容都基于NumPy以及构建于其上的库。它提供了以下功能(不限于此): +NumPy NumPy(Numerical Python 的简称)是 Python 科学计算的基础包。本书大部分内容都基于 NumPy 以及构建于其上的库。它提供了以下功能(不限于此): -* 快速高效的多维数组对象ndarray。 +* 快速高效的多维数组对象 ndarray。 * 用于对数组执行元素级计算以及直接对数组执行数学运算的函数。 * 用于读写硬盘上基于数组的数据集的工具。 * 线性代数运算、傅里叶变换,以及随机数生成。 - -成熟的C API, 用于Python插件和原生C、C++、Fortran代码访问NumPy的数据结构和计算工具。 + -成熟的 C API, 用于 Python 插件和原生 C、C++、Fortran 代码访问 NumPy 的数据结构和计算工具。 -除了为Python提供快速的数组处理能力,NumPy在数据分析方面还有另外一个主要作用,即作为在算法和库之间传递数据的容器。对于数值型数据,NumPy数组在存储和处理数据时要比内置的Python数据结构高效得多。此外,由低级语言(比如C和Fortran)编写的库可以直接操作NumPy数组中的数据,无需进行任何数据复制工作。因此,许多Python的数值计算工具要么使用NumPy数组作为主要的数据结构,要么可以与NumPy进行无缝交互操作。 +除了为 Python 提供快速的数组处理能力,NumPy 在数据分析方面还有另外一个主要作用,即作为在算法和库之间传递数据的容器。对于数值型数据,NumPy 数组在存储和处理数据时要比内置的 Python 数据结构高效得多。此外,由低级语言(比如 C 和 Fortran)编写的库可以直接操作 NumPy 数组中的数据,无需进行任何数据复制工作。因此,许多 Python 的数值计算工具要么使用 NumPy 数组作为主要的数据结构,要么可以与 NumPy 进行无缝交互操作。 ### pandas -pandas提供了快速便捷处理结构化数据的大量数据结构和函数。自从2010年出现以来,它助使Python成为强大而高效的数据分析环境。本书用得最多的pandas对象是DataFrame,它是一个面向列(column-oriented)的二维表结构,另一个是Series,一个一维的标签化数组对象。 +pandas 提供了快速便捷处理结构化数据的大量数据结构和函数。自从 2010 年出现以来,它助使 Python 成为强大而高效的数据分析环境。本书用得最多的 pandas 对象是 DataFrame,它是一个面向列(column-oriented)的二维表结构,另一个是 Series,一个一维的标签化数组对象。 -pandas兼具NumPy高性能的数组计算功能以及电子表格和关系型数据库(如SQL)灵活的数据处理功能。它提供了复杂精细的索引功能,能更加便捷地完成重塑、切片和切块、聚合以及选取数据子集等操作。因为数据操作、准备、清洗是数据分析最重要的技能,pandas是本书的重点。 +pandas 兼具 NumPy 高性能的数组计算功能以及电子表格和关系型数据库(如 SQL)灵活的数据处理功能。它提供了复杂精细的索引功能,能更加便捷地完成重塑、切片和切块、聚合以及选取数据子集等操作。因为数据操作、准备、清洗是数据分析最重要的技能,pandas 是本书的重点。 -作为背景,我是在2008年初开始开发pandas的,那时我任职于AQR Capital Management,一家量化投资管理公司,我有许多工作需求都不能用任何单一的工具解决: +作为背景,我是在 2008 年初开始开发 pandas 的,那时我任职于 AQR Capital Management,一家量化投资管理公司,我有许多工作需求都不能用任何单一的工具解决: * 有标签轴的数据结构,支持自动或清晰的数据对齐。这可以防止由于数据不对齐,或处理来源不同的索引不同的数据,所造成的错误。 * 集成时间序列功能。 * 相同的数据结构用于处理时间序列数据和非时间序列数据。 * 保存元数据的算术运算和压缩。 * 灵活处理缺失数据。 -* 合并和其它流行数据库(例如基于SQL的数据库)的关系操作。 +* 合并和其它流行数据库(例如基于 SQL 的数据库)的关系操作。 -我想只用一种工具就实现所有功能,并使用通用软件开发语言。Python是一个不错的候选语言,但是此时没有集成的数据结构和工具来实现。我一开始就是想把pandas设计为一款适用于金融和商业分析的工具,pandas专注于深度时间序列功能和工具,适用于时间索引化的数据。 +我想只用一种工具就实现所有功能,并使用通用软件开发语言。Python 是一个不错的候选语言,但是此时没有集成的数据结构和工具来实现。我一开始就是想把 pandas 设计为一款适用于金融和商业分析的工具,pandas 专注于深度时间序列功能和工具,适用于时间索引化的数据。 -对于使用R语言进行统计计算的用户,肯定不会对DataFrame这个名字感到陌生,因为它源自于R的data.frame对象。但与Python不同,data frames是构建于R和它的标准库。因此,pandas的许多功能不属于R或它的扩展包。 +对于使用 R 语言进行统计计算的用户,肯定不会对 DataFrame 这个名字感到陌生,因为它源自于 R 的 data.frame 对象。但与 Python 不同,data frames 是构建于 R 和它的标准库。因此,pandas 的许多功能不属于 R 或它的扩展包。 -pandas这个名字源于panel data(面板数据,这是多维结构化数据集在计量经济学中的术语)以及Python data analysis(Python数据分析)。 +pandas 这个名字源于 panel data(面板数据,这是多维结构化数据集在计量经济学中的术语)以及 Python data analysis(Python 数据分析)。 ### matplotlib -matplotlib是最流行的用于绘制图表和其它二维数据可视化的Python库。它最初由John D.Hunter(JDH)创建,目前由一个庞大的开发团队维护。它非常适合创建出版物上用的图表。虽然还有其它的Python可视化库,matplotlib却是使用最广泛的,并且它和其它生态工具配合也非常完美。我认为,可以使用它作为默认的可视化工具。 +matplotlib 是最流行的用于绘制图表和其它二维数据可视化的 Python 库。它最初由 John D.Hunter(JDH)创建,目前由一个庞大的开发团队维护。它非常适合创建出版物上用的图表。虽然还有其它的 Python 可视化库,matplotlib 却是使用最广泛的,并且它和其它生态工具配合也非常完美。我认为,可以使用它作为默认的可视化工具。 -### IPython和Jupyter +### IPython 和 Jupyter -IPython项目起初是Fernando Pérez在2001年的一个用以加强和Python交互的子项目。在随后的16年中,它成为了Python数据栈最重要的工具之一。虽然IPython本身没有提供计算和数据分析的工具,它却可以大大提高交互式计算和软件开发的生产率。IPython鼓励“执行-探索”的工作流,区别于其它编程软件的“编辑-编译-运行”的工作流。它还可以方便地访问系统的shell和文件系统。因为大部分的数据分析代码包括探索、试错和重复,IPython可以使工作更快。 +IPython 项目起初是 Fernando Pérez 在 2001 年的一个用以加强和 Python 交互的子项目。在随后的 16 年中,它成为了 Python 数据栈最重要的工具之一。虽然 IPython 本身没有提供计算和数据分析的工具,它却可以大大提高交互式计算和软件开发的生产率。IPython 鼓励“执行-探索”的工作流,区别于其它编程软件的“编辑-编译-运行”的工作流。它还可以方便地访问系统的 shell 和文件系统。因为大部分的数据分析代码包括探索、试错和重复,IPython 可以使工作更快。 -2014年,Fernando和IPython团队宣布了Jupyter项目,一个更宽泛的多语言交互计算工具的计划。IPython web notebook变成了Jupyter notebook,现在支持40种编程语言。IPython现在可以作为Jupyter使用Python的内核(一种编程语言模式)。 +2014 年,Fernando 和 IPython 团队宣布了 Jupyter 项目,一个更宽泛的多语言交互计算工具的计划。IPython web notebook 变成了 Jupyter notebook,现在支持 40 种编程语言。IPython 现在可以作为 Jupyter 使用 Python 的内核(一种编程语言模式)。 -IPython变成了Jupyter庞大开源项目(一个交互和探索式计算的高效环境)中的一个组件。它最老也是最简单的模式,现在是一个用于编写、测试、调试Python代码的强化shell。你还可以使用通过Jupyter Notebook,一个支持多种语言的交互式网络代码“笔记本”,来使用IPython。IPython shell 和Jupyter notebooks特别适合进行数据探索和可视化。 +IPython 变成了 Jupyter 庞大开源项目(一个交互和探索式计算的高效环境)中的一个组件。它最老也是最简单的模式,现在是一个用于编写、测试、调试 Python 代码的强化 shell。你还可以使用通过 Jupyter Notebook,一个支持多种语言的交互式网络代码“笔记本”,来使用 IPython。IPython shell 和 Jupyter notebooks 特别适合进行数据探索和可视化。 -Jupyter notebooks还可以编写Markdown和HTML内容,它提供了一种创建代码和文本的富文本方法。其它编程语言也在Jupyter中植入了内核,好让在Jupyter中可以使用Python以外的语言。 +Jupyter notebooks 还可以编写 Markdown 和 HTML 内容,它提供了一种创建代码和文本的富文本方法。其它编程语言也在 Jupyter 中植入了内核,好让在 Jupyter 中可以使用 Python 以外的语言。 -对我个人而言,我的大部分Python工作都要用到IPython,包括运行、调试和测试代码。 +对我个人而言,我的大部分 Python 工作都要用到 IPython,包括运行、调试和测试代码。 -在本书的GitHub页面,你可以找到包含各章节所有代码实例的Jupyter notebooks。 +在本书的 GitHub 页面,你可以找到包含各章节所有代码实例的 Jupyter notebooks。 ### SciPy -SciPy是一组专门解决科学计算中各种标准问题域的包的集合,主要包括下面这些包: +SciPy 是一组专门解决科学计算中各种标准问题域的包的集合,主要包括下面这些包: * scipy.integrate:数值积分例程和微分方程求解器。 -* scipy.linalg:扩展了由numpy.linalg提供的线性代数例程和矩阵分解功能。 +* scipy.linalg:扩展了由 numpy.linalg 提供的线性代数例程和矩阵分解功能。 * scipy.optimize:函数优化器(最小化器)以及根查找算法。 * scipy.signal:信号处理工具。 * scipy.sparse:稀疏矩阵和稀疏线性系统求解器。 -* scipy.special:SPECFUN(这是一个实现了许多常用数学函数(如伽玛函数)的Fortran库)的包装器。 +* scipy.special:SPECFUN(这是一个实现了许多常用数学函数(如伽玛函数)的 Fortran 库)的包装器。 * scipy.stats:标准连续和离散概率分布(如密度函数、采样器、连续分布函数等)、各种统计检验方法,以及更好的描述统计法。 -NumPy和SciPy结合使用,便形成了一个相当完备和成熟的计算平台,可以处理多种传统的科学计算问题。 +NumPy 和 SciPy 结合使用,便形成了一个相当完备和成熟的计算平台,可以处理多种传统的科学计算问题。 ### scikit-learn -2010年诞生以来,scikit-learn成为了Python的通用机器学习工具包。仅仅七年,就汇聚了全世界超过1500名贡献者。它的子模块包括: +2010 年诞生以来,scikit-learn 成为了 Python 的通用机器学习工具包。仅仅七年,就汇聚了全世界超过 1500 名贡献者。它的子模块包括: * 分类:SVM、近邻、随机森林、逻辑回归等等。 * 回归:Lasso、岭回归等等。 @@ -122,33 +122,33 @@ NumPy和SciPy结合使用,便形成了一个相当完备和成熟的计算平 * 选型:网格搜索、交叉验证、度量。 * 预处理:特征提取、标准化。 -与pandas、statsmodels和IPython一起,scikit-learn对于Python成为高效数据科学编程语言起到了关键作用。虽然本书不会详细讲解scikit-learn,我会简要介绍它的一些模型,以及用其它工具如何使用这些模型。 +与 pandas、statsmodels 和 IPython 一起,scikit-learn 对于 Python 成为高效数据科学编程语言起到了关键作用。虽然本书不会详细讲解 scikit-learn,我会简要介绍它的一些模型,以及用其它工具如何使用这些模型。 ### statsmodels -statsmodels是一个统计分析包,起源于斯坦福大学统计学教授Jonathan Taylor,他设计了多种流行于R语言的回归分析模型。Skipper Seabold和Josef Perktold在2010年正式创建了statsmodels项目,随后汇聚了大量的使用者和贡献者。受到R的公式系统的启发,Nathaniel Smith发展出了Patsy项目,它提供了statsmodels的公式或模型的规范框架。 +statsmodels 是一个统计分析包,起源于斯坦福大学统计学教授 Jonathan Taylor,他设计了多种流行于 R 语言的回归分析模型。Skipper Seabold 和 Josef Perktold 在 2010 年正式创建了 statsmodels 项目,随后汇聚了大量的使用者和贡献者。受到 R 的公式系统的启发,Nathaniel Smith 发展出了 Patsy 项目,它提供了 statsmodels 的公式或模型的规范框架。 -与scikit-learn比较,statsmodels包含经典统计学和经济计量学的算法。包括如下子模块: +与 scikit-learn 比较,statsmodels 包含经典统计学和经济计量学的算法。包括如下子模块: * 回归模型:线性回归,广义线性模型,健壮线性模型,线性混合效应模型等等。 * 方差分析(ANOVA)。 -* 时间序列分析:AR,ARMA,ARIMA,VAR和其它模型。 +* 时间序列分析:AR,ARMA,ARIMA,VAR 和其它模型。 * 非参数方法: 核密度估计,核回归。 * 统计模型结果可视化。 -statsmodels更关注与统计推断,提供不确定估计和参数p-值。相反的,scikit-learn注重预测。 +statsmodels 更关注与统计推断,提供不确定估计和参数 p-值。相反的,scikit-learn 注重预测。 -同scikit-learn一样,我也只是简要介绍statsmodels,以及如何用NumPy和pandas使用它。 +同 scikit-learn 一样,我也只是简要介绍 statsmodels,以及如何用 NumPy 和 pandas 使用它。 ## 1.4 安装和设置 -由于人们用Python所做的事情不同,所以没有一个普适的Python及其插件包的安装方案。由于许多读者的Python科学计算环境都不能完全满足本书的需要,所以接下来我将详细介绍各个操作系统上的安装方法。我推荐免费的Anaconda安装包。写作本书时,Anaconda提供Python 2.7和3.6两个版本,以后可能发生变化。本书使用的是Python 3.6,因此推荐选择Python 3.6或更高版本。 +由于人们用 Python 所做的事情不同,所以没有一个普适的 Python 及其插件包的安装方案。由于许多读者的 Python 科学计算环境都不能完全满足本书的需要,所以接下来我将详细介绍各个操作系统上的安装方法。我推荐免费的 Anaconda 安装包。写作本书时,Anaconda 提供 Python 2.7 和 3.6 两个版本,以后可能发生变化。本书使用的是 Python 3.6,因此推荐选择 Python 3.6 或更高版本。 ### Windows -要在Windows上运行,先下载[Anaconda安装包](https://www.anaconda.com/download/)。推荐跟随Anaconda下载页面的Windows安装指导,安装指导在写作本书和读者看到此文的的这段时间内可能发生变化。 +要在 Windows 上运行,先下载[Anaconda 安装包](https://www.anaconda.com/download/)。推荐跟随 Anaconda 下载页面的 Windows 安装指导,安装指导在写作本书和读者看到此文的的这段时间内可能发生变化。 -现在,来确认设置是否正确。打开命令行窗口(`cmd.exe`),输入`python`以打开Python解释器。可以看到类似下面的Anaconda版本的输出: +现在,来确认设置是否正确。打开命令行窗口(`cmd.exe`),输入`python`以打开 Python 解释器。可以看到类似下面的 Anaconda 版本的输出: ```text C:\Users\wesm>python @@ -157,33 +157,33 @@ Python 3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul 5 2016, 11:41:13) >>> ``` -要退出shell,按Ctrl-D(Linux或macOS上),Ctrl-Z(Windows上),或输入命令`exit()`,再按Enter。 +要退出 shell,按 Ctrl-D(Linux 或 macOS 上),Ctrl-Z(Windows 上),或输入命令`exit()`,再按 Enter。 ### Apple \(OS X, macOS\) -下载OS X Anaconda安装包,它的名字类似Anaconda3-4.1.0-MacOSX-x86\_64.pkg。双击.pkg文件,运行安装包。安装包运行时,会自动将Anaconda执行路径添加到`.bash_profile`文件,它位于`/Users/$USER/.bash_profile`。 +下载 OS X Anaconda 安装包,它的名字类似 Anaconda3-4.1.0-MacOSX-x86\_64.pkg。双击.pkg 文件,运行安装包。安装包运行时,会自动将 Anaconda 执行路径添加到`.bash_profile`文件,它位于`/Users/$USER/.bash_profile`。 -为了确认成功,在系统shell打开IPython: +为了确认成功,在系统 shell 打开 IPython: ```text $ ipython ``` -要退出shell,按Ctrl-D,或输入命令`exit()`,再按Enter。 +要退出 shell,按 Ctrl-D,或输入命令`exit()`,再按 Enter。 ### GNU/Linux -Linux版本很多,这里给出Debian、Ubantu、CentOS和Fedora的安装方法。安装包是一个脚本文件,必须在shell中运行。取决于系统是32位还是64位,要么选择x86 \(32位\)或x86\_64 \(64位\)安装包。随后你会得到一个文件,名字类似于`Anaconda3-4.1.0-Linux-x86_64.sh`。用bash进行安装: +Linux 版本很多,这里给出 Debian、Ubantu、CentOS 和 Fedora 的安装方法。安装包是一个脚本文件,必须在 shell 中运行。取决于系统是 32 位还是 64 位,要么选择 x86 \(32 位\)或 x86\_64 \(64 位\)安装包。随后你会得到一个文件,名字类似于`Anaconda3-4.1.0-Linux-x86_64.sh`。用 bash 进行安装: ```text $ bash Anaconda3-4.1.0-Linux-x86_64.sh ``` -> 笔记:某些Linux版本在包管理器中有满足需求的Python包,只需用类似apt的工具安装就行。这里讲的用Anaconda安装,适用于不同的Linux安装包,也很容易将包升级到最新版本。 +> 笔记:某些 Linux 版本在包管理器中有满足需求的 Python 包,只需用类似 apt 的工具安装就行。这里讲的用 Anaconda 安装,适用于不同的 Linux 安装包,也很容易将包升级到最新版本。 -接受许可之后,会向你询问在哪里放置Anaconda的文件。我推荐将文件安装到默认的home目录,例如`/home/$USER/anaconda`。 +接受许可之后,会向你询问在哪里放置 Anaconda 的文件。我推荐将文件安装到默认的 home 目录,例如`/home/$USER/anaconda`。 -Anaconda安装包可能会询问你是否将`bin/`目录添加到`$PATH`变量。如果在安装之后有任何问题,你可以修改文件`.bashrc`(或`.zshrc`,如果使用的是zsh shell)为类似以下的内容: +Anaconda 安装包可能会询问你是否将`bin/`目录添加到`$PATH`变量。如果在安装之后有任何问题,你可以修改文件`.bashrc`(或`.zshrc`,如果使用的是 zsh shell)为类似以下的内容: ```text export PATH=/home/$USER/anaconda/bin:$PATH @@ -191,15 +191,15 @@ export PATH=/home/$USER/anaconda/bin:$PATH 做完之后,你可以开启一个新窗口,或再次用`~/.bashrc`执行`.bashrc`。 -### 安装或升级Python包 +### 安装或升级 Python 包 -在你阅读本书的时候,你可能想安装另外的不在Anaconda中的Python包。通常,可以用以下命令安装: +在你阅读本书的时候,你可能想安装另外的不在 Anaconda 中的 Python 包。通常,可以用以下命令安装: ```text conda install package_name ``` -如果这个命令不行,也可以用pip包管理工具: +如果这个命令不行,也可以用 pip 包管理工具: ```text pip install package_name @@ -211,7 +211,7 @@ pip install package_name conda update package_name ``` -pip可以用`--upgrade`升级: +pip 可以用`--upgrade`升级: ```text pip install --upgrade package_name @@ -219,56 +219,56 @@ pip install --upgrade package_name 本书中,你有许多机会尝试这些命令。 -> 注意:当你使用conda和pip二者安装包时,千万不要用pip升级conda的包,这样会导致环境发生问题。当使用Anaconda或Miniconda时,最好首先使用conda进行升级。 +> 注意:当你使用 conda 和 pip 二者安装包时,千万不要用 pip 升级 conda 的包,这样会导致环境发生问题。当使用 Anaconda 或 Miniconda 时,最好首先使用 conda 进行升级。 Python 2 和 Python 3 -第一版的Python 3.x出现于2008年。它有一系列的变化,与之前的Python 2.x代码有不兼容的地方。因为从1991年Python出现算起,已经过了17年,Python 3 的出现被视为吸取一些列教训的更优结果。 +第一版的 Python 3.x 出现于 2008 年。它有一系列的变化,与之前的 Python 2.x 代码有不兼容的地方。因为从 1991 年 Python 出现算起,已经过了 17 年,Python 3 的出现被视为吸取一些列教训的更优结果。 -2012年,因为许多包还没有完全支持Python 3,许多科学和数据分析社区还是在使用Python 2.x。因此,本书第一版使用的是Python 2.7。现在,用户可以在Python 2.x和Python 3.x间自由选择,二者都有良好的支持。 +2012 年,因为许多包还没有完全支持 Python 3,许多科学和数据分析社区还是在使用 Python 2.x。因此,本书第一版使用的是 Python 2.7。现在,用户可以在 Python 2.x 和 Python 3.x 间自由选择,二者都有良好的支持。 -但是,Python 2.x在2020年就会到期(包括重要的安全补丁),因此再用Python 2.7就不是好的选择了。因此,本书使用了Python 3.6,这一广泛使用、支持良好的稳定版本。我们已经称Python 2.x为“遗留版本”,简称Python 3.x为“Python”。我建议你也是如此。 +但是,Python 2.x 在 2020 年就会到期(包括重要的安全补丁),因此再用 Python 2.7 就不是好的选择了。因此,本书使用了 Python 3.6,这一广泛使用、支持良好的稳定版本。我们已经称 Python 2.x 为“遗留版本”,简称 Python 3.x 为“Python”。我建议你也是如此。 -本书基于Python 3.6。你的Python版本也许高于3.6,但是示例代码应该是向前兼容的。一些示例代码可能在Python 2.7上有所不同,或完全不兼容。 +本书基于 Python 3.6。你的 Python 版本也许高于 3.6,但是示例代码应该是向前兼容的。一些示例代码可能在 Python 2.7 上有所不同,或完全不兼容。 ### 集成开发环境(IDEs)和文本编辑器 -当被问到我的标准开发环境,我几乎总是回答“IPython加文本编辑器”。我通常在编程时,反复在IPython或Jupyter notebooks中测试和调试每条代码。也可以交互式操作数据,和可视化验证数据操作中某一特殊集合。在shell中使用pandas和NumPy也很容易。 +当被问到我的标准开发环境,我几乎总是回答“IPython 加文本编辑器”。我通常在编程时,反复在 IPython 或 Jupyter notebooks 中测试和调试每条代码。也可以交互式操作数据,和可视化验证数据操作中某一特殊集合。在 shell 中使用 pandas 和 NumPy 也很容易。 -但是,当创建软件时,一些用户可能更想使用特点更为丰富的IDE,而不仅仅是原始的Emacs或Vim的文本编辑器。以下是一些IDE: +但是,当创建软件时,一些用户可能更想使用特点更为丰富的 IDE,而不仅仅是原始的 Emacs 或 Vim 的文本编辑器。以下是一些 IDE: -* PyDev(免费),基于Eclipse平台的IDE; -* JetBrains的PyCharm(商业用户需要订阅,开源开发者免费); -* Visual Studio(Windows用户)的Python Tools; -* Spyder(免费),Anaconda附带的IDE; +* PyDev(免费),基于 Eclipse 平台的 IDE; +* JetBrains 的 PyCharm(商业用户需要订阅,开源开发者免费); +* Visual Studio(Windows 用户)的 Python Tools; +* Spyder(免费),Anaconda 附带的 IDE; * Komodo IDE(商业)。 -因为Python的流行,大多数文本编辑器,比如Atom和Sublime Text 3,对Python的支持也非常好。 +因为 Python 的流行,大多数文本编辑器,比如 Atom 和 Sublime Text 3,对 Python 的支持也非常好。 ## 1.5 社区和会议 -除了在网上搜索,各式各样的科学和数据相关的Python邮件列表是非常有帮助的,很容易获得回答。包括: +除了在网上搜索,各式各样的科学和数据相关的 Python 邮件列表是非常有帮助的,很容易获得回答。包括: -* pydata:一个Google群组列表,用以回答Python数据分析和pandas的问题; -* pystatsmodels: statsmodels或pandas相关的问题; -* scikit-learn和Python机器学习邮件列表,scikit-learn@python.org; -* numpy-discussion:和NumPy相关的问题; -* scipy-user:SciPy和科学计算的问题; +* pydata:一个 Google 群组列表,用以回答 Python 数据分析和 pandas 的问题; +* pystatsmodels: statsmodels 或 pandas 相关的问题; +* scikit-learn 和 Python 机器学习邮件列表,scikit-learn@python.org; +* numpy-discussion:和 NumPy 相关的问题; +* scipy-user:SciPy 和科学计算的问题; -因为这些邮件列表的URLs可以很容易搜索到,但因为可能发生变化,所以没有给出。 +因为这些邮件列表的 URLs 可以很容易搜索到,但因为可能发生变化,所以没有给出。 -每年,世界各地会举办许多Python开发者大会。如果你想结识其他有相同兴趣的人,如果可能的话,我建议你去参加一个。许多会议会对无力支付入场费和差旅费的人提供财力帮助。下面是一些会议: +每年,世界各地会举办许多 Python 开发者大会。如果你想结识其他有相同兴趣的人,如果可能的话,我建议你去参加一个。许多会议会对无力支付入场费和差旅费的人提供财力帮助。下面是一些会议: -* PyCon和EuroPython:北美和欧洲的两大Python会议; -* SciPy和EuroSciPy:北美和欧洲两大面向科学计算的会议; +* PyCon 和 EuroPython:北美和欧洲的两大 Python 会议; +* SciPy 和 EuroSciPy:北美和欧洲两大面向科学计算的会议; * PyData:世界范围内,一些列的地区性会议,专注数据科学和数据分析; -* 国际和地区的PyCon会议([http://pycon.org有完整列表)](http://pycon.org有完整列表)) 。 +* 国际和地区的 PyCon 会议([完整列表](http://pycon.org))。 ## 1.6 本书导航 -如果之前从未使用过Python,那你可能需要先看看本书的第2章和第3章,我简要介绍了Python的特点,IPython和Jupyter notebooks。这些知识是为本书后面的内容做铺垫。如果你已经掌握Python,可以选择跳过。 +如果之前从未使用过 Python,那你可能需要先看看本书的第 2 章和第 3 章,我简要介绍了 Python 的特点,IPython 和 Jupyter notebooks。这些知识是为本书后面的内容做铺垫。如果你已经掌握 Python,可以选择跳过。 -接下来,简单地介绍了NumPy的关键特性,附录A中是更高级的NumPy功能。然后,我介绍了pandas,本书剩余的内容全部是使用pandas、NumPy和matplotlib处理数据分析的问题。我已经尽量让全书的结构循序渐进,但偶尔会有章节之间的交叉,有时用到的概念还没有介绍过。 +接下来,简单地介绍了 NumPy 的关键特性,附录 A 中是更高级的 NumPy 功能。然后,我介绍了 pandas,本书剩余的内容全部是使用 pandas、NumPy 和 matplotlib 处理数据分析的问题。我已经尽量让全书的结构循序渐进,但偶尔会有章节之间的交叉,有时用到的概念还没有介绍过。 尽管读者各自的工作任务不同,大体可以分为几类: @@ -294,24 +294,24 @@ Python 2 和 Python 3 ### 代码示例 -本书大部分代码示例的输入形式和输出结果都会按照其在IPython shell或Jupyter notebooks中执行时的样子进行排版: +本书大部分代码示例的输入形式和输出结果都会按照其在 IPython shell 或 Jupyter notebooks 中执行时的样子进行排版: ```text In [5]: CODE EXAMPLE Out[5]: OUTPUT ``` -但你看到类似的示例代码,就是让你在`in`的部分输入代码,按Enter键执行(Jupyter中是按Shift-Enter)。然后就可以在`out`看到输出。 +但你看到类似的示例代码,就是让你在`in`的部分输入代码,按 Enter 键执行(Jupyter 中是按 Shift-Enter)。然后就可以在`out`看到输出。 ### 示例数据 -各章的示例数据都存放在GitHub上:[http://github.com/pydata/pydata-book。](http://github.com/pydata/pydata-book。) 下载这些数据的方法有二:使用git版本控制命令行程序;直接从网站上下载该GitHub库的zip文件。如果遇到了问题,可以到我的个人主页,[http://wesmckinney.com/,](http://wesmckinney.com/,) 获取最新的指导。 +[各章的示例数据都存放在 GitHub 上](http://github.com/pydata/pydata-book)。下载这些数据的方法有二:使用 git 版本控制命令行程序;直接从网站上下载该 GitHub 库的 zip 文件。如果遇到了问题,可以到[我的个人主页](http://wesmckinney.com/),获取最新的指导。 -为了让所有示例都能重现,我已经尽我所能使其包含所有必需的东西,但仍然可能会有一些错误或遗漏。如果出现这种情况的话,请给我发邮件:wesmckinn@gmail.com。报告本书错误的最好方法是O’Reilly的errata页面,[http://www.bit.ly/pyDataAnalysis\_errata。](http://www.bit.ly/pyDataAnalysis_errata。) +为了让所有示例都能重现,我已经尽我所能使其包含所有必需的东西,但仍然可能会有一些错误或遗漏。如果出现这种情况的话,请给我发邮件:wesmckinn@gmail.com。[报告本书错误的最好方法是 O’Reilly 的 errata 页面](http://www.bit.ly/pyDataAnalysis_errata)。 ### 引入惯例 -Python社区已经广泛采取了一些常用模块的命名惯例: +Python 社区已经广泛采取了一些常用模块的命名惯例: ```python import numpy as np @@ -321,13 +321,13 @@ import seaborn as sns import statsmodels as sm ``` -也就是说,当你看到np.arange时,就应该想到它引用的是NumPy中的arange函数。这样做的原因是:在Python软件开发过程中,不建议直接引入类似NumPy这种大型库的全部内容(from numpy import \*)。 +也就是说,当你看到 np.arange 时,就应该想到它引用的是 NumPy 中的 arange 函数。这样做的原因是:在 Python 软件开发过程中,不建议直接引入类似 NumPy 这种大型库的全部内容(from numpy import \*)。 ### 行话 由于你可能不太熟悉书中使用的一些有关编程和数据科学方面的常用术语,所以我在这里先给出其简单定义: -数据规整(Munge/Munging/Wrangling) 指的是将非结构化和(或)散乱数据处理为结构化或整洁形式的整个过程。这几个词已经悄悄成为当今数据黑客们的行话了。Munge这个词跟Lunge押韵。 +数据规整(Munge/Munging/Wrangling) 指的是将非结构化和(或)散乱数据处理为结构化或整洁形式的整个过程。这几个词已经悄悄成为当今数据黑客们的行话了。Munge 这个词跟 Lunge 押韵。 伪码(Pseudocode) 算法或过程的“代码式”描述,而这些代码本身并不是实际有效的源代码。 diff --git a/docs/10.md b/docs/10.md index 34e13be..f85e1d5 100644 --- a/docs/10.md +++ b/docs/10.md @@ -1,31 +1,31 @@ # 第 10 章 数据聚合与分组运算 -对数据集进行分组并对各组应用一个函数(无论是聚合还是转换),通常是数据分析工作中的重要环节。在将数据集加载、融合、准备好之后,通常就是计算分组统计或生成透视表。pandas提供了一个灵活高效的gruopby功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要等操作。 +对数据集进行分组并对各组应用一个函数(无论是聚合还是转换),通常是数据分析工作中的重要环节。在将数据集加载、融合、准备好之后,通常就是计算分组统计或生成透视表。pandas 提供了一个灵活高效的 gruopby 功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要等操作。 -关系型数据库和SQL(Structured Query Language,结构化查询语言)能够如此流行的原因之一就是其能够方便地对数据进行连接、过滤、转换和聚合。但是,像SQL这样的查询语言所能执行的分组运算的种类很有限。在本章中你将会看到,由于Python和pandas强大的表达能力,我们可以执行复杂得多的分组运算(利用任何可以接受pandas对象或NumPy数组的函数)。在本章中,你将会学到: +关系型数据库和 SQL(Structured Query Language,结构化查询语言)能够如此流行的原因之一就是其能够方便地对数据进行连接、过滤、转换和聚合。但是,像 SQL 这样的查询语言所能执行的分组运算的种类很有限。在本章中你将会看到,由于 Python 和 pandas 强大的表达能力,我们可以执行复杂得多的分组运算(利用任何可以接受 pandas 对象或 NumPy 数组的函数)。在本章中,你将会学到: -- 使用一个或多个键(形式可以是函数、数组或DataFrame列名)分割pandas对象。 +- 使用一个或多个键(形式可以是函数、数组或 DataFrame 列名)分割 pandas 对象。 - 计算分组的概述统计,比如数量、平均值或标准差,或是用户定义的函数。 - 应用组内转换或其他运算,如规格化、线性回归、排名或选取子集等。 - 计算透视表或交叉表。 - 执行分位数分析以及其它统计分组分析。 ->笔记:对时间序列数据的聚合(groupby的特殊用法之一)也称作重采样(resampling),本书将在第11章中单独对其进行讲解。 +>笔记:对时间序列数据的聚合(groupby 的特殊用法之一)也称作重采样(resampling),本书将在第 11 章中单独对其进行讲解。 -# 10.1 GroupBy机制 +# 10.1 GroupBy 机制 -Hadley Wickham(许多热门R语言包的作者)创造了一个用于表示分组运算的术语"split-apply-combine"(拆分-应用-合并)。第一个阶段,pandas对象(无论是Series、DataFrame还是其他的)中的数据会根据你所提供的一个或多个键被拆分(split)为多组。拆分操作是在对象的特定轴上执行的。例如,DataFrame可以在其行(axis=0)或列(axis=1)上进行分组。然后,将一个函数应用(apply)到各个分组并产生一个新值。最后,所有这些函数的执行结果会被合并(combine)到最终的结果对象中。结果对象的形式一般取决于数据上所执行的操作。图10-1大致说明了一个简单的分组聚合过程。 +Hadley Wickham(许多热门 R 语言包的作者)创造了一个用于表示分组运算的术语"split-apply-combine"(拆分-应用-合并)。第一个阶段,pandas 对象(无论是 Series、DataFrame 还是其他的)中的数据会根据你所提供的一个或多个键被拆分(split)为多组。拆分操作是在对象的特定轴上执行的。例如,DataFrame 可以在其行(axis=0)或列(axis=1)上进行分组。然后,将一个函数应用(apply)到各个分组并产生一个新值。最后,所有这些函数的执行结果会被合并(combine)到最终的结果对象中。结果对象的形式一般取决于数据上所执行的操作。图 10-1 大致说明了一个简单的分组聚合过程。 -![图10-1 分组聚合演示](img/7178691-e5c671e09ecf94be.png) +![图 10-1 分组聚合演示](img/7178691-e5c671e09ecf94be.png) 分组键可以有多种形式,且类型不必相同: - 列表或数组,其长度与待分组的轴一样。 -- 表示DataFrame某个列名的值。 -- 字典或Series,给出待分组轴上的值与分组名之间的对应关系。 +- 表示 DataFrame 某个列名的值。 +- 字典或 Series,给出待分组轴上的值与分组名之间的对应关系。 - 函数,用于处理轴索引或索引中的各个标签。 -注意,后三种都只是快捷方式而已,其最终目的仍然是产生一组用于拆分对象的值。如果觉得这些东西看起来很抽象,不用担心,我将在本章中给出大量有关于此的示例。首先来看看下面这个非常简单的表格型数据集(以DataFrame的形式): +注意,后三种都只是快捷方式而已,其最终目的仍然是产生一组用于拆分对象的值。如果觉得这些东西看起来很抽象,不用担心,我将在本章中给出大量有关于此的示例。首先来看看下面这个非常简单的表格型数据集(以 DataFrame 的形式): ```python In [10]: df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'], @@ -43,7 +43,7 @@ Out[11]: 4 1.965781 1.246435 a one ``` -假设你想要按key1进行分组,并计算data1列的平均值。实现该功能的方式有很多,而我们这里要用的是:访问data1,并根据key1调用groupby: +假设你想要按 key1 进行分组,并计算 data1 列的平均值。实现该功能的方式有很多,而我们这里要用的是:访问 data1,并根据 key1 调用 groupby: ```python In [12]: grouped = df['data1'].groupby(df['key1']) @@ -51,7 +51,7 @@ In [13]: grouped Out[13]: ``` -变量grouped是一个GroupBy对象。它实际上还没有进行任何计算,只是含有一些有关分组键df['key1']的中间数据而已。换句话说,该对象已经有了接下来对各分组执行运算所需的一切信息。例如,我们可以调用GroupBy的mean方法来计算分组平均值: +变量 grouped 是一个 GroupBy 对象。它实际上还没有进行任何计算,只是含有一些有关分组键 df['key1']的中间数据而已。换句话说,该对象已经有了接下来对各分组执行运算所需的一切信息。例如,我们可以调用 GroupBy 的 mean 方法来计算分组平均值: ```python In [14]: grouped.mean() Out[14]: @@ -61,7 +61,7 @@ b -0.537585 Name: data1, dtype: float64 ``` -稍后我将详细讲解.mean()的调用过程。这里最重要的是,数据(Series)根据分组键进行了聚合,产生了一个新的Series,其索引为key1列中的唯一值。之所以结果中索引的名称为key1,是因为原始DataFrame的列df['key1']就叫这个名字。 +稍后我将详细讲解.mean()的调用过程。这里最重要的是,数据(Series)根据分组键进行了聚合,产生了一个新的 Series,其索引为 key1 列中的唯一值。之所以结果中索引的名称为 key1,是因为原始 DataFrame 的列 df['key1']就叫这个名字。 如果我们一次传入多个数组的列表,就会得到不同的结果: ```python @@ -77,7 +77,7 @@ b one -0.519439 Name: data1, dtype: float64 ``` -这里,我通过两个键对数据进行了分组,得到的Series具有一个层次化索引(由唯一的键对组成): +这里,我通过两个键对数据进行了分组,得到的 Series 具有一个层次化索引(由唯一的键对组成): ```python In [17]: means.unstack() Out[17]: @@ -87,7 +87,7 @@ a 0.880536 0.478943 b -0.519439 -0.555730 ``` -在这个例子中,分组键均为Series。实际上,分组键可以是任何长度适当的数组: +在这个例子中,分组键均为 Series。实际上,分组键可以是任何长度适当的数组: ```python In [18]: states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio']) @@ -102,7 +102,7 @@ Ohio 2005 -0.380219 Name: data1, dtype: float64 ``` -通常,分组信息就位于相同的要处理DataFrame中。这里,你还可以将列名(可以是字符串、数字或其他Python对象)用作分组键: +通常,分组信息就位于相同的要处理 DataFrame 中。这里,你还可以将列名(可以是字符串、数字或其他 Python 对象)用作分组键: ```python In [21]: df.groupby('key1').mean() Out[21]: @@ -121,9 +121,9 @@ b one -0.519439 0.281746 two -0.555730 0.769023 ``` -你可能已经注意到了,第一个例子在执行df.groupby('key1').mean()时,结果中没有key2列。这是因为df['key2']不是数值数据(俗称“麻烦列”),所以被从结果中排除了。默认情况下,所有数值列都会被聚合,虽然有时可能会被过滤为一个子集,稍后就会碰到。 +你可能已经注意到了,第一个例子在执行 df.groupby('key1').mean()时,结果中没有 key2 列。这是因为 df['key2']不是数值数据(俗称“麻烦列”),所以被从结果中排除了。默认情况下,所有数值列都会被聚合,虽然有时可能会被过滤为一个子集,稍后就会碰到。 -无论你准备拿groupby做什么,都有可能会用到GroupBy的size方法,它可以返回一个含有分组大小的Series: +无论你准备拿 groupby 做什么,都有可能会用到 GroupBy 的 size 方法,它可以返回一个含有分组大小的 Series: ```python In [23]: df.groupby(['key1', 'key2']).size() Out[23]: @@ -138,7 +138,7 @@ dtype: int64 注意,任何分组关键词中的缺失值,都会被从结果中除去。 ## 对分组进行迭代 -GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)。看下面的例子: +GroupBy 对象支持迭代,可以产生一组二元元组(由分组名和数据块组成)。看下面的例子: ```python In [24]: for name, group in df.groupby('key1'): ....: print(name) @@ -187,7 +187,7 @@ Out[27]: 3 -0.555730 0.769023 b two ``` -groupby默认是在axis=0上进行分组的,通过设置也可以在其他任何轴上进行分组。拿上面例子中的df来说,我们可以根据dtype对列进行分组: +groupby 默认是在 axis=0 上进行分组的,通过设置也可以在其他任何轴上进行分组。拿上面例子中的 df 来说,我们可以根据 dtype 对列进行分组: ```python In [28]: df.dtypes Out[28]: @@ -223,7 +223,7 @@ object ``` ## 选取一列或列的子集 -对于由DataFrame产生的GroupBy对象,如果用一个(单个字符串)或一组(字符串数组)列名对其进行索引,就能实现选取部分列进行聚合的目的。也就是说: +对于由 DataFrame 产生的 GroupBy 对象,如果用一个(单个字符串)或一组(字符串数组)列名对其进行索引,就能实现选取部分列进行聚合的目的。也就是说: ```python df.groupby('key1')['data1'] df.groupby('key1')[['data2']] @@ -235,7 +235,7 @@ df['data1'].groupby(df['key1']) df[['data2']].groupby(df['key1']) ``` -尤其对于大数据集,很可能只需要对部分列进行聚合。例如,在前面那个数据集中,如果只需计算data2列的平均值并以DataFrame形式得到结果,可以这样写: +尤其对于大数据集,很可能只需要对部分列进行聚合。例如,在前面那个数据集中,如果只需计算 data2 列的平均值并以 DataFrame 形式得到结果,可以这样写: ```python In [31]: df.groupby(['key1', 'key2'])[['data2']].mean() Out[31]: @@ -247,7 +247,7 @@ b one 0.281746 two 0.769023 ``` -这种索引操作所返回的对象是一个已分组的DataFrame(如果传入的是列表或数组)或已分组的Series(如果传入的是标量形式的单个列名): +这种索引操作所返回的对象是一个已分组的 DataFrame(如果传入的是列表或数组)或已分组的 Series(如果传入的是标量形式的单个列名): ```python In [32]: s_grouped = df.groupby(['key1', 'key2'])['data2'] @@ -264,8 +264,8 @@ b one 0.281746 Name: data2, dtype: float64 ``` -##通过字典或Series进行分组 -除数组以外,分组信息还可以其他形式存在。来看另一个示例DataFrame: +##通过字典或 Series 进行分组 +除数组以外,分组信息还可以其他形式存在。来看另一个示例 DataFrame: ```python In [35]: people = pd.DataFrame(np.random.randn(5, 5), ....: columns=['a', 'b', 'c', 'd', 'e'], @@ -289,7 +289,7 @@ In [38]: mapping = {'a': 'red', 'b': 'red', 'c': 'blue', ....: 'd': 'blue', 'e': 'red', 'f' : 'orange'} ``` -现在,你可以将这个字典传给groupby,来构造数组,但我们可以直接传递字典(我包含了键“f”来强调,存在未使用的分组键是可以的): +现在,你可以将这个字典传给 groupby,来构造数组,但我们可以直接传递字典(我包含了键“f”来强调,存在未使用的分组键是可以的): ```python In [39]: by_column = people.groupby(mapping, axis=1) @@ -303,7 +303,7 @@ Jim 0.524712 1.770545 Travis -4.230992 -2.405455 ``` -Series也有同样的功能,它可以被看做一个固定大小的映射: +Series 也有同样的功能,它可以被看做一个固定大小的映射: ```python In [41]: map_series = pd.Series(mapping) @@ -328,7 +328,7 @@ Travis 2 3 ``` ##通过函数进行分组 -比起使用字典或Series,使用Python函数是一种更原生的方法定义分组映射。任何被当做分组键的函数都会在各个索引值上被调用一次,其返回值就会被用作分组名称。具体点说,以上一小节的示例DataFrame为例,其索引值为人的名字。你可以计算一个字符串长度的数组,更简单的方法是传入len函数: +比起使用字典或 Series,使用 Python 函数是一种更原生的方法定义分组映射。任何被当做分组键的函数都会在各个索引值上被调用一次,其返回值就会被用作分组名称。具体点说,以上一小节的示例 DataFrame 为例,其索引值为人的名字。你可以计算一个字符串长度的数组,更简单的方法是传入 len 函数: ```python In [44]: people.groupby(len).sum() Out[44]: @@ -338,7 +338,7 @@ Out[44]: 6 -0.713544 -0.831154 -2.370232 -1.860761 -0.860757 ``` -将函数跟数组、列表、字典、Series混合使用也不是问题,因为任何东西在内部都会被转换为数组: +将函数跟数组、列表、字典、Series 混合使用也不是问题,因为任何东西在内部都会被转换为数组: ```python In [45]: key_list = ['one', 'one', 'one', 'two', 'two'] @@ -370,7 +370,7 @@ tenor 1 3 5 1 3 3 0.069877 0.246674 -0.011862 1.004812 1.327195 ``` -要根据级别分组,使用level关键字传递级别序号或名字: +要根据级别分组,使用 level 关键字传递级别序号或名字: ```python In [50]: hier_df.groupby(level='cty', axis=1).count() Out[50]: @@ -383,14 +383,14 @@ cty JP US # 10.2 数据聚合 -聚合指的是任何能够从数组产生标量值的数据转换过程。之前的例子已经用过一些,比如mean、count、min以及sum等。你可能想知道在GroupBy对象上调用mean()时究竟发生了什么。许多常见的聚合运算(如表10-1所示)都有进行优化。然而,除了这些方法,你还可以使用其它的。 +聚合指的是任何能够从数组产生标量值的数据转换过程。之前的例子已经用过一些,比如 mean、count、min 以及 sum 等。你可能想知道在 GroupBy 对象上调用 mean()时究竟发生了什么。许多常见的聚合运算(如表 10-1 所示)都有进行优化。然而,除了这些方法,你还可以使用其它的。 -![表10-1 经过优化的groupby方法](img/7178691-ba8de524e08b1b6f.png) +![表 10-1 经过优化的 groupby 方法](img/7178691-ba8de524e08b1b6f.png) -你可以使用自己发明的聚合运算,还可以调用分组对象上已经定义好的任何方法。例如,quantile可以计算Series或DataFrame列的样本分位数。 +你可以使用自己发明的聚合运算,还可以调用分组对象上已经定义好的任何方法。例如,quantile 可以计算 Series 或 DataFrame 列的样本分位数。 -虽然quantile并没有明确地实现于GroupBy,但它是一个Series方法,所以这里是能用的。实际上,GroupBy会高效地对Series进行切片,然后对各片调用piece.quantile(0.9),最后将这些结果组装成最终结果: +虽然 quantile 并没有明确地实现于 GroupBy,但它是一个 Series 方法,所以这里是能用的。实际上,GroupBy 会高效地对 Series 进行切片,然后对各片调用 piece.quantile(0.9),最后将这些结果组装成最终结果: ```python In [51]: df Out[51]: @@ -411,7 +411,7 @@ b -0.523068 Name: data1, dtype: float64 ``` -如果要使用你自己的聚合函数,只需将其传入aggregate或agg方法即可: +如果要使用你自己的聚合函数,只需将其传入 aggregate 或 agg 方法即可: ```python In [54]: def peak_to_peak(arr): ....: return arr.max() - arr.min() @@ -423,7 +423,7 @@ a 2.170488 1.300498 b 0.036292 0.487276 ``` -你可能注意到注意,有些方法(如describe)也是可以用在这里的,即使严格来讲,它们并非聚合运算: +你可能注意到注意,有些方法(如 describe)也是可以用在这里的,即使严格来讲,它们并非聚合运算: ```python In [56]: grouped.describe() Out[56]: @@ -444,13 +444,13 @@ a 1.319920 1.393406 b 0.647203 0.769023 ``` -在后面的10.3节,我将详细说明这到底是怎么回事。 +在后面的 10.3 节,我将详细说明这到底是怎么回事。 ->笔记:自定义聚合函数要比表10-1中那些经过优化的函数慢得多。这是因为在构造中间分组数据块时存在非常大的开销(函数调用、数据重排等)。 +>笔记:自定义聚合函数要比表 10-1 中那些经过优化的函数慢得多。这是因为在构造中间分组数据块时存在非常大的开销(函数调用、数据重排等)。 ## 面向列的多函数应用 -回到前面小费的例子。使用read_csv导入数据之后,我们添加了一个小费百分比的列tip_pct: +回到前面小费的例子。使用 read_csv 导入数据之后,我们添加了一个小费百分比的列 tip_pct: ```python In [57]: tips = pd.read_csv('examples/tips.csv') @@ -468,13 +468,13 @@ Out[59]: 5 25.29 4.71 No Sun Dinner 4 0.186240 ``` -你已经看到,对Series或DataFrame列的聚合运算其实就是使用aggregate(使用自定义函数)或调用诸如mean、std之类的方法。然而,你可能希望对不同的列使用不同的聚合函数,或一次应用多个函数。其实这也好办,我将通过一些示例来进行讲解。首先,我根据天和smoker对tips进行分组: +你已经看到,对 Series 或 DataFrame 列的聚合运算其实就是使用 aggregate(使用自定义函数)或调用诸如 mean、std 之类的方法。然而,你可能希望对不同的列使用不同的聚合函数,或一次应用多个函数。其实这也好办,我将通过一些示例来进行讲解。首先,我根据天和 smoker 对 tips 进行分组: ```python In [60]: grouped = tips.groupby(['day', 'smoker']) ``` -注意,对于表10-1中的那些描述统计,可以将函数名以字符串的形式传入: +注意,对于表 10-1 中的那些描述统计,可以将函数名以字符串的形式传入: ```python In [61]: grouped_pct = grouped['tip_pct'] @@ -493,7 +493,7 @@ Thur No 0.160298 Name: tip_pct, dtype: float64 ``` -如果传入一组函数或函数名,得到的DataFrame的列就会以相应的函数命名: +如果传入一组函数或函数名,得到的 DataFrame 的列就会以相应的函数命名: ```python In [63]: grouped_pct.agg(['mean', 'std', peak_to_peak]) @@ -512,7 +512,7 @@ Thur No 0.160298 0.038774 0.193350 这里,我们传递了一组聚合函数进行聚合,独立对数据分组进行评估。 -你并非一定要接受GroupBy自动给出的那些列名,特别是lambda函数,它们的名称是'',这样的辨识度就很低了(通过函数的__name__属性看看就知道了)。因此,如果传入的是一个由(name,function)元组组成的列表,则各元组的第一个元素就会被用作DataFrame的列名(可以将这种二元元组列表看做一个有序映射): +你并非一定要接受 GroupBy 自动给出的那些列名,特别是 lambda 函数,它们的名称是'',这样的辨识度就很低了(通过函数的 __name__ 属性看看就知道了)。因此,如果传入的是一个由(name,function)元组组成的列表,则各元组的第一个元素就会被用作 DataFrame 的列名(可以将这种二元元组列表看做一个有序映射): ```python In [64]: grouped_pct.agg([('foo', 'mean'), ('bar', np.std)]) @@ -529,7 +529,7 @@ Thur No 0.160298 0.038774 Yes 0.163863 0.039389 ``` -对于DataFrame,你还有更多选择,你可以定义一组应用于全部列的一组函数,或不同的列应用不同的函数。假设我们想要对tip_pct和total_bill列计算三个统计信息: +对于 DataFrame,你还有更多选择,你可以定义一组应用于全部列的一组函数,或不同的列应用不同的函数。假设我们想要对 tip_pct 和 total_bill 列计算三个统计信息: ```python In [65]: functions = ['count', 'mean', 'max'] @@ -551,7 +551,7 @@ Thur No 45 0.160298 0.266312 45 17.113111 41.19 Yes 17 0.163863 0.241255 17 19.190588 43.11 ``` -如你所见,结果DataFrame拥有层次化的列,这相当于分别对各列进行聚合,然后用concat将结果组装到一起,使用列名用作keys参数: +如你所见,结果 DataFrame 拥有层次化的列,这相当于分别对各列进行聚合,然后用 concat 将结果组装到一起,使用列名用作 keys 参数: ```python In [68]: result['tip_pct'] @@ -588,7 +588,7 @@ Thur No 0.160298 0.001503 17.113111 59.625081 Yes 0.163863 0.001551 19.190588 69.808518 ``` -现在,假设你想要对一个列或不同的列应用不同的函数。具体的办法是向agg传入一个从列名映射到函数的字典: +现在,假设你想要对一个列或不同的列应用不同的函数。具体的办法是向 agg 传入一个从列名映射到函数的字典: ```python In [71]: grouped.agg({'tip' : np.max, 'size' : 'sum'}) @@ -620,11 +620,11 @@ Thur No 0.072961 0.266312 0.160298 0.038774 112 Yes 0.090014 0.241255 0.163863 0.039389 40 ``` -只有将多个函数应用到至少一列时,DataFrame才会拥有层次化的列。 +只有将多个函数应用到至少一列时,DataFrame 才会拥有层次化的列。 ## 以“没有行索引”的形式返回聚合数据 -到目前为止,所有示例中的聚合数据都有由唯一的分组键组成的索引(可能还是层次化的)。由于并不总是需要如此,所以你可以向groupby传入as_index=False以禁用该功能: +到目前为止,所有示例中的聚合数据都有由唯一的分组键组成的索引(可能还是层次化的)。由于并不总是需要如此,所以你可以向 groupby 传入 as_index=False 以禁用该功能: ```python In [73]: tips.groupby(['day', 'smoker'], as_index=False).mean() @@ -640,15 +640,15 @@ Out[73]: 7 Thur Yes 19.190588 3.030000 2.352941 0.163863 ``` -当然,对结果调用reset_index也能得到这种形式的结果。使用as_index=False方法可以避免一些不必要的计算。 +当然,对结果调用 reset_index 也能得到这种形式的结果。使用 as_index=False 方法可以避免一些不必要的计算。 # 10.3 apply:一般性的“拆分-应用-合并” -最通用的GroupBy方法是apply,本节剩余部分将重点讲解它。如图10-2所示,apply会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起。 +最通用的 GroupBy 方法是 apply,本节剩余部分将重点讲解它。如图 10-2 所示,apply 会将待处理的对象拆分成多个片段,然后对各片段调用传入的函数,最后尝试将各片段组合到一起。 -![图10-2 分组聚合示例](img/7178691-7e8bb217f599b4ae.png) +![图 10-2 分组聚合示例](img/7178691-7e8bb217f599b4ae.png) -回到之前那个小费数据集,假设你想要根据分组选出最高的5个tip_pct值。首先,编写一个选取指定列具有最大值的行的函数: +回到之前那个小费数据集,假设你想要根据分组选出最高的 5 个 tip_pct 值。首先,编写一个选取指定列具有最大值的行的函数: ```python In [74]: def top(df, n=5, column='tip_pct'): ....: return df.sort_values(by=column)[-n:] @@ -664,7 +664,7 @@ Out[75]: 172 7.25 5.15 Yes Sun Dinner 2 0.710345 ``` -现在,如果对smoker分组并用该函数调用apply,就会得到: +现在,如果对 smoker 分组并用该函数调用 apply,就会得到: ```python In [76]: tips.groupby('smoker').apply(top) Out[76]: @@ -682,9 +682,9 @@ Yes 109 14.31 4.00 Yes Sat Dinner 2 0.279525 172 7.25 5.15 Yes Sun Dinner 2 0.710345 ``` -这里发生了什么?top函数在DataFrame的各个片段上调用,然后结果由pandas.concat组装到一起,并以分组名称进行了标记。于是,最终结果就有了一个层次化索引,其内层索引值来自原DataFrame。 +这里发生了什么?top 函数在 DataFrame 的各个片段上调用,然后结果由 pandas.concat 组装到一起,并以分组名称进行了标记。于是,最终结果就有了一个层次化索引,其内层索引值来自原 DataFrame。 -如果传给apply的函数能够接受其他参数或关键字,则可以将这些内容放在函数名后面一并传入: +如果传给 apply 的函数能够接受其他参数或关键字,则可以将这些内容放在函数名后面一并传入: ```python In [77]: tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill') Out[77]: @@ -700,9 +700,9 @@ Yes Fri 95 40.17 4.73 Yes Fri Dinner 4 0.117750 Thur 197 43.11 5.00 Yes Thur Lunch 4 0.115982 ``` ->笔记:除这些基本用法之外,能否充分发挥apply的威力很大程度上取决于你的创造力。传入的那个函数能做什么全由你说了算,它只需返回一个pandas对象或标量值即可。本章后续部分的示例主要用于讲解如何利用groupby解决各种各样的问题。 +>笔记:除这些基本用法之外,能否充分发挥 apply 的威力很大程度上取决于你的创造力。传入的那个函数能做什么全由你说了算,它只需返回一个 pandas 对象或标量值即可。本章后续部分的示例主要用于讲解如何利用 groupby 解决各种各样的问题。 -可能你已经想起来了,之前我在GroupBy对象上调用过describe: +可能你已经想起来了,之前我在 GroupBy 对象上调用过 describe: ```python In [78]: result = tips.groupby('smoker')['tip_pct'].describe() @@ -740,7 +740,7 @@ max No 0.291990 dtype: float64 ``` -在GroupBy中,当你调用诸如describe之类的方法时,实际上只是应用了下面两条代码的快捷方式而已: +在 GroupBy 中,当你调用诸如 describe 之类的方法时,实际上只是应用了下面两条代码的快捷方式而已: ```python f = lambda x: x.describe() grouped.apply(f) @@ -748,7 +748,7 @@ grouped.apply(f) ## 禁止分组键 -从上面的例子中可以看出,分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将group_keys=False传入groupby即可禁止该效果: +从上面的例子中可以看出,分组键会跟原始对象的索引共同构成结果对象中的层次化索引。将 group_keys=False 传入 groupby 即可禁止该效果: ```python In [81]: tips.groupby('smoker', group_keys=False).apply(top) Out[81]: @@ -767,7 +767,7 @@ Out[81]: ## 分位数和桶分析 -我曾在第8章中讲过,pandas有一些能根据指定面元或样本分位数将数据拆分成多块的工具(比如cut和qcut)。将这些函数跟groupby结合起来,就能非常轻松地实现对数据集的桶(bucket)或分位数(quantile)分析了。以下面这个简单的随机数据集为例,我们利用cut将其装入长度相等的桶中: +我曾在第 8 章中讲过,pandas 有一些能根据指定面元或样本分位数将数据拆分成多块的工具(比如 cut 和 qcut)。将这些函数跟 groupby 结合起来,就能非常轻松地实现对数据集的桶(bucket)或分位数(quantile)分析了。以下面这个简单的随机数据集为例,我们利用 cut 将其装入长度相等的桶中: ```python In [82]: frame = pd.DataFrame({'data1': np.random.randn(1000), ....: 'data2': np.random.randn(1000)}) @@ -791,7 +791,7 @@ Categories (4, interval[float64]): [(-2.956, -1.23] < (-1.23, 0.489] < (0.489, 2 208] < (2.208, 3.928]] ``` -由cut返回的Categorical对象可直接传递到groupby。因此,我们可以像下面这样对data2列做一些统计计算: +由 cut 返回的 Categorical 对象可直接传递到 groupby。因此,我们可以像下面这样对 data2 列做一些统计计算: ```python In [85]: def get_stats(group): ....: return {'min': group.min(), 'max': group.max(), @@ -809,7 +809,7 @@ data1 (2.208, 3.928] 10.0 1.765640 0.024750 -1.929776 ``` -这些都是长度相等的桶。要根据样本分位数得到大小相等的桶,使用qcut即可。传入labels=False即可只获取分位数的编号: +这些都是长度相等的桶。要根据样本分位数得到大小相等的桶,使用 qcut 即可。传入 labels=False 即可只获取分位数的编号: ```python # Return quantile numbers In [88]: grouping = pd.qcut(frame.data1, 10, labels=False) @@ -832,11 +832,11 @@ data1 9 100.0 2.377020 0.220122 -2.064111 ``` -我们会在第12章详细讲解pandas的Categorical类型。 +我们会在第 12 章详细讲解 pandas 的 Categorical 类型。 ## 示例:用特定于分组的值填充缺失值 -对于缺失数据的清理工作,有时你会用dropna将其替换掉,而有时则可能会希望用一个固定值或由数据集本身所衍生出来的值去填充NA值。这时就得使用fillna这个工具了。在下面这个例子中,我用平均值去填充NA值: +对于缺失数据的清理工作,有时你会用 dropna 将其替换掉,而有时则可能会希望用一个固定值或由数据集本身所衍生出来的值去填充 NA 值。这时就得使用 fillna 这个工具了。在下面这个例子中,我用平均值去填充 NA 值: ```python In [91]: s = pd.Series(np.random.randn(6)) @@ -863,7 +863,7 @@ Out[94]: dtype: float64 ``` -假设你需要对不同的分组填充不同的值。一种方法是将数据分组,并使用apply和一个能够对各数据块调用fillna的函数即可。下面是一些有关美国几个州的示例数据,这些州又被分为东部和西部: +假设你需要对不同的分组填充不同的值。一种方法是将数据分组,并使用 apply 和一个能够对各数据块调用 fillna 的函数即可。下面是一些有关美国几个州的示例数据,这些州又被分为东部和西部: ```python In [95]: states = ['Ohio', 'New York', 'Vermont', 'Florida', ....: 'Oregon', 'Nevada', 'California', 'Idaho'] @@ -885,7 +885,7 @@ Idaho -1.613716 dtype: float64 ``` -['East'] * 4产生了一个列表,包括了['East']中元素的四个拷贝。将这些列表串联起来。 +['East'] * 4 产生了一个列表,包括了['East']中元素的四个拷贝。将这些列表串联起来。 将一些值设为缺失: ```python @@ -910,7 +910,7 @@ West 0.717926 dtype: float64 ``` -我们可以用分组平均值去填充NA值: +我们可以用分组平均值去填充 NA 值: ```python In [102]: fill_mean = lambda g: g.fillna(g.mean()) @@ -927,7 +927,7 @@ Idaho 0.717926 dtype: float64 ``` -另外,也可以在代码中预定义各组的填充值。由于分组具有一个name属性,所以我们可以拿来用一下: +另外,也可以在代码中预定义各组的填充值。由于分组具有一个 name 属性,所以我们可以拿来用一下: ```python In [104]: fill_values = {'East': 0.5, 'West': -1} @@ -948,7 +948,7 @@ dtype: float64 ## 示例:随机采样和排列 -假设你想要从一个大数据集中随机抽取(进行替换或不替换)样本以进行蒙特卡罗模拟(Monte Carlo simulation)或其他分析工作。“抽取”的方式有很多,这里使用的方法是对Series使用sample方法: +假设你想要从一个大数据集中随机抽取(进行替换或不替换)样本以进行蒙特卡罗模拟(Monte Carlo simulation)或其他分析工作。“抽取”的方式有很多,这里使用的方法是对 Series 使用 sample 方法: ```python # Hearts, Spades, Clubs, Diamonds suits = ['H', 'S', 'C', 'D'] @@ -961,7 +961,7 @@ for suit in ['H', 'S', 'C', 'D']: deck = pd.Series(card_val, index=cards) ``` -现在我有了一个长度为52的Series,其索引包括牌名,值则是21点或其他游戏中用于计分的点数(为了简单起见,我当A的点数为1): +现在我有了一个长度为 52 的 Series,其索引包括牌名,值则是 21 点或其他游戏中用于计分的点数(为了简单起见,我当 A 的点数为 1): ```python In [108]: deck[:13] Out[108]: @@ -981,7 +981,7 @@ QH 10 dtype: int64 ``` -现在,根据我上面所讲的,从整副牌中抽出5张,代码如下: +现在,根据我上面所讲的,从整副牌中抽出 5 张,代码如下: ```python In [109]: def draw(deck, n=5): .....: return deck.sample(n) @@ -996,7 +996,7 @@ KC 10 dtype: int64 ``` -假设你想要从每种花色中随机抽取两张牌。由于花色是牌名的最后一个字符,所以我们可以据此进行分组,并使用apply: +假设你想要从每种花色中随机抽取两张牌。由于花色是牌名的最后一个字符,所以我们可以据此进行分组,并使用 apply: ```python In [111]: get_suit = lambda card: card[-1] # last letter is suit @@ -1030,7 +1030,7 @@ dtype: int64 ## 示例:分组加权平均数和相关系数 -根据groupby的“拆分-应用-合并”范式,可以进行DataFrame的列与列之间或两个Series之间的运算(比如分组加权平均)。以下面这个数据集为例,它含有分组键、值以及一些权重值: +根据 groupby 的“拆分-应用-合并”范式,可以进行 DataFrame 的列与列之间或两个 Series 之间的运算(比如分组加权平均)。以下面这个数据集为例,它含有分组键、值以及一些权重值: ```python In [114]: df = pd.DataFrame({'category': ['a', 'a', 'a', 'a', .....: 'b', 'b', 'b', 'b'], @@ -1050,7 +1050,7 @@ Out[115]: 7 b 0.253321 0.955905 ``` -然后可以利用category计算分组加权平均数: +然后可以利用 category 计算分组加权平均数: ```python In [116]: grouped = df.groupby('category') @@ -1064,7 +1064,7 @@ b -0.122262 dtype: float64 ``` -另一个例子,考虑一个来自Yahoo!Finance的数据集,其中含有几只股票和标准普尔500指数(符号SPX)的收盘价: +另一个例子,考虑一个来自 Yahoo!Finance 的数据集,其中含有几只股票和标准普尔 500 指数(符号 SPX)的收盘价: ```python In [119]: close_px = pd.read_csv('examples/stock_px_2.csv', parse_dates=True, .....: index_col=0) @@ -1089,17 +1089,17 @@ Out[121]: 2011-10-14 422.00 27.27 78.11 1224.58 ``` -来做一个比较有趣的任务:计算一个由日收益率(通过百分数变化计算)与SPX之间的年度相关系数组成的DataFrame。下面是一个实现办法,我们先创建一个函数,用它计算每列和SPX列的成对相关系数: +来做一个比较有趣的任务:计算一个由日收益率(通过百分数变化计算)与 SPX 之间的年度相关系数组成的 DataFrame。下面是一个实现办法,我们先创建一个函数,用它计算每列和 SPX 列的成对相关系数: ```python In [122]: spx_corr = lambda x: x.corrwith(x['SPX']) ``` -接下来,我们使用pct_change计算close_px的百分比变化: +接下来,我们使用 pct_change 计算 close_px 的百分比变化: ```python In [123]: rets = close_px.pct_change().dropna() ``` -最后,我们用年对百分比变化进行分组,可以用一个一行的函数,从每行的标签返回每个datetime标签的year属性: +最后,我们用年对百分比变化进行分组,可以用一个一行的函数,从每行的标签返回每个 datetime 标签的 year 属性: ```python In [124]: get_year = lambda x: x.year @@ -1119,7 +1119,7 @@ Out[126]: 2011 0.691931 0.800996 0.859975 1.0 ``` -当然,你还可以计算列与列之间的相关系数。这里,我们计算Apple和Microsoft的年相关系数: +当然,你还可以计算列与列之间的相关系数。这里,我们计算 Apple 和 Microsoft 的年相关系数: ```python In [127]: by_year.apply(lambda g: g['AAPL'].corr(g['MSFT'])) Out[127]: @@ -1137,7 +1137,7 @@ dtype: float64 ## 示例:组级别的线性回归 -顺着上一个例子继续,你可以用groupby执行更为复杂的分组统计分析,只要函数返回的是pandas对象或标量值即可。例如,我可以定义下面这个regress函数(利用statsmodels计量经济学库)对各数据块执行普通最小二乘法(Ordinary Least Squares,OLS)回归: +顺着上一个例子继续,你可以用 groupby 执行更为复杂的分组统计分析,只要函数返回的是 pandas 对象或标量值即可。例如,我可以定义下面这个 regress 函数(利用 statsmodels 计量经济学库)对各数据块执行普通最小二乘法(Ordinary Least Squares,OLS)回归: ```python import statsmodels.api as sm def regress(data, yvar, xvars): @@ -1148,7 +1148,7 @@ def regress(data, yvar, xvars): return result.params ``` -现在,为了按年计算AAPL对SPX收益率的线性回归,执行: +现在,为了按年计算 AAPL 对 SPX 收益率的线性回归,执行: ```python In [129]: by_year.apply(regress, 'AAPL', ['SPX']) Out[129]: @@ -1166,9 +1166,9 @@ Out[129]: # 10.4 透视表和交叉表 -透视表(pivot table)是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行聚合,并根据行和列上的分组键将数据分配到各个矩形区域中。在Python和pandas中,可以通过本章所介绍的groupby功能以及(能够利用层次化索引的)重塑运算制作透视表。DataFrame有一个pivot_table方法,此外还有一个顶级的pandas.pivot_table函数。除能为groupby提供便利之外,pivot_table还可以添加分项小计,也叫做margins。 +透视表(pivot table)是各种电子表格程序和其他数据分析软件中一种常见的数据汇总工具。它根据一个或多个键对数据进行聚合,并根据行和列上的分组键将数据分配到各个矩形区域中。在 Python 和 pandas 中,可以通过本章所介绍的 groupby 功能以及(能够利用层次化索引的)重塑运算制作透视表。DataFrame 有一个 pivot_table 方法,此外还有一个顶级的 pandas.pivot_table 函数。除能为 groupby 提供便利之外,pivot_table 还可以添加分项小计,也叫做 margins。 -回到小费数据集,假设我想要根据day和smoker计算分组平均数(pivot_table的默认聚合类型),并将day和smoker放到行上: +回到小费数据集,假设我想要根据 day 和 smoker 计算分组平均数(pivot_table 的默认聚合类型),并将 day 和 smoker 放到行上: ```python In [130]: tips.pivot_table(index=['day', 'smoker']) Out[130]: @@ -1184,7 +1184,7 @@ Thur No 2.488889 2.673778 0.160298 17.113111 Yes 2.352941 3.030000 0.163863 19.190588 ``` -可以用groupby直接来做。现在,假设我们只想聚合tip_pct和size,而且想根据time进行分组。我将smoker放到列上,把day放到行上: +可以用 groupby 直接来做。现在,假设我们只想聚合 tip_pct 和 size,而且想根据 time 进行分组。我将 smoker 放到列上,把 day 放到行上: ```python In [131]: tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'], .....: columns='smoker') @@ -1200,7 +1200,7 @@ Lunch Fri 3.000000 1.833333 0.187735 0.188937 Thur 2.500000 2.352941 0.160311 0.163863 ``` -还可以对这个表作进一步的处理,传入margins=True添加分项小计。这将会添加标签为All的行和列,其值对应于单个等级中所有数据的分组统计: +还可以对这个表作进一步的处理,传入 margins=True 添加分项小计。这将会添加标签为 All 的行和列,其值对应于单个等级中所有数据的分组统计: ```python In [132]: tips.pivot_table(['tip_pct', 'size'], index=['time', 'day'], .....: columns='smoker', margins=True) @@ -1217,9 +1217,9 @@ Lunch Fri 3.000000 1.833333 2.000000 0.187735 0.188937 0.188765 All 2.668874 2.408602 2.569672 0.159328 0.163196 0.160803 ``` -这里,All值为平均数:不单独考虑烟民与非烟民(All列),不单独考虑行分组两个级别中的任何单项(All行)。 +这里,All 值为平均数:不单独考虑烟民与非烟民(All 列),不单独考虑行分组两个级别中的任何单项(All 行)。 -要使用其他的聚合函数,将其传给aggfunc即可。例如,使用count或len可以得到有关分组大小的交叉表(计数或频率): +要使用其他的聚合函数,将其传给 aggfunc 即可。例如,使用 count 或 len 可以得到有关分组大小的交叉表(计数或频率): ```python In [133]: tips.pivot_table('tip_pct', index=['time', 'smoker'], columns='day', .....: aggfunc=len, margins=True) @@ -1233,7 +1233,7 @@ Lunch No 1.0 NaN NaN 44.0 45.0 All 19.0 87.0 76.0 62.0 244.0 ``` -如果存在空的组合(也就是NA),你可能会希望设置一个fill_value: +如果存在空的组合(也就是 NA),你可能会希望设置一个 fill_value: ```python In [134]: tips.pivot_table('tip_pct', index=['time', 'size', 'smoker'], .....: columns='day', aggfunc='mean', fill_value=0) @@ -1264,13 +1264,13 @@ Lunch 1 No 0.000000 0.000000 0.000000 0.181728 [21 rows x 4 columns] ``` -pivot_table的参数说明请参见表10-2。 +pivot_table 的参数说明请参见表 10-2。 -![表10-2 pivot_table的选项](img/7178691-c9e01844c4803a42.png) +![表 10-2 pivot_table 的选项](img/7178691-c9e01844c4803a42.png) ## 交叉表:crosstab -交叉表(cross-tabulation,简称crosstab)是一种用于计算分组频率的特殊透视表。看下面的例子: +交叉表(cross-tabulation,简称 crosstab)是一种用于计算分组频率的特殊透视表。看下面的例子: ```python In [138]: data Out[138]: @@ -1287,7 +1287,7 @@ Out[138]: 9 10 USA Right-handed ``` -作为调查分析的一部分,我们可能想要根据国籍和用手习惯对这段数据进行统计汇总。虽然可以用pivot_table实现该功能,但是pandas.crosstab函数会更方便: +作为调查分析的一部分,我们可能想要根据国籍和用手习惯对这段数据进行统计汇总。虽然可以用 pivot_table 实现该功能,但是 pandas.crosstab 函数会更方便: ```python In [139]: pd.crosstab(data.Nationality, data.Handedness, margins=True) Out[139]: @@ -1298,7 +1298,7 @@ USA 1 4 5 All 3 7 10 ``` -crosstab的前两个参数可以是数组或Series,或是数组列表。就像小费数据: +crosstab 的前两个参数可以是数组或 Series,或是数组列表。就像小费数据: ```python In [140]: pd.crosstab([tips.time, tips.day], tips.smoker, margins=True) Out[140]: @@ -1315,6 +1315,6 @@ All 151 93 244 # 10.5 总结 -掌握pandas数据分组工具既有助于数据清理,也有助于建模或统计分析工作。在第14章,我们会看几个例子,对真实数据使用groupby。 +掌握 pandas 数据分组工具既有助于数据清理,也有助于建模或统计分析工作。在第 14 章,我们会看几个例子,对真实数据使用 groupby。 在下一章,我们将关注时间序列数据。 diff --git a/docs/11.md b/docs/11.md index f4137ee..6d92e16 100644 --- a/docs/11.md +++ b/docs/11.md @@ -1,21 +1,21 @@ # 第 11 章 时间序列 -时间序列(time series)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学、经济学、生态学、神经科学、物理学等。在多个时间点观察或测量到的任何事物都可以形成一段时间序列。很多时间序列是固定频率的,也就是说,数据点是根据某种规律定期出现的(比如每15秒、每5分钟、每月出现一次)。时间序列也可以是不定期的,没有固定的时间单位或单位之间的偏移量。时间序列数据的意义取决于具体的应用场景,主要有以下几种: +时间序列(time series)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学、经济学、生态学、神经科学、物理学等。在多个时间点观察或测量到的任何事物都可以形成一段时间序列。很多时间序列是固定频率的,也就是说,数据点是根据某种规律定期出现的(比如每 15 秒、每 5 分钟、每月出现一次)。时间序列也可以是不定期的,没有固定的时间单位或单位之间的偏移量。时间序列数据的意义取决于具体的应用场景,主要有以下几种: - 时间戳(timestamp),特定的时刻。 -- 固定时期(period),如2007年1月或2010年全年。 +- 固定时期(period),如 2007 年 1 月或 2010 年全年。 - 时间间隔(interval),由起始和结束时间戳表示。时期(period)可以被看做间隔(interval)的特例。 - 实验或过程时间,每个时间点都是相对于特定起始时间的一个度量。例如,从放入烤箱时起,每秒钟饼干的直径。 -本章主要讲解前3种时间序列。许多技术都可用于处理实验型时间序列,其索引可能是一个整数或浮点数(表示从实验开始算起已经过去的时间)。最简单也最常见的时间序列都是用时间戳进行索引的。 +本章主要讲解前 3 种时间序列。许多技术都可用于处理实验型时间序列,其索引可能是一个整数或浮点数(表示从实验开始算起已经过去的时间)。最简单也最常见的时间序列都是用时间戳进行索引的。 ->提示:pandas也支持基于timedeltas的指数,它可以有效代表实验或经过的时间。这本书不涉及timedelta指数,但你可以学习pandas的文档(http://pandas.pydata.org/)。 +>提示:pandas 也支持基于 timedeltas 的指数,它可以有效代表实验或经过的时间。这本书不涉及 timedelta 指数,但你可以学习 pandas 的文档(http://pandas.pydata.org/)。 -pandas提供了许多内置的时间序列处理工具和数据算法。因此,你可以高效处理非常大的时间序列,轻松地进行切片/切块、聚合、对定期/不定期的时间序列进行重采样等。有些工具特别适合金融和经济应用,你当然也可以用它们来分析服务器日志数据。 +pandas 提供了许多内置的时间序列处理工具和数据算法。因此,你可以高效处理非常大的时间序列,轻松地进行切片/切块、聚合、对定期/不定期的时间序列进行重采样等。有些工具特别适合金融和经济应用,你当然也可以用它们来分析服务器日志数据。 # 11.1 日期和时间数据类型及工具 -Python标准库包含用于日期(date)和时间(time)数据的数据类型,而且还有日历方面的功能。我们主要会用到datetime、time以及calendar模块。datetime.datetime(也可以简写为datetime)是用得最多的数据类型: +Python 标准库包含用于日期(date)和时间(time)数据的数据类型,而且还有日历方面的功能。我们主要会用到 datetime、time 以及 calendar 模块。datetime.datetime(也可以简写为 datetime)是用得最多的数据类型: ```python In [10]: from datetime import datetime @@ -29,7 +29,7 @@ In [13]: now.year, now.month, now.day Out[13]: (2017, 9, 25) ``` -datetime以毫秒形式存储日期和时间。timedelta表示两个datetime对象之间的时间差: +datetime 以毫秒形式存储日期和时间。timedelta 表示两个 datetime 对象之间的时间差: ```python In [14]: delta = datetime(2011, 1, 7) - datetime(2008, 6, 24, 8, 15) @@ -44,7 +44,7 @@ In [17]: delta.seconds Out[17]: 56700 ``` -可以给datetime对象加上(或减去)一个或多个timedelta,这样会产生一个新对象: +可以给 datetime 对象加上(或减去)一个或多个 timedelta,这样会产生一个新对象: ```python In [18]: from datetime import timedelta @@ -58,17 +58,17 @@ In [21]: start - 2 * timedelta(12) Out[21]: datetime.datetime(2010, 12, 14, 0, 0) ``` -datetime模块中的数据类型参见表10-1。虽然本章主要讲的是pandas数据类型和高级时间序列处理,但你肯定会在Python的其他地方遇到有关datetime的数据类型。 +datetime 模块中的数据类型参见表 10-1。虽然本章主要讲的是 pandas 数据类型和高级时间序列处理,但你肯定会在 Python 的其他地方遇到有关 datetime 的数据类型。 -表11-1 datetime模块中的数据类型 +表 11-1 datetime 模块中的数据类型 ![](img/7178691-4af261a305a70aeb.png) tzinfo 存储时区信息的基本类型 -## 字符串和datetime的相互转换 +## 字符串和 datetime 的相互转换 -利用str或strftime方法(传入一个格式化字符串),datetime对象和pandas的Timestamp对象(稍后就会介绍)可以被格式化为字符串: +利用 str 或 strftime 方法(传入一个格式化字符串),datetime 对象和 pandas 的 Timestamp 对象(稍后就会介绍)可以被格式化为字符串: ```python In [22]: stamp = datetime(2011, 1, 3) @@ -79,15 +79,15 @@ In [24]: stamp.strftime('%Y-%m-%d') Out[24]: '2011-01-03' ``` -表11-2列出了全部的格式化编码。 +表 11-2 列出了全部的格式化编码。 -表11-2 datetime格式定义(兼容ISO C89) +表 11-2 datetime 格式定义(兼容 ISO C89) ![](img/7178691-50c751823754df58.png) ![](img/7178691-de0181e1f6b45eaf.png) -datetime.strptime可以用这些格式化编码将字符串转换为日期: +datetime.strptime 可以用这些格式化编码将字符串转换为日期: ```python In [25]: value = '2011-01-03' @@ -102,28 +102,28 @@ Out[28]: datetime.datetime(2011, 8, 6, 0, 0)] ``` -datetime.strptime是通过已知格式进行日期解析的最佳方式。但是每次都要编写格式定义是很麻烦的事情,尤其是对于一些常见的日期格式。这种情况下,你可以用dateutil这个第三方包中的parser.parse方法(pandas中已经自动安装好了): +datetime.strptime 是通过已知格式进行日期解析的最佳方式。但是每次都要编写格式定义是很麻烦的事情,尤其是对于一些常见的日期格式。这种情况下,你可以用 dateutil 这个第三方包中的 parser.parse 方法(pandas 中已经自动安装好了): ```python In [29]: from dateutil.parser import parse In [30]: parse('2011-01-03') Out[30]: datetime.datetime(2011, 1, 3, 0, 0) ``` -dateutil可以解析几乎所有人类能够理解的日期表示形式: +dateutil 可以解析几乎所有人类能够理解的日期表示形式: ```python In [31]: parse('Jan 31, 1997 10:45 PM') Out[31]: datetime.datetime(1997, 1, 31, 22, 45) ``` -在国际通用的格式中,日出现在月的前面很普遍,传入dayfirst=True即可解决这个问题: +在国际通用的格式中,日出现在月的前面很普遍,传入 dayfirst=True 即可解决这个问题: ```python In [32]: parse('6/12/2011', dayfirst=True) Out[32]: datetime.datetime(2011, 12, 6, 0, 0) ``` -pandas通常是用于处理成组日期的,不管这些日期是DataFrame的轴索引还是列。to_datetime方法可以解析多种不同的日期表示形式。对标准日期格式(如ISO8601)的解析非常快: +pandas 通常是用于处理成组日期的,不管这些日期是 DataFrame 的轴索引还是列。to_datetime 方法可以解析多种不同的日期表示形式。对标准日期格式(如 ISO8601)的解析非常快: ```python In [33]: datestrs = ['2011-07-06 12:00:00', '2011-08-06 00:00:00'] @@ -148,19 +148,19 @@ In [38]: pd.isnull(idx) Out[38]: array([False, False, True], dtype=bool) ``` -NaT(Not a Time)是pandas中时间戳数据的null值。 +NaT(Not a Time)是 pandas 中时间戳数据的 null 值。 ->注意:dateutil.parser是一个实用但不完美的工具。比如说,它会把一些原本不是日期的字符串认作是日期(比如"42"会被解析为2042年的今天)。 +>注意:dateutil.parser 是一个实用但不完美的工具。比如说,它会把一些原本不是日期的字符串认作是日期(比如"42"会被解析为 2042 年的今天)。 -datetime对象还有一些特定于当前环境(位于不同国家或使用不同语言的系统)的格式化选项。例如,德语或法语系统所用的月份简写就与英语系统所用的不同。表11-3进行了总结。 +datetime 对象还有一些特定于当前环境(位于不同国家或使用不同语言的系统)的格式化选项。例如,德语或法语系统所用的月份简写就与英语系统所用的不同。表 11-3 进行了总结。 -表11-3 特定于当前环境的日期格式 +表 11-3 特定于当前环境的日期格式 ![](img/7178691-cf0119398273e2b0.png) # 11.2 时间序列基础 -pandas最基本的时间序列类型就是以时间戳(通常以Python字符串或datatime对象表示)为索引的Series: +pandas 最基本的时间序列类型就是以时间戳(通常以 Python 字符串或 datatime 对象表示)为索引的 Series: ```python In [39]: from datetime import datetime @@ -181,7 +181,7 @@ Out[42]: dtype: float64 ``` -这些datetime对象实际上是被放在一个DatetimeIndex中的: +这些 datetime 对象实际上是被放在一个 DatetimeIndex 中的: ```python In [43]: ts.index Out[43]: @@ -190,7 +190,7 @@ DatetimeIndex(['2011-01-02', '2011-01-05', '2011-01-07', '2011-01-08', dtype='datetime64[ns]', freq=None) ``` -跟其他Series一样,不同索引的时间序列之间的算术运算会自动按日期对齐: +跟其他 Series 一样,不同索引的时间序列之间的算术运算会自动按日期对齐: ```python In [44]: ts + ts[::2] Out[44]: @@ -205,13 +205,13 @@ dtype: float64 ts[::2] 是每隔两个取一个。 -pandas用NumPy的datetime64数据类型以纳秒形式存储时间戳: +pandas 用 NumPy 的 datetime64 数据类型以纳秒形式存储时间戳: ```python In [45]: ts.index.dtype Out[45]: dtype('笔记:用户可以根据实际需求自定义一些频率类以便提供pandas所没有的日期逻辑,但具体的细节超出了本书的范围。 +>笔记:用户可以根据实际需求自定义一些频率类以便提供 pandas 所没有的日期逻辑,但具体的细节超出了本书的范围。 -表11-4 时间序列的基础频率 +表 11-4 时间序列的基础频率 ![](img/7178691-ff139312cd972204.png) @@ -619,9 +619,9 @@ DatetimeIndex(['2000-01-01 00:00:00', '2000-01-01 01:30:00', ![](img/7178691-d09e577a10d0e6eb.png) -## WOM日期 +## WOM 日期 -WOM(Week Of Month)是一种非常实用的频率类,它以WOM开头。它使你能获得诸如“每月第3个星期五”之类的日期: +WOM(Week Of Month)是一种非常实用的频率类,它以 WOM 开头。它使你能获得诸如“每月第 3 个星期五”之类的日期: ```python In [89]: rng = pd.date_range('2012-01-01', '2012-09-01', freq='WOM-3FRI') @@ -639,7 +639,7 @@ Out[90]: ## 移动(超前和滞后)数据 -移动(shifting)指的是沿着时间轴将数据前移或后移。Series和DataFrame都有一个shift方法用于执行单纯的前移或后移操作,保持索引不变: +移动(shifting)指的是沿着时间轴将数据前移或后移。Series 和 DataFrame 都有一个 shift 方法用于执行单纯的前移或后移操作,保持索引不变: ```python In [91]: ts = pd.Series(np.random.randn(4), ....: index=pd.date_range('1/1/2000', periods=4, freq='M')) @@ -671,12 +671,12 @@ Freq: M, dtype: float64 当我们这样进行移动时,就会在时间序列的前面或后面产生缺失数据。 -shift通常用于计算一个时间序列或多个时间序列(如DataFrame的列)中的百分比变化。可以这样表达: +shift 通常用于计算一个时间序列或多个时间序列(如 DataFrame 的列)中的百分比变化。可以这样表达: ```python ts / ts.shift(1) - 1 ``` -由于单纯的移位操作不会修改索引,所以部分数据会被丢弃。因此,如果频率已知,则可以将其传给shift以便实现对时间戳进行位移而不是对数据进行简单位移: +由于单纯的移位操作不会修改索引,所以部分数据会被丢弃。因此,如果频率已知,则可以将其传给 shift 以便实现对时间戳进行位移而不是对数据进行简单位移: ```python In [95]: ts.shift(2, freq='M') Out[95]: @@ -708,7 +708,7 @@ Freq: M, dtype: float64 ## 通过偏移量对日期进行位移 -pandas的日期偏移量还可以用在datetime或Timestamp对象上: +pandas 的日期偏移量还可以用在 datetime 或 Timestamp 对象上: ```python In [98]: from pandas.tseries.offsets import Day, MonthEnd @@ -718,7 +718,7 @@ In [100]: now + 3 * Day() Out[100]: Timestamp('2011-11-20 00:00:00') ``` -如果加的是锚点偏移量(比如MonthEnd),第一次增量会将原日期向前滚动到符合频率规则的下一个日期: +如果加的是锚点偏移量(比如 MonthEnd),第一次增量会将原日期向前滚动到符合频率规则的下一个日期: ```python In [101]: now + MonthEnd() Out[101]: Timestamp('2011-11-30 00:00:00') @@ -727,7 +727,7 @@ In [102]: now + MonthEnd(2) Out[102]: Timestamp('2011-12-31 00:00:00') ``` -通过锚点偏移量的rollforward和rollback方法,可明确地将日期向前或向后“滚动”: +通过锚点偏移量的 rollforward 和 rollback 方法,可明确地将日期向前或向后“滚动”: ```python In [103]: offset = MonthEnd() @@ -738,7 +738,7 @@ In [105]: offset.rollback(now) Out[105]: Timestamp('2011-10-31 00:00:00') ``` -日期偏移量还有一个巧妙的用法,即结合groupby使用这两个“滚动”方法: +日期偏移量还有一个巧妙的用法,即结合 groupby 使用这两个“滚动”方法: ```python In [106]: ts = pd.Series(np.random.randn(20), .....: index=pd.date_range('1/15/2000', periods=20, freq='4d')) @@ -775,7 +775,7 @@ Out[108]: dtype: float64 ``` -当然,更简单、更快速地实现该功能的办法是使用resample(11.6小节将对此进行详细介绍): +当然,更简单、更快速地实现该功能的办法是使用 resample(11.6 小节将对此进行详细介绍): ```python In [109]: ts.resample('M').mean() Out[109]: @@ -787,11 +787,11 @@ Freq: M, dtype: float64 # 11.4 时区处理 -时间序列处理工作中最让人不爽的就是对时区的处理。许多人都选择以协调世界时(UTC,它是格林尼治标准时间(Greenwich Mean Time)的接替者,目前已经是国际标准了)来处理时间序列。时区是以UTC偏移量的形式表示的。例如,夏令时期间,纽约比UTC慢4小时,而在全年其他时间则比UTC慢5小时。 +时间序列处理工作中最让人不爽的就是对时区的处理。许多人都选择以协调世界时(UTC,它是格林尼治标准时间(Greenwich Mean Time)的接替者,目前已经是国际标准了)来处理时间序列。时区是以 UTC 偏移量的形式表示的。例如,夏令时期间,纽约比 UTC 慢 4 小时,而在全年其他时间则比 UTC 慢 5 小时。 -在Python中,时区信息来自第三方库pytz,它使Python可以使用Olson数据库(汇编了世界时区信息)。这对历史数据非常重要,这是因为由于各地政府的各种突发奇想,夏令时转变日期(甚至UTC偏移量)已经发生过多次改变了。就拿美国来说,DST转变时间自1900年以来就改变过多次! +在 Python 中,时区信息来自第三方库 pytz,它使 Python 可以使用 Olson 数据库(汇编了世界时区信息)。这对历史数据非常重要,这是因为由于各地政府的各种突发奇想,夏令时转变日期(甚至 UTC 偏移量)已经发生过多次改变了。就拿美国来说,DST 转变时间自 1900 年以来就改变过多次! -有关pytz库的更多信息,请查阅其文档。就本书而言,由于pandas包装了pytz的功能,因此你可以不用记忆其API,只要记得时区的名称即可。时区名可以在shell中看到,也可以通过文档查看: +有关 pytz 库的更多信息,请查阅其文档。就本书而言,由于 pandas 包装了 pytz 的功能,因此你可以不用记忆其 API,只要记得时区的名称即可。时区名可以在 shell 中看到,也可以通过文档查看: ```python In [110]: import pytz @@ -799,7 +799,7 @@ In [111]: pytz.common_timezones[-5:] Out[111]: ['US/Eastern', 'US/Hawaii', 'US/Mountain', 'US/Pacific', 'UTC'] ``` -要从pytz中获取时区对象,使用pytz.timezone即可: +要从 pytz 中获取时区对象,使用 pytz.timezone 即可: ```python In [112]: tz = pytz.timezone('America/New_York') @@ -807,11 +807,11 @@ In [113]: tz Out[113]: ``` -pandas中的方法既可以接受时区名也可以接受这些对象。 +pandas 中的方法既可以接受时区名也可以接受这些对象。 # 时区本地化和转换 -默认情况下,pandas中的时间序列是单纯(naive)的时区。看看下面这个时间序列: +默认情况下,pandas 中的时间序列是单纯(naive)的时区。看看下面这个时间序列: ```python In [114]: rng = pd.date_range('3/9/2012 9:30', periods=6, freq='D') @@ -828,7 +828,7 @@ Out[116]: Freq: D, dtype: float64 ``` -其索引的tz字段为None: +其索引的 tz 字段为 None: ```python In [117]: print(ts.index.tz) None @@ -846,7 +846,7 @@ DatetimeIndex(['2012-03-09 09:30:00+00:00', '2012-03-10 09:30:00+00:00', dtype='datetime64[ns, UTC]', freq='D') ``` -从单纯到本地化的转换是通过tz_localize方法处理的: +从单纯到本地化的转换是通过 tz_localize 方法处理的: ```python In [119]: ts Out[119]: @@ -878,7 +878,7 @@ DatetimeIndex(['2012-03-09 09:30:00+00:00', '2012-03-10 09:30:00+00:00', dtype='datetime64[ns, UTC]', freq='D') ``` -一旦时间序列被本地化到某个特定时区,就可以用tz_convert将其转换到别的时区了: +一旦时间序列被本地化到某个特定时区,就可以用 tz_convert 将其转换到别的时区了: ```python In [123]: ts_utc.tz_convert('America/New_York') Out[123]: @@ -891,7 +891,7 @@ Out[123]: Freq: D, dtype: float64 ``` -对于上面这种时间序列(它跨越了美国东部时区的夏令时转变期),我们可以将其本地化到EST,然后转换为UTC或柏林时间: +对于上面这种时间序列(它跨越了美国东部时区的夏令时转变期),我们可以将其本地化到 EST,然后转换为 UTC 或柏林时间: ```python In [124]: ts_eastern = ts.tz_localize('America/New_York') @@ -916,7 +916,7 @@ Out[126]: Freq: D, dtype: float64 ``` -tz_localize和tz_convert也是DatetimeIndex的实例方法: +tz_localize 和 tz_convert 也是 DatetimeIndex 的实例方法: ```python In [127]: ts.index.tz_localize('Asia/Shanghai') Out[127]: @@ -928,9 +928,9 @@ DatetimeIndex(['2012-03-09 09:30:00+08:00', '2012-03-10 09:30:00+08:00', >注意:对单纯时间戳的本地化操作还会检查夏令时转变期附近容易混淆或不存在的时间。 -## 操作时区意识型Timestamp对象 +## 操作时区意识型 Timestamp 对象 -跟时间序列和日期范围差不多,独立的Timestamp对象也能被从单纯型(naive)本地化为时区意识型(time zone-aware),并从一个时区转换到另一个时区: +跟时间序列和日期范围差不多,独立的 Timestamp 对象也能被从单纯型(naive)本地化为时区意识型(time zone-aware),并从一个时区转换到另一个时区: ```python In [128]: stamp = pd.Timestamp('2011-03-12 04:00') @@ -940,7 +940,7 @@ In [130]: stamp_utc.tz_convert('America/New_York') Out[130]: Timestamp('2011-03-11 23:00:00-0500', tz='America/New_York') ``` -在创建Timestamp时,还可以传入一个时区信息: +在创建 Timestamp 时,还可以传入一个时区信息: ```python In [131]: stamp_moscow = pd.Timestamp('2011-03-12 04:00', tz='Europe/Moscow') @@ -948,7 +948,7 @@ In [132]: stamp_moscow Out[132]: Timestamp('2011-03-12 04:00:00+0300', tz='Europe/Moscow') ``` -时区意识型Timestamp对象在内部保存了一个UTC时间戳值(自UNIX纪元(1970年1月1日)算起的纳秒数)。这个UTC值在时区转换过程中是不会发生变化的: +时区意识型 Timestamp 对象在内部保存了一个 UTC 时间戳值(自 UNIX 纪元(1970 年 1 月 1 日)算起的纳秒数)。这个 UTC 值在时区转换过程中是不会发生变化的: ```python In [133]: stamp_utc.value Out[133]: 1299902400000000000 @@ -957,7 +957,7 @@ In [134]: stamp_utc.tz_convert('America/New_York').value Out[134]: 1299902400000000000 ``` -当使用pandas的DateOffset对象执行时间算术运算时,运算过程会自动关注是否存在夏令时转变期。这里,我们创建了在DST转变之前的时间戳。首先,来看夏令时转变前的30分钟: +当使用 pandas 的 DateOffset 对象执行时间算术运算时,运算过程会自动关注是否存在夏令时转变期。这里,我们创建了在 DST 转变之前的时间戳。首先,来看夏令时转变前的 30 分钟: ```python In [135]: from pandas.tseries.offsets import Hour @@ -970,7 +970,7 @@ In [138]: stamp + Hour() Out[138]: Timestamp('2012-03-12 02:30:00-0400', tz='US/Eastern') ``` -然后,夏令时转变前90分钟: +然后,夏令时转变前 90 分钟: ```python In [139]: stamp = pd.Timestamp('2012-11-04 00:30', tz='US/Eastern') @@ -983,7 +983,7 @@ Out[141]: Timestamp('2012-11-04 01:30:00-0500', tz='US/Eastern') ## 不同时区之间的运算 -如果两个时间序列的时区不同,在将它们合并到一起时,最终结果就会是UTC。由于时间戳其实是以UTC存储的,所以这是一个很简单的运算,并不需要发生任何转换: +如果两个时间序列的时区不同,在将它们合并到一起时,最终结果就会是 UTC。由于时间戳其实是以 UTC 存储的,所以这是一个很简单的运算,并不需要发生任何转换: ```python In [142]: rng = pd.date_range('3/7/2012 9:30', periods=10, freq='B') @@ -1020,7 +1020,7 @@ DatetimeIndex(['2012-03-07 09:30:00+00:00', '2012-03-08 09:30:00+00:00', # 11.5 时期及其算术运算 -时期(period)表示的是时间区间,比如数日、数月、数季、数年等。Period类所表示的就是这种数据类型,其构造函数需要用到一个字符串或整数,以及表11-4中的频率: +时期(period)表示的是时间区间,比如数日、数月、数季、数年等。Period 类所表示的就是这种数据类型,其构造函数需要用到一个字符串或整数,以及表 11-4 中的频率: ```python In [149]: p = pd.Period(2007, freq='A-DEC') @@ -1028,7 +1028,7 @@ In [150]: p Out[150]: Period('2007', 'A-DEC') ``` -这里,这个Period对象表示的是从2007年1月1日到2007年12月31日之间的整段时间。只需对Period对象加上或减去一个整数即可达到根据其频率进行位移的效果: +这里,这个 Period 对象表示的是从 2007 年 1 月 1 日到 2007 年 12 月 31 日之间的整段时间。只需对 Period 对象加上或减去一个整数即可达到根据其频率进行位移的效果: ```python In [151]: p + 5 Out[151]: Period('2012', 'A-DEC') @@ -1037,13 +1037,13 @@ In [152]: p - 2 Out[152]: Period('2005', 'A-DEC') ``` -如果两个Period对象拥有相同的频率,则它们的差就是它们之间的单位数量: +如果两个 Period 对象拥有相同的频率,则它们的差就是它们之间的单位数量: ```python In [153]: pd.Period('2014', freq='A-DEC') - p Out[153]: 7 ``` -period_range函数可用于创建规则的时期范围: +period_range 函数可用于创建规则的时期范围: ```python In [154]: rng = pd.period_range('2000-01-01', '2000-06-30', freq='M') @@ -1052,7 +1052,7 @@ Out[155]: PeriodIndex(['2000-01', '2000-02', '2000-03', '2000-04', '2000-05', '2 00-06'], dtype='period[M]', freq='M') ``` -PeriodIndex类保存了一组Period,它可以在任何pandas数据结构中被用作轴索引: +PeriodIndex 类保存了一组 Period,它可以在任何 pandas 数据结构中被用作轴索引: ```python In [156]: pd.Series(np.random.randn(6), index=rng) Out[156]: @@ -1065,7 +1065,7 @@ Out[156]: Freq: M, dtype: float64 ``` -如果你有一个字符串数组,你也可以使用PeriodIndex类: +如果你有一个字符串数组,你也可以使用 PeriodIndex 类: ```python In [157]: values = ['2001Q3', '2002Q2', '2003Q1'] @@ -1078,7 +1078,7 @@ Out[159]: PeriodIndex(['2001Q3', '2002Q2', '2003Q1'], dtype='period[Q-DEC]', fre ## 时期的频率转换 -Period和PeriodIndex对象都可以通过其asfreq方法被转换成别的频率。假设我们有一个年度时期,希望将其转换为当年年初或年末的一个月度时期。该任务非常简单: +Period 和 PeriodIndex 对象都可以通过其 asfreq 方法被转换成别的频率。假设我们有一个年度时期,希望将其转换为当年年初或年末的一个月度时期。该任务非常简单: ```python In [160]: p = pd.Period('2007', freq='A-DEC') @@ -1092,7 +1092,7 @@ In [163]: p.asfreq('M', how='end') Out[163]: Period('2007-12', 'M') ``` -你可以将Period('2007','A-DEC')看做一个被划分为多个月度时期的时间段中的游标。图11-1对此进行了说明。对于一个不以12月结束的财政年度,月度子时期的归属情况就不一样了: +你可以将 Period('2007','A-DEC')看做一个被划分为多个月度时期的时间段中的游标。图 11-1 对此进行了说明。对于一个不以 12 月结束的财政年度,月度子时期的归属情况就不一样了: ```python In [164]: p = pd.Period('2007', freq='A-JUN') @@ -1106,9 +1106,9 @@ In [167]: p.asfreq('M', 'end') Out[167]: Period('2007-06', 'M') ``` -![图11-1 Period频率转换示例](img/7178691-d201200d0e65676f.png) +![图 11-1 Period 频率转换示例](img/7178691-d201200d0e65676f.png) -在将高频率转换为低频率时,超时期(superperiod)是由子时期(subperiod)所属的位置决定的。例如,在A-JUN频率中,月份“2007年8月”实际上是属于周期“2008年”的: +在将高频率转换为低频率时,超时期(superperiod)是由子时期(subperiod)所属的位置决定的。例如,在 A-JUN 频率中,月份“2007 年 8 月”实际上是属于周期“2008 年”的: ```python In [168]: p = pd.Period('Aug-2007', 'M') @@ -1116,7 +1116,7 @@ In [169]: p.asfreq('A-JUN') Out[169]: Period('2008', 'A-JUN') ``` -完整的PeriodIndex或TimeSeries的频率转换方式也是如此: +完整的 PeriodIndex 或 TimeSeries 的频率转换方式也是如此: ```python In [170]: rng = pd.period_range('2006', '2009', freq='A-DEC') @@ -1153,7 +1153,7 @@ Freq: B, dtype: float64 ## 按季度计算的时期频率 -季度型数据在会计、金融等领域中很常见。许多季度型数据都会涉及“财年末”的概念,通常是一年12个月中某月的最后一个日历日或工作日。就这一点来说,时期"2012Q4"根据财年末的不同会有不同的含义。pandas支持12种可能的季度型频率,即Q-JAN到Q-DEC: +季度型数据在会计、金融等领域中很常见。许多季度型数据都会涉及“财年末”的概念,通常是一年 12 个月中某月的最后一个日历日或工作日。就这一点来说,时期"2012Q4"根据财年末的不同会有不同的含义。pandas 支持 12 种可能的季度型频率,即 Q-JAN 到 Q-DEC: ```python In [175]: p = pd.Period('2012Q4', freq='Q-JAN') @@ -1162,7 +1162,7 @@ In [176]: p Out[176]: Period('2012Q4', 'Q-JAN') ``` -在以1月结束的财年中,2012Q4是从11月到1月(将其转换为日型频率就明白了)。图11-2对此进行了说明: +在以 1 月结束的财年中,2012Q4 是从 11 月到 1 月(将其转换为日型频率就明白了)。图 11-2 对此进行了说明: ```python In [177]: p.asfreq('D', 'start') @@ -1172,9 +1172,9 @@ In [178]: p.asfreq('D', 'end') Out[178]: Period('2012-01-31', 'D') ``` -![图11.2 不同季度型频率之间的转换](img/7178691-e2e1d52c9766f6ff.png) +![图 11.2 不同季度型频率之间的转换](img/7178691-e2e1d52c9766f6ff.png) -因此,Period之间的算术运算会非常简单。例如,要获取该季度倒数第二个工作日下午4点的时间戳,你可以这样: +因此,Period 之间的算术运算会非常简单。例如,要获取该季度倒数第二个工作日下午 4 点的时间戳,你可以这样: ```python In [179]: p4pm = (p.asfreq('B', 'e') - 1).asfreq('T', 's') + 16 * 60 @@ -1186,7 +1186,7 @@ In [181]: p4pm.to_timestamp() Out[181]: Timestamp('2012-01-30 16:00:00') ``` -period_range可用于生成季度型范围。季度型范围的算术运算也跟上面是一样的: +period_range 可用于生成季度型范围。季度型范围的算术运算也跟上面是一样的: ```python In [182]: rng = pd.period_range('2011Q3', '2012Q4', freq='Q-JAN') @@ -1217,9 +1217,9 @@ Out[187]: dtype: int64 ``` -## 将Timestamp转换为Period(及其反向过程) +## 将 Timestamp 转换为 Period(及其反向过程) -通过使用to_period方法,可以将由时间戳索引的Series和DataFrame对象转换为以时期索引: +通过使用 to_period 方法,可以将由时间戳索引的 Series 和 DataFrame 对象转换为以时期索引: ```python In [188]: rng = pd.date_range('2000-01-01', periods=3, freq='M') @@ -1243,7 +1243,7 @@ Out[192]: Freq: M, dtype: float64 ``` -由于时期指的是非重叠时间区间,因此对于给定的频率,一个时间戳只能属于一个时期。新PeriodIndex的频率默认是从时间戳推断而来的,你也可以指定任何别的频率。结果中允许存在重复时期: +由于时期指的是非重叠时间区间,因此对于给定的频率,一个时间戳只能属于一个时期。新 PeriodIndex 的频率默认是从时间戳推断而来的,你也可以指定任何别的频率。结果中允许存在重复时期: ```python In [193]: rng = pd.date_range('1/29/2000', periods=6, freq='D') @@ -1270,7 +1270,7 @@ Out[196]: Freq: M, dtype: float64 ``` -要转换回时间戳,使用to_timestamp即可: +要转换回时间戳,使用 to_timestamp 即可: ```python In [197]: pts = ts2.to_period() @@ -1296,7 +1296,7 @@ Out[199]: Freq: D, dtype: float64 ``` -## 通过数组创建PeriodIndex +## 通过数组创建 PeriodIndex 固定频率的数据集通常会将时间信息分开存放在多个列中。例如,在下面这个宏观经济数据集中,年度和季度就分别存放在不同的列中: ```python @@ -1368,7 +1368,7 @@ Out[203]: Name: quarter, Length: 203, dtype: float64 ``` -通过将这些数组以及一个频率传入PeriodIndex,就可以将它们合并成DataFrame的一个索引: +通过将这些数组以及一个频率传入 PeriodIndex,就可以将它们合并成 DataFrame 的一个索引: ```python In [204]: index = pd.PeriodIndex(year=data.year, quarter=data.quarter, .....: freq='Q-DEC') @@ -1412,9 +1412,9 @@ Freq: Q-DEC, Name: infl, Length: 203, dtype: float64 # 11.6 重采样及频率转换 -重采样(resampling)指的是将时间序列从一个频率转换到另一个频率的处理过程。将高频率数据聚合到低频率称为降采样(downsampling),而将低频率数据转换到高频率则称为升采样(upsampling)。并不是所有的重采样都能被划分到这两个大类中。例如,将W-WED(每周三)转换为W-FRI既不是降采样也不是升采样。 +重采样(resampling)指的是将时间序列从一个频率转换到另一个频率的处理过程。将高频率数据聚合到低频率称为降采样(downsampling),而将低频率数据转换到高频率则称为升采样(upsampling)。并不是所有的重采样都能被划分到这两个大类中。例如,将 W-WED(每周三)转换为 W-FRI 既不是降采样也不是升采样。 -pandas对象都带有一个resample方法,它是各种频率转换工作的主力函数。resample有一个类似于groupby的API,调用resample可以分组数据,然后会调用一个聚合函数: +pandas 对象都带有一个 resample 方法,它是各种频率转换工作的主力函数。resample 有一个类似于 groupby 的 API,调用 resample 可以分组数据,然后会调用一个聚合函数: ```python In [208]: rng = pd.date_range('2000-01-01', periods=100, freq='D') @@ -1462,21 +1462,21 @@ Out[212]: Freq: M, dtype: float64 ``` -resample是一个灵活高效的方法,可用于处理非常大的时间序列。我将通过一系列的示例说明其用法。表11-5总结它的一些选项。 +resample 是一个灵活高效的方法,可用于处理非常大的时间序列。我将通过一系列的示例说明其用法。表 11-5 总结它的一些选项。 -表11-5 resample方法的参数 +表 11-5 resample 方法的参数 ![](img/7178691-b40a57086c904e83.png) ## 降采样 -将数据聚合到规律的低频率是一件非常普通的时间序列处理任务。待聚合的数据不必拥有固定的频率,期望的频率会自动定义聚合的面元边界,这些面元用于将时间序列拆分为多个片段。例如,要转换到月度频率('M'或'BM'),数据需要被划分到多个单月时间段中。各时间段都是半开放的。一个数据点只能属于一个时间段,所有时间段的并集必须能组成整个时间帧。在用resample对数据进行降采样时,需要考虑两样东西: +将数据聚合到规律的低频率是一件非常普通的时间序列处理任务。待聚合的数据不必拥有固定的频率,期望的频率会自动定义聚合的面元边界,这些面元用于将时间序列拆分为多个片段。例如,要转换到月度频率('M'或'BM'),数据需要被划分到多个单月时间段中。各时间段都是半开放的。一个数据点只能属于一个时间段,所有时间段的并集必须能组成整个时间帧。在用 resample 对数据进行降采样时,需要考虑两样东西: - 各区间哪边是闭合的。 - 如何标记各个聚合面元,用区间的开头还是末尾。 -为了说明,我们来看一些“1分钟”数据: +为了说明,我们来看一些“1 分钟”数据: ```python In [213]: rng = pd.date_range('2000-01-01', periods=12, freq='T') @@ -1499,7 +1499,7 @@ Out[215]: Freq: T, dtype: int64 ``` -假设你想要通过求和的方式将这些数据聚合到“5分钟”块中: +假设你想要通过求和的方式将这些数据聚合到“5 分钟”块中: ```python In [216]: ts.resample('5min', closed='right').sum() Out[216]: @@ -1510,7 +1510,7 @@ Out[216]: Freq: 5T, dtype: int64 ``` -传入的频率将会以“5分钟”的增量定义面元边界。默认情况下,面元的右边界是包含的,因此00:00到00:05的区间中是包含00:05的。传入closed='left'会让区间以左边界闭合: +传入的频率将会以“5 分钟”的增量定义面元边界。默认情况下,面元的右边界是包含的,因此 00:00 到 00:05 的区间中是包含 00:05 的。传入 closed='left'会让区间以左边界闭合: ```python In [217]: ts.resample('5min', closed='right').sum() Out[217]: @@ -1521,7 +1521,7 @@ Out[217]: Freq: 5T, dtype: int64 ``` -如你所见,最终的时间序列是以各面元右边界的时间戳进行标记的。传入label='right'即可用面元的邮编界对其进行标记: +如你所见,最终的时间序列是以各面元右边界的时间戳进行标记的。传入 label='right'即可用面元的邮编界对其进行标记: ```python In [218]: ts.resample('5min', closed='right', label='right').sum() Out[218]: @@ -1532,11 +1532,11 @@ Out[218]: Freq: 5T, dtype: int64 ``` -图11-3说明了“1分钟”数据被转换为“5分钟”数据的处理过程。 +图 11-3 说明了“1 分钟”数据被转换为“5 分钟”数据的处理过程。 -![图11-3 各种closed、label约定的“5分钟”重采样演示](img/7178691-7a77f47844f2ee8c.png) +![图 11-3 各种 closed、label 约定的“5 分钟”重采样演示](img/7178691-7a77f47844f2ee8c.png) -最后,你可能希望对结果索引做一些位移,比如从右边界减去一秒以便更容易明白该时间戳到底表示的是哪个区间。只需通过loffset设置一个字符串或日期偏移量即可实现这个目的: +最后,你可能希望对结果索引做一些位移,比如从右边界减去一秒以便更容易明白该时间戳到底表示的是哪个区间。只需通过 loffset 设置一个字符串或日期偏移量即可实现这个目的: ```python In [219]: ts.resample('5min', closed='right', .....: label='right', loffset='-1s').sum() @@ -1550,11 +1550,11 @@ Out[219]: 2000-01-01 00:04:59 15 ``` -此外,也可以通过调用结果对象的shift方法来实现该目的,这样就不需要设置loffset了。 +此外,也可以通过调用结果对象的 shift 方法来实现该目的,这样就不需要设置 loffset 了。 -##OHLC重采样 +##OHLC 重采样 -金融领域中有一种无所不在的时间序列聚合方式,即计算各面元的四个值:第一个值(open,开盘)、最后一个值(close,收盘)、最大值(high,最高)以及最小值(low,最低)。传入how='ohlc'即可得到一个含有这四种聚合值的DataFrame。整个过程很高效,只需一次扫描即可计算出结果: +金融领域中有一种无所不在的时间序列聚合方式,即计算各面元的四个值:第一个值(open,开盘)、最后一个值(close,收盘)、最大值(high,最高)以及最小值(low,最低)。传入 how='ohlc'即可得到一个含有这四种聚合值的 DataFrame。整个过程很高效,只需一次扫描即可计算出结果: ```python In [220]: ts.resample('5min').ohlc() Out[220]: @@ -1566,7 +1566,7 @@ Out[220]: ##升采样和插值 -在将数据从低频率转换到高频率时,就不需要聚合了。我们来看一个带有一些周型数据的DataFrame: +在将数据从低频率转换到高频率时,就不需要聚合了。我们来看一个带有一些周型数据的 DataFrame: ```python In [221]: frame = pd.DataFrame(np.random.randn(2, 4), .....: index=pd.date_range('1/1/2000', periods=2, @@ -1580,7 +1580,7 @@ Out[222]: 2000-01-12 -0.046662 0.927238 0.482284 -0.867130 ``` -当你对这个数据进行聚合,每组只有一个值,这样就会引入缺失值。我们使用asfreq方法转换成高频,不经过聚合: +当你对这个数据进行聚合,每组只有一个值,这样就会引入缺失值。我们使用 asfreq 方法转换成高频,不经过聚合: ```python In [223]: df_daily = frame.resample('D').asfreq() @@ -1597,7 +1597,7 @@ Out[224]: 2000-01-12 -0.046662 0.927238 0.482284 -0.867130 ``` -假设你想要用前面的周型值填充“非星期三”。resampling的填充和插值方式跟fillna和reindex的一样: +假设你想要用前面的周型值填充“非星期三”。resampling 的填充和插值方式跟 fillna 和 reindex 的一样: ```python In [225]: frame.resample('D').ffill() Out[225]: @@ -1664,7 +1664,7 @@ Out[231]: 2001 0.046303 0.163344 0.251503 -0.157276 ``` -升采样要稍微麻烦一些,因为你必须决定在新频率中各区间的哪端用于放置原来的值,就像asfreq方法那样。convention参数默认为'start',也可设置为'end': +升采样要稍微麻烦一些,因为你必须决定在新频率中各区间的哪端用于放置原来的值,就像 asfreq 方法那样。convention 参数默认为'start',也可设置为'end': ```python # Q-DEC: Quarterly, year ending in December @@ -1695,7 +1695,7 @@ Out[233]: - 在降采样中,目标频率必须是源频率的子时期(subperiod)。 - 在升采样中,目标频率必须是源频率的超时期(superperiod)。 -如果不满足这些条件,就会引发异常。这主要影响的是按季、年、周计算的频率。例如,由Q-MAR定义的时间区间只能升采样为A-MAR、A-JUN、A-SEP、A-DEC等: +如果不满足这些条件,就会引发异常。这主要影响的是按季、年、周计算的频率。例如,由 Q-MAR 定义的时间区间只能升采样为 A-MAR、A-JUN、A-SEP、A-DEC 等: ```python In [234]: annual_frame.resample('Q-MAR').ffill() Out[234]: @@ -1724,7 +1724,7 @@ In [236]: close_px = close_px_all[['AAPL', 'MSFT', 'XOM']] In [237]: close_px = close_px.resample('B').ffill() ``` -现在引入rolling运算符,它与resample和groupby很像。可以在TimeSeries或DataFrame以及一个window(表示期数,见图11-4)上调用它: +现在引入 rolling 运算符,它与 resample 和 groupby 很像。可以在 TimeSeries 或 DataFrame 以及一个 window(表示期数,见图 11-4)上调用它: ```python In [238]: close_px.AAPL.plot() Out[238]: @@ -1732,11 +1732,11 @@ Out[238]: In [239]: close_px.AAPL.rolling(250).mean().plot() ``` -![图11-4 苹果公司股价的250日均线](img/7178691-3327483eab730b09.png) +![图 11-4 苹果公司股价的 250 日均线](img/7178691-3327483eab730b09.png) -表达式rolling(250)与groupby很像,但不是对其进行分组,而是创建一个按照250天分组的滑动窗口对象。然后,我们就得到了苹果公司股价的250天的移动窗口。 +表达式 rolling(250)与 groupby 很像,但不是对其进行分组,而是创建一个按照 250 天分组的滑动窗口对象。然后,我们就得到了苹果公司股价的 250 天的移动窗口。 -默认情况下,rolling函数需要窗口中所有的值为非NA值。可以修改该行为以解决缺失数据的问题。其实,在时间序列开始处尚不足窗口期的那些数据就是个特例(见图11-5): +默认情况下,rolling 函数需要窗口中所有的值为非 NA 值。可以修改该行为以解决缺失数据的问题。其实,在时间序列开始处尚不足窗口期的那些数据就是个特例(见图 11-5): ```python In [241]: appl_std250 = close_px.AAPL.rolling(250, min_periods=10).std() @@ -1754,21 +1754,21 @@ Freq: B, Name: AAPL, dtype: float64 In [243]: appl_std250.plot() ``` -![图11-5 苹果公司250日每日回报标准差](img/7178691-15f565bed1ccad09.png) +![图 11-5 苹果公司 250 日每日回报标准差](img/7178691-15f565bed1ccad09.png) -要计算扩展窗口平均(expanding window mean),可以使用expanding而不是rolling。“扩展”意味着,从时间序列的起始处开始窗口,增加窗口直到它超过所有的序列。apple_std250时间序列的扩展窗口平均如下所示: +要计算扩展窗口平均(expanding window mean),可以使用 expanding 而不是 rolling。“扩展”意味着,从时间序列的起始处开始窗口,增加窗口直到它超过所有的序列。apple_std250 时间序列的扩展窗口平均如下所示: ```python In [244]: expanding_mean = appl_std250.expanding().mean() ``` -对DataFrame调用rolling_mean(以及与之类似的函数)会将转换应用到所有的列上(见图11-6): +对 DataFrame 调用 rolling_mean(以及与之类似的函数)会将转换应用到所有的列上(见图 11-6): ```python In [246]: close_px.rolling(60).mean().plot(logy=True) ``` -![图11-6 各股价60日均线(对数Y轴)](img/7178691-979f748052b2279f.png) +![图 11-6 各股价 60 日均线(对数 Y 轴)](img/7178691-979f748052b2279f.png) -rolling函数也可以接受一个指定固定大小时间补偿字符串,而不是一组时期。这样可以方便处理不规律的时间序列。这些字符串也可以传递给resample。例如,我们可以计算20天的滚动均值,如下所示: +rolling 函数也可以接受一个指定固定大小时间补偿字符串,而不是一组时期。这样可以方便处理不规律的时间序列。这些字符串也可以传递给 resample。例如,我们可以计算 20 天的滚动均值,如下所示: ```python In [247]: close_px.rolling('20D').mean() Out[247]: @@ -1803,7 +1803,7 @@ Out[247]: 由于指数加权统计会赋予近期的观测值更大的权数,因此相对于等权统计,它能“适应”更快的变化。 -除了rolling和expanding,pandas还有ewm运算符。下面这个例子对比了苹果公司股价的30日移动平均和span=30的指数加权移动平均(如图11-7所示): +除了 rolling 和 expanding,pandas 还有 ewm 运算符。下面这个例子对比了苹果公司股价的 30 日移动平均和 span=30 的指数加权移动平均(如图 11-7 所示): ```python In [249]: aapl_px = close_px.AAPL['2006':'2007'] @@ -1820,11 +1820,11 @@ Out[253]: In [254]: plt.legend() ``` -![图11-7 简单移动平均与指数加权移动平均](img/7178691-dae48defe3749fad.png) +![图 11-7 简单移动平均与指数加权移动平均](img/7178691-dae48defe3749fad.png) ## 二元移动窗口函数 -有些统计运算(如相关系数和协方差)需要在两个时间序列上执行。例如,金融分析师常常对某只股票对某个参考指数(如标准普尔500指数)的相关系数感兴趣。要进行说明,我们先计算我们感兴趣的时间序列的百分数变化: +有些统计运算(如相关系数和协方差)需要在两个时间序列上执行。例如,金融分析师常常对某只股票对某个参考指数(如标准普尔 500 指数)的相关系数感兴趣。要进行说明,我们先计算我们感兴趣的时间序列的百分数变化: ```python In [256]: spx_px = close_px_all['SPX'] @@ -1833,28 +1833,28 @@ In [257]: spx_rets = spx_px.pct_change() In [258]: returns = close_px.pct_change() ``` -调用rolling之后,corr聚合函数开始计算与spx_rets滚动相关系数(结果见图11-8): +调用 rolling 之后,corr 聚合函数开始计算与 spx_rets 滚动相关系数(结果见图 11-8): ```python In [259]: corr = returns.AAPL.rolling(125, min_periods=100).corr(spx_rets) In [260]: corr.plot() ``` -![图11-8 AAPL 6个月的回报与标准普尔500指数的相关系数](img/7178691-e81e0f602b4db0ed.png) +![图 11-8 AAPL 6 个月的回报与标准普尔 500 指数的相关系数](img/7178691-e81e0f602b4db0ed.png) -假设你想要一次性计算多只股票与标准普尔500指数的相关系数。虽然编写一个循环并新建一个DataFrame不是什么难事,但比较啰嗦。其实,只需传入一个TimeSeries和一个DataFrame,rolling_corr就会自动计算TimeSeries(本例中就是spx_rets)与DataFrame各列的相关系数。结果如图11-9所示: +假设你想要一次性计算多只股票与标准普尔 500 指数的相关系数。虽然编写一个循环并新建一个 DataFrame 不是什么难事,但比较啰嗦。其实,只需传入一个 TimeSeries 和一个 DataFrame,rolling_corr 就会自动计算 TimeSeries(本例中就是 spx_rets)与 DataFrame 各列的相关系数。结果如图 11-9 所示: ```python In [262]: corr = returns.rolling(125, min_periods=100).corr(spx_rets) In [263]: corr.plot() ``` -![图11-9 3只股票6个月的回报与标准普尔500指数的相关系数](img/7178691-0a54a028a62b9b50.png) +![图 11-9 3 只股票 6 个月的回报与标准普尔 500 指数的相关系数](img/7178691-0a54a028a62b9b50.png) ## 用户定义的移动窗口函数 -rolling_apply函数使你能够在移动窗口上应用自己设计的数组函数。唯一要求的就是:该函数要能从数组的各个片段中产生单个值(即约简)。比如说,当我们用rolling(...).quantile(q)计算样本分位数时,可能对样本中特定值的百分等级感兴趣。scipy.stats.percentileofscore函数就能达到这个目的(结果见图11-10): +rolling_apply 函数使你能够在移动窗口上应用自己设计的数组函数。唯一要求的就是:该函数要能从数组的各个片段中产生单个值(即约简)。比如说,当我们用 rolling(...).quantile(q)计算样本分位数时,可能对样本中特定值的百分等级感兴趣。scipy.stats.percentileofscore 函数就能达到这个目的(结果见图 11-10): ```python In [265]: from scipy.stats import percentileofscore @@ -1865,12 +1865,12 @@ In [267]: result = returns.AAPL.rolling(250).apply(score_at_2percent) In [268]: result.plot() ``` -![图11-10 AAPL 2%回报率的百分等级(一年窗口期)](img/7178691-af49e84a90c23c1e.png) +![图 11-10 AAPL 2%回报率的百分等级(一年窗口期)](img/7178691-af49e84a90c23c1e.png) -如果你没安装SciPy,可以使用conda或pip安装。 +如果你没安装 SciPy,可以使用 conda 或 pip 安装。 # 11.8 总结 与前面章节接触的数据相比,时间序列数据要求不同类型的分析和数据转换工具。 -在接下来的章节中,我们将学习一些高级的pandas方法和如何开始使用建模库statsmodels和scikit-learn。 +在接下来的章节中,我们将学习一些高级的 pandas 方法和如何开始使用建模库 statsmodels 和 scikit-learn。 diff --git a/docs/12.md b/docs/12.md index 76419f9..0612030 100644 --- a/docs/12.md +++ b/docs/12.md @@ -1,14 +1,14 @@ # 第 12 章 pandas 高级应用 -前面的章节关注于不同类型的数据规整流程和NumPy、pandas与其它库的特点。随着时间的发展,pandas发展出了更多适合高级用户的功能。本章就要深入学习pandas的高级功能。 +前面的章节关注于不同类型的数据规整流程和 NumPy、pandas 与其它库的特点。随着时间的发展,pandas 发展出了更多适合高级用户的功能。本章就要深入学习 pandas 的高级功能。 # 12.1 分类数据 -这一节介绍的是pandas的分类类型。我会向你展示通过使用它,提高性能和内存的使用率。我还会介绍一些在统计和机器学习中使用分类数据的工具。 +这一节介绍的是 pandas 的分类类型。我会向你展示通过使用它,提高性能和内存的使用率。我还会介绍一些在统计和机器学习中使用分类数据的工具。 ## 背景和目的 -表中的一列通常会有重复的包含不同值的小集合的情况。我们已经学过了unique和value_counts,它们可以从数组提取出不同的值,并分别计算频率: +表中的一列通常会有重复的包含不同值的小集合的情况。我们已经学过了 unique 和 value_counts,它们可以从数组提取出不同的值,并分别计算频率: ```python In [10]: import numpy as np; import pandas as pd @@ -62,7 +62,7 @@ Out[18]: dtype: object ``` -可以使用take方法存储原始的字符串Series: +可以使用 take 方法存储原始的字符串 Series: ```python In [19]: dim.take(values) Out[19]: @@ -84,9 +84,9 @@ dtype: object - 重命名分类。 - 加入一个新的分类,不改变已经存在的分类的顺序或位置。 -## pandas的分类类型 +## pandas 的分类类型 -pandas有一个特殊的分类类型,用于保存使用整数分类表示法的数据。看一个之前的Series例子: +pandas 有一个特殊的分类类型,用于保存使用整数分类表示法的数据。看一个之前的 Series 例子: ```python In [20]: fruits = ['apple', 'orange', 'apple', 'apple'] * 2 @@ -111,7 +111,7 @@ Out[23]: 7 7 apple 4 0.425778 ``` -这里,df['fruit']是一个Python字符串对象的数组。我们可以通过调用它,将它转变为分类: +这里,df['fruit']是一个 Python 字符串对象的数组。我们可以通过调用它,将它转变为分类: ```python In [24]: fruit_cat = df['fruit'].astype('category') @@ -129,7 +129,7 @@ Name: fruit, dtype: category Categories (2, object): [apple, orange] ``` -fruit_cat的值不是NumPy数组,而是一个pandas.Categorical实例: +fruit_cat 的值不是 NumPy 数组,而是一个 pandas.Categorical 实例: ```python In [26]: c = fruit_cat.values @@ -137,7 +137,7 @@ In [27]: type(c) Out[27]: pandas.core.categorical.Categorical ``` -分类对象有categories和codes属性: +分类对象有 categories 和 codes 属性: ```python In [28]: c.categories Out[28]: Index(['apple', 'orange'], dtype='object') @@ -146,7 +146,7 @@ In [29]: c.codes Out[29]: array([0, 1, 0, 0, 0, 1, 0, 0], dtype=int8) ``` -你可将DataFrame的列通过分配转换结果,转换为分类: +你可将 DataFrame 的列通过分配转换结果,转换为分类: ```python In [30]: df['fruit'] = df['fruit'].astype('category') @@ -164,7 +164,7 @@ Name: fruit, dtype: category Categories (2, object): [apple, orange] ``` -你还可以从其它Python序列直接创建pandas.Categorical: +你还可以从其它 Python 序列直接创建 pandas.Categorical: ```python In [32]: my_categories = pd.Categorical(['foo', 'bar', 'baz', 'foo', 'bar']) @@ -174,7 +174,7 @@ Out[33]: Categories (3, object): [bar, baz, foo] ``` -如果你已经从其它源获得了分类编码,你还可以使用from_codes构造器: +如果你已经从其它源获得了分类编码,你还可以使用 from_codes 构造器: ```python In [34]: categories = ['foo', 'bar', 'baz'] @@ -188,7 +188,7 @@ Out[37]: Categories (3, object): [foo, bar, baz] ``` -与显示指定不同,分类变换不认定指定的分类顺序。因此取决于输入数据的顺序,categories数组的顺序会不同。当使用from_codes或其它的构造器时,你可以指定分类一个有意义的顺序: +与显示指定不同,分类变换不认定指定的分类顺序。因此取决于输入数据的顺序,categories 数组的顺序会不同。当使用 from_codes 或其它的构造器时,你可以指定分类一个有意义的顺序: ```python In [38]: ordered_cat = pd.Categorical.from_codes(codes, categories, ....: ordered=True) @@ -199,7 +199,7 @@ Out[39]: Categories (3, object): [foo < bar < baz] ``` -输出[foo < bar < baz]指明‘foo’位于‘bar’的前面,以此类推。无序的分类实例可以通过as_ordered排序: +输出[foo < bar < baz]指明‘foo’位于‘bar’的前面,以此类推。无序的分类实例可以通过 as_ordered 排序: ```python In [40]: my_cats_2.as_ordered() Out[40]: @@ -211,9 +211,9 @@ Categories (3, object): [foo < bar < baz] ## 用分类进行计算 -与非编码版本(比如字符串数组)相比,使用pandas的Categorical有些类似。某些pandas组件,比如groupby函数,更适合进行分类。还有一些函数可以使用有序标志位。 +与非编码版本(比如字符串数组)相比,使用 pandas 的 Categorical 有些类似。某些 pandas 组件,比如 groupby 函数,更适合进行分类。还有一些函数可以使用有序标志位。 -来看一些随机的数值数据,使用pandas.qcut面元函数。它会返回pandas.Categorical,我们之前使用过pandas.cut,但没解释分类是如何工作的: +来看一些随机的数值数据,使用 pandas.qcut 面元函数。它会返回 pandas.Categorical,我们之前使用过 pandas.cut,但没解释分类是如何工作的: ```python In [41]: np.random.seed(12345) @@ -238,7 +238,7 @@ Categories (4, interval[float64]): [(-2.95, -0.684] < (-0.684, -0.0101] < (-0.01 (0.63, 3.928]] ``` -虽然有用,确切的样本分位数与分位的名称相比,不利于生成汇总。我们可以使用labels参数qcut,实现目的: +虽然有用,确切的样本分位数与分位的名称相比,不利于生成汇总。我们可以使用 labels 参数 qcut,实现目的: ```python In [46]: bins = pd.qcut(draws, 4, labels=['Q1', 'Q2', 'Q3', 'Q4']) @@ -252,7 +252,7 @@ In [48]: bins.codes[:10] Out[48]: array([1, 2, 1, 1, 3, 3, 2, 2, 3, 3], dtype=int8) ``` -加上标签的面元分类不包含数据面元边界的信息,因此可以使用groupby提取一些汇总信息: +加上标签的面元分类不包含数据面元边界的信息,因此可以使用 groupby 提取一些汇总信息: ```python In [49]: bins = pd.Series(bins, name='quartile') @@ -284,7 +284,7 @@ Categories (4, object): [Q1 < Q2 < Q3 < Q4] ## 用分类提高性能 -如果你是在一个特定数据集上做大量分析,将其转换为分类可以极大地提高效率。DataFrame列的分类使用的内存通常少的多。来看一些包含一千万元素的Series,和一些不同的分类: +如果你是在一个特定数据集上做大量分析,将其转换为分类可以极大地提高效率。DataFrame 列的分类使用的内存通常少的多。来看一些包含一千万元素的 Series,和一些不同的分类: ```python In [53]: N = 10000000 @@ -314,11 +314,11 @@ CPU times: user 490 ms, sys: 240 ms, total: 730 ms Wall time: 726 ms ``` -GroupBy使用分类操作明显更快,是因为底层的算法使用整数编码数组,而不是字符串数组。 +GroupBy 使用分类操作明显更快,是因为底层的算法使用整数编码数组,而不是字符串数组。 ## 分类方法 -包含分类数据的Series有一些特殊的方法,类似于Series.str字符串方法。它还提供了方便的分类和编码的使用方法。看下面的Series: +包含分类数据的 Series 有一些特殊的方法,类似于 Series.str 字符串方法。它还提供了方便的分类和编码的使用方法。看下面的 Series: ```python In [60]: s = pd.Series(['a', 'b', 'c', 'd'] * 2) @@ -338,7 +338,7 @@ dtype: category Categories (4, object): [a, b, c, d] ``` -特别的cat属性提供了分类方法的入口: +特别的 cat 属性提供了分类方法的入口: ```python In [63]: cat_s.cat.codes Out[63]: @@ -356,7 +356,7 @@ In [64]: cat_s.cat.categories Out[64]: Index(['a', 'b', 'c', 'd'], dtype='object') ``` -假设我们知道这个数据的实际分类集,超出了数据中的四个值。我们可以使用set_categories方法改变它们: +假设我们知道这个数据的实际分类集,超出了数据中的四个值。我们可以使用 set_categories 方法改变它们: ```python In [65]: actual_categories = ['a', 'b', 'c', 'd', 'e'] @@ -376,7 +376,7 @@ dtype: category Categories (5, object): [a, b, c, d, e] ``` -虽然数据看起来没变,新的分类将反映在它们的操作中。例如,如果有的话,value_counts表示分类: +虽然数据看起来没变,新的分类将反映在它们的操作中。例如,如果有的话,value_counts 表示分类: ```python In [68]: cat_s.value_counts() Out[68]: @@ -396,7 +396,7 @@ e 0 dtype: int64 ``` -在大数据集中,分类经常作为节省内存和高性能的便捷工具。过滤完大DataFrame或Series之后,许多分类可能不会出现在数据中。我们可以使用remove_unused_categories方法删除没看到的分类: +在大数据集中,分类经常作为节省内存和高性能的便捷工具。过滤完大 DataFrame 或 Series 之后,许多分类可能不会出现在数据中。我们可以使用 remove_unused_categories 方法删除没看到的分类: ```python In [70]: cat_s3 = cat_s[cat_s.isin(['a', 'b'])] @@ -419,21 +419,21 @@ dtype: category Categories (2, object): [a, b] ``` -表12-1列出了可用的分类方法。 +表 12-1 列出了可用的分类方法。 -![表12-1 pandas的Series的分类方法](img/7178691-6c602152c2bba658.png) +![表 12-1 pandas 的 Series 的分类方法](img/7178691-6c602152c2bba658.png) ## 为建模创建虚拟变量 -当你使用统计或机器学习工具时,通常会将分类数据转换为虚拟变量,也称为one-hot编码。这包括创建一个不同类别的列的DataFrame;这些列包含给定分类的1s,其它为0。 +当你使用统计或机器学习工具时,通常会将分类数据转换为虚拟变量,也称为 one-hot 编码。这包括创建一个不同类别的列的 DataFrame;这些列包含给定分类的 1s,其它为 0。 看前面的例子: ```python In [73]: cat_s = pd.Series(['a', 'b', 'c', 'd'] * 2, dtype='category') ``` -前面的第7章提到过,pandas.get_dummies函数可以转换这个分类数据为包含虚拟变量的DataFrame: +前面的第 7 章提到过,pandas.get_dummies 函数可以转换这个分类数据为包含虚拟变量的 DataFrame: ```python In [74]: pd.get_dummies(cat_s) Out[74]: @@ -448,13 +448,13 @@ Out[74]: 7 0 0 0 1 ``` -# 12.2 GroupBy高级应用 +# 12.2 GroupBy 高级应用 -尽管我们在第10章已经深度学习了Series和DataFrame的Groupby方法,还有一些方法也是很有用的。 +尽管我们在第 10 章已经深度学习了 Series 和 DataFrame 的 Groupby 方法,还有一些方法也是很有用的。 ## 分组转换和“解封”GroupBy -在第10章,我们在分组操作中学习了apply方法,进行转换。还有另一个transform方法,它与apply很像,但是对使用的函数有一定限制: +在第 10 章,我们在分组操作中学习了 apply 方法,进行转换。还有另一个 transform 方法,它与 apply 很像,但是对使用的函数有一定限制: - 它可以产生向分组形状广播标量值 - 它可以产生一个和输入组形状相同的对象 @@ -495,7 +495,7 @@ c 6.5 Name: value, dtype: float64 ``` -假设我们想产生一个和df['value']形状相同的Series,但值替换为按键分组的平均值。我们可以传递函数lambda x: x.mean()进行转换: +假设我们想产生一个和 df['value']形状相同的 Series,但值替换为按键分组的平均值。我们可以传递函数 lambda x: x.mean()进行转换: ```python In [79]: g.transform(lambda x: x.mean()) Out[79]: @@ -514,7 +514,7 @@ Out[79]: Name: value, dtype: float64 ``` -对于内置的聚合函数,我们可以传递一个字符串假名作为GroupBy的agg方法: +对于内置的聚合函数,我们可以传递一个字符串假名作为 GroupBy 的 agg 方法: ```python In [80]: g.transform('mean') Out[80]: @@ -533,7 +533,7 @@ Out[80]: Name: value, dtype: float64 ``` -与apply类似,transform的函数会返回Series,但是结果必须与输入大小相同。举个例子,我们可以用lambda函数将每个分组乘以2: +与 apply 类似,transform 的函数会返回 Series,但是结果必须与输入大小相同。举个例子,我们可以用 lambda 函数将每个分组乘以 2: ```python In [81]: g.transform(lambda x: x * 2) Out[81]: @@ -577,7 +577,7 @@ def normalize(x): return (x - x.mean()) / x.std() ``` -我们用transform或apply可以获得等价的结果: +我们用 transform 或 apply 可以获得等价的结果: ```python In [84]: g.transform(normalize) Out[84]: @@ -612,7 +612,7 @@ Out[85]: Name: value, dtype: float64 ``` -内置的聚合函数,比如mean或sum,通常比apply函数快,也比transform快。这允许我们进行一个所谓的解封(unwrapped)分组操作: +内置的聚合函数,比如 mean 或 sum,通常比 apply 函数快,也比 transform 快。这允许我们进行一个所谓的解封(unwrapped)分组操作: ```python In [86]: g.transform('mean') Out[86]: @@ -653,7 +653,7 @@ Name: value, dtype: float64 ## 分组的时间重采样 -对于时间序列数据,resample方法从语义上是一个基于内在时间的分组操作。下面是一个示例表: +对于时间序列数据,resample 方法从语义上是一个基于内在时间的分组操作。下面是一个示例表: ```python In [89]: N = 15 @@ -682,7 +682,7 @@ Out[92]: 14 2017-05-20 00:14:00 14 ``` -这里,我们可以用time作为索引,然后重采样: +这里,我们可以用 time 作为索引,然后重采样: ```python In [93]: df.set_index('time').resample('5min').count() Out[93]: @@ -693,7 +693,7 @@ time 2017-05-20 00:10:00 5 ``` -假设DataFrame包含多个时间序列,用一个额外的分组键的列进行标记: +假设 DataFrame 包含多个时间序列,用一个额外的分组键的列进行标记: ```python In [94]: df2 = pd.DataFrame({'time': times.repeat(3), ....: 'key': np.tile(['a', 'b', 'c'], N), @@ -711,12 +711,12 @@ Out[95]: 6 a 2017-05-20 00:02:00 6.0 ``` -要对每个key值进行相同的重采样,我们引入pandas.TimeGrouper对象: +要对每个 key 值进行相同的重采样,我们引入 pandas.TimeGrouper 对象: ```python In [96]: time_key = pd.TimeGrouper('5min') ``` -我们然后设定时间索引,用key和time_key分组,然后聚合: +我们然后设定时间索引,用 key 和 time_key 分组,然后聚合: ```python In [97]: resampled = (df2.set_index('time') ....: .groupby(['key', time_key]) @@ -750,7 +750,7 @@ key time value 8 c 2017-05-20 00:10:00 190.0 ``` -使用TimeGrouper的限制是时间必须是Series或DataFrame的索引。 +使用 TimeGrouper 的限制是时间必须是 Series 或 DataFrame 的索引。 # 12.3 链式编程技术 @@ -762,7 +762,7 @@ df2['col1_demeaned'] = df2['col1'] - df2['col1'].mean() result = df2.groupby('key').col1_demeaned.std() ``` -虽然这里没有使用真实的数据,这个例子却指出了一些新方法。首先,DataFrame.assign方法是一个df[k] = v形式的函数式的列分配方法。它不是就地修改对象,而是返回新的修改过的DataFrame。因此,下面的语句是等价的: +虽然这里没有使用真实的数据,这个例子却指出了一些新方法。首先,DataFrame.assign 方法是一个 df[k] = v 形式的函数式的列分配方法。它不是就地修改对象,而是返回新的修改过的 DataFrame。因此,下面的语句是等价的: ```python # Usual non-functional way df2 = df.copy() @@ -772,7 +772,7 @@ df2['k'] = v df2 = df.assign(k=v) ``` -就地分配可能会比assign快,但是assign可以方便地进行链式编程: +就地分配可能会比 assign 快,但是 assign 可以方便地进行链式编程: ```python result = (df2.assign(col1_demeaned=df2.col1 - df2.col2.mean()) .groupby('key') @@ -781,7 +781,7 @@ result = (df2.assign(col1_demeaned=df2.col1 - df2.col2.mean()) 我使用外括号,这样便于添加换行符。 -使用链式编程时要注意,你可能会需要涉及临时对象。在前面的例子中,我们不能使用load_data的结果,直到它被赋值给临时变量df。为了这么做,assign和许多其它pandas函数可以接收类似函数的参数,即可调用对象(callable)。为了展示可调用对象,看一个前面例子的片段: +使用链式编程时要注意,你可能会需要涉及临时对象。在前面的例子中,我们不能使用 load_data 的结果,直到它被赋值给临时变量 df。为了这么做,assign 和许多其它 pandas 函数可以接收类似函数的参数,即可调用对象(callable)。为了展示可调用对象,看一个前面例子的片段: ```python df = load_data() df2 = df[df['col2'] < 0] @@ -793,7 +793,7 @@ df = (load_data() [lambda x: x['col2'] < 0]) ``` -这里,load_data的结果没有赋值给某个变量,因此传递到[ ]的函数在这一步被绑定到了对象。 +这里,load_data 的结果没有赋值给某个变量,因此传递到[ ]的函数在这一步被绑定到了对象。 我们可以把整个过程写为一个单链表达式: ```python @@ -808,7 +808,7 @@ result = (load_data() ## 管道方法 -你可以用Python内置的pandas函数和方法,用带有可调用对象的链式编程做许多工作。但是,有时你需要使用自己的函数,或是第三方库的函数。这时就要用到管道方法。 +你可以用 Python 内置的 pandas 函数和方法,用带有可调用对象的链式编程做许多工作。但是,有时你需要使用自己的函数,或是第三方库的函数。这时就要用到管道方法。 看下面的函数调用: ```python @@ -817,16 +817,16 @@ b = g(a, v2, arg3=v3) c = h(b, arg4=v4) ``` -当使用接收、返回Series或DataFrame对象的函数式,你可以调用pipe将其重写: +当使用接收、返回 Series 或 DataFrame 对象的函数式,你可以调用 pipe 将其重写: ```python result = (df.pipe(f, arg1=v1) .pipe(g, v2, arg3=v3) .pipe(h, arg4=v4)) ``` -f(df)和df.pipe(f)是等价的,但是pipe使得链式声明更容易。 +f(df)和 df.pipe(f)是等价的,但是 pipe 使得链式声明更容易。 -pipe的另一个有用的地方是提炼操作为可复用的函数。看一个从列减去分组方法的例子: +pipe 的另一个有用的地方是提炼操作为可复用的函数。看一个从列减去分组方法的例子: ```python g = df.groupby(['key1', 'key2']) df['col1'] = df['col1'] - g.transform('mean') @@ -850,6 +850,6 @@ result = (df[df.col1 < 0] # 12.4 总结 -和其它许多开源项目一样,pandas仍然在不断的变化和进步中。和本书中其它地方一样,这里的重点是放在接下来几年不会发生什么改变且稳定的功能。 +和其它许多开源项目一样,pandas 仍然在不断的变化和进步中。和本书中其它地方一样,这里的重点是放在接下来几年不会发生什么改变且稳定的功能。 -为了深入学习pandas的知识,我建议你学习官方文档,并阅读开发团队发布的文档更新。我们还邀请你加入pandas的开发工作:修改bug、创建新功能、完善文档。 +为了深入学习 pandas 的知识,我建议你学习官方文档,并阅读开发团队发布的文档更新。我们还邀请你加入 pandas 的开发工作:修改 bug、创建新功能、完善文档。 diff --git a/docs/13.md b/docs/13.md index c324f9d..1e4b409 100644 --- a/docs/13.md +++ b/docs/13.md @@ -1,18 +1,18 @@ # 第 13 章 Python 建模库介绍 -本书中,我已经介绍了Python数据分析的编程基础。因为数据分析师和科学家总是在数据规整和准备上花费大量时间,这本书的重点在于掌握这些功能。 +本书中,我已经介绍了 Python 数据分析的编程基础。因为数据分析师和科学家总是在数据规整和准备上花费大量时间,这本书的重点在于掌握这些功能。 -开发模型选用什么库取决于应用本身。许多统计问题可以用简单方法解决,比如普通的最小二乘回归,其它问题可能需要复杂的机器学习方法。幸运的是,Python已经成为了运用这些分析方法的语言之一,因此读完此书,你可以探索许多工具。 +开发模型选用什么库取决于应用本身。许多统计问题可以用简单方法解决,比如普通的最小二乘回归,其它问题可能需要复杂的机器学习方法。幸运的是,Python 已经成为了运用这些分析方法的语言之一,因此读完此书,你可以探索许多工具。 -本章中,我会回顾一些pandas的特点,在你胶着于pandas数据规整和模型拟合和评分时,它们可能派上用场。然后我会简短介绍两个流行的建模工具,statsmodels和scikit-learn。这二者每个都值得再写一本书,我就不做全面的介绍,而是建议你学习两个项目的线上文档和其它基于Python的数据科学、统计和机器学习的书籍。 +本章中,我会回顾一些 pandas 的特点,在你胶着于 pandas 数据规整和模型拟合和评分时,它们可能派上用场。然后我会简短介绍两个流行的建模工具,statsmodels 和 scikit-learn。这二者每个都值得再写一本书,我就不做全面的介绍,而是建议你学习两个项目的线上文档和其它基于 Python 的数据科学、统计和机器学习的书籍。 -# 13.1 pandas与模型代码的接口 +# 13.1 pandas 与模型代码的接口 -模型开发的通常工作流是使用pandas进行数据加载和清洗,然后切换到建模库进行建模。开发模型的重要一环是机器学习中的“特征工程”。它可以描述从原始数据集中提取信息的任何数据转换或分析,这些数据集可能在建模中有用。本书中学习的数据聚合和GroupBy工具常用于特征工程中。 +模型开发的通常工作流是使用 pandas 进行数据加载和清洗,然后切换到建模库进行建模。开发模型的重要一环是机器学习中的“特征工程”。它可以描述从原始数据集中提取信息的任何数据转换或分析,这些数据集可能在建模中有用。本书中学习的数据聚合和 GroupBy 工具常用于特征工程中。 优秀的特征工程超出了本书的范围,我会尽量直白地介绍一些用于数据操作和建模切换的方法。 -pandas与其它分析库通常是靠NumPy的数组联系起来的。将DataFrame转换为NumPy数组,可以使用.values属性: +pandas 与其它分析库通常是靠 NumPy 的数组联系起来的。将 DataFrame 转换为 NumPy 数组,可以使用.values 属性: ```python In [10]: import pandas as pd @@ -44,7 +44,7 @@ array([[ 1. , 0.01, -1.5 ], [ 5. , 0. , -2. ]]) ``` -要转换回DataFrame,可以传递一个二维ndarray,可带有列名: +要转换回 DataFrame,可以传递一个二维 ndarray,可带有列名: ```python In [16]: df2 = pd.DataFrame(data.values, columns=['one', 'two', 'three']) @@ -58,7 +58,7 @@ Out[17]: 4 5.0 0.00 -2.0 ``` ->笔记:最好当数据是均匀的时候使用.values属性。例如,全是数值类型。如果数据是不均匀的,结果会是Python对象的ndarray: +>笔记:最好当数据是均匀的时候使用.values 属性。例如,全是数值类型。如果数据是不均匀的,结果会是 Python 对象的 ndarray: >```python >In [18]: df3 = data.copy() > @@ -82,7 +82,7 @@ Out[17]: > [5, 0.0, -2.0, 'e']], dtype=object) >``` -对于一些模型,你可能只想使用列的子集。我建议你使用loc,用values作索引: +对于一些模型,你可能只想使用列的子集。我建议你使用 loc,用 values 作索引: ```python In [22]: model_cols = ['x0', 'x1'] @@ -95,9 +95,9 @@ array([[ 1. , 0.01], [ 5. , 0. ]]) ``` -一些库原生支持pandas,会自动完成工作:从DataFrame转换到NumPy,将模型的参数名添加到输出表的列或Series。其它情况,你可以手工进行“元数据管理”。 +一些库原生支持 pandas,会自动完成工作:从 DataFrame 转换到 NumPy,将模型的参数名添加到输出表的列或 Series。其它情况,你可以手工进行“元数据管理”。 -在第12章,我们学习了pandas的Categorical类型和pandas.get_dummies函数。假设数据集中有一个非数值列: +在第 12 章,我们学习了 pandas 的 Categorical 类型和 pandas.get_dummies 函数。假设数据集中有一个非数值列: ```python In [24]: data['category'] = pd.Categorical(['a', 'b', 'a', 'a', 'b'], ....: categories=['a', 'b']) @@ -112,7 +112,7 @@ Out[25]: 4 5 0.00 -2.0 b ``` -如果我们想替换category列为虚变量,我们可以创建虚变量,删除category列,然后添加到结果: +如果我们想替换 category 列为虚变量,我们可以创建虚变量,删除 category 列,然后添加到结果: ```python In [26]: dummies = pd.get_dummies(data.category, prefix='category') @@ -128,18 +128,18 @@ Out[28]: 4 5 0.00 -2.0 0 1 ``` -用虚变量拟合某些统计模型会有一些细微差别。当你不只有数字列时,使用Patsy(下一节的主题)可能更简单,更不容易出错。 +用虚变量拟合某些统计模型会有一些细微差别。当你不只有数字列时,使用 Patsy(下一节的主题)可能更简单,更不容易出错。 -# 13.2 用Patsy创建模型描述 +# 13.2 用 Patsy 创建模型描述 -Patsy是Python的一个库,使用简短的字符串“公式语法”描述统计模型(尤其是线性模型),可能是受到了R和S统计编程语言的公式语法的启发。 +Patsy 是 Python 的一个库,使用简短的字符串“公式语法”描述统计模型(尤其是线性模型),可能是受到了 R 和 S 统计编程语言的公式语法的启发。 -Patsy适合描述statsmodels的线性模型,因此我会关注于它的主要特点,让你尽快掌握。Patsy的公式是一个特殊的字符串语法,如下所示: +Patsy 适合描述 statsmodels 的线性模型,因此我会关注于它的主要特点,让你尽快掌握。Patsy 的公式是一个特殊的字符串语法,如下所示: ```python y ~ x0 + x1 ``` -a+b不是将a与b相加的意思,而是为模型创建的设计矩阵。patsy.dmatrices函数接收一个公式字符串和一个数据集(可以是DataFrame或数组的字典),为线性模型创建设计矩阵: +a+b 不是将 a 与 b 相加的意思,而是为模型创建的设计矩阵。patsy.dmatrices 函数接收一个公式字符串和一个数据集(可以是 DataFrame 或数组的字典),为线性模型创建设计矩阵: ```python In [29]: data = pd.DataFrame({ ....: 'x0': [1, 2, 3, 4, 5], @@ -189,7 +189,7 @@ DesignMatrix with shape (5, 3) 'x1' (column 2) ``` -这些Patsy的DesignMatrix实例是NumPy的ndarray,带有附加元数据: +这些 Patsy 的 DesignMatrix 实例是 NumPy 的 ndarray,带有附加元数据: ```python In [35]: np.asarray(y) Out[35]: @@ -208,7 +208,7 @@ array([[ 1. , 1. , 0.01], [ 1. , 5. , 0. ]]) ``` -你可能想Intercept是哪里来的。这是线性模型(比如普通最小二乘回归)的惯例用法。添加 +0 到模型可以不显示intercept: +你可能想 Intercept 是哪里来的。这是线性模型(比如普通最小二乘回归)的惯例用法。添加 +0 到模型可以不显示 intercept: ```python In [37]: patsy.dmatrices('y ~ x0 + x1 + 0', data)[1] Out[37]: @@ -224,12 +224,12 @@ DesignMatrix with shape (5, 2) 'x1' (column 1) ``` -Patsy对象可以直接传递到算法(比如numpy.linalg.lstsq)中,它执行普通最小二乘回归: +Patsy 对象可以直接传递到算法(比如 numpy.linalg.lstsq)中,它执行普通最小二乘回归: ```python In [38]: coef, resid, _, _ = np.linalg.lstsq(X, y) ``` -模型的元数据保留在design_info属性中,因此你可以重新附加列名到拟合系数,以获得一个Series,例如: +模型的元数据保留在 design_info 属性中,因此你可以重新附加列名到拟合系数,以获得一个 Series,例如: ```python In [39]: coef Out[39]: @@ -247,9 +247,9 @@ x1 -0.265464 dtype: float64 ``` -## 用Patsy公式进行数据转换 +## 用 Patsy 公式进行数据转换 -你可以将Python代码与patsy公式结合。在评估公式时,库将尝试查找在封闭作用域内使用的函数: +你可以将 Python 代码与 patsy 公式结合。在评估公式时,库将尝试查找在封闭作用域内使用的函数: ```python In [42]: y, X = patsy.dmatrices('y ~ x0 + np.log(np.abs(x1) + 1)', data) @@ -268,7 +268,7 @@ DesignMatrix with shape (5, 3) 'np.log(np.abs(x1) + 1)' (column 2) ``` -常见的变量转换包括标准化(平均值为0,方差为1)和中心化(减去平均值)。Patsy有内置的函数进行这样的工作: +常见的变量转换包括标准化(平均值为 0,方差为 1)和中心化(减去平均值)。Patsy 有内置的函数进行这样的工作: ```python In [44]: y, X = patsy.dmatrices('y ~ standardize(x0) + center(x1)', data) @@ -289,7 +289,7 @@ DesignMatrix with shape (5, 3) 作为建模的一步,你可能拟合模型到一个数据集,然后用另一个数据集评估模型。另一个数据集可能是剩余的部分或是新数据。当执行中心化和标准化转变,用新数据进行预测要格外小心。因为你必须使用平均值或标准差转换新数据集,这也称作状态转换。 -patsy.build_design_matrices函数可以使用原始样本数据集的保存信息,来转换新数据,: +patsy.build_design_matrices 函数可以使用原始样本数据集的保存信息,来转换新数据,: ```python In [46]: new_data = pd.DataFrame({ ....: 'x0': [6, 7, 8, 9], @@ -312,7 +312,7 @@ Out[48]: 'center(x1)' (column 2)] ``` -因为Patsy中的加号不是加法的意义,当你按照名称将数据集的列相加时,你必须用特殊I函数将它们封装起来: +因为 Patsy 中的加号不是加法的意义,当你按照名称将数据集的列相加时,你必须用特殊 I 函数将它们封装起来: ```python In [49]: y, X = patsy.dmatrices('y ~ I(x0 + x1)', data) @@ -330,15 +330,15 @@ DesignMatrix with shape (5, 2) 'I(x0 + x1)' (column 1) ``` -Patsy的patsy.builtins模块还有一些其它的内置转换。请查看线上文档。 +Patsy 的 patsy.builtins 模块还有一些其它的内置转换。请查看线上文档。 分类数据有一个特殊的转换类,下面进行讲解。 -## 分类数据和Patsy +## 分类数据和 Patsy 非数值数据可以用多种方式转换为模型设计矩阵。完整的讲解超出了本书范围,最好和统计课一起学习。 -当你在Patsy公式中使用非数值数据,它们会默认转换为虚变量。如果有截距,会去掉一个,避免共线性: +当你在 Patsy 公式中使用非数值数据,它们会默认转换为虚变量。如果有截距,会去掉一个,避免共线性: ```python In [51]: data = pd.DataFrame({ ....: 'key1': ['a', 'a', 'b', 'b', 'a', 'b', 'a', 'b'], @@ -386,7 +386,7 @@ DesignMatrix with shape (8, 2) 'key1' (columns 0:2) ``` -使用C函数,数值列可以截取为分类量: +使用 C 函数,数值列可以截取为分类量: ```python In [56]: y, X = patsy.dmatrices('v2 ~ C(key2)', data) @@ -407,7 +407,7 @@ DesignMatrix with shape (8, 2) 'C(key2)' (column 1) ``` -当你在模型中使用多个分类名,事情就会变复杂,因为会包括key1:key2形式的相交部分,它可以用在方差(ANOVA)模型分析中: +当你在模型中使用多个分类名,事情就会变复杂,因为会包括 key1:key2 形式的相交部分,它可以用在方差(ANOVA)模型分析中: ```python In [58]: data['key2'] = data['key2'].map({0: 'zero', 1: 'one'}) @@ -464,13 +464,13 @@ key1[T.b]:key2[T.zero] 'key1:key2' (column 3) ``` -Patsy提供转换分类数据的其它方法,包括以特定顺序转换。请参阅线上文档。 +Patsy 提供转换分类数据的其它方法,包括以特定顺序转换。请参阅线上文档。 -# 13.3 statsmodels介绍 +# 13.3 statsmodels 介绍 -statsmodels是Python进行拟合多种统计模型、进行统计试验和数据探索可视化的库。Statsmodels包含许多经典的统计方法,但没有贝叶斯方法和机器学习模型。 +statsmodels 是 Python 进行拟合多种统计模型、进行统计试验和数据探索可视化的库。Statsmodels 包含许多经典的统计方法,但没有贝叶斯方法和机器学习模型。 -statsmodels包含的模型有: +statsmodels 包含的模型有: - 线性模型,广义线性模型和健壮线性模型 - 线性混合效应模型 @@ -478,13 +478,13 @@ statsmodels包含的模型有: - 时间序列过程和状态空间模型 - 广义矩估计 -下面,我会使用一些基本的statsmodels工具,探索Patsy公式和pandasDataFrame对象如何使用模型接口。 +下面,我会使用一些基本的 statsmodels 工具,探索 Patsy 公式和 pandasDataFrame 对象如何使用模型接口。 ## 估计线性模型 -statsmodels有多种线性回归模型,包括从基本(比如普通最小二乘)到复杂(比如迭代加权最小二乘法)的。 +statsmodels 有多种线性回归模型,包括从基本(比如普通最小二乘)到复杂(比如迭代加权最小二乘法)的。 -statsmodels的线性模型有两种不同的接口:基于数组和基于公式。它们可以通过API模块引入: +statsmodels 的线性模型有两种不同的接口:基于数组和基于公式。它们可以通过 API 模块引入: ```python import statsmodels.api as sm import statsmodels.formula.api as smf @@ -510,7 +510,7 @@ beta = [0.1, 0.3, 0.5] y = np.dot(X, beta) + eps ``` -这里,我使用了“真实”模型和可知参数beta。此时,dnorm可用来生成正态分布数据,带有特定均值和方差。现在有: +这里,我使用了“真实”模型和可知参数 beta。此时,dnorm 可用来生成正态分布数据,带有特定均值和方差。现在有: ```python In [66]: X[:5] Out[66]: @@ -524,7 +524,7 @@ In [67]: y[:5] Out[67]: array([ 0.4279, -0.6735, -0.0909, -0.4895,-0.1289]) ``` -像之前Patsy看到的,线性模型通常要拟合一个截距。sm.add_constant函数可以添加一个截距的列到现存的矩阵: +像之前 Patsy 看到的,线性模型通常要拟合一个截距。sm.add_constant 函数可以添加一个截距的列到现存的矩阵: ```python In [68]: X_model = sm.add_constant(X) @@ -537,12 +537,12 @@ array([[ 1. , -0.1295, -1.2128, 0.5042], [ 1. , 1.2433, -0.3738, -0.5226]]) ``` -sm.OLS类可以拟合一个普通最小二乘回归: +sm.OLS 类可以拟合一个普通最小二乘回归: ```python In [70]: model = sm.OLS(y, X) ``` -这个模型的fit方法返回了一个回归结果对象,它包含估计的模型参数和其它内容: +这个模型的 fit 方法返回了一个回归结果对象,它包含估计的模型参数和其它内容: ```python In [71]: results = model.fit() @@ -550,7 +550,7 @@ In [72]: results.params Out[72]: array([ 0.1783, 0.223 , 0.501 ]) ``` -对结果使用summary方法可以打印模型的详细诊断结果: +对结果使用 summary 方法可以打印模型的详细诊断结果: ```python In [73]: print(results.summary()) OLS Regression Results @@ -582,7 +582,7 @@ Warnings: specified. ``` -这里的参数名为通用名x1, x2等等。假设所有的模型参数都在一个DataFrame中: +这里的参数名为通用名 x1, x2 等等。假设所有的模型参数都在一个 DataFrame 中: ```python In [74]: data = pd.DataFrame(X, columns=['col0', 'col1', 'col2']) @@ -598,7 +598,7 @@ Out[76]: 4 1.243269 -0.373799 -0.522629 -0.128941 ``` -现在,我们使用statsmodels的公式API和Patsy的公式字符串: +现在,我们使用 statsmodels 的公式 API 和 Patsy 的公式字符串: ```python In [77]: results = smf.ols('y ~ col0 + col1 + col2', data=data).fit() @@ -619,7 +619,7 @@ col2 6.303971 dtype: float64 ``` -观察下statsmodels是如何返回Series结果的,附带有DataFrame的列名。当使用公式和pandas对象时,我们不需要使用add_constant。 +观察下 statsmodels 是如何返回 Series 结果的,附带有 DataFrame 的列名。当使用公式和 pandas 对象时,我们不需要使用 add_constant。 给出一个样本外数据,你可以根据估计的模型参数计算预测值: ```python @@ -633,11 +633,11 @@ Out[80]: dtype: float64 ``` -statsmodels的线性模型结果还有其它的分析、诊断和可视化工具。除了普通最小二乘模型,还有其它的线性模型。 +statsmodels 的线性模型结果还有其它的分析、诊断和可视化工具。除了普通最小二乘模型,还有其它的线性模型。 ## 估计时间序列过程 -statsmodels的另一模型类是进行时间序列分析,包括自回归过程、卡尔曼滤波和其它态空间模型,和多元自回归模型。 +statsmodels 的另一模型类是进行时间序列分析,包括自回归过程、卡尔曼滤波和其它态空间模型,和多元自回归模型。 用自回归结构和噪声来模拟一些时间序列数据: ```python @@ -655,7 +655,7 @@ for i in range(N): values.append(new_x) ``` -这个数据有AR(2)结构(两个延迟),参数是0.8和-0.4。拟合AR模型时,你可能不知道滞后项的个数,因此可以用较多的滞后量来拟合这个模型: +这个数据有 AR(2)结构(两个延迟),参数是 0.8 和-0.4。拟合 AR 模型时,你可能不知道滞后项的个数,因此可以用较多的滞后量来拟合这个模型: ```python In [82]: MAXLAGS = 5 @@ -670,17 +670,17 @@ In [85]: results.params Out[85]: array([-0.0062, 0.7845, -0.4085, -0.0136, 0.015 , 0.0143]) ``` -更多的细节以及如何解释结果超出了本书的范围,可以通过statsmodels文档学习更多。 +更多的细节以及如何解释结果超出了本书的范围,可以通过 statsmodels 文档学习更多。 -# 13.4 scikit-learn介绍 +# 13.4 scikit-learn 介绍 -scikit-learn是一个广泛使用、用途多样的Python机器学习库。它包含多种标准监督和非监督机器学习方法和模型选择和评估、数据转换、数据加载和模型持久化工具。这些模型可以用于分类、聚合、预测和其它任务。 +scikit-learn 是一个广泛使用、用途多样的 Python 机器学习库。它包含多种标准监督和非监督机器学习方法和模型选择和评估、数据转换、数据加载和模型持久化工具。这些模型可以用于分类、聚合、预测和其它任务。 -机器学习方面的学习和应用scikit-learn和TensorFlow解决实际问题的线上和纸质资料很多。本节中,我会简要介绍scikit-learn API的风格。 +机器学习方面的学习和应用 scikit-learn 和 TensorFlow 解决实际问题的线上和纸质资料很多。本节中,我会简要介绍 scikit-learn API 的风格。 -写作此书的时候,scikit-learn并没有和pandas深度结合,但是有些第三方包在开发中。尽管如此,pandas非常适合在模型拟合前处理数据集。 +写作此书的时候,scikit-learn 并没有和 pandas 深度结合,但是有些第三方包在开发中。尽管如此,pandas 非常适合在模型拟合前处理数据集。 -举个例子,我用一个Kaggle竞赛的经典数据集,关于泰坦尼克号乘客的生还率。我们用pandas加载测试和训练数据集: +举个例子,我用一个 Kaggle 竞赛的经典数据集,关于泰坦尼克号乘客的生还率。我们用 pandas 加载测试和训练数据集: ```python In [86]: train = pd.read_csv('datasets/titanic/train.csv') @@ -705,7 +705,7 @@ Out[88]: 3 0 113803 53.1000 C123 S ``` -statsmodels和scikit-learn通常不能接收缺失数据,因此我们要查看列是否包含缺失值: +statsmodels 和 scikit-learn 通常不能接收缺失数据,因此我们要查看列是否包含缺失值: ```python In [89]: train.isnull().sum() Out[89]: @@ -750,14 +750,14 @@ In [92]: train['Age'] = train['Age'].fillna(impute_value) In [93]: test['Age'] = test['Age'].fillna(impute_value) ``` -现在我们需要指定模型。我增加了一个列IsFemale,作为“Sex”列的编码: +现在我们需要指定模型。我增加了一个列 IsFemale,作为“Sex”列的编码: ```python In [94]: train['IsFemale'] = (train['Sex'] == 'female').astype(int) In [95]: test['IsFemale'] = (test['Sex'] == 'female').astype(int) ``` -然后,我们确定一些模型变量,并创建NumPy数组: +然后,我们确定一些模型变量,并创建 NumPy 数组: ```python In [96]: predictors = ['Pclass', 'IsFemale', 'Age'] @@ -779,14 +779,14 @@ In [101]: y_train[:5] Out[101]: array([0, 1, 1, 1, 0]) ``` -我不能保证这是一个好模型,但它的特征都符合。我们用scikit-learn的LogisticRegression模型,创建一个模型实例: +我不能保证这是一个好模型,但它的特征都符合。我们用 scikit-learn 的 LogisticRegression 模型,创建一个模型实例: ```python In [102]: from sklearn.linear_model import LogisticRegression In [103]: model = LogisticRegression() ``` -与statsmodels类似,我们可以用模型的fit方法,将它拟合到训练数据: +与 statsmodels 类似,我们可以用模型的 fit 方法,将它拟合到训练数据: ```python In [104]: model.fit(X_train, y_train) Out[104]: @@ -796,7 +796,7 @@ LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, verbose=0, warm_start=False) ``` -现在,我们可以用model.predict,对测试数据进行预测: +现在,我们可以用 model.predict,对测试数据进行预测: ```python In [105]: y_predict = model.predict(X_test) @@ -811,7 +811,7 @@ Out[106]: array([0, 0, 0, 0, 1, 0, 1, 0, 1, 0]) 在实际中,模型训练经常有许多额外的复杂因素。许多模型有可以调节的参数,有些方法(比如交叉验证)可以用来进行参数调节,避免对训练数据过拟合。这通常可以提高预测性或对新数据的健壮性。 -交叉验证通过分割训练数据来模拟样本外预测。基于模型的精度得分(比如均方差),可以对模型参数进行网格搜索。有些模型,如logistic回归,有内置的交叉验证的估计类。例如,logisticregressioncv类可以用一个参数指定网格搜索对模型的正则化参数C的粒度: +交叉验证通过分割训练数据来模拟样本外预测。基于模型的精度得分(比如均方差),可以对模型参数进行网格搜索。有些模型,如 logistic 回归,有内置的交叉验证的估计类。例如,logisticregressioncv 类可以用一个参数指定网格搜索对模型的正则化参数 C 的粒度: ```python In [107]: from sklearn.linear_model import LogisticRegressionCV @@ -825,7 +825,7 @@ LogisticRegressionCV(Cs=10, class_weight=None, cv=None, dual=False, refit=True, scoring=None, solver='lbfgs', tol=0.0001, verbose=0) ``` -要手动进行交叉验证,你可以使用cross_val_score帮助函数,它可以处理数据分割。例如,要交叉验证我们的带有四个不重叠训练数据的模型,可以这样做: +要手动进行交叉验证,你可以使用 cross_val_score 帮助函数,它可以处理数据分割。例如,要交叉验证我们的带有四个不重叠训练数据的模型,可以这样做: ```python In [110]: from sklearn.model_selection import cross_val_score @@ -841,7 +841,7 @@ Out[113]: array([ 0.7723, 0.8027, 0.7703, 0.7883]) # 13.5 继续学习 -我只是介绍了一些Python建模库的表面内容,现在有越来越多的框架用于各种统计和机器学习,它们都是用Python或Python用户界面实现的。 +我只是介绍了一些 Python 建模库的表面内容,现在有越来越多的框架用于各种统计和机器学习,它们都是用 Python 或 Python 用户界面实现的。 这本书的重点是数据规整,有其它的书是关注建模和数据科学工具的。其中优秀的有: @@ -851,4 +851,4 @@ Out[113]: array([ 0.7723, 0.8027, 0.7703, 0.7883]) - Sebastian Raschka (Packt Publishing) 的《Python Machine Learning》 - Aurélien Géron (O’Reilly) 的《Hands-On Machine Learning with Scikit-Learn and TensorFlow》 -虽然书是学习的好资源,但是随着底层开源软件的发展,书的内容会过时。最好是不断熟悉各种统计和机器学习框架的文档,学习最新的功能和API。 +虽然书是学习的好资源,但是随着底层开源软件的发展,书的内容会过时。最好是不断熟悉各种统计和机器学习框架的文档,学习最新的功能和 API。 diff --git a/docs/14.md b/docs/14.md index 446d922..5459f8e 100644 --- a/docs/14.md +++ b/docs/14.md @@ -2,13 +2,13 @@ 本书正文的最后一章,我们来看一些真实世界的数据集。对于每个数据集,我们会用之前介绍的方法,从原始数据中提取有意义的内容。展示的方法适用于其它数据集,也包括你的。本章包含了一些各种各样的案例数据集,可以用来练习。 -案例数据集可以在Github仓库找到,见第一章。 +案例数据集可以在 Github 仓库找到,见第一章。 -#14.1 来自Bitly的USA.gov数据 +#14.1 来自 Bitly 的 USA.gov 数据 -2011年,URL缩短服务Bitly跟美国政府网站USA.gov合作,提供了一份从生成.gov或.mil短链接的用户那里收集来的匿名数据。在2011年,除实时数据之外,还可以下载文本文件形式的每小时快照。写作此书时(2017年),这项服务已经关闭,但我们保存一份数据用于本书的案例。 +2011 年,URL 缩短服务 Bitly 跟美国政府网站 USA.gov 合作,提供了一份从生成.gov 或.mil 短链接的用户那里收集来的匿名数据。在 2011 年,除实时数据之外,还可以下载文本文件形式的每小时快照。写作此书时(2017 年),这项服务已经关闭,但我们保存一份数据用于本书的案例。 -以每小时快照为例,文件中各行的格式为JSON(即JavaScript Object Notation,这是一种常用的Web数据格式)。例如,如果我们只读取某个文件中的第一行,那么所看到的结果应该是下面这样: +以每小时快照为例,文件中各行的格式为 JSON(即 JavaScript Object Notation,这是一种常用的 Web 数据格式)。例如,如果我们只读取某个文件中的第一行,那么所看到的结果应该是下面这样: ```python In [5]: path = 'datasets/bitly_usagov/example.txt' @@ -22,14 +22,14 @@ Out[6]: '{ "a": "Mozilla\\/5.0 (Windows NT 6.1; WOW64) AppleWebKit\\/535.11 1331822918, "cy": "Danvers", "ll": [ 42.576698, -70.954903 ] }\n' ``` -Python有内置或第三方模块可以将JSON字符串转换成Python字典对象。这里,我将使用json模块及其loads函数逐行加载已经下载好的数据文件: +Python 有内置或第三方模块可以将 JSON 字符串转换成 Python 字典对象。这里,我将使用 json 模块及其 loads 函数逐行加载已经下载好的数据文件: ```python import json path = 'datasets/bitly_usagov/example.txt' records = [json.loads(line) for line in open(path)] ``` -现在,records对象就成为一组Python字典了: +现在,records 对象就成为一组 Python 字典了: ```python In [18]: records[0] Out[18]: @@ -52,9 +52,9 @@ Chrome/17.0.963.78 Safari/535.11', 'u': 'http://www.ncbi.nlm.nih.gov/pubmed/22415991'} ``` -##用纯Python代码对时区进行计数 +##用纯 Python 代码对时区进行计数 -假设我们想要知道该数据集中最常出现的是哪个时区(即tz字段),得到答案的办法有很多。首先,我们用列表推导式取出一组时区: +假设我们想要知道该数据集中最常出现的是哪个时区(即 tz 字段),得到答案的办法有很多。首先,我们用列表推导式取出一组时区: ```python In [12]: time_zones = [rec['tz'] for rec in records] --------------------------------------------------------------------------- @@ -66,7 +66,7 @@ KeyError Traceback (most recent call last) KeyError: 'tz' ``` -晕!原来并不是所有记录都有时区字段。这个好办,只需在列表推导式末尾加上一个if 'tz'in rec判断即可: +晕!原来并不是所有记录都有时区字段。这个好办,只需在列表推导式末尾加上一个 if 'tz'in rec 判断即可: ```python In [13]: time_zones = [rec['tz'] for rec in records if 'tz' in rec] @@ -84,7 +84,7 @@ Out[14]: ''] ``` -只看前10个时区,我们发现有些是未知的(即空的)。虽然可以将它们过滤掉,但现在暂时先留着。接下来,为了对时区进行计数,这里介绍两个办法:一个较难(只使用标准Python库),另一个较简单(使用pandas)。计数的办法之一是在遍历时区的过程中将计数值保存在字典中: +只看前 10 个时区,我们发现有些是未知的(即空的)。虽然可以将它们过滤掉,但现在暂时先留着。接下来,为了对时区进行计数,这里介绍两个办法:一个较难(只使用标准 Python 库),另一个较简单(使用 pandas)。计数的办法之一是在遍历时区的过程中将计数值保存在字典中: ```python def get_counts(sequence): counts = {} @@ -96,7 +96,7 @@ def get_counts(sequence): return counts ``` -如果使用Python标准库的更高级工具,那么你可能会将代码写得更简洁一些: +如果使用 Python 标准库的更高级工具,那么你可能会将代码写得更简洁一些: ```python from collections import defaultdict @@ -107,7 +107,7 @@ def get_counts2(sequence): return counts ``` -我将逻辑写到函数中是为了获得更高的复用性。要用它对时区进行处理,只需将time_zones传入即可: +我将逻辑写到函数中是为了获得更高的复用性。要用它对时区进行处理,只需将 time_zones 传入即可: ```python In [17]: counts = get_counts(time_zones) @@ -118,7 +118,7 @@ In [19]: len(time_zones) Out[19]: 3440 ``` -如果想要得到前10位的时区及其计数值,我们需要用到一些有关字典的处理技巧: +如果想要得到前 10 位的时区及其计数值,我们需要用到一些有关字典的处理技巧: ```python def top_counts(count_dict, n=10): value_key_pairs = [(count, tz) for tz, count in count_dict.items()] @@ -142,7 +142,7 @@ Out[21]: (1251, 'America/New_York')] ``` -如果你搜索Python的标准库,你能找到collections.Counter类,它可以使这项工作更简单: +如果你搜索 Python 的标准库,你能找到 collections.Counter 类,它可以使这项工作更简单: ```python In [22]: from collections import Counter @@ -162,9 +162,9 @@ Out[24]: ('America/Sao_Paulo', 33)] ``` -## 用pandas对时区进行计数 +## 用 pandas 对时区进行计数 -从原始记录的集合创建DateFrame,与将记录列表传递到pandas.DataFrame一样简单: +从原始记录的集合创建 DateFrame,与将记录列表传递到 pandas.DataFrame 一样简单: ```python In [25]: import pandas as pd @@ -210,7 +210,7 @@ Out[28]: Name: tz, dtype: object ``` -这里frame的输出形式是摘要视图(summary view),主要用于较大的DataFrame对象。我们然后可以对Series使用value_counts方法: +这里 frame 的输出形式是摘要视图(summary view),主要用于较大的 DataFrame 对象。我们然后可以对 Series 使用 value_counts 方法: ```python In [29]: tz_counts = frame['tz'].value_counts() @@ -229,7 +229,7 @@ America/Sao_Paulo 33 Name: tz, dtype: int64 ``` -我们可以用matplotlib可视化这个数据。为此,我们先给记录中未知或缺失的时区填上一个替代值。fillna函数可以替换缺失值(NA),而未知值(空字符串)则可以通过布尔型数组索引加以替换: +我们可以用 matplotlib 可视化这个数据。为此,我们先给记录中未知或缺失的时区填上一个替代值。fillna 函数可以替换缺失值(NA),而未知值(空字符串)则可以通过布尔型数组索引加以替换: ```python In [31]: clean_tz = frame['tz'].fillna('Missing') @@ -252,7 +252,7 @@ Europe/Madrid 35 Name: tz, dtype: int64 ``` -此时,我们可以用seaborn包创建水平柱状图(结果见图14-1): +此时,我们可以用 seaborn 包创建水平柱状图(结果见图 14-1): ```python In [36]: import seaborn as sns @@ -261,9 +261,9 @@ In [37]: subset = tz_counts[:10] In [38]: sns.barplot(y=subset.index, x=subset.values) ``` -![图14-1 usa.gov示例数据中最常出现的时区](img/7178691-aa267c1d399a78f0.png) +![图 14-1 usa.gov 示例数据中最常出现的时区](img/7178691-aa267c1d399a78f0.png) -a字段含有执行URL短缩操作的浏览器、设备、应用程序的相关信息: +a 字段含有执行 URL 短缩操作的浏览器、设备、应用程序的相关信息: ```python In [39]: frame['a'][1] Out[39]: 'GoogleMaps/RochesterNY' @@ -302,12 +302,12 @@ BlackBerry8520/5.0.0.681 4 dtype: int64 ``` -现在,假设你想按Windows和非Windows用户对时区统计信息进行分解。为了简单起见,我们假定只要agent字符串中含有"Windows"就认为该用户为Windows用户。由于有的agent缺失,所以首先将它们从数据中移除: +现在,假设你想按 Windows 和非 Windows 用户对时区统计信息进行分解。为了简单起见,我们假定只要 agent 字符串中含有"Windows"就认为该用户为 Windows 用户。由于有的 agent 缺失,所以首先将它们从数据中移除: ```python In [45]: cframe = frame[frame.a.notnull()] ``` -然后计算出各行是否含有Windows的值: +然后计算出各行是否含有 Windows 的值: ```python In [47]: cframe['os'] = np.where(cframe['a'].str.contains('Windows'), ....: 'Windows', 'Not Windows') @@ -327,7 +327,7 @@ Name: os, dtype: object In [49]: by_tz_os = cframe.groupby(['tz', 'os']) ``` -分组计数,类似于value_counts函数,可以用size来计算。并利用unstack对计数结果进行重塑: +分组计数,类似于 value_counts 函数,可以用 size 来计算。并利用 unstack 对计数结果进行重塑: ```python In [50]: agg_counts = by_tz_os.size().unstack().fillna(0) @@ -347,7 +347,7 @@ America/Argentina/Cordoba 0.0 1.0 America/Argentina/Mendoza 0.0 1.0 ``` -最后,我们来选取最常出现的时区。为了达到这个目的,我根据agg_counts中的行数构造了一个间接索引数组: +最后,我们来选取最常出现的时区。为了达到这个目的,我根据 agg_counts 中的行数构造了一个间接索引数组: ```python # Use to sort in ascending order In [52]: indexer = agg_counts.sum(1).argsort() @@ -368,7 +368,7 @@ America/Argentina/Mendoza 55 dtype: int64 ``` -然后我通过take按照这个顺序截取了最后10行最大值: +然后我通过 take 按照这个顺序截取了最后 10 行最大值: ```python In [54]: count_subset = agg_counts.take(indexer[-10:]) @@ -388,7 +388,7 @@ America/Chicago 115.0 285.0 America/New_York 339.0 912.0 ``` -pandas有一个简便方法nlargest,可以做同样的工作: +pandas 有一个简便方法 nlargest,可以做同样的工作: ```python In [56]: agg_counts.sum(1).nlargest(10) Out[56]: @@ -406,7 +406,7 @@ America/Sao_Paulo 33.0 dtype: float64 ``` -然后,如这段代码所示,可以用柱状图表示。我传递一个额外参数到seaborn的barpolt函数,来画一个堆积条形图(见图14-2): +然后,如这段代码所示,可以用柱状图表示。我传递一个额外参数到 seaborn 的 barpolt 函数,来画一个堆积条形图(见图 14-2): ```python # Rearrange the data for plotting In [58]: count_subset = count_subset.stack() @@ -432,9 +432,9 @@ Out[61]: In [62]: sns.barplot(x='total', y='tz', hue='os', data=count_subset) ``` -![图14-2 最常出现时区的Windows和非Windows用户](img/7178691-053612a5655b68d9.png) +![图 14-2 最常出现时区的 Windows 和非 Windows 用户](img/7178691-053612a5655b68d9.png) -这张图不容易看出Windows用户在小分组中的相对比例,因此标准化分组百分比之和为1: +这张图不容易看出 Windows 用户在小分组中的相对比例,因此标准化分组百分比之和为 1: ```python def norm_total(group): group['normed_total'] = group.total / group.total.sum() @@ -443,25 +443,25 @@ def norm_total(group): results = count_subset.groupby('tz').apply(norm_total) ``` -再次画图,见图14-3: +再次画图,见图 14-3: ```python In [65]: sns.barplot(x='normed_total', y='tz', hue='os', data=results) ``` -![图14-3 最常出现时区的Windows和非Windows用户的百分比](img/7178691-60ee355801daf412.png) +![图 14-3 最常出现时区的 Windows 和非 Windows 用户的百分比](img/7178691-60ee355801daf412.png) -我们还可以用groupby的transform方法,更高效的计算标准化的和: +我们还可以用 groupby 的 transform 方法,更高效的计算标准化的和: ```python In [66]: g = count_subset.groupby('tz') In [67]: results2 = count_subset.total / g.total.transform('sum') ``` -# 14.2 MovieLens 1M数据集 +# 14.2 MovieLens 1M 数据集 -GroupLens Research(http://www.grouplens.org/node/73)采集了一组从20世纪90年末到21世纪初由MovieLens用户提供的电影评分数据。这些数据中包括电影评分、电影元数据(风格类型和年代)以及关于用户的人口统计学数据(年龄、邮编、性别和职业等)。基于机器学习算法的推荐系统一般都会对此类数据感兴趣。虽然我不会在本书中详细介绍机器学习技术,但我会告诉你如何对这种数据进行切片切块以满足实际需求。 +GroupLens Research(http://www.grouplens.org/node/73)采集了一组从 20 世纪 90 年末到 21 世纪初由 MovieLens 用户提供的电影评分数据。这些数据中包括电影评分、电影元数据(风格类型和年代)以及关于用户的人口统计学数据(年龄、邮编、性别和职业等)。基于机器学习算法的推荐系统一般都会对此类数据感兴趣。虽然我不会在本书中详细介绍机器学习技术,但我会告诉你如何对这种数据进行切片切块以满足实际需求。 -MovieLens 1M数据集含有来自6000名用户对4000部电影的100万条评分数据。它分为三个表:评分、用户信息和电影信息。将该数据从zip文件中解压出来之后,可以通过pandas.read_table将各个表分别读到一个pandas DataFrame对象中: +MovieLens 1M 数据集含有来自 6000 名用户对 4000 部电影的 100 万条评分数据。它分为三个表:评分、用户信息和电影信息。将该数据从 zip 文件中解压出来之后,可以通过 pandas.read_table 将各个表分别读到一个 pandas DataFrame 对象中: ```python import pandas as pd @@ -480,7 +480,7 @@ movies = pd.read_table('datasets/movielens/movies.dat', sep='::', header=None, names=mnames) ``` -利用Python的切片语法,通过查看每个DataFrame的前几行即可验证数据加载工作是否一切顺利: +利用 Python 的切片语法,通过查看每个 DataFrame 的前几行即可验证数据加载工作是否一切顺利: ```python In [69]: users[:5] Out[69]: @@ -526,7 +526,7 @@ Out[72]: [1000209 rows x 4 columns] ``` -注意,其中的年龄和职业是以编码形式给出的,它们的具体含义请参考该数据集的README文件。分析散布在三个表中的数据可不是一件轻松的事情。假设我们想要根据性别和年龄计算某部电影的平均得分,如果将所有数据都合并到一个表中的话问题就简单多了。我们先用pandas的merge函数将ratings跟users合并到一起,然后再将movies也合并进去。pandas会根据列名的重叠情况推断出哪些列是合并(或连接)键: +注意,其中的年龄和职业是以编码形式给出的,它们的具体含义请参考该数据集的 README 文件。分析散布在三个表中的数据可不是一件轻松的事情。假设我们想要根据性别和年龄计算某部电影的平均得分,如果将所有数据都合并到一个表中的话问题就简单多了。我们先用 pandas 的 merge 函数将 ratings 跟 users 合并到一起,然后再将 movies 也合并进去。pandas 会根据列名的重叠情况推断出哪些列是合并(或连接)键: ```python In [73]: data = pd.merge(pd.merge(ratings, users), movies) @@ -573,7 +573,7 @@ genres Drama Name: 0, dtype: object ``` -为了按性别计算每部电影的平均得分,我们可以使用pivot_table方法: +为了按性别计算每部电影的平均得分,我们可以使用 pivot_table 方法: ```python In [76]: mean_ratings = data.pivot_table('rating', index='title', ....: columns='gender', aggfunc='mean') @@ -589,7 +589,7 @@ $1,000,000 Duck (1971) 3.375000 2.761905 ...And Justice for All (1979) 3.828571 3.689024 ``` -该操作产生了另一个DataFrame,其内容为电影平均得分,行标为电影名称(索引),列标为性别。现在,我打算过滤掉评分数据不够250条的电影(随便选的一个数字)。为了达到这个目的,我先对title进行分组,然后利用size()得到一个含有各电影分组大小的Series对象: +该操作产生了另一个 DataFrame,其内容为电影平均得分,行标为电影名称(索引),列标为性别。现在,我打算过滤掉评分数据不够 250 条的电影(随便选的一个数字)。为了达到这个目的,我先对 title 进行分组,然后利用 size()得到一个含有各电影分组大小的 Series 对象: ```python In [78]: ratings_by_title = data.groupby('title').size() @@ -626,7 +626,7 @@ Index([''burbs, The (1989)', '10 Things I Hate About You (1999)', dtype='object', name='title', length=1216) ``` -标题索引中含有评分数据大于250条的电影名称,然后我们就可以据此从前面的mean_ratings中选取所需的行了: +标题索引中含有评分数据大于 250 条的电影名称,然后我们就可以据此从前面的 mean_ratings 中选取所需的行了: ```python # Select rows on the index In [82]: mean_ratings = mean_ratings.loc[active_titles] @@ -649,7 +649,7 @@ eXistenZ (1999) 3.098592 3.289086 [1216 rows x 2 columns] ``` -为了了解女性观众最喜欢的电影,我们可以对F列降序排列: +为了了解女性观众最喜欢的电影,我们可以对 F 列降序排列: ```python In [85]: top_female_ratings = mean_ratings.sort_values(by='F', ascending=False) @@ -671,7 +671,7 @@ Usual Suspects, The (1995) 4.513317 4.518248 ## 计算评分分歧 -假设我们想要找出男性和女性观众分歧最大的电影。一个办法是给mean_ratings加上一个用于存放平均得分之差的列,并对其进行排序: +假设我们想要找出男性和女性观众分歧最大的电影。一个办法是给 mean_ratings 加上一个用于存放平均得分之差的列,并对其进行排序: ```python In [87]: mean_ratings['diff'] = mean_ratings['M'] - mean_ratings['F'] ``` @@ -696,7 +696,7 @@ Age of Innocence, The (1993) 3.827068 3.339506 -0.487561 Free Willy (1993) 2.921348 2.438776 -0.482573 ``` -对排序结果反序并取出前10行,得到的则是男性观众更喜欢的电影: +对排序结果反序并取出前 10 行,得到的则是男性观众更喜欢的电影: ```python # Reverse order of rows, take first 10 rows In [90]: sorted_by_diff[::-1][:10] @@ -742,11 +742,11 @@ Name: rating, dtype: float64 可能你已经注意到了,电影分类是以竖线(|)分隔的字符串形式给出的。如果想对电影分类进行分析的话,就需要先将其转换成更有用的形式才行。 -# 14.3 1880-2010年间全美婴儿姓名 +# 14.3 1880-2010 年间全美婴儿姓名 -美国社会保障总署(SSA)提供了一份从1880年到现在的婴儿名字频率数据。Hadley Wickham(许多流行R包的作者)经常用这份数据来演示R的数据处理功能。 +美国社会保障总署(SSA)提供了一份从 1880 年到现在的婴儿名字频率数据。Hadley Wickham(许多流行 R 包的作者)经常用这份数据来演示 R 的数据处理功能。 -我们要做一些数据规整才能加载这个数据集,这么做就会产生一个如下的DataFrame: +我们要做一些数据规整才能加载这个数据集,这么做就会产生一个如下的 DataFrame: ```python In [4]: names.head(10) Out[4]: @@ -773,11 +773,11 @@ Out[4]: 利用前面介绍过的那些工具,这些分析工作都能很轻松地完成,我会讲解其中的一些。 -到编写本书时为止,美国社会保障总署将该数据库按年度制成了多个数据文件,其中给出了每个性别/名字组合的出生总数。这些文件的原始档案可以在这里获取:[http://www.ssa.gov/oact/babynames/limits.html](http://www.ssa.gov/oact/babynames/limits.html)。 +到编写本书时为止,美国社会保障总署将该数据库按年度制成了多个数据文件,其中给出了每个性别/名字组合的出生总数。[这些文件的原始档案可以在这里获取](http://www.ssa.gov/oact/babynames/limits.html)。 如果你在阅读本书的时候这个页面已经不见了,也可以用搜索引擎找找。 -下载"National data"文件names.zip,解压后的目录中含有一组文件(如yob1880.txt)。我用UNIX的head命令查看了其中一个文件的前10行(在Windows上,你可以用more命令,或直接在文本编辑器中打开): +下载"National data"文件 names.zip,解压后的目录中含有一组文件(如 yob1880.txt)。我用 UNIX 的 head 命令查看了其中一个文件的前 10 行(在 Windows 上,你可以用 more 命令,或直接在文本编辑器中打开): ``` In [94]: !head -n 10 datasets/babynames/yob1880.txt Mary,F,7065 @@ -792,7 +792,7 @@ Bertha,F,1320 Sarah,F,1288 ``` -由于这是一个非常标准的以逗号隔开的格式,所以可以用pandas.read_csv将其加载到DataFrame中: +由于这是一个非常标准的以逗号隔开的格式,所以可以用 pandas.read_csv 将其加载到 DataFrame 中: ```python In [95]: import pandas as pd @@ -817,7 +817,7 @@ Out[97]: [2000 rows x 3 columns] ``` -这些文件中仅含有当年出现超过5次的名字。为了简单起见,我们可以用births列的sex分组小计表示该年度的births总计: +这些文件中仅含有当年出现超过 5 次的名字。为了简单起见,我们可以用 births 列的 sex 分组小计表示该年度的 births 总计: ```python In [98]: names1880.groupby('sex').births.sum() Out[98]: @@ -827,7 +827,7 @@ M 110493 Name: births, dtype: int64 ``` -由于该数据集按年度被分隔成了多个文件,所以第一件事情就是要将所有数据都组装到一个DataFrame里面,并加上一个year字段。使用pandas.concat即可达到这个目的: +由于该数据集按年度被分隔成了多个文件,所以第一件事情就是要将所有数据都组装到一个 DataFrame 里面,并加上一个 year 字段。使用 pandas.concat 即可达到这个目的: ```python years = range(1880, 2011) @@ -845,7 +845,7 @@ for year in years: names = pd.concat(pieces, ignore_index=True) ``` -这里需要注意几件事情。第一,concat默认是按行将多个DataFrame组合到一起的;第二,必须指定ignore_index=True,因为我们不希望保留read_csv所返回的原始行号。现在我们得到了一个非常大的DataFrame,它含有全部的名字数据: +这里需要注意几件事情。第一,concat 默认是按行将多个 DataFrame 组合到一起的;第二,必须指定 ignore_index=True,因为我们不希望保留 read_csv 所返回的原始行号。现在我们得到了一个非常大的 DataFrame,它含有全部的名字数据: ```python In [100]: names Out[100]: @@ -864,7 +864,7 @@ Out[100]: [1690784 rows x 4 columns] ``` -有了这些数据之后,我们就可以利用groupby或pivot_table在year和sex级别上对其进行聚合了,如图14-4所示: +有了这些数据之后,我们就可以利用 groupby 或 pivot_table 在 year 和 sex 级别上对其进行聚合了,如图 14-4 所示: ```python In [101]: total_births = names.pivot_table('births', index='year', .....: columns='sex', aggfunc=sum) @@ -882,9 +882,9 @@ year In [103]: total_births.plot(title='Total births by sex and year') ``` -![图14-4 按性别和年度统计的总出生数](img/7178691-7643b150d88aae11.png) +![图 14-4 按性别和年度统计的总出生数](img/7178691-7643b150d88aae11.png) -下面我们来插入一个prop列,用于存放指定名字的婴儿数相对于总出生数的比例。prop值为0.02表示每100名婴儿中有2名取了当前这个名字。因此,我们先按year和sex分组,然后再将新列加到各个分组上: +下面我们来插入一个 prop 列,用于存放指定名字的婴儿数相对于总出生数的比例。prop 值为 0.02 表示每 100 名婴儿中有 2 名取了当前这个名字。因此,我们先按 year 和 sex 分组,然后再将新列加到各个分组上: ```python def add_prop(group): group['prop'] = group.births / group.births.sum() @@ -911,7 +911,7 @@ Out[105]: [1690784 rows x 5 columns] ``` -在执行这样的分组处理时,一般都应该做一些有效性检查,比如验证所有分组的prop的总和是否为1: +在执行这样的分组处理时,一般都应该做一些有效性检查,比如验证所有分组的 prop 的总和是否为 1: ```python In [106]: names.groupby(['year', 'sex']).prop.sum() Out[106]: @@ -930,7 +930,7 @@ year sex Name: prop, Length: 262, dtype: float64 ``` -工作完成。为了便于实现更进一步的分析,我需要取出该数据的一个子集:每对sex/year组合的前1000个名字。这又是一个分组操作: +工作完成。为了便于实现更进一步的分析,我需要取出该数据的一个子集:每对 sex/year 组合的前 1000 个名字。这又是一个分组操作: ```python def get_top1000(group): return group.sort_values(by='births', ascending=False)[:1000] @@ -940,7 +940,7 @@ top1000 = grouped.apply(get_top1000) top1000.reset_index(inplace=True, drop=True) ``` -如果你喜欢DIY的话,也可以这样: +如果你喜欢 DIY 的话,也可以这样: ```python pieces = [] for year, group in names.groupby(['year', 'sex']): @@ -967,25 +967,25 @@ Out[108]: [261877 rows x 5 columns] ``` -接下来的数据分析工作就针对这个top1000数据集了。 +接下来的数据分析工作就针对这个 top1000 数据集了。 ## 分析命名趋势 -有了完整的数据集和刚才生成的top1000数据集,我们就可以开始分析各种命名趋势了。首先将前1000个名字分为男女两个部分: +有了完整的数据集和刚才生成的 top1000 数据集,我们就可以开始分析各种命名趋势了。首先将前 1000 个名字分为男女两个部分: ```python In [109]: boys = top1000[top1000.sex == 'M'] In [110]: girls = top1000[top1000.sex == 'F'] ``` -这是两个简单的时间序列,只需稍作整理即可绘制出相应的图表(比如每年叫做John和Mary的婴儿数)。我们先生成一张按year和name统计的总出生数透视表: +这是两个简单的时间序列,只需稍作整理即可绘制出相应的图表(比如每年叫做 John 和 Mary 的婴儿数)。我们先生成一张按 year 和 name 统计的总出生数透视表: ```python In [111]: total_births = top1000.pivot_table('births', index='year', .....: columns='name', .....: aggfunc=sum) ``` -现在,我们用DataFrame的plot方法绘制几个名字的曲线图(见图14-5): +现在,我们用 DataFrame 的 plot 方法绘制几个名字的曲线图(见图 14-5): ```python In [112]: total_births.info() @@ -1000,13 +1000,13 @@ In [114]: subset.plot(subplots=True, figsize=(12, 10), grid=False, .....: title="Number of births per year") ``` -![图14-5 几个男孩和女孩名字随时间变化的使用数量](img/7178691-33f0f97656367a53.png) +![图 14-5 几个男孩和女孩名字随时间变化的使用数量](img/7178691-33f0f97656367a53.png) 从图中可以看出,这几个名字在美国人民的心目中已经风光不再了。但事实并非如此简单,我们在下一节中就能知道是怎么一回事了。 ## 评估命名多样性的增长 -一种解释是父母愿意给小孩起常见的名字越来越少。这个假设可以从数据中得到验证。一个办法是计算最流行的1000个名字所占的比例,我按year和sex进行聚合并绘图(见图14-6): +一种解释是父母愿意给小孩起常见的名字越来越少。这个假设可以从数据中得到验证。一个办法是计算最流行的 1000 个名字所占的比例,我按 year 和 sex 进行聚合并绘图(见图 14-6): ```python In [116]: table = top1000.pivot_table('prop', index='year', .....: columns='sex', aggfunc=sum) @@ -1016,9 +1016,9 @@ In [117]: table.plot(title='Sum of table1000.prop by year and sex', ) ``` -![图14-6 分性别统计的前1000个名字在总出生人数中的比例](img/7178691-63e1ddc326a033b9.png) +![图 14-6 分性别统计的前 1000 个名字在总出生人数中的比例](img/7178691-63e1ddc326a033b9.png) -从图中可以看出,名字的多样性确实出现了增长(前1000项的比例降低)。另一个办法是计算占总出生人数前50%的不同名字的数量,这个数字不太好计算。我们只考虑2010年男孩的名字: +从图中可以看出,名字的多样性确实出现了增长(前 1000 项的比例降低)。另一个办法是计算占总出生人数前 50%的不同名字的数量,这个数字不太好计算。我们只考虑 2010 年男孩的名字: ```python In [118]: df = boys[boys.year == 2010] @@ -1039,7 +1039,7 @@ Out[119]: [1000 rows x 5 columns] ``` -在对prop降序排列之后,我们想知道前面多少个名字的人数加起来才够50%。虽然编写一个for循环确实也能达到目的,但NumPy有一种更聪明的矢量方式。先计算prop的累计和cumsum,然后再通过searchsorted方法找出0.5应该被插入在哪个位置才能保证不破坏顺序: +在对 prop 降序排列之后,我们想知道前面多少个名字的人数加起来才够 50%。虽然编写一个 for 循环确实也能达到目的,但 NumPy 有一种更聪明的矢量方式。先计算 prop 的累计和 cumsum,然后再通过 searchsorted 方法找出 0.5 应该被插入在哪个位置才能保证不破坏顺序: ```python In [120]: prop_cumsum = df.sort_values(by='prop', ascending=False).prop.cumsum() @@ -1061,7 +1061,7 @@ In [122]: prop_cumsum.values.searchsorted(0.5) Out[122]: 116 ``` -由于数组索引是从0开始的,因此我们要给这个结果加1,即最终结果为117。拿1900年的数据来做个比较,这个数字要小得多: +由于数组索引是从 0 开始的,因此我们要给这个结果加 1,即最终结果为 117。拿 1900 年的数据来做个比较,这个数字要小得多: ```python In [123]: df = boys[boys.year == 1900] @@ -1071,7 +1071,7 @@ In [125]: in1900.values.searchsorted(0.5) + 1 Out[125]: 25 ``` -现在就可以对所有year/sex组合执行这个计算了。按这两个字段进行groupby处理,然后用一个函数计算各分组的这个值: +现在就可以对所有 year/sex 组合执行这个计算了。按这两个字段进行 groupby 处理,然后用一个函数计算各分组的这个值: ```python def get_quantile_count(group, q=0.5): group = group.sort_values(by='prop', ascending=False) @@ -1081,7 +1081,7 @@ diversity = top1000.groupby(['year', 'sex']).apply(get_quantile_count) diversity = diversity.unstack('sex') ``` -现在,diversity这个DataFrame拥有两个时间序列(每个性别各一个,按年度索引)。通过IPython,你可以查看其内容,还可以像之前那样绘制图表(如图14-7所示): +现在,diversity 这个 DataFrame 拥有两个时间序列(每个性别各一个,按年度索引)。通过 IPython,你可以查看其内容,还可以像之前那样绘制图表(如图 14-7 所示): ```python In [128]: diversity.head() Out[128]: @@ -1096,13 +1096,13 @@ year In [129]: diversity.plot(title="Number of popular names in top 50%") ``` -![图14-7 按年度统计的密度表](img/7178691-574b53a383cad681.png) +![图 14-7 按年度统计的密度表](img/7178691-574b53a383cad681.png) 从图中可以看出,女孩名字的多样性总是比男孩的高,而且还在变得越来越高。读者们可以自己分析一下具体是什么在驱动这个多样性(比如拼写形式的变化)。 ## “最后一个字母”的变革 -2007年,一名婴儿姓名研究人员Laura Wattenberg在她自己的网站上指出(http://www.babynamewizard.com):近百年来,男孩名字在最后一个字母上的分布发生了显著的变化。为了了解具体的情况,我首先将全部出生数据在年度、性别以及末字母上进行了聚合: +2007 年,一名婴儿姓名研究人员 Laura Wattenberg 在她自己的网站上指出(http://www.babynamewizard.com):近百年来,男孩名字在最后一个字母上的分布发生了显著的变化。为了了解具体的情况,我首先将全部出生数据在年度、性别以及末字母上进行了聚合: ```python # extract last letter from name column get_last_letter = lambda x: x[-1] @@ -1164,7 +1164,7 @@ z 0.002439 0.000659 0.000704 0.000170 0.000184 0.001831 [26 rows x 6 columns] ``` -有了这个字母比例数据之后,就可以生成一张各年度各性别的条形图了,如图14-8所示: +有了这个字母比例数据之后,就可以生成一张各年度各性别的条形图了,如图 14-8 所示: ```python import matplotlib.pyplot as plt @@ -1174,9 +1174,9 @@ letter_prop['F'].plot(kind='bar', rot=0, ax=axes[1], title='Female', legend=False) ``` -![图14-8 男孩女孩名字中各个末字母的比例](img/7178691-67686f38e66ef5f1.png) +![图 14-8 男孩女孩名字中各个末字母的比例](img/7178691-67686f38e66ef5f1.png) -可以看出,从20世纪60年代开始,以字母"n"结尾的男孩名字出现了显著的增长。回到之前创建的那个完整表,按年度和性别对其进行规范化处理,并在男孩名字中选取几个字母,最后进行转置以便将各个列做成一个时间序列: +可以看出,从 20 世纪 60 年代开始,以字母"n"结尾的男孩名字出现了显著的增长。回到之前创建的那个完整表,按年度和性别对其进行规范化处理,并在男孩名字中选取几个字母,最后进行转置以便将各个列做成一个时间序列: ```python In [138]: letter_prop = table / table.sum() @@ -1193,16 +1193,16 @@ year 1884 0.086120 0.149915 0.080405 ``` -有了这个时间序列的DataFrame之后,就可以通过其plot方法绘制出一张趋势图了(如图14-9所示): +有了这个时间序列的 DataFrame 之后,就可以通过其 plot 方法绘制出一张趋势图了(如图 14-9 所示): ```python In [143]: dny_ts.plot() ``` -![图14-9 各年出生的男孩中名字以d/n/y结尾的人数比例](img/7178691-51c431b2490424c2.png) +![图 14-9 各年出生的男孩中名字以 d/n/y 结尾的人数比例](img/7178691-51c431b2490424c2.png) ## 变成女孩名字的男孩名字(以及相反的情况) -另一个有趣的趋势是,早年流行于男孩的名字近年来“变性了”,例如Lesley或Leslie。回到top1000数据集,找出其中以"lesl"开头的一组名字: +另一个有趣的趋势是,早年流行于男孩的名字近年来“变性了”,例如 Lesley 或 Leslie。回到 top1000 数据集,找出其中以"lesl"开头的一组名字: ```python In [144]: all_names = pd.Series(top1000.name.unique()) @@ -1252,16 +1252,16 @@ year 2010 1.0 NaN ``` -最后,就可以轻松绘制一张分性别的年度曲线图了(如图2-10所示): +最后,就可以轻松绘制一张分性别的年度曲线图了(如图 2-10 所示): ```python In [153]: table.plot(style={'M': 'k-', 'F': 'k--'}) ``` -![图14-10 各年度使用“Lesley型”名字的男女比例](img/7178691-b99d98f8bb5fc695.png) +![图 14-10 各年度使用“Lesley 型”名字的男女比例](img/7178691-b99d98f8bb5fc695.png) -# 14.4 USDA食品数据库 +# 14.4 USDA 食品数据库 -美国农业部(USDA)制作了一份有关食物营养信息的数据库。Ashley Williams制作了该数据的JSON版(http://ashleyw.co.uk/project/food-nutrient-database)。其中的记录如下所示: +美国农业部(USDA)制作了一份有关食物营养信息的数据库。Ashley Williams 制作了该数据的 JSON 版(http://ashleyw.co.uk/project/food-nutrient-database)。其中的记录如下所示: ```python { "id": 21441, @@ -1294,7 +1294,7 @@ Wing, meat and skin with breading", 每种食物都带有若干标识性属性以及两个有关营养成分和分量的列表。这种形式的数据不是很适合分析工作,因此我们需要做一些规整化以使其具有更好用的形式。 -从上面列举的那个网址下载并解压数据之后,你可以用任何喜欢的JSON库将其加载到Python中。我用的是Python内置的json模块: +从上面列举的那个网址下载并解压数据之后,你可以用任何喜欢的 JSON 库将其加载到 Python 中。我用的是 Python 内置的 json 模块: ```python In [154]: import json @@ -1304,7 +1304,7 @@ In [156]: len(db) Out[156]: 6636 ``` -db中的每个条目都是一个含有某种食物全部数据的字典。nutrients字段是一个字典列表,其中的每个字典对应一种营养成分: +db 中的每个条目都是一个含有某种食物全部数据的字典。nutrients 字段是一个字典列表,其中的每个字典对应一种营养成分: ```python In [157]: db[0].keys() Out[157]: dict_keys(['id', 'description', 'tags', 'manufacturer', 'group', 'porti @@ -1331,7 +1331,7 @@ Out[160]: 6 Energy Energy kJ 1573.00 ``` -在将字典列表转换为DataFrame时,可以只抽取其中的一部分字段。这里,我们将取出食物的名称、分类、编号以及制造商等信息: +在将字典列表转换为 DataFrame 时,可以只抽取其中的一部分字段。这里,我们将取出食物的名称、分类、编号以及制造商等信息: ```python In [161]: info_keys = ['description', 'group', 'id', 'manufacturer'] @@ -1365,7 +1365,7 @@ dtypes: int64(1), object(3) memory usage: 207.5+ KB ``` -通过value_counts,你可以查看食物类别的分布情况: +通过 value_counts,你可以查看食物类别的分布情况: ```python In [165]: pd.value_counts(info.group)[:10] Out[165]: @@ -1382,9 +1382,9 @@ Fruits and Fruit Juices 328 Name: group, dtype: int64 ``` -现在,为了对全部营养数据做一些分析,最简单的办法是将所有食物的营养成分整合到一个大表中。我们分几个步骤来实现该目的。首先,将各食物的营养成分列表转换为一个DataFrame,并添加一个表示编号的列,然后将该DataFrame添加到一个列表中。最后通过concat将这些东西连接起来就可以了: +现在,为了对全部营养数据做一些分析,最简单的办法是将所有食物的营养成分整合到一个大表中。我们分几个步骤来实现该目的。首先,将各食物的营养成分列表转换为一个 DataFrame,并添加一个表示编号的列,然后将该 DataFrame 添加到一个列表中。最后通过 concat 将这些东西连接起来就可以了: -顺利的话,nutrients的结果是: +顺利的话,nutrients 的结果是: ```python In [167]: nutrients Out[167]: @@ -1404,7 +1404,7 @@ Out[167]: [389355 rows x 5 columns] ``` -我发现这个DataFrame中无论如何都会有一些重复项,所以直接丢弃就可以了: +我发现这个 DataFrame 中无论如何都会有一些重复项,所以直接丢弃就可以了: ```python In [168]: nutrients.duplicated().sum() # number of duplicates Out[168]: 14179 @@ -1412,7 +1412,7 @@ Out[168]: 14179 In [169]: nutrients = nutrients.drop_duplicates() ``` -由于两个DataFrame对象中都有"group"和"description",所以为了明确到底谁是谁,我们需要对它们进行重命名: +由于两个 DataFrame 对象中都有"group"和"description",所以为了明确到底谁是谁,我们需要对它们进行重命名: ```python In [170]: col_mapping = {'description' : 'food', .....: 'group' : 'fgroup'} @@ -1451,7 +1451,7 @@ Out[175]: [375176 rows x 5 columns] ``` -做完这些,就可以将info跟nutrients合并起来: +做完这些,就可以将 info 跟 nutrients 合并起来: ```python In [176]: ndata = pd.merge(nutrients, info, on='id', how='outer') @@ -1483,14 +1483,14 @@ manufacturer Name: 30000, dtype: object ``` -我们现在可以根据食物分类和营养类型画出一张中位值图(如图14-11所示): +我们现在可以根据食物分类和营养类型画出一张中位值图(如图 14-11 所示): ```python In [180]: result = ndata.groupby(['nutrient', 'fgroup'])['value'].quantile(0.5) In [181]: result['Zinc, Zn'].sort_values().plot(kind='barh') ``` -![图片14-11 根据营养分类得出的锌中位值](img/7178691-99b176d022a444c0.png) +![图片 14-11 根据营养分类得出的锌中位值](img/7178691-99b176d022a444c0.png) 只要稍微动一动脑子,就可以发现各营养成分最为丰富的食物是什么了: ```python @@ -1505,7 +1505,7 @@ max_foods = by_nutrient.apply(get_maximum)[['value', 'food']] max_foods.food = max_foods.food.str[:50] ``` -由于得到的DataFrame很大,所以不方便在书里面全部打印出来。这里只给出"Amino Acids"营养分组: +由于得到的 DataFrame 很大,所以不方便在书里面全部打印出来。这里只给出"Amino Acids"营养分组: ```python In [183]: max_foods.loc['Amino Acids']['food'] Out[183]: @@ -1524,9 +1524,9 @@ Valine Soy protein isolate, PROTEIN TECHNOLOGIES INTE... Name: food, Length: 19, dtype: object ``` -# 14.5 2012联邦选举委员会数据库 +# 14.5 2012 联邦选举委员会数据库 -美国联邦选举委员会发布了有关政治竞选赞助方面的数据。其中包括赞助者的姓名、职业、雇主、地址以及出资额等信息。我们对2012年美国总统大选的数据集比较感兴趣(http://www.fec.gov/disclosurep/PDownload.do)。我在2012年6月下载的数据集是一个150MB的CSV文件(P00000001-ALL.csv),我们先用pandas.read_csv将其加载进来: +美国联邦选举委员会发布了有关政治竞选赞助方面的数据。其中包括赞助者的姓名、职业、雇主、地址以及出资额等信息。我们对 2012 年美国总统大选的数据集比较感兴趣(http://www.fec.gov/disclosurep/PDownload.do)。我在 2012 年 6 月下载的数据集是一个 150MB 的 CSV 文件(P00000001-ALL.csv),我们先用 pandas.read_csv 将其加载进来: ```python In [184]: fec = pd.read_csv('datasets/fec/P00000001-ALL.csv') @@ -1555,7 +1555,7 @@ dtypes: float64(1), int64(1), object(14) memory usage: 122.3+ MB ``` -该DataFrame中的记录如下所示: +该 DataFrame 中的记录如下所示: ```python In [186]: fec.iloc[123456] Out[186]: @@ -1575,7 +1575,7 @@ Name: 123456, Length: 16, dtype: object 你可能已经想出了许多办法从这些竞选赞助数据中抽取有关赞助人和赞助模式的统计信息。我将在接下来的内容中介绍几种不同的分析工作(运用到目前为止已经学到的方法)。 -不难看出,该数据中没有党派信息,因此最好把它加进去。通过unique,你可以获取全部的候选人名单: +不难看出,该数据中没有党派信息,因此最好把它加进去。通过 unique,你可以获取全部的候选人名单: ```python In [187]: unique_cands = fec.cand_nm.unique() @@ -1608,7 +1608,7 @@ parties = {'Bachmann, Michelle': 'Republican', 'Santorum, Rick': 'Republican'} ``` -现在,通过这个映射以及Series对象的map方法,你可以根据候选人姓名得到一组党派信息: +现在,通过这个映射以及 Series 对象的 map 方法,你可以根据候选人姓名得到一组党派信息: ```python In [191]: fec.cand_nm[123456:123461] Out[191]: @@ -1652,7 +1652,7 @@ Name: contb_receipt_amt, dtype: int64 In [196]: fec = fec[fec.contb_receipt_amt > 0] ``` -由于Barack Obama和Mitt Romney是最主要的两名候选人,所以我还专门准备了一个子集,只包含针对他们两人的竞选活动的赞助信息: +由于 Barack Obama 和 Mitt Romney 是最主要的两名候选人,所以我还专门准备了一个子集,只包含针对他们两人的竞选活动的赞助信息: ```python In [197]: fec_mrbo = fec[fec.cand_nm.isin(['Obama, Barack','Romney, Mitt'])] ``` @@ -1676,7 +1676,7 @@ PROFESSOR 12555 Name: contbr_occupation, dtype: int64 ``` -不难看出,许多职业都涉及相同的基本工作类型,或者同一样东西有多种变体。下面的代码片段可以清理一些这样的数据(将一个职业信息映射到另一个)。注意,这里巧妙地利用了dict.get,它允许没有映射关系的职业也能“通过”: +不难看出,许多职业都涉及相同的基本工作类型,或者同一样东西有多种变体。下面的代码片段可以清理一些这样的数据(将一个职业信息映射到另一个)。注意,这里巧妙地利用了 dict.get,它允许没有映射关系的职业也能“通过”: ```python occ_mapping = { 'INFORMATION REQUESTED PER BEST EFFORTS' : 'NOT PROVIDED', @@ -1704,7 +1704,7 @@ f = lambda x: emp_mapping.get(x, x) fec.contbr_employer = fec.contbr_employer.map(f) ``` -现在,你可以通过pivot_table根据党派和职业对数据进行聚合,然后过滤掉总出资额不足200万美元的数据: +现在,你可以通过 pivot_table 根据党派和职业对数据进行聚合,然后过滤掉总出资额不足 200 万美元的数据: ```python In [201]: by_occupation = fec.pivot_table('contb_receipt_amt', .....: index='contbr_occupation', @@ -1730,14 +1730,14 @@ SELF-EMPLOYED 672393.40 1.640253e+06 [17 rows x 2 columns] ``` -把这些数据做成柱状图看起来会更加清楚('barh'表示水平柱状图,如图14-12所示): +把这些数据做成柱状图看起来会更加清楚('barh'表示水平柱状图,如图 14-12 所示): ```python In [205]: over_2mm.plot(kind='barh') ``` -![图14-12 对各党派总出资额最高的职业](img/7178691-d2254e547c6ce537.png) +![图 14-12 对各党派总出资额最高的职业](img/7178691-d2254e547c6ce537.png) -你可能还想了解一下对Obama和Romney总出资额最高的职业和企业。为此,我们先对候选人进行分组,然后使用本章前面介绍的类似top的方法: +你可能还想了解一下对 Obama 和 Romney 总出资额最高的职业和企业。为此,我们先对候选人进行分组,然后使用本章前面介绍的类似 top 的方法: ```python def get_top_amounts(group, key, n=5): totals = group.groupby(key)['contb_receipt_amt'].sum() @@ -1783,7 +1783,7 @@ Name: contb_receipt_amt, Length: 20, dtype: float64 ## 对出资额分组 -还可以对该数据做另一种非常实用的分析:利用cut函数根据出资额的大小将数据离散化到多个面元中: +还可以对该数据做另一种非常实用的分析:利用 cut 函数根据出资额的大小将数据离散化到多个面元中: ```python In [210]: bins = np.array([0, 1, 10, 100, 1000, 10000, .....: 100000, 1000000, 10000000]) @@ -1828,7 +1828,7 @@ contb_receipt_amt (1000000, 10000000] 4.0 NaN ``` -从这个数据中可以看出,在小额赞助方面,Obama获得的数量比Romney多得多。你还可以对出资额求和并在面元内规格化,以便图形化显示两位候选人各种赞助额度的比例(见图14-13): +从这个数据中可以看出,在小额赞助方面,Obama 获得的数量比 Romney 多得多。你还可以对出资额求和并在面元内规格化,以便图形化显示两位候选人各种赞助额度的比例(见图 14-13): ```python In [216]: bucket_sums = grouped.contb_receipt_amt.sum().unstack(0) @@ -1851,7 +1851,7 @@ contb_receipt_amt In [219]: normed_sums[:-2].plot(kind='barh') ``` -![图14-13 两位候选人收到的各种捐赠额度的总额比例](img/7178691-77e8c8d3c784692b.png) +![图 14-13 两位候选人收到的各种捐赠额度的总额比例](img/7178691-77e8c8d3c784692b.png) 我排除了两个最大的面元,因为这些不是由个人捐赠的。 @@ -1907,4 +1907,4 @@ FL 0.467417 0.532583 我们已经完成了正文的最后一章。附录中有一些额外的内容,可能对你有用。 -本书第一版出版已经有5年了,Python已经成为了一个流行的、广泛使用的数据分析语言。你从本书中学到的方法,在相当长的一段时间都是可用的。我希望本书介绍的工具和库对你的工作有用。 +本书第一版出版已经有 5 年了,Python 已经成为了一个流行的、广泛使用的数据分析语言。你从本书中学到的方法,在相当长的一段时间都是可用的。我希望本书介绍的工具和库对你的工作有用。 diff --git a/docs/2.md b/docs/2.md index 4a07fc1..58057a0 100644 --- a/docs/2.md +++ b/docs/2.md @@ -1,24 +1,24 @@ # 第 2 章 Python 语法基础,IPython 和 Jupyter Notebooks -当我在2011年和2012年写作本书的第一版时,可用的学习Python数据分析的资源很少。这部分上是一个鸡和蛋的问题:我们现在使用的库,比如pandas、scikit-learn和statsmodels,那时相对来说并不成熟。2017年,数据科学、数据分析和机器学习的资源已经很多,原来通用的科学计算拓展到了计算机科学家、物理学家和其它研究领域的工作人员。学习Python和成为软件工程师的优秀书籍也有了。 +当我在 2011 年和 2012 年写作本书的第一版时,可用的学习 Python 数据分析的资源很少。这部分上是一个鸡和蛋的问题:我们现在使用的库,比如 pandas、scikit-learn 和 statsmodels,那时相对来说并不成熟。2017 年,数据科学、数据分析和机器学习的资源已经很多,原来通用的科学计算拓展到了计算机科学家、物理学家和其它研究领域的工作人员。学习 Python 和成为软件工程师的优秀书籍也有了。 -因为这本书是专注于Python数据处理的,对于一些Python的数据结构和库的特性难免不足。因此,本章和第3章的内容只够你能学习本书后面的内容。 +因为这本书是专注于 Python 数据处理的,对于一些 Python 的数据结构和库的特性难免不足。因此,本章和第 3 章的内容只够你能学习本书后面的内容。 -在我来看,没有必要为了数据分析而去精通Python。我鼓励你使用IPython shell和Jupyter试验示例代码,并学习不同类型、函数和方法的文档。虽然我已尽力让本书内容循序渐进,但读者偶尔仍会碰到没有之前介绍过的内容。 +在我来看,没有必要为了数据分析而去精通 Python。我鼓励你使用 IPython shell 和 Jupyter 试验示例代码,并学习不同类型、函数和方法的文档。虽然我已尽力让本书内容循序渐进,但读者偶尔仍会碰到没有之前介绍过的内容。 -本书大部分内容关注的是基于表格的分析和处理大规模数据集的数据准备工具。为了使用这些工具,必须首先将混乱的数据规整为整洁的表格(或结构化)形式。幸好,Python是一个理想的语言,可以快速整理数据。Python使用得越熟练,越容易准备新数据集以进行分析。 +本书大部分内容关注的是基于表格的分析和处理大规模数据集的数据准备工具。为了使用这些工具,必须首先将混乱的数据规整为整洁的表格(或结构化)形式。幸好,Python 是一个理想的语言,可以快速整理数据。Python 使用得越熟练,越容易准备新数据集以进行分析。 -最好在IPython和Jupyter中亲自尝试本书中使用的工具。当你学会了如何启动Ipython和Jupyter,我建议你跟随示例代码进行练习。与任何键盘驱动的操作环境一样,记住常见的命令也是学习曲线的一部分。 +最好在 IPython 和 Jupyter 中亲自尝试本书中使用的工具。当你学会了如何启动 Ipython 和 Jupyter,我建议你跟随示例代码进行练习。与任何键盘驱动的操作环境一样,记住常见的命令也是学习曲线的一部分。 -> 笔记:本章没有介绍Python的某些概念,如类和面向对象编程,你可能会发现它们在Python数据分析中很有用。 为了加强Python知识,我建议你学习官方Python教程,[https://docs.python.org/3/,或是通用的Python教程书籍,比如:](https://docs.python.org/3/,或是通用的Python教程书籍,比如:) +> 笔记:本章没有介绍 Python 的某些概念,如类和面向对象编程,你可能会发现它们在 Python 数据分析中很有用。 为了加强 Python 知识,我建议你学习[官方 Python 教程](https://docs.python.org/3/),或是通用的 Python 教程书籍,比如: > -> * Python Cookbook,第3版,David Beazley和Brian K. Jones著(O’Reilly) -> * 流畅的Python,Luciano Ramalho著 \(O’Reilly\) -> * 高效的Python,Brett Slatkin著 \(Pearson\) +> * Python Cookbook,第 3 版,David Beazley 和 Brian K. Jones 著(O’Reilly) +> * 流畅的 Python,Luciano Ramalho 著 \(O’Reilly\) +> * 高效的 Python,Brett Slatkin 著 \(Pearson\) -## 2.1 Python解释器 +## 2.1 Python 解释器 -Python是解释性语言。Python解释器同一时间只能运行一个程序的一条语句。标准的交互Python解释器可以在命令行中通过键入`python`命令打开: +Python 是解释性语言。Python 解释器同一时间只能运行一个程序的一条语句。标准的交互 Python 解释器可以在命令行中通过键入`python`命令打开: ```text $ python @@ -30,9 +30,9 @@ Type "help", "copyright", "credits" or "license" for more information. 5 ``` -`>>>`提示输入代码。要退出Python解释器返回终端,可以输入`exit()`或按Ctrl-D。 +`>>>`提示输入代码。要退出 Python 解释器返回终端,可以输入`exit()`或按 Ctrl-D。 -运行Python程序只需调用Python的同时,使用一个`.py`文件作为它的第一个参数。假设创建了一个`hello_world.py`文件,它的内容是: +运行 Python 程序只需调用 Python 的同时,使用一个`.py`文件作为它的第一个参数。假设创建了一个`hello_world.py`文件,它的内容是: ```python print('Hello world') @@ -45,7 +45,7 @@ $ python hello_world.py Hello world ``` -一些Python程序员总是这样执行Python代码的,从事数据分析和科学计算的人却会使用IPython,一个强化的Python解释器,或Jupyter notebooks,一个网页代码笔记本,它原先是IPython的一个子项目。在本章中,我介绍了如何使用IPython和Jupyter,在附录A中有更深入的介绍。当你使用`%run`命令,IPython会同样执行指定文件中的代码,结束之后,还可以与结果交互: +一些 Python 程序员总是这样执行 Python 代码的,从事数据分析和科学计算的人却会使用 IPython,一个强化的 Python 解释器,或 Jupyter notebooks,一个网页代码笔记本,它原先是 IPython 的一个子项目。在本章中,我介绍了如何使用 IPython 和 Jupyter,在附录 A 中有更深入的介绍。当你使用`%run`命令,IPython 会同样执行指定文件中的代码,结束之后,还可以与结果交互: ```text $ ipython @@ -64,15 +64,15 @@ Hello world In [2]: ``` -IPython默认采用序号的格式`In [2]:`,与标准的`>>>`提示符不同。 +IPython 默认采用序号的格式`In [2]:`,与标准的`>>>`提示符不同。 -## 2.2 IPython基础 +## 2.2 IPython 基础 -在本节中,我们会教你打开运行IPython shell和jupyter notebook,并介绍一些基本概念。 +在本节中,我们会教你打开运行 IPython shell 和 jupyter notebook,并介绍一些基本概念。 -### 运行IPython Shell +### 运行 IPython Shell -你可以用`ipython`在命令行打开IPython Shell,就像打开普通的Python解释器: +你可以用`ipython`在命令行打开 IPython Shell,就像打开普通的 Python 解释器: ```text $ ipython @@ -90,7 +90,7 @@ In [2]: a Out[2]: 5 ``` -你可以通过输入代码并按Return(或Enter),运行任意Python语句。当你只输入一个变量,它会显示代表的对象: +你可以通过输入代码并按 Return(或 Enter),运行任意 Python 语句。当你只输入一个变量,它会显示代表的对象: ```python In [5]: import numpy as np @@ -108,9 +108,9 @@ Out[7]: 6: 0.09290787674371767} ``` -前两行是Python代码语句;第二条语句创建一个名为`data`的变量,它引用一个新创建的Python字典。最后一行打印`data`的值。 +前两行是 Python 代码语句;第二条语句创建一个名为`data`的变量,它引用一个新创建的 Python 字典。最后一行打印`data`的值。 -许多Python对象被格式化为更易读的形式,或称作`pretty-printed`,它与普通的`print`不同。如果在标准Python解释器中打印上述`data`变量,则可读性要降低: +许多 Python 对象被格式化为更易读的形式,或称作`pretty-printed`,它与普通的`print`不同。如果在标准 Python 解释器中打印上述`data`变量,则可读性要降低: ```text >>> from numpy.random import randn @@ -121,11 +121,11 @@ Out[7]: 6: 0.3308507317325902} ``` -IPython还支持执行任意代码块(通过一个华丽的复制-粘贴方法)和整段Python脚本的功能。你也可以使用Jupyter notebook运行大代码块,接下来就会看到。 +IPython 还支持执行任意代码块(通过一个华丽的复制-粘贴方法)和整段 Python 脚本的功能。你也可以使用 Jupyter notebook 运行大代码块,接下来就会看到。 -### 运行Jupyter Notebook +### 运行 Jupyter Notebook -notebook是Jupyter项目的重要组件之一,它是一个代码、文本(有标记或无标记)、数据可视化或其它输出的交互式文档。Jupyter Notebook需要与内核互动,内核是Jupyter与其它编程语言的交互编程协议。Python的Jupyter内核是使用IPython。要启动Jupyter,在命令行中输入`jupyter notebook`: +notebook 是 Jupyter 项目的重要组件之一,它是一个代码、文本(有标记或无标记)、数据可视化或其它输出的交互式文档。Jupyter Notebook 需要与内核互动,内核是 Jupyter 与其它编程语言的交互编程协议。Python 的 Jupyter 内核是使用 IPython。要启动 Jupyter,在命令行中输入`jupyter notebook`: ```text $ jupyter notebook @@ -139,25 +139,25 @@ all kernels (twice to skip confirmation). Created new window in existing browser session. ``` -在多数平台上,Jupyter会自动打开默认的浏览器(除非指定了`--no-browser`)。或者,可以在启动notebook之后,手动打开网页`http://localhost:8888/`。图2-1展示了Google Chrome中的notebook。 +在多数平台上,Jupyter 会自动打开默认的浏览器(除非指定了`--no-browser`)。或者,可以在启动 notebook 之后,手动打开网页`http://localhost:8888/`。图 2-1 展示了 Google Chrome 中的 notebook。 -> 笔记:许多人使用Jupyter作为本地的计算环境,但它也可以部署到服务器上远程访问。这里不做介绍,如果需要的话,鼓励读者自行到网上学习。 +> 笔记:许多人使用 Jupyter 作为本地的计算环境,但它也可以部署到服务器上远程访问。这里不做介绍,如果需要的话,鼓励读者自行到网上学习。 ![图2-1 Jupyter notebook启动页面](img/7178691-c76c4f40777d3ef1.png) -要新建一个notebook,点击按钮New,选择“Python3”或“conda\[默认项\]”。如果是第一次,点击空格,输入一行Python代码。然后按Shift-Enter执行。 +要新建一个 notebook,点击按钮 New,选择“Python3”或“conda\[默认项\]”。如果是第一次,点击空格,输入一行 Python 代码。然后按 Shift-Enter 执行。 ![图2-2 Jupyter新notebook页面](img/7178691-86a6813291ead445.png) -当保存notebook时(File目录下的Save and Checkpoint),会创建一个后缀名为`.ipynb`的文件。这是一个自包含文件格式,包含当前笔记本中的所有内容(包括所有已评估的代码输出)。可以被其它Jupyter用户加载和编辑。要加载存在的notebook,把它放到启动notebook进程的相同目录内。你可以用本书的示例代码练习,见图2-3。 +当保存 notebook 时(File 目录下的 Save and Checkpoint),会创建一个后缀名为`.ipynb`的文件。这是一个自包含文件格式,包含当前笔记本中的所有内容(包括所有已评估的代码输出)。可以被其它 Jupyter 用户加载和编辑。要加载存在的 notebook,把它放到启动 notebook 进程的相同目录内。你可以用本书的示例代码练习,见图 2-3。 -虽然Jupyter notebook和IPython shell使用起来不同,本章中几乎所有的命令和工具都可以通用。 +虽然 Jupyter notebook 和 IPython shell 使用起来不同,本章中几乎所有的命令和工具都可以通用。 ![图2-3 Jupyter查看一个存在的notebook的页面](img/7178691-bc9a0b4c30363747.png) -### Tab补全 +### Tab 补全 -从外观上,IPython shell和标准的Python解释器只是看起来不同。IPython shell的进步之一是具备其它IDE和交互计算分析环境都有的tab补全功能。在shell中输入表达式,按下Tab,会搜索已输入变量(对象、函数等等)的命名空间: +从外观上,IPython shell 和标准的 Python 解释器只是看起来不同。IPython shell 的进步之一是具备其它 IDE 和交互计算分析环境都有的 tab 补全功能。在 shell 中输入表达式,按下 Tab,会搜索已输入变量(对象、函数等等)的命名空间: ```text In [1]: an_apple = 27 @@ -168,7 +168,7 @@ In [3]: an an_apple and an_example any ``` -在这个例子中,IPython呈现出了之前两个定义的变量和Python的关键字和内建的函数`any`。当然,你也可以补全任何对象的方法和属性: +在这个例子中,IPython 呈现出了之前两个定义的变量和 Python 的关键字和内建的函数`any`。当然,你也可以补全任何对象的方法和属性: ```text In [3]: b = [1, 2, 3] @@ -190,11 +190,11 @@ datetime.datetime datetime.MINYEAR datetime.timezone datetime.datetime_CAPI datetime.time datetime.tzinfo ``` -在Jupyter notebook和新版的IPython(5.0及以上),自动补全功能是下拉框的形式。 +在 Jupyter notebook 和新版的 IPython(5.0 及以上),自动补全功能是下拉框的形式。 -> 笔记:注意,默认情况下,IPython会隐藏下划线开头的方法和属性,比如魔术方法和内部的“私有”方法和属性,以避免混乱的显示(和让新手迷惑!)这些也可以tab补全,但是你必须首先键入一个下划线才能看到它们。如果你喜欢总是在tab补全中看到这样的方法,你可以IPython配置中进行设置。可以在IPython文档中查找方法。 +> 笔记:注意,默认情况下,IPython 会隐藏下划线开头的方法和属性,比如魔术方法和内部的“私有”方法和属性,以避免混乱的显示(和让新手迷惑!)这些也可以 tab 补全,但是你必须首先键入一个下划线才能看到它们。如果你喜欢总是在 tab 补全中看到这样的方法,你可以 IPython 配置中进行设置。可以在 IPython 文档中查找方法。 -除了补全命名、对象和模块属性,Tab还可以补全其它的。当输入看似文件路径时(即使是Python字符串),按下Tab也可以补全电脑上对应的文件信息: +除了补全命名、对象和模块属性,Tab 还可以补全其它的。当输入看似文件路径时(即使是 Python 字符串),按下 Tab 也可以补全电脑上对应的文件信息: ```text In [7]: datasets/movielens/ @@ -206,9 +206,9 @@ datasets/movielens/movies.dat datasets/movielens/README datasets/movielens/ratings.dat datasets/movielens/users.dat ``` -结合`%run`,tab补全可以节省许多键盘操作。 +结合`%run`,tab 补全可以节省许多键盘操作。 -另外,tab补全可以补全函数的关键词参数(包括等于号=)。见图2-4。 +另外,tab 补全可以补全函数的关键词参数(包括等于号=)。见图 2-4。 ![图2-4 Jupyter notebook中自动补全函数关键词](img/7178691-8188b0386238c16a.png) @@ -290,7 +290,7 @@ File: Type: function ``` -?还有一个用途,就是像Unix或Windows命令行一样搜索IPython的命名空间。字符与通配符结合可以匹配所有的名字。例如,我们可以获得所有包含load的顶级NumPy命名空间: +?还有一个用途,就是像 Unix 或 Windows 命令行一样搜索 IPython 的命名空间。字符与通配符结合可以匹配所有的名字。例如,我们可以获得所有包含 load 的顶级 NumPy 命名空间: ```python In [13]: np.*load*? @@ -301,9 +301,9 @@ np.loadtxt np.pkgload ``` -### %run命令 +### %run 命令 -你可以用`%run`命令运行所有的Python程序。假设有一个文件`ipython_script_test.py`: +你可以用`%run`命令运行所有的 Python 程序。假设有一个文件`ipython_script_test.py`: ```python def f(x, y, z): @@ -322,7 +322,7 @@ result = f(a, b, c) In [14]: %run ipython_script_test.py ``` -这段脚本运行在空的命名空间(没有import和其它定义的变量),因此结果和普通的运行方式`python script.py`相同。文件中所有定义的变量(import、函数和全局变量,除非抛出异常),都可以在IPython shell中随后访问: +这段脚本运行在空的命名空间(没有 import 和其它定义的变量),因此结果和普通的运行方式`python script.py`相同。文件中所有定义的变量(import、函数和全局变量,除非抛出异常),都可以在 IPython shell 中随后访问: ```python In [15]: c @@ -332,11 +332,11 @@ In [16]: result Out[16]: 1.4666666666666666 ``` -如果一个Python脚本需要命令行参数(在`sys.argv`中查找),可以在文件路径之后传递,就像在命令行上运行一样。 +如果一个 Python 脚本需要命令行参数(在`sys.argv`中查找),可以在文件路径之后传递,就像在命令行上运行一样。 -> 笔记:如果想让一个脚本访问IPython已经定义过的变量,可以使用`%run -i`。 +> 笔记:如果想让一个脚本访问 IPython 已经定义过的变量,可以使用`%run -i`。 -在Jupyter notebook中,你也可以使用`%load`,它将脚本导入到一个代码格中: +在 Jupyter notebook 中,你也可以使用`%load`,它将脚本导入到一个代码格中: ```text >>> %load ipython_script_test.py @@ -352,13 +352,13 @@ Out[16]: 1.4666666666666666 ### 中断运行的代码 -代码运行时按Ctrl-C,无论是%run或长时间运行命令,都会导致`KeyboardInterrupt`。这会导致几乎所有Python程序立即停止,除非一些特殊情况。 +代码运行时按 Ctrl-C,无论是%run 或长时间运行命令,都会导致`KeyboardInterrupt`。这会导致几乎所有 Python 程序立即停止,除非一些特殊情况。 -> 警告:当Python代码调用了一些编译的扩展模块,按Ctrl-C不一定将执行的程序立即停止。在这种情况下,你必须等待,直到控制返回Python解释器,或者在更糟糕的情况下强制终止Python进程。 +> 警告:当 Python 代码调用了一些编译的扩展模块,按 Ctrl-C 不一定将执行的程序立即停止。在这种情况下,你必须等待,直到控制返回 Python 解释器,或者在更糟糕的情况下强制终止 Python 进程。 ### 从剪贴板执行程序 -如果使用Jupyter notebook,你可以将代码复制粘贴到任意代码格执行。在IPython shell中也可以从剪贴板执行。假设在其它应用中复制了如下代码: +如果使用 Jupyter notebook,你可以将代码复制粘贴到任意代码格执行。在 IPython shell 中也可以从剪贴板执行。假设在其它应用中复制了如下代码: ```python x = 5 @@ -396,21 +396,21 @@ Pasting code; enter '--' alone on the line to stop or use Ctrl-D. :-- ``` -使用`%cpaste`,你可以粘贴任意多的代码再运行。你可能想在运行前,先看看代码。如果粘贴了错误的代码,可以用Ctrl-C中断。 +使用`%cpaste`,你可以粘贴任意多的代码再运行。你可能想在运行前,先看看代码。如果粘贴了错误的代码,可以用 Ctrl-C 中断。 ### 键盘快捷键 -IPython有许多键盘快捷键进行导航提示(类似Emacs文本编辑器或UNIX bash Shell)和交互shell的历史命令。表2-1总结了常见的快捷键。图2-5展示了一部分,如移动光标。 +IPython 有许多键盘快捷键进行导航提示(类似 Emacs 文本编辑器或 UNIX bash Shell)和交互 shell 的历史命令。表 2-1 总结了常见的快捷键。图 2-5 展示了一部分,如移动光标。 ![图2-5 IPython shell中一些快捷键的说明](img/7178691-9ed3866ea25c11f8.png) ![表2-1 IPython的标准快捷键](img/7178691-e179f5ea00e50691.png) -Jupyter notebooks有另外一套庞大的快捷键。因为它的快捷键比IPython的变化快,建议你参阅Jupyter notebook的帮助文档。 +Jupyter notebooks 有另外一套庞大的快捷键。因为它的快捷键比 IPython 的变化快,建议你参阅 Jupyter notebook 的帮助文档。 ### 魔术命令 -IPython中特殊的命令(Python中没有)被称作“魔术”命令。这些命令可以使普通任务更便捷,更容易控制IPython系统。魔术命令是在指令前添加百分号%前缀。例如,可以用`%timeit`(这个命令后面会详谈)测量任何Python语句,例如矩阵乘法,的执行时间: +IPython 中特殊的命令(Python 中没有)被称作“魔术”命令。这些命令可以使普通任务更便捷,更容易控制 IPython 系统。魔术命令是在指令前添加百分号%前缀。例如,可以用`%timeit`(这个命令后面会详谈)测量任何 Python 语句,例如矩阵乘法,的执行时间: ```python In [20]: a = np.random.randn(100, 100) @@ -419,7 +419,7 @@ In [20]: %timeit np.dot(a, a) 10000 loops, best of 3: 20.9 µs per loop ``` -魔术命令可以被看做IPython中运行的命令行。许多魔术命令有“命令行”选项,可以通过?查看: +魔术命令可以被看做 IPython 中运行的命令行。许多魔术命令有“命令行”选项,可以通过?查看: ```text In [21]: %debug? @@ -458,7 +458,7 @@ optional arguments: 魔术函数默认可以不用百分号,只要没有变量和函数名相同。这个特点被称为“自动魔术”,可以用`%automagic`打开或关闭。 -一些魔术函数与Python函数很像,它的结果可以赋值给一个变量: +一些魔术函数与 Python 函数很像,它的结果可以赋值给一个变量: ```text In [22]: %pwd @@ -470,22 +470,22 @@ In [24]: foo Out[24]: '/home/wesm/code/pydata-book' ``` -IPython的文档可以在shell中打开,我建议你用`%quickref`或`%magic`学习下所有特殊命令。表2-2列出了一些可以提高生产率的交互计算和Python开发的IPython指令。 +IPython 的文档可以在 shell 中打开,我建议你用`%quickref`或`%magic`学习下所有特殊命令。表 2-2 列出了一些可以提高生产率的交互计算和 Python 开发的 IPython 指令。 ![表2-2 一些常用的IPython魔术命令](img/7178691-c72b11add9b8ccf8.png) -### 集成Matplotlib +### 集成 Matplotlib -IPython在分析计算领域能够流行的原因之一是它非常好的集成了数据可视化和其它用户界面库,比如matplotlib。不用担心以前没用过matplotlib,本书后面会详细介绍。`%matplotlib`魔术函数配置了IPython shell和Jupyter notebook中的matplotlib。这点很重要,其它创建的图不会出现(notebook)或获取session的控制,直到结束(shell)。 +IPython 在分析计算领域能够流行的原因之一是它非常好的集成了数据可视化和其它用户界面库,比如 matplotlib。不用担心以前没用过 matplotlib,本书后面会详细介绍。`%matplotlib`魔术函数配置了 IPython shell 和 Jupyter notebook 中的 matplotlib。这点很重要,其它创建的图不会出现(notebook)或获取 session 的控制,直到结束(shell)。 -在IPython shell中,运行`%matplotlib`可以进行设置,可以创建多个绘图窗口,而不会干扰控制台session: +在 IPython shell 中,运行`%matplotlib`可以进行设置,可以创建多个绘图窗口,而不会干扰控制台 session: ```text In [26]: %matplotlib Using matplotlib backend: Qt4Agg ``` -在JUpyter中,命令有所不同(图2-6): +在 JUpyter 中,命令有所不同(图 2-6): ```text In [26]: %matplotlib inline @@ -493,17 +493,17 @@ In [26]: %matplotlib inline ![图2-6 Jupyter行内matplotlib作图](img/7178691-3ab3738a92a15486.png) -## 2.3 Python语法基础 +## 2.3 Python 语法基础 -在本节中,我将概述基本的Python概念和语言机制。在下一章,我将详细介绍Python的数据结构、函数和其它内建工具。 +在本节中,我将概述基本的 Python 概念和语言机制。在下一章,我将详细介绍 Python 的数据结构、函数和其它内建工具。 ### 语言的语义 -Python的语言设计强调的是可读性、简洁和清晰。有些人称Python为“可执行的伪代码”。 +Python 的语言设计强调的是可读性、简洁和清晰。有些人称 Python 为“可执行的伪代码”。 ### 使用缩进,而不是括号 -Python使用空白字符(tab和空格)来组织代码,而不是像其它语言,比如R、C++、JAVA和Perl那样使用括号。看一个排序算法的`for`循环: +Python 使用空白字符(tab 和空格)来组织代码,而不是像其它语言,比如 R、C++、JAVA 和 Perl 那样使用括号。看一个排序算法的`for`循环: ```python for x in array: @@ -513,25 +513,25 @@ for x in array: greater.append(x) ``` -冒号标志着缩进代码块的开始,冒号之后的所有代码的缩进量必须相同,直到代码块结束。不管是否喜欢这种形式,使用空白符是Python程序员开发的一部分,在我看来,这可以让python的代码可读性大大优于其它语言。虽然期初看起来很奇怪,经过一段时间,你就能适应了。 +冒号标志着缩进代码块的开始,冒号之后的所有代码的缩进量必须相同,直到代码块结束。不管是否喜欢这种形式,使用空白符是 Python 程序员开发的一部分,在我看来,这可以让 python 的代码可读性大大优于其它语言。虽然期初看起来很奇怪,经过一段时间,你就能适应了。 -> 笔记:我强烈建议你使用四个空格作为默认的缩进,可以使用tab代替四个空格。许多文本编辑器的设置是使用制表位替代空格。某些人使用tabs或不同数目的空格数,常见的是使用两个空格。大多数情况下,四个空格是大多数人采用的方法,因此建议你也这样做。 +> 笔记:我强烈建议你使用四个空格作为默认的缩进,可以使用 tab 代替四个空格。许多文本编辑器的设置是使用制表位替代空格。某些人使用 tabs 或不同数目的空格数,常见的是使用两个空格。大多数情况下,四个空格是大多数人采用的方法,因此建议你也这样做。 -你应该已经看到,Python的语句不需要用分号结尾。但是,分号却可以用来给同在一行的语句切分: +你应该已经看到,Python 的语句不需要用分号结尾。但是,分号却可以用来给同在一行的语句切分: ```python a = 5; b = 6; c = 7 ``` -Python不建议将多条语句放到一行,这会降低代码的可读性。 +Python 不建议将多条语句放到一行,这会降低代码的可读性。 ### 万物皆对象 -Python语言的一个重要特性就是它的对象模型的一致性。每个数字、字符串、数据结构、函数、类、模块等等,都是在Python解释器的自有“盒子”内,它被认为是Python对象。每个对象都有类型(例如,字符串或函数)和内部数据。在实际中,这可以让语言非常灵活,因为函数也可以被当做对象使用。 +Python 语言的一个重要特性就是它的对象模型的一致性。每个数字、字符串、数据结构、函数、类、模块等等,都是在 Python 解释器的自有“盒子”内,它被认为是 Python 对象。每个对象都有类型(例如,字符串或函数)和内部数据。在实际中,这可以让语言非常灵活,因为函数也可以被当做对象使用。 ### 注释 -任何前面带有井号\#的文本都会被Python解释器忽略。这通常被用来添加注释。有时,你会想排除一段代码,但并不删除。简便的方法就是将其注释掉: +任何前面带有井号\#的文本都会被 Python 解释器忽略。这通常被用来添加注释。有时,你会想排除一段代码,但并不删除。简便的方法就是将其注释掉: ```python results = [] @@ -557,7 +557,7 @@ result = f(x, y, z) g() ``` -几乎Python中的每个对象都有附加的函数,称作方法,可以用来访问对象的内容。可以用下面的语句调用: +几乎 Python 中的每个对象都有附加的函数,称作方法,可以用来访问对象的内容。可以用下面的语句调用: ```python obj.some_method(x, y, z) @@ -573,19 +573,19 @@ result = f(a, b, c, d=5, e='foo') ### 变量和参数传递 -当在Python中创建变量(或名字),你就在等号右边创建了一个对这个变量的引用。考虑一个整数列表: +当在 Python 中创建变量(或名字),你就在等号右边创建了一个对这个变量的引用。考虑一个整数列表: ```python In [8]: a = [1, 2, 3] ``` -假设将a赋值给一个新变量b: +假设将 a 赋值给一个新变量 b: ```python In [9]: b = a ``` -在有些方法中,这个赋值会将数据\[1, 2, 3\]也复制。在Python中,a和b实际上是同一个对象,即原有列表\[1, 2, 3\](见图2-7)。你可以在a中添加一个元素,然后检查b: +在有些方法中,这个赋值会将数据\[1, 2, 3\]也复制。在 Python 中,a 和 b 实际上是同一个对象,即原有列表\[1, 2, 3\](见图 2-7)。你可以在 a 中添加一个元素,然后检查 b: ```python In [10]: a.append(4) @@ -596,7 +596,7 @@ Out[11]: [1, 2, 3, 4] ![图2-7 对同一对象的双重引用](img/7178691-3e3a8c6b9c5040fc.png) -理解Python的引用的含义,数据是何时、如何、为何复制的,是非常重要的。尤其是当你用Python处理大的数据集时。 +理解 Python 的引用的含义,数据是何时、如何、为何复制的,是非常重要的。尤其是当你用 Python 处理大的数据集时。 > 笔记:赋值也被称作绑定,我们是把一个名字绑定给一个对象。变量名有时可能被称为绑定变量。 @@ -620,7 +620,7 @@ Out[29]: [1, 2, 3, 4] ### 动态引用,强类型 -与许多编译语言(如JAVA和C++)对比,Python中的对象引用不包含附属的类型。下面的代码是没有问题的: +与许多编译语言(如 JAVA 和 C++)对比,Python 中的对象引用不包含附属的类型。下面的代码是没有问题的: ```python In [12]: a = 5 @@ -634,7 +634,7 @@ In [15]: type(a) Out[15]: str ``` -变量是在特殊命名空间中的对象的名字,类型信息保存在对象自身中。一些人可能会说Python不是“类型化语言”。这是不正确的,看下面的例子: +变量是在特殊命名空间中的对象的名字,类型信息保存在对象自身中。一些人可能会说 Python 不是“类型化语言”。这是不正确的,看下面的例子: ```text In [16]: '5' + 5 @@ -645,7 +645,7 @@ TypeError Traceback (most recent call last) TypeError: must be str, not int ``` -在某些语言中,例如Visual Basic,字符串‘5’可能被默许转换(或投射)为整数,因此会产生10。但在其它语言中,例如JavaScript,整数5会被投射成字符串,结果是联结字符串‘55’。在这个方面,Python被认为是强类型化语言,意味着每个对象都有明确的类型(或类),默许转换只会发生在特定的情况下,例如: +在某些语言中,例如 Visual Basic,字符串‘5’可能被默许转换(或投射)为整数,因此会产生 10。但在其它语言中,例如 JavaScript,整数 5 会被投射成字符串,结果是联结字符串‘55’。在这个方面,Python 被认为是强类型化语言,意味着每个对象都有明确的类型(或类),默许转换只会发生在特定的情况下,例如: ```text In [17]: a = 4.5 @@ -683,7 +683,7 @@ Out[25]: True ### 属性和方法 -Python的对象通常都有属性(其它存储在对象内部的Python对象)和方法(对象的附属函数可以访问对象的内部数据)。可以用`obj.attribute_name`访问属性和方法: +Python 的对象通常都有属性(其它存储在对象内部的 Python 对象)和方法(对象的附属函数可以访问对象的内部数据)。可以用`obj.attribute_name`访问属性和方法: ```text In [1]: a = 'foo' @@ -721,7 +721,7 @@ def isiterable(obj): return False ``` -这个函数会返回字符串以及大多数Python集合类型为`True`: +这个函数会返回字符串以及大多数 Python 集合类型为`True`: ```text In [29]: isiterable('a string') @@ -734,7 +734,7 @@ In [31]: isiterable(5) Out[31]: False ``` -我总是用这个功能编写可以接受多种输入类型的函数。常见的例子是编写一个函数可以接受任意类型的序列(list、tuple、ndarray)或是迭代器。你可先检验对象是否是列表(或是NUmPy数组),如果不是的话,将其转变成列表: +我总是用这个功能编写可以接受多种输入类型的函数。常见的例子是编写一个函数可以接受任意类型的序列(list、tuple、ndarray)或是迭代器。你可先检验对象是否是列表(或是 NUmPy 数组),如果不是的话,将其转变成列表: ```python if not isinstance(x, list) and isiterable(x): @@ -743,7 +743,7 @@ if not isinstance(x, list) and isiterable(x): ### 引入 -在Python中,模块就是一个有`.py`扩展名、包含Python代码的文件。假设有以下模块: +在 Python 中,模块就是一个有`.py`扩展名、包含 Python 代码的文件。假设有以下模块: ```python # some_module.py @@ -796,7 +796,7 @@ In [34]: 5 <= 2 Out[34]: False ``` -表2-3列出了所有的二元运算符。 +表 2-3 列出了所有的二元运算符。 要判断两个引用是否指向同一个对象,可以使用`is`方法。`is not`可以判断两个对象是不同的: @@ -814,7 +814,7 @@ In [39]: a is not c Out[39]: True ``` -因为`list`总是创建一个新的Python列表(即复制),我们可以断定c是不同于a的。使用`is`比较与`==`运算符不同,如下: +因为`list`总是创建一个新的 Python 列表(即复制),我们可以断定 c 是不同于 a 的。使用`is`比较与`==`运算符不同,如下: ```python In [40]: a == c @@ -834,7 +834,7 @@ Out[42]: True ### 可变与不可变对象 -Python中的大多数对象,比如列表、字典、NumPy数组,和用户定义的类型(类),都是可变的。意味着这些对象或包含的值可以被修改: +Python 中的大多数对象,比如列表、字典、NumPy 数组,和用户定义的类型(类),都是可变的。意味着这些对象或包含的值可以被修改: ```python In [43]: a_list = ['foo', 2, [4, 5]] @@ -862,13 +862,13 @@ TypeError: 'tuple' object does not support item assignment ### 标量类型 -Python的标准库中有一些内建的类型,用于处理数值数据、字符串、布尔值,和日期时间。这些单值类型被称为标量类型,本书中称其为标量。表2-4列出了主要的标量。日期和时间处理会另外讨论,因为它们是标准库的`datetime`模块提供的。 +Python 的标准库中有一些内建的类型,用于处理数值数据、字符串、布尔值,和日期时间。这些单值类型被称为标量类型,本书中称其为标量。表 2-4 列出了主要的标量。日期和时间处理会另外讨论,因为它们是标准库的`datetime`模块提供的。 ![表2-4 Python的标量](img/7178691-27a30ac3e7d262a1.png) ### 数值类型 -Python的主要数值类型是`int`和`float`。`int`可以存储任意大的数: +Python 的主要数值类型是`int`和`float`。`int`可以存储任意大的数: ```python In [48]: ival = 17239871 @@ -877,7 +877,7 @@ In [49]: ival ** 6 Out[49]: 26254519291092456596965462913230729701102721 ``` -浮点数使用Python的`float`类型。每个数都是双精度(64位)的值。也可以用科学计数法表示: +浮点数使用 Python 的`float`类型。每个数都是双精度(64 位)的值。也可以用科学计数法表示: ```python In [50]: fval = 7.243 @@ -892,7 +892,7 @@ In [52]: 3 / 2 Out[52]: 1.5 ``` -要获得C-风格的整除(去掉小数部分),可以使用底除运算符//: +要获得 C-风格的整除(去掉小数部分),可以使用底除运算符//: ```python In [53]: 3 // 2 @@ -901,7 +901,7 @@ Out[53]: 1 ### 字符串 -许多人是因为Python强大而灵活的字符串处理而使用Python的。你可以用单引号或双引号来写字符串: +许多人是因为 Python 强大而灵活的字符串处理而使用 Python 的。你可以用单引号或双引号来写字符串: ```python a = 'one way of writing a string' @@ -917,14 +917,14 @@ spans multiple lines """ ``` -字符串`c`实际包含四行文本,"""后面和lines后面的换行符。可以用`count`方法计算`c`中的新的行: +字符串`c`实际包含四行文本,"""后面和 lines 后面的换行符。可以用`count`方法计算`c`中的新的行: ```python In [55]: c.count('\n') Out[55]: 3 ``` -Python的字符串是不可变的,不能修改字符串: +Python 的字符串是不可变的,不能修改字符串: ```python In [56]: a = 'this is a string' @@ -949,7 +949,7 @@ In [60]: a Out[60]: 'this is a string' ``` -许多Python对象使用`str`函数可以被转化为字符串: +许多 Python 对象使用`str`函数可以被转化为字符串: ```python In [61]: a = 5.6 @@ -960,7 +960,7 @@ In [63]: print(s) 5.6 ``` -字符串是一个序列的Unicode字符,因此可以像其它序列,比如列表和元组(下一章会详细介绍两者)一样处理: +字符串是一个序列的 Unicode 字符,因此可以像其它序列,比如列表和元组(下一章会详细介绍两者)一样处理: ```python In [64]: s = 'python' @@ -972,9 +972,9 @@ In [66]: s[:3] Out[66]: 'pyt' ``` -语法`s[:3]`被称作切片,适用于许多Python序列。后面会更详细的介绍,本书中用到很多切片。 +语法`s[:3]`被称作切片,适用于许多 Python 序列。后面会更详细的介绍,本书中用到很多切片。 -反斜杠是转义字符,意思是它备用来表示特殊字符,比如换行符\n或Unicode字符。要写一个包含反斜杠的字符串,需要进行转义: +反斜杠是转义字符,意思是它备用来表示特殊字符,比如换行符\n 或 Unicode 字符。要写一个包含反斜杠的字符串,需要进行转义: ```python In [67]: s = '12\\34' @@ -983,7 +983,7 @@ In [68]: print(s) 12\34 ``` -如果字符串中包含许多反斜杠,但没有特殊字符,这样做就很麻烦。幸好,可以在字符串前面加一个r,表明字符就是它自身: +如果字符串中包含许多反斜杠,但没有特殊字符,这样做就很麻烦。幸好,可以在字符串前面加一个 r,表明字符就是它自身: ```python In [69]: s = r'this\has\no\special\characters' @@ -992,7 +992,7 @@ In [70]: s Out[70]: 'this\\has\\no\\special\\characters' ``` -r表示raw。 +r 表示 raw。 将两个字符串合并,会产生一个新的字符串: @@ -1005,7 +1005,7 @@ In [73]: a + b Out[73]: 'this is the first half and this is the second half' ``` -字符串的模板化或格式化,是另一个重要的主题。Python 3拓展了此类的方法,这里只介绍一些。字符串对象有`format`方法,可以替换格式化的参数为字符串,产生一个新的字符串: +字符串的模板化或格式化,是另一个重要的主题。Python 3 拓展了此类的方法,这里只介绍一些。字符串对象有`format`方法,可以替换格式化的参数为字符串,产生一个新的字符串: ```python In [74]: template = '{0:.2f} {1:s} are worth US${2:d}' @@ -1024,13 +1024,13 @@ In [75]: template.format(4.5560, 'Argentine Pesos', 1) Out[75]: '4.56 Argentine Pesos are worth US$1' ``` -字符串格式化是一个很深的主题,有多种方法和大量的选项,可以控制字符串中的值是如何格式化的。推荐参阅Python官方文档。 +字符串格式化是一个很深的主题,有多种方法和大量的选项,可以控制字符串中的值是如何格式化的。推荐参阅 Python 官方文档。 -这里概括介绍字符串处理,第8章的数据分析会详细介绍。 +这里概括介绍字符串处理,第 8 章的数据分析会详细介绍。 -### 字节和Unicode +### 字节和 Unicode -在Python 3及以上版本中,Unicode是一级的字符串类型,这样可以更一致的处理ASCII和Non-ASCII文本。在老的Python版本中,字符串都是字节,不使用Unicode编码。假如知道字符编码,可以将其转化为Unicode。看一个例子: +在 Python 3 及以上版本中,Unicode 是一级的字符串类型,这样可以更一致的处理 ASCII 和 Non-ASCII 文本。在老的 Python 版本中,字符串都是字节,不使用 Unicode 编码。假如知道字符编码,可以将其转化为 Unicode。看一个例子: ```python In [76]: val = "español" @@ -1039,7 +1039,7 @@ In [77]: val Out[77]: 'español' ``` -可以用`encode`将这个Unicode字符串编码为UTF-8: +可以用`encode`将这个 Unicode 字符串编码为 UTF-8: ```python In [78]: val_utf8 = val.encode('utf-8') @@ -1051,14 +1051,14 @@ In [80]: type(val_utf8) Out[80]: bytes ``` -如果你知道一个字节对象的Unicode编码,用`decode`方法可以解码: +如果你知道一个字节对象的 Unicode 编码,用`decode`方法可以解码: ```python In [81]: val_utf8.decode('utf-8') Out[81]: 'español' ``` -虽然UTF-8编码已经变成主流,但因为历史的原因,你仍然可能碰到其它编码的数据: +虽然 UTF-8 编码已经变成主流,但因为历史的原因,你仍然可能碰到其它编码的数据: ```python In [82]: val.encode('latin1') @@ -1071,9 +1071,9 @@ In [84]: val.encode('utf-16le') Out[84]: b'e\x00s\x00p\x00a\x00\xf1\x00o\x00l\x00' ``` -工作中碰到的文件很多都是字节对象,盲目地将所有数据编码为Unicode是不可取的。 +工作中碰到的文件很多都是字节对象,盲目地将所有数据编码为 Unicode 是不可取的。 -虽然用的不多,你可以在字节文本的前面加上一个b: +虽然用的不多,你可以在字节文本的前面加上一个 b: ```python In [85]: bytes_val = b'this is bytes' @@ -1089,7 +1089,7 @@ Out[88]: 'this is bytes' ### 布尔值 -Python中的布尔值有两个,True和False。比较和其它条件表达式可以用True和False判断。布尔值可以与and和or结合使用: +Python 中的布尔值有两个,True 和 False。比较和其它条件表达式可以用 True 和 False 判断。布尔值可以与 and 和 or 结合使用: ```python In [89]: True and True @@ -1101,7 +1101,7 @@ Out[90]: True ### 类型转换 -str、bool、int和float也是函数,可以用来转换类型: +str、bool、int 和 float 也是函数,可以用来转换类型: ```python In [91]: s = '3.14159' @@ -1123,7 +1123,7 @@ Out[96]: False ### None -None是Python的空值类型。如果一个函数没有明确的返回值,就会默认返回None: +None 是 Python 的空值类型。如果一个函数没有明确的返回值,就会默认返回 None: ```python In [97]: a = None @@ -1137,7 +1137,7 @@ In [100]: b is not None Out[100]: True ``` -None也常常作为函数的默认参数: +None 也常常作为函数的默认参数: ```python def add_and_maybe_multiply(a, b, c=None): @@ -1149,7 +1149,7 @@ def add_and_maybe_multiply(a, b, c=None): return result ``` -另外,None不仅是一个保留字,还是唯一的NoneType的实例: +另外,None 不仅是一个保留字,还是唯一的 NoneType 的实例: ```python In [101]: type(None) @@ -1158,7 +1158,7 @@ Out[101]: NoneType ### 日期和时间 -Python内建的`datetime`模块提供了`datetime`、`date`和`time`类型。`datetime`类型结合了`date`和`time`,是最常使用的: +Python 内建的`datetime`模块提供了`datetime`、`date`和`time`类型。`datetime`类型结合了`date`和`time`,是最常使用的: ```python In [102]: from datetime import datetime, date, time @@ -1182,7 +1182,7 @@ In [107]: dt.time() Out[107]: datetime.time(20, 30, 21) ``` -`strftime`方法可以将datetime格式化为字符串: +`strftime`方法可以将 datetime 格式化为字符串: ```python In [108]: dt.strftime('%m/%d/%Y %H:%M') @@ -1196,11 +1196,11 @@ In [109]: datetime.strptime('20091031', '%Y%m%d') Out[109]: datetime.datetime(2009, 10, 31, 0, 0) ``` -表2-5列出了所有的格式化命令。 +表 2-5 列出了所有的格式化命令。 ![表2-5 Datetime格式化指令(与ISO C89兼容)](img/7178691-100f9a20c1536553.png) -当你聚类或对时间序列进行分组,替换datetimes的time字段有时会很有用。例如,用0替换分和秒: +当你聚类或对时间序列进行分组,替换 datetimes 的 time 字段有时会很有用。例如,用 0 替换分和秒: ```python In [110]: dt.replace(minute=0, second=0) @@ -1209,7 +1209,7 @@ Out[110]: datetime.datetime(2011, 10, 29, 20, 0) 因为`datetime.datetime`是不可变类型,上面的方法会产生新的对象。 -两个datetime对象的差会产生一个`datetime.timedelta`类型: +两个 datetime 对象的差会产生一个`datetime.timedelta`类型: ```python In [111]: dt2 = datetime(2011, 11, 15, 22, 30) @@ -1223,7 +1223,7 @@ In [114]: type(delta) Out[114]: datetime.timedelta ``` -结果`timedelta(17, 7179)`指明了`timedelta`将17天、7179秒的编码方式。 +结果`timedelta(17, 7179)`指明了`timedelta`将 17 天、7179 秒的编码方式。 将`timedelta`添加到`datetime`,会产生一个新的偏移`datetime`: @@ -1237,18 +1237,18 @@ Out[116]: datetime.datetime(2011, 11, 15, 22, 30) ### 控制流 -Python有若干内建的关键字进行条件逻辑、循环和其它控制流操作。 +Python 有若干内建的关键字进行条件逻辑、循环和其它控制流操作。 -### if、elif和else +### if、elif 和 else -if是最广为人知的控制流语句。它检查一个条件,如果为True,就执行后面的语句: +if 是最广为人知的控制流语句。它检查一个条件,如果为 True,就执行后面的语句: ```python if x < 0: print('It's negative') ``` -`if`后面可以跟一个或多个`elif`,所有条件都是False时,还可以添加一个`else`: +`if`后面可以跟一个或多个`elif`,所有条件都是 False 时,还可以添加一个`else`: ```python if x < 0: @@ -1261,7 +1261,7 @@ else: print('Positive and larger than or equal to 5') ``` -如果某个条件为True,后面的`elif`就不会被执行。当使用and和or时,复合条件语句是从左到右执行: +如果某个条件为 True,后面的`elif`就不会被执行。当使用 and 和 or 时,复合条件语句是从左到右执行: ```python In [117]: a = 5; b = 7 @@ -1273,7 +1273,7 @@ In [119]: if a < b or c > d: Made it ``` -在这个例子中,`c > d`不会被执行,因为第一个比较是True: +在这个例子中,`c > d`不会被执行,因为第一个比较是 True: 也可以把比较式串在一起: @@ -1282,16 +1282,16 @@ In [120]: 4 > 3 > 2 > 1 Out[120]: True ``` -### for循环 +### for 循环 -for循环是在一个集合(列表或元组)中进行迭代,或者就是一个迭代器。for循环的标准语法是: +for 循环是在一个集合(列表或元组)中进行迭代,或者就是一个迭代器。for 循环的标准语法是: ```python for value in collection: # do something with value ``` -你可以用continue使for循环提前,跳过剩下的部分。看下面这个例子,将一个列表中的整数相加,跳过None: +你可以用 continue 使 for 循环提前,跳过剩下的部分。看下面这个例子,将一个列表中的整数相加,跳过 None: ```python sequence = [1, 2, None, 4, None, 5] @@ -1302,7 +1302,7 @@ for value in sequence: total += value ``` -可以用`break`跳出for循环。下面的代码将各元素相加,直到遇到5: +可以用`break`跳出 for 循环。下面的代码将各元素相加,直到遇到 5: ```python sequence = [1, 2, 0, 4, 6, 5, 2, 1] @@ -1313,7 +1313,7 @@ for value in sequence: total_until_5 += value ``` -break只中断for循环的最内层,其余的for循环仍会运行: +break 只中断 for 循环的最内层,其余的 for 循环仍会运行: ```python In [121]: for i in range(4): @@ -1334,16 +1334,16 @@ In [121]: for i in range(4): (3, 3) ``` -如果集合或迭代器中的元素序列(元组或列表),可以用for循环将其方便地拆分成变量: +如果集合或迭代器中的元素序列(元组或列表),可以用 for 循环将其方便地拆分成变量: ```python for a, b, c in iterator: # do something ``` -### While循环 +### While 循环 -while循环指定了条件和代码,当条件为False或用break退出循环,代码才会退出: +while 循环指定了条件和代码,当条件为 False 或用 break 退出循环,代码才会退出: ```python x = 256 @@ -1357,7 +1357,7 @@ while x > 0: ### pass -pass是Python中的非操作语句。代码块不需要任何动作时可以使用(作为未执行代码的占位符);因为Python需要使用空白字符划定代码块,所以需要pass: +pass 是 Python 中的非操作语句。代码块不需要任何动作时可以使用(作为未执行代码的占位符);因为 Python 需要使用空白字符划定代码块,所以需要 pass: ```python if x < 0: @@ -1371,7 +1371,7 @@ else: ### range -range函数返回一个迭代器,它产生一个均匀分布的整数序列: +range 函数返回一个迭代器,它产生一个均匀分布的整数序列: ```python In [122]: range(10) @@ -1381,7 +1381,7 @@ In [123]: list(range(10)) Out[123]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -range的三个参数是(起点,终点,步进): +range 的三个参数是(起点,终点,步进): ```python In [124]: list(range(0, 20, 2)) @@ -1391,7 +1391,7 @@ In [125]: list(range(5, 0, -1)) Out[125]: [5, 4, 3, 2, 1] ``` -可以看到,range产生的整数不包括终点。range的常见用法是用序号迭代序列: +可以看到,range 产生的整数不包括终点。range 的常见用法是用序号迭代序列: ```python seq = [1, 2, 3, 4] @@ -1399,7 +1399,7 @@ for i in range(len(seq)): val = seq[i] ``` -可以使用list来存储range在其他数据结构中生成的所有整数,默认的迭代器形式通常是你想要的。下面的代码对0到99999中3或5的倍数求和: +可以使用 list 来存储 range 在其他数据结构中生成的所有整数,默认的迭代器形式通常是你想要的。下面的代码对 0 到 99999 中 3 或 5 的倍数求和: ```python sum = 0 @@ -1409,17 +1409,17 @@ for i in range(100000): sum += i ``` -虽然range可以产生任意大的数,但任意时刻耗用的内存却很小。 +虽然 range 可以产生任意大的数,但任意时刻耗用的内存却很小。 ### 三元表达式 -Python中的三元表达式可以将if-else语句放到一行里。语法如下: +Python 中的三元表达式可以将 if-else 语句放到一行里。语法如下: ```python value = true-expr if condition else false-expr ``` -`true-expr`或`false-expr`可以是任何Python代码。它和下面的代码效果相同: +`true-expr`或`false-expr`可以是任何 Python 代码。它和下面的代码效果相同: ```python if condition: @@ -1437,7 +1437,7 @@ In [127]: 'Non-negative' if x >= 0 else 'Negative' Out[127]: 'Non-negative' ``` -和if-else一样,只有一个表达式会被执行。因此,三元表达式中的if和else可以包含大量的计算,但只有True的分支会被执行。因此,三元表达式中的if和else可以包含大量的计算,但只有True的分支会被执行。 +和 if-else 一样,只有一个表达式会被执行。因此,三元表达式中的 if 和 else 可以包含大量的计算,但只有 True 的分支会被执行。因此,三元表达式中的 if 和 else 可以包含大量的计算,但只有 True 的分支会被执行。 虽然使用三元表达式可以压缩代码,但会降低代码可读性。 diff --git a/docs/3.md b/docs/3.md index 57452ac..19f6e29 100644 --- a/docs/3.md +++ b/docs/3.md @@ -1,14 +1,14 @@ # 第 3 章 Python 的数据结构、函数和文件 -本章讨论Python的内置功能,这些功能本书会用到很多。虽然扩展库,比如pandas和Numpy,使处理大数据集很方便,但它们是和Python的内置数据处理工具一同使用的。 +本章讨论 Python 的内置功能,这些功能本书会用到很多。虽然扩展库,比如 pandas 和 Numpy,使处理大数据集很方便,但它们是和 Python 的内置数据处理工具一同使用的。 -我们会从Python最基础的数据结构开始:元组、列表、字典和集合。然后会讨论创建你自己的、可重复使用的Python函数。最后,会学习Python的文件对象,以及如何与本地硬盘交互。 +我们会从 Python 最基础的数据结构开始:元组、列表、字典和集合。然后会讨论创建你自己的、可重复使用的 Python 函数。最后,会学习 Python 的文件对象,以及如何与本地硬盘交互。 # 3.1 数据结构和序列 -Python的数据结构简单而强大。通晓它们才能成为熟练的Python程序员。 +Python 的数据结构简单而强大。通晓它们才能成为熟练的 Python 程序员。 ## 元组 -元组是一个固定长度,不可改变的Python序列对象。创建元组的最简单方式,是用逗号分隔一列值: +元组是一个固定长度,不可改变的 Python 序列对象。创建元组的最简单方式,是用逗号分隔一列值: ```python In [1]: tup = 4, 5, 6 @@ -38,7 +38,7 @@ In [7]: tup Out[7]: ('s', 't', 'r', 'i', 'n', 'g') ``` -可以用方括号访问元组中的元素。和C、C++、JAVA等语言一样,序列是从0开始的: +可以用方括号访问元组中的元素。和 C、C++、JAVA 等语言一样,序列是从 0 开始的: ```python In [8]: tup[0] @@ -84,7 +84,7 @@ Out[14]: ('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar') 对象本身并没有被复制,只是引用了它。 ## 拆分元组 -如果你想将元组赋值给类似元组的变量,Python会试图拆分等号右边的值: +如果你想将元组赋值给类似元组的变量,Python 会试图拆分等号右边的值: ```python In [15]: tup = (4, 5, 6) @@ -114,7 +114,7 @@ a = b b = tmp ``` -但是在Python中,替换可以这样做: +但是在 Python 中,替换可以这样做: ```python In [21]: a, b = 1, 2 @@ -148,7 +148,7 @@ a=7, b=8, c=9 另一个常见用法是从函数返回多个值。后面会详解。 -Python最近新增了更多高级的元组拆分功能,允许从元组的开头“摘取”几个元素。它使用了特殊的语法``*rest``,这也用在函数签名中以抓取任意长度列表的位置参数: +Python 最近新增了更多高级的元组拆分功能,允许从元组的开头“摘取”几个元素。它使用了特殊的语法``*rest``,这也用在函数签名中以抓取任意长度列表的位置参数: ```python In [29]: values = 1, 2, 3, 4, 5 @@ -162,13 +162,13 @@ In [32]: rest Out[32]: [3, 4, 5] ``` -``rest``的部分是想要舍弃的部分,rest的名字不重要。作为惯用写法,许多Python程序员会将不需要的变量使用下划线: +``rest``的部分是想要舍弃的部分,rest 的名字不重要。作为惯用写法,许多 Python 程序员会将不需要的变量使用下划线: ```python In [33]: a, b, *_ = values ``` -## tuple方法 +## tuple 方法 因为元组的大小和内容不能修改,它的实例方法都很轻量。其中一个很有用的就是``count``(也适用于列表),它可以统计某个值得出现频率: ```python @@ -229,11 +229,11 @@ In [48]: b_list Out[48]: ['foo', 'red', 'peekaboo', 'baz', 'dwarf'] ``` -插入的序号必须在0和列表长度之间。 +插入的序号必须在 0 和列表长度之间。 >警告:与``append``相比,``insert``耗费的计算量大,因为对后续元素的引用必须在内部迁移,以便为新元素提供空间。如果要在序列的头部和尾部插入元素,你可能需要使用``collections.deque``,一个双尾部队列。 -insert的逆运算是pop,它移除并返回指定位置的元素: +insert 的逆运算是 pop,它移除并返回指定位置的元素: ```python In [49]: b_list.pop(2) @@ -257,7 +257,7 @@ In [54]: b_list Out[54]: ['red', 'baz', 'dwarf', 'foo'] ``` -如果不考虑性能,使用``append``和``remove``,可以把Python的列表当做完美的“多重集”数据结构。 +如果不考虑性能,使用``append``和``remove``,可以把 Python 的列表当做完美的“多重集”数据结构。 用``in``可以检查列表是否包含某个值: @@ -266,14 +266,14 @@ In [55]: 'dwarf' in b_list Out[55]: True ``` -否定``in``可以再加一个not: +否定``in``可以再加一个 not: ```python In [56]: 'dwarf' not in b_list Out[56]: False ``` -在列表中检查是否存在某个值远比字典和集合速度慢,因为Python是线性搜索列表中的值,但在字典和集合中,在同样的时间内还可以检查其它项(基于哈希表)。 +在列表中检查是否存在某个值远比字典和集合速度慢,因为 Python 是线性搜索列表中的值,但在字典和集合中,在同样的时间内还可以检查其它项(基于哈希表)。 ## 串联和组合列表 与元组类似,可以用加号将两个列表串联起来: @@ -322,7 +322,7 @@ In [63]: a Out[63]: [1, 2, 3, 5, 7] ``` -``sort``有一些选项,有时会很好用。其中之一是二级排序key,可以用这个key进行排序。例如,我们可以按长度对字符串进行排序: +``sort``有一些选项,有时会很好用。其中之一是二级排序 key,可以用这个 key 进行排序。例如,我们可以按长度对字符串进行排序: ```python In [64]: b = ['saw', 'small', 'He', 'foxes', 'six'] @@ -397,9 +397,9 @@ In [80]: seq[-6:-2] Out[80]: [6, 3, 5, 6] ``` -需要一段时间来熟悉使用切片,尤其是当你之前学的是R或MATLAB。图3-1展示了正整数和负整数的切片。在图中,指数标示在边缘以表明切片是在哪里开始哪里结束的。 +需要一段时间来熟悉使用切片,尤其是当你之前学的是 R 或 MATLAB。图 3-1 展示了正整数和负整数的切片。在图中,指数标示在边缘以表明切片是在哪里开始哪里结束的。 -![图3-1 Python切片演示](img/7178691-522e2b688b755ff3.png) +![图 3-1 Python 切片演示](img/7178691-522e2b688b755ff3.png) 在第二个冒号后面使用``step``,可以隔一个取一个元素: @@ -416,9 +416,9 @@ Out[82]: [1, 0, 6, 5, 3, 6, 3, 2, 7] ``` ## 序列函数 -Python有一些有用的序列函数。 +Python 有一些有用的序列函数。 -## enumerate函数 +## enumerate 函数 迭代一个序列时,你可能想跟踪当前项的序号。手动的方法可能是下面这样: ```python @@ -428,7 +428,7 @@ for value in collection: i += 1 ``` -因为这么做很常见,Python内建了一个``enumerate``函数,可以返回``(i, value)``元组序列: +因为这么做很常见,Python 内建了一个``enumerate``函数,可以返回``(i, value)``元组序列: ```python for i, value in enumerate(collection): @@ -449,7 +449,7 @@ In [86]: mapping Out[86]: {'bar': 1, 'baz': 2, 'foo': 0} ``` -## sorted函数 +## sorted 函数 ``sorted``函数可以从任意序列的元素返回一个新的排好序的列表: ```python @@ -462,7 +462,7 @@ Out[88]: [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's'] ``sorted``函数可以接受和``sort``相同的参数。 -## zip函数 +## zip 函数 ``zip``可以将多个列表、元组或其它序列成对组合成一个元组列表: ```python @@ -511,7 +511,7 @@ In [99]: last_names Out[99]: ('Ryan', 'Clemens', 'Curt') ``` -## reversed函数 +## reversed 函数 ``reversed``可以从后向前迭代一个序列: ```python @@ -519,10 +519,10 @@ In [100]: list(reversed(range(10))) Out[100]: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] ``` -要记住``reversed``是一个生成器(后面详细介绍),只有实体化(即列表或for循环)之后才能创建翻转的序列。 +要记住``reversed``是一个生成器(后面详细介绍),只有实体化(即列表或 for 循环)之后才能创建翻转的序列。 ## 字典 -字典可能是Python最为重要的数据结构。它更为常见的名字是哈希映射或关联数组。它是键值对的大小可变集合,键和值都是Python对象。创建字典的方法之一是使用尖括号,用冒号分隔键和值: +字典可能是 Python 最为重要的数据结构。它更为常见的名字是哈希映射或关联数组。它是键值对的大小可变集合,键和值都是 Python 对象。创建字典的方法之一是使用尖括号,用冒号分隔键和值: ```python In [101]: empty_dict = {} @@ -620,7 +620,7 @@ for key, value in zip(key_list, value_list): mapping[key] = value ``` -因为字典本质上是2元元组的集合,dict可以接受2元元组的列表: +因为字典本质上是 2 元元组的集合,dict 可以接受 2 元元组的列表: ```python In [121]: mapping = dict(zip(range(5), reversed(range(5)))) @@ -640,12 +640,12 @@ else: value = default_value ``` -因此,dict的方法get和pop可以取默认值进行返回,上面的if-else语句可以简写成下面: +因此,dict 的方法 get 和 pop 可以取默认值进行返回,上面的 if-else 语句可以简写成下面: ```python value = some_dict.get(key, default_value) ``` -get默认会返回None,如果不存在键,pop会抛出一个例外。关于设定值,常见的情况是在字典的值是属于其它集合,如列表。例如,你可以通过首字母,将一个列表中的单词分类: +get 默认会返回 None,如果不存在键,pop 会抛出一个例外。关于设定值,常见的情况是在字典的值是属于其它集合,如列表。例如,你可以通过首字母,将一个列表中的单词分类: ```python In [123]: words = ['apple', 'bat', 'bar', 'atom', 'book'] @@ -663,7 +663,7 @@ In [126]: by_letter Out[126]: {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']} ``` -``setdefault``方法就正是干这个的。前面的for循环可以改写为: +``setdefault``方法就正是干这个的。前面的 for 循环可以改写为: ```python for word in words: letter = word[0] @@ -679,7 +679,7 @@ for word in words: ``` ## 有效的键类型 -字典的值可以是任意Python对象,而键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的)。这被称为“可哈希性”。可以用``hash``函数检测一个对象是否是可哈希的(可被用作字典的键): +字典的值可以是任意 Python 对象,而键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的)。这被称为“可哈希性”。可以用``hash``函数检测一个对象是否是可哈希的(可被用作字典的键): ```python In [127]: hash('string') @@ -707,7 +707,7 @@ Out[132]: {(1, 2, 3): 5} ``` ## 集合 -集合是无序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以用两种方式创建集合:通过set函数或使用尖括号set语句: +集合是无序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以用两种方式创建集合:通过 set 函数或使用尖括号 set 语句: ```python In [133]: set([2, 2, 2, 1, 3, 3]) @@ -745,9 +745,9 @@ In [140]: a & b Out[140]: {3, 4, 5} ``` -表3-1列出了常用的集合方法。 +表 3-1 列出了常用的集合方法。 -![表3-1 Python的集合操作](img/7178691-980efe5d98ecc4d6.png) +![表 3-1 Python 的集合操作](img/7178691-980efe5d98ecc4d6.png) 所有逻辑集合操作都有另外的原地实现方法,可以直接用结果替代集合的内容。对于大的集合,这么做效率更高: @@ -798,13 +798,13 @@ Out[153]: True ``` ## 列表、集合和字典推导式 -列表推导式是Python最受喜爱的特性之一。它允许用户方便的从一个集合过滤元素,形成列表,在传递参数的过程中还可以修改元素。形式如下: +列表推导式是 Python 最受喜爱的特性之一。它允许用户方便的从一个集合过滤元素,形成列表,在传递参数的过程中还可以修改元素。形式如下: ```python [expr for val in collection if condition] ``` -它等同于下面的for循环; +它等同于下面的 for 循环; ```python result = [] @@ -813,7 +813,7 @@ for val in collection: result.append(expr) ``` -filter条件可以被忽略,只留下表达式就行。例如,给定一个字符串列表,我们可以过滤出长度在2及以下的字符串,并将其转换成大写: +filter 条件可以被忽略,只留下表达式就行。例如,给定一个字符串列表,我们可以过滤出长度在 2 及以下的字符串,并将其转换成大写: ```python In [154]: strings = ['a', 'as', 'bat', 'car', 'dove', 'python'] @@ -866,7 +866,7 @@ In [161]: all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'], .....: ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']] ``` -你可能是从一些文件得到的这些名字,然后想按照语言进行分类。现在假设我们想用一个列表包含所有的名字,这些名字中包含两个或更多的e。可以用for循环来做: +你可能是从一些文件得到的这些名字,然后想按照语言进行分类。现在假设我们想用一个列表包含所有的名字,这些名字中包含两个或更多的 e。可以用 for 循环来做: ```python names_of_interest = [] @@ -885,7 +885,7 @@ In [163]: result Out[163]: ['Steven'] ``` -嵌套列表推导式看起来有些复杂。列表推导式的for部分是根据嵌套的顺序,过滤条件还是放在最后。下面是另一个例子,我们将一个整数元组的列表扁平化成了一个整数列表: +嵌套列表推导式看起来有些复杂。列表推导式的 for 部分是根据嵌套的顺序,过滤条件还是放在最后。下面是另一个例子,我们将一个整数元组的列表扁平化成了一个整数列表: ```python In [164]: some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)] @@ -896,7 +896,7 @@ In [166]: flattened Out[166]: [1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -记住,for表达式的顺序是与嵌套for循环的顺序一样(而不是列表推导式的顺序): +记住,for 表达式的顺序是与嵌套 for 循环的顺序一样(而不是列表推导式的顺序): ```python flattened = [] @@ -916,7 +916,7 @@ Out[167]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 这段代码产生了一个列表的列表,而不是扁平化的只包含元素的列表。 # 3.2 函数 -函数是Python中最主要也是最重要的代码组织和复用手段。作为最重要的原则,如果你要重复使用相同或非常类似的代码,就需要写一个函数。通过给函数起一个名字,还可以提高代码的可读性。 +函数是 Python 中最主要也是最重要的代码组织和复用手段。作为最重要的原则,如果你要重复使用相同或非常类似的代码,就需要写一个函数。通过给函数起一个名字,还可以提高代码的可读性。 函数使用``def``关键字声明,用``return``关键字返回值: @@ -928,9 +928,9 @@ def my_function(x, y, z=1.5): return z / (x + y) ``` -同时拥有多条return语句也是可以的。如果到达函数末尾时没有遇到任何一条return语句,则返回None。 +同时拥有多条 return 语句也是可以的。如果到达函数末尾时没有遇到任何一条 return 语句,则返回 None。 -函数可以有一些位置参数(positional)和一些关键字参数(keyword)。关键字参数通常用于指定默认值或可选参数。在上面的函数中,x和y是位置参数,而z则是关键字参数。也就是说,该函数可以下面这两种方式进行调用: +函数可以有一些位置参数(positional)和一些关键字参数(keyword)。关键字参数通常用于指定默认值或可选参数。在上面的函数中,x 和 y 是位置参数,而 z 则是关键字参数。也就是说,该函数可以下面这两种方式进行调用: ```python my_function(5, 6, z=0.7) @@ -948,7 +948,7 @@ my_function(10, 20) >这种写法可以提高可读性。 ## 命名空间、作用域,和局部函数 -函数可以访问两种不同作用域中的变量:全局(global)和局部(local)。Python有一种更科学的用于描述变量作用域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。局部命名空间是在函数被调用时创建的,函数参数会立即填入该命名空间。在函数执行完毕之后,局部命名空间就会被销毁(会有一些例外的情况,具体请参见后面介绍闭包的那一节)。看看下面这个函数: +函数可以访问两种不同作用域中的变量:全局(global)和局部(local)。Python 有一种更科学的用于描述变量作用域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。局部命名空间是在函数被调用时创建的,函数参数会立即填入该命名空间。在函数执行完毕之后,局部命名空间就会被销毁(会有一些例外的情况,具体请参见后面介绍闭包的那一节)。看看下面这个函数: ```python def func(): @@ -957,7 +957,7 @@ def func(): a.append(i) ``` -调用func()之后,首先会创建出空列表a,然后添加5个元素,最后a会在该函数退出的时候被销毁。假如我们像下面这样定义a: +调用 func()之后,首先会创建出空列表 a,然后添加 5 个元素,最后 a 会在该函数退出的时候被销毁。假如我们像下面这样定义 a: ```python a = [] @@ -966,7 +966,7 @@ def func(): a.append(i) ``` -虽然可以在函数中对全局变量进行赋值操作,但是那些变量必须用global关键字声明成全局的才行: +虽然可以在函数中对全局变量进行赋值操作,但是那些变量必须用 global 关键字声明成全局的才行: ```python In [168]: a = None @@ -981,10 +981,10 @@ In [170]: print(a) [] ``` ->注意:我常常建议人们不要频繁使用global关键字。因为全局变量一般是用于存放系统的某些状态的。如果你发现自己用了很多,那可能就说明得要来点儿面向对象编程了(即使用类)。 +>注意:我常常建议人们不要频繁使用 global 关键字。因为全局变量一般是用于存放系统的某些状态的。如果你发现自己用了很多,那可能就说明得要来点儿面向对象编程了(即使用类)。 ## 返回多个值 -在我第一次用Python编程时(之前已经习惯了Java和C++),最喜欢的一个功能是:函数可以返回多个值。下面是一个简单的例子: +在我第一次用 Python 编程时(之前已经习惯了 Java 和 C++),最喜欢的一个功能是:函数可以返回多个值。下面是一个简单的例子: ```python def f(): @@ -1002,7 +1002,7 @@ a, b, c = f() return_value = f() ``` -这里的return_value将会是一个含有3个返回值的三元元组。此外,还有一种非常具有吸引力的多值返回方式——返回字典: +这里的 return_value 将会是一个含有 3 个返回值的三元元组。此外,还有一种非常具有吸引力的多值返回方式——返回字典: ```python def f(): @@ -1015,7 +1015,7 @@ def f(): 取决于工作内容,第二种方法可能很有用。 ## 函数也是对象 -由于Python函数都是对象,因此,在其他语言中较难表达的一些设计思想在Python中就要简单很多了。假设我们有下面这样一个字符串数组,希望对其进行一些数据清理工作并执行一堆转换: +由于 Python 函数都是对象,因此,在其他语言中较难表达的一些设计思想在 Python 中就要简单很多了。假设我们有下面这样一个字符串数组,希望对其进行一些数据清理工作并执行一堆转换: ```python In [171]: states = [' Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda', @@ -1082,9 +1082,9 @@ Out[175]: 'West Virginia'] ``` -这种多函数模式使你能在很高的层次上轻松修改字符串的转换方式。此时的clean_strings也更具可复用性! +这种多函数模式使你能在很高的层次上轻松修改字符串的转换方式。此时的 clean_strings 也更具可复用性! -还可以将函数用作其他函数的参数,比如内置的map函数,它用于在一组数据上应用一个函数: +还可以将函数用作其他函数的参数,比如内置的 map 函数,它用于在一组数据上应用一个函数: ```python In [176]: for x in map(remove_punctuation, states): @@ -1099,7 +1099,7 @@ West virginia ``` ## 匿名(lambda)函数 -Python支持一种被称为匿名的、或lambda函数。它仅由单条语句组成,该语句的结果就是返回值。它是通过lambda关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。 +Python 支持一种被称为匿名的、或 lambda 函数。它仅由单条语句组成,该语句的结果就是返回值。它是通过 lambda 关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。 ```python def short_function(x): @@ -1108,7 +1108,7 @@ def short_function(x): equiv_anon = lambda x: x * 2 ``` -本书其余部分一般将其称为lambda函数。它们在数据分析工作中非常方便,因为你会发现很多数据转换函数都以函数作为参数的。直接传入lambda函数比编写完整函数声明要少输入很多字(也更清晰),甚至比将lambda函数赋值给一个变量还要少输入很多字。看看下面这个简单得有些傻的例子: +本书其余部分一般将其称为 lambda 函数。它们在数据分析工作中非常方便,因为你会发现很多数据转换函数都以函数作为参数的。直接传入 lambda 函数比编写完整函数声明要少输入很多字(也更清晰),甚至比将 lambda 函数赋值给一个变量还要少输入很多字。看看下面这个简单得有些傻的例子: ```python def apply_to_list(some_list, f): @@ -1118,7 +1118,7 @@ ints = [4, 0, 1, 5, 6] apply_to_list(ints, lambda x: x * 2) ``` -虽然你可以直接编写[x *2for x in ints],但是这里我们可以非常轻松地传入一个自定义运算给apply_to_list函数。 +虽然你可以直接编写[x *2for x in ints],但是这里我们可以非常轻松地传入一个自定义运算给 apply_to_list 函数。 再来看另外一个例子。假设有一组字符串,你想要根据各字符串不同字母的数量对其进行排序: @@ -1126,7 +1126,7 @@ apply_to_list(ints, lambda x: x * 2) In [177]: strings = ['foo', 'card', 'bar', 'aaaa', 'abab'] ``` -这里,我们可以传入一个lambda函数到列表的sort方法: +这里,我们可以传入一个 lambda 函数到列表的 sort 方法: ```python In [178]: strings.sort(key=lambda x: len(set(list(x)))) @@ -1135,7 +1135,7 @@ In [179]: strings Out[179]: ['aaaa', 'foo', 'abab', 'bar', 'card'] ``` ->笔记:lambda函数之所以会被称为匿名函数,与def声明的函数不同,原因之一就是这种函数对象本身是没有提供名称__name__属性。 +>笔记:lambda 函数之所以会被称为匿名函数,与 def 声明的函数不同,原因之一就是这种函数对象本身是没有提供名称 __name__ 属性。 ## 柯里化:部分参数应用 柯里化(currying)是一个有趣的计算机科学术语,它指的是通过“部分参数应用”(partial argument application)从现有函数派生出新函数的技术。例如,假设我们有一个执行两数相加的简单函数: @@ -1145,20 +1145,20 @@ def add_numbers(x, y): return x + y ``` -通过这个函数,我们可以派生出一个新的只有一个参数的函数——add_five,它用于对其参数加5: +通过这个函数,我们可以派生出一个新的只有一个参数的函数——add_five,它用于对其参数加 5: ```python add_five = lambda y: add_numbers(5, y) ``` -add_numbers的第二个参数称为“柯里化的”(curried)。这里没什么特别花哨的东西,因为我们其实就只是定义了一个可以调用现有函数的新函数而已。内置的functools模块可以用partial函数将此过程简化: +add_numbers 的第二个参数称为“柯里化的”(curried)。这里没什么特别花哨的东西,因为我们其实就只是定义了一个可以调用现有函数的新函数而已。内置的 functools 模块可以用 partial 函数将此过程简化: ```python from functools import partial add_five = partial(add_numbers, 5) ``` ## 生成器 -能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是Python的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。比如说,对字典进行迭代可以得到其所有的键: +能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是 Python 的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。比如说,对字典进行迭代可以得到其所有的键: ```python In [180]: some_dict = {'a': 1, 'b': 2, 'c': 3} @@ -1170,7 +1170,7 @@ b c ``` -当你编写for key in some_dict时,Python解释器首先会尝试从some_dict创建一个迭代器: +当你编写 for key in some_dict 时,Python 解释器首先会尝试从 some_dict 创建一个迭代器: ```python In [182]: dict_iterator = iter(some_dict) @@ -1179,14 +1179,14 @@ In [183]: dict_iterator Out[183]: ``` -迭代器是一种特殊对象,它可以在诸如for循环之类的上下文中向Python解释器输送对象。大部分能接受列表之类的对象的方法也都可以接受任何可迭代对象。比如min、max、sum等内置方法以及list、tuple等类型构造器: +迭代器是一种特殊对象,它可以在诸如 for 循环之类的上下文中向 Python 解释器输送对象。大部分能接受列表之类的对象的方法也都可以接受任何可迭代对象。比如 min、max、sum 等内置方法以及 list、tuple 等类型构造器: ```python In [184]: list(dict_iterator) Out[184]: ['a', 'b', 'c'] ``` -生成器(generator)是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的return替换为yeild即可: +生成器(generator)是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的 return 替换为 yeild 即可: ```python def squares(n=10): @@ -1240,8 +1240,8 @@ In [192]: dict((i, i **2) for i in range(5)) Out[192]: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} ``` -## itertools模块 -标准库itertools模块中有一组用于许多常见数据算法的生成器。例如,groupby可以接受任何序列和一个函数。它根据函数的返回值对序列中的连续元素进行分组。下面是一个例子: +## itertools 模块 +标准库 itertools 模块中有一组用于许多常见数据算法的生成器。例如,groupby 可以接受任何序列和一个函数。它根据函数的返回值对序列中的连续元素进行分组。下面是一个例子: ```python In [193]: import itertools @@ -1258,12 +1258,12 @@ A ['Albert'] S ['Steven'] ``` -表3-2中列出了一些我经常用到的itertools函数。建议参阅Python官方文档,进一步学习。 +表 3-2 中列出了一些我经常用到的 itertools 函数。建议参阅 Python 官方文档,进一步学习。 -![表3-2 一些有用的itertools函数](img/7178691-111823d8767a104d.png) +![表 3-2 一些有用的 itertools 函数](img/7178691-111823d8767a104d.png) ## 错误和异常处理 -优雅地处理Python的错误和异常是构建健壮程序的重要部分。在数据分析中,许多函数函数只用于部分输入。例如,Python的float函数可以将字符串转换成浮点数,但输入有误时,有``ValueError``错误: +优雅地处理 Python 的错误和异常是构建健壮程序的重要部分。在数据分析中,许多函数函数只用于部分输入。例如,Python 的 float 函数可以将字符串转换成浮点数,但输入有误时,有``ValueError``错误: ```python In [197]: float('1.2345') @@ -1277,7 +1277,7 @@ ValueError Traceback (most recent call last) ValueError: could not convert string to float: 'something' ``` -假如想优雅地处理float的错误,让它返回输入值。我们可以写一个函数,在try/except中调用float: +假如想优雅地处理 float 的错误,让它返回输入值。我们可以写一个函数,在 try/except 中调用 float: ```python def attempt_float(x): @@ -1287,7 +1287,7 @@ def attempt_float(x): return x ``` -当float(x)抛出异常时,才会执行except的部分: +当 float(x)抛出异常时,才会执行 except 的部分: ```python In [200]: attempt_float('1.2345') @@ -1297,7 +1297,7 @@ In [201]: attempt_float('something') Out[201]: 'something' ``` -你可能注意到float抛出的异常不仅是ValueError: +你可能注意到 float 抛出的异常不仅是 ValueError: ```python In [202]: float((1, 2)) @@ -1308,7 +1308,7 @@ TypeError Traceback (most recent call last) TypeError: float() argument must be a string or a number, not 'tuple' ``` -你可能只想处理ValueError,TypeError错误(输入不是字符串或数值)可能是合理的bug。可以写一个异常类型: +你可能只想处理 ValueError,TypeError 错误(输入不是字符串或数值)可能是合理的 bug。可以写一个异常类型: ```python def attempt_float(x): @@ -1345,7 +1345,7 @@ def attempt_float(x): return x ``` -某些情况下,你可能不想抑制异常,你想无论try部分的代码是否成功,都执行一段代码。可以使用finally: +某些情况下,你可能不想抑制异常,你想无论 try 部分的代码是否成功,都执行一段代码。可以使用 finally: ```python f = open(path, 'w') @@ -1356,7 +1356,7 @@ finally: f.close() ``` -这里,文件处理f总会被关闭。相似的,你可以用else让只在try部分成功的情况下,才执行代码: +这里,文件处理 f 总会被关闭。相似的,你可以用 else 让只在 try 部分成功的情况下,才执行代码: ```python f = open(path, 'w') @@ -1371,8 +1371,8 @@ finally: f.close() ``` -## IPython的异常 -如果是在%run一个脚本或一条语句时抛出异常,IPython默认会打印完整的调用栈(traceback),在栈的每个点都会有几行上下文: +## IPython 的异常 +如果是在%run 一个脚本或一条语句时抛出异常,IPython 默认会打印完整的调用栈(traceback),在栈的每个点都会有几行上下文: ```python In [10]: %run examples/ipython_bug.py @@ -1400,12 +1400,12 @@ AssertionError Traceback (most recent call last) AssertionError: ``` -自身就带有文本是相对于Python标准解释器的极大优点。你可以用魔术命令``%xmode``,从Plain(与Python标准解释器相同)到Verbose(带有函数的参数值)控制文本显示的数量。后面可以看到,发生错误之后,(用%debug或%pdb magics)可以进入stack进行事后调试。 +自身就带有文本是相对于 Python 标准解释器的极大优点。你可以用魔术命令``%xmode``,从 Plain(与 Python 标准解释器相同)到 Verbose(带有函数的参数值)控制文本显示的数量。后面可以看到,发生错误之后,(用%debug 或%pdb magics)可以进入 stack 进行事后调试。 # 3.3 文件和操作系统 -本书的代码示例大多使用诸如pandas.read_csv之类的高级工具将磁盘上的数据文件读入Python数据结构。但我们还是需要了解一些有关Python文件处理方面的基础知识。好在它本来就很简单,这也是Python在文本和文件处理方面的如此流行的原因之一。 +本书的代码示例大多使用诸如 pandas.read_csv 之类的高级工具将磁盘上的数据文件读入 Python 数据结构。但我们还是需要了解一些有关 Python 文件处理方面的基础知识。好在它本来就很简单,这也是 Python 在文本和文件处理方面的如此流行的原因之一。 -为了打开一个文件以便读写,可以使用内置的open函数以及一个相对或绝对的文件路径: +为了打开一个文件以便读写,可以使用内置的 open 函数以及一个相对或绝对的文件路径: ```python In [207]: path = 'examples/segismundo.txt' @@ -1413,14 +1413,14 @@ In [207]: path = 'examples/segismundo.txt' In [208]: f = open(path) ``` -默认情况下,文件是以只读模式('r')打开的。然后,我们就可以像处理列表那样来处理这个文件句柄f了,比如对行进行迭代: +默认情况下,文件是以只读模式('r')打开的。然后,我们就可以像处理列表那样来处理这个文件句柄 f 了,比如对行进行迭代: ```python for line in f: pass ``` -从文件中取出的行都带有完整的行结束符(EOL),因此你常常会看到下面这样的代码(得到一组没有EOL的行): +从文件中取出的行都带有完整的行结束符(EOL),因此你常常会看到下面这样的代码(得到一组没有 EOL 的行): ```python In [209]: lines = [x.rstrip() for x in open(path)] @@ -1443,13 +1443,13 @@ Out[210]: ''] ``` -如果使用open创建文件对象,一定要用close关闭它。关闭文件可以返回操作系统资源: +如果使用 open 创建文件对象,一定要用 close 关闭它。关闭文件可以返回操作系统资源: ```python In [211]: f.close() ``` -用with语句可以可以更容易地清理打开的文件: +用 with 语句可以可以更容易地清理打开的文件: ```python In [212]: with open(path) as f: .....: lines = [x.rstrip() for x in f] @@ -1457,11 +1457,11 @@ In [212]: with open(path) as f: 这样可以在退出代码块时,自动关闭文件。 -如果输入f =open(path,'w'),就会有一个新文件被创建在examples/segismundo.txt,并覆盖掉该位置原来的任何数据。另外有一个x文件模式,它可以创建可写的文件,但是如果文件路径存在,就无法创建。表3-3列出了所有的读/写模式。 +如果输入 f =open(path,'w'),就会有一个新文件被创建在 examples/segismundo.txt,并覆盖掉该位置原来的任何数据。另外有一个 x 文件模式,它可以创建可写的文件,但是如果文件路径存在,就无法创建。表 3-3 列出了所有的读/写模式。 -![表3-3 Python的文件模式](img/7178691-28274484129f0ea7.png) +![表 3-3 Python 的文件模式](img/7178691-28274484129f0ea7.png) -对于可读文件,一些常用的方法是read、seek和tell。read会从文件返回字符。字符的内容是由文件的编码决定的(如UTF-8),如果是二进制模式打开的就是原始字节: +对于可读文件,一些常用的方法是 read、seek 和 tell。read 会从文件返回字符。字符的内容是由文件的编码决定的(如 UTF-8),如果是二进制模式打开的就是原始字节: ```python In [213]: f = open(path) @@ -1475,7 +1475,7 @@ In [216]: f2.read(10) Out[216]: b'Sue\xc3\xb1a el ' ``` -read模式会将文件句柄的位置提前,提前的数量是读取的字节数。tell可以给出当前的位置: +read 模式会将文件句柄的位置提前,提前的数量是读取的字节数。tell 可以给出当前的位置: ```python In [217]: f.tell() @@ -1485,7 +1485,7 @@ In [218]: f2.tell() Out[218]: 10 ``` -尽管我们从文件读取了10个字符,位置却是11,这是因为用默认的编码用了这么多字节才解码了这10个字符。你可以用sys模块检查默认的编码: +尽管我们从文件读取了 10 个字符,位置却是 11,这是因为用默认的编码用了这么多字节才解码了这 10 个字符。你可以用 sys 模块检查默认的编码: ```python In [219]: import sys @@ -1494,7 +1494,7 @@ In [220]: sys.getdefaultencoding() Out[220]: 'utf-8' ``` -seek将文件位置更改为文件中的指定字节: +seek 将文件位置更改为文件中的指定字节: ```python In [221]: f.seek(3) @@ -1512,7 +1512,7 @@ In [223]: f.close() In [224]: f2.close() ``` -向文件写入,可以使用文件的write或writelines方法。例如,我们可以创建一个无空行版的prof_mod.py: +向文件写入,可以使用文件的 write 或 writelines 方法。例如,我们可以创建一个无空行版的 prof_mod.py: ```python In [225]: with open('tmp.txt', 'w') as handle: @@ -1535,12 +1535,12 @@ Out[227]: 'aunque ninguno lo entiende.\n'] ``` -表3-4列出了一些最常用的文件方法。 +表 3-4 列出了一些最常用的文件方法。 -![表3-4 Python重要的文件方法或属性](img/7178691-d25bd6e730afeb39.png) +![表 3-4 Python 重要的文件方法或属性](img/7178691-d25bd6e730afeb39.png) -## 文件的字节和Unicode -Python文件的默认操作是“文本模式”,也就是说,你需要处理Python的字符串(即Unicode)。它与“二进制模式”相对,文件模式加一个b。我们来看上一节的文件(UTF-8编码、包含非ASCII字符): +## 文件的字节和 Unicode +Python 文件的默认操作是“文本模式”,也就是说,你需要处理 Python 的字符串(即 Unicode)。它与“二进制模式”相对,文件模式加一个 b。我们来看上一节的文件(UTF-8 编码、包含非 ASCII 字符): ```python In [230]: with open(path) as f: @@ -1550,7 +1550,7 @@ In [231]: chars Out[231]: 'Sueña el r' ``` -UTF-8是长度可变的Unicode编码,所以当我从文件请求一定数量的字符时,Python会从文件读取足够多(可能少至10或多至40字节)的字节进行解码。如果以“rb”模式打开文件,则读取确切的请求字节数: +UTF-8 是长度可变的 Unicode 编码,所以当我从文件请求一定数量的字符时,Python 会从文件读取足够多(可能少至 10 或多至 40 字节)的字节进行解码。如果以“rb”模式打开文件,则读取确切的请求字节数: ```python In [232]: with open(path, 'rb') as f: @@ -1560,7 +1560,7 @@ In [233]: data Out[233]: b'Sue\xc3\xb1a el ' ``` -取决于文本的编码,你可以将字节解码为str对象,但只有当每个编码的Unicode字符都完全成形时才能这么做: +取决于文本的编码,你可以将字节解码为 str 对象,但只有当每个编码的 Unicode 字符都完全成形时才能这么做: ```python In [234]: data.decode('utf8') @@ -1575,7 +1575,7 @@ UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc3 in position 3: unexpect d end of data ``` -文本模式结合了open的编码选项,提供了一种更方便的方法将Unicode转换为另一种编码: +文本模式结合了 open 的编码选项,提供了一种更方便的方法将 Unicode 转换为另一种编码: ```python In [236]: sink_path = 'sink.txt' @@ -1589,7 +1589,7 @@ In [238]: with open(sink_path, encoding='iso-8859-1') as f: Sueña el r ``` -注意,不要在二进制模式中使用seek。如果文件位置位于定义Unicode字符的字节的中间位置,读取后面会产生错误: +注意,不要在二进制模式中使用 seek。如果文件位置位于定义 Unicode 字符的字节的中间位置,读取后面会产生错误: ```python In [240]: f = open(path) @@ -1618,7 +1618,7 @@ tart byte In [244]: f.close() ``` -如果你经常要对非ASCII字符文本进行数据分析,通晓Python的Unicode功能是非常重要的。更多内容,参阅Python官方文档。 +如果你经常要对非 ASCII 字符文本进行数据分析,通晓 Python 的 Unicode 功能是非常重要的。更多内容,参阅 Python 官方文档。 # 3.4 结论 -我们已经学过了Python的基础、环境和语法,接下来学习NumPy和Python的面向数组计算。 +我们已经学过了 Python 的基础、环境和语法,接下来学习 NumPy 和 Python 的面向数组计算。 diff --git a/docs/4.md b/docs/4.md index b8786f6..a32ca38 100644 --- a/docs/4.md +++ b/docs/4.md @@ -1,18 +1,18 @@ # 第 4 章 NumPy 基础:数组和矢量计算 -NumPy(Numerical Python的简称)是Python数值计算最重要的基础包。大多数提供科学计算的包都是用NumPy的数组作为构建基础。 +NumPy(Numerical Python 的简称)是 Python 数值计算最重要的基础包。大多数提供科学计算的包都是用 NumPy 的数组作为构建基础。 -NumPy的部分功能如下: +NumPy 的部分功能如下: - ndarray,一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组。 - 用于对整组数据进行快速运算的标准数学函数(无需编写循环)。 - 用于读写磁盘数据的工具以及用于操作内存映射文件的工具。 - 线性代数、随机数生成以及傅里叶变换功能。 -- 用于集成由C、C++、Fortran等语言编写的代码的A C API。 +- 用于集成由 C、C++、Fortran 等语言编写的代码的 A C API。 -由于NumPy提供了一个简单易用的C API,因此很容易将数据传递给由低级语言编写的外部库,外部库也能以NumPy数组的形式将数据返回给Python。这个功能使Python成为一种包装C/C++/Fortran历史代码库的选择,并使被包装库拥有一个动态的、易用的接口。 +由于 NumPy 提供了一个简单易用的 C API,因此很容易将数据传递给由低级语言编写的外部库,外部库也能以 NumPy 数组的形式将数据返回给 Python。这个功能使 Python 成为一种包装 C/C++/Fortran 历史代码库的选择,并使被包装库拥有一个动态的、易用的接口。 -NumPy本身并没有提供多么高级的数据分析功能,理解NumPy数组以及面向数组的计算将有助于你更加高效地使用诸如pandas之类的工具。因为NumPy是一个很大的题目,我会在附录A中介绍更多NumPy高级功能,比如广播。 +NumPy 本身并没有提供多么高级的数据分析功能,理解 NumPy 数组以及面向数组的计算将有助于你更加高效地使用诸如 pandas 之类的工具。因为 NumPy 是一个很大的题目,我会在附录 A 中介绍更多 NumPy 高级功能,比如广播。 对于大部分数据分析应用而言,我最关注的功能主要集中在: @@ -20,19 +20,19 @@ NumPy本身并没有提供多么高级的数据分析功能,理解NumPy数组 - 常用的数组算法,如排序、唯一化、集合运算等。 - 高效的描述统计和数据聚合/摘要运算。 - 用于异构数据集的合并/连接运算的数据对齐和关系型数据运算。 -- 将条件逻辑表述为数组表达式(而不是带有if-elif-else分支的循环)。 +- 将条件逻辑表述为数组表达式(而不是带有 if-elif-else 分支的循环)。 - 数据的分组运算(聚合、转换、函数应用等)。。 -虽然NumPy提供了通用的数值数据处理的计算基础,但大多数读者可能还是想将pandas作为统计和分析工作的基础,尤其是处理表格数据时。pandas还提供了一些NumPy所没有的领域特定的功能,如时间序列处理等。 +虽然 NumPy 提供了通用的数值数据处理的计算基础,但大多数读者可能还是想将 pandas 作为统计和分析工作的基础,尤其是处理表格数据时。pandas 还提供了一些 NumPy 所没有的领域特定的功能,如时间序列处理等。 ->笔记:Python的面向数组计算可以追溯到1995年,Jim Hugunin创建了Numeric库。接下来的10年,许多科学编程社区纷纷开始使用Python的数组编程,但是进入21世纪,库的生态系统变得碎片化了。2005年,Travis Oliphant从Numeric和Numarray项目整了出了NumPy项目,进而所有社区都集合到了这个框架下。 +>笔记:Python 的面向数组计算可以追溯到 1995 年,Jim Hugunin 创建了 Numeric 库。接下来的 10 年,许多科学编程社区纷纷开始使用 Python 的数组编程,但是进入 21 世纪,库的生态系统变得碎片化了。2005 年,Travis Oliphant 从 Numeric 和 Numarray 项目整了出了 NumPy 项目,进而所有社区都集合到了这个框架下。 -NumPy之于数值计算特别重要的原因之一,是因为它可以高效处理大数组的数据。这是因为: +NumPy 之于数值计算特别重要的原因之一,是因为它可以高效处理大数组的数据。这是因为: -- NumPy是在一个连续的内存块中存储数据,独立于其他Python内置对象。NumPy的C语言编写的算法库可以操作内存,而不必进行类型检查或其它前期工作。比起Python的内置序列,NumPy数组使用的内存更少。 -- NumPy可以在整个数组上执行复杂的计算,而不需要Python的for循环。 +- NumPy 是在一个连续的内存块中存储数据,独立于其他 Python 内置对象。NumPy 的 C 语言编写的算法库可以操作内存,而不必进行类型检查或其它前期工作。比起 Python 的内置序列,NumPy 数组使用的内存更少。 +- NumPy 可以在整个数组上执行复杂的计算,而不需要 Python 的 for 循环。 -要搞明白具体的性能差距,考察一个包含一百万整数的数组,和一个等价的Python列表: +要搞明白具体的性能差距,考察一个包含一百万整数的数组,和一个等价的 Python 列表: ```python In [7]: import numpy as np @@ -41,7 +41,7 @@ In [8]: my_arr = np.arange(1000000) In [9]: my_list = list(range(1000000)) ``` -各个序列分别乘以2: +各个序列分别乘以 2: ```python In [10]: %time for _ in range(10): my_arr2 = my_arr * 2 CPU times: user 20 ms, sys: 50 ms, total: 70 ms @@ -52,12 +52,12 @@ CPU times: user 760 ms, sys: 290 ms, total: 1.05 s Wall time: 1.05 s ``` -基于NumPy的算法要比纯Python快10到100倍(甚至更快),并且使用的内存更少。 +基于 NumPy 的算法要比纯 Python 快 10 到 100 倍(甚至更快),并且使用的内存更少。 -# 4.1 NumPy的ndarray:一种多维数组对象 -NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器。你可以利用这种数组对整块数据执行一些数学运算,其语法跟标量元素之间的运算一样。 +# 4.1 NumPy 的 ndarray:一种多维数组对象 +NumPy 最重要的一个特点就是其 N 维数组对象(即 ndarray),该对象是一个快速而灵活的大数据集容器。你可以利用这种数组对整块数据执行一些数学运算,其语法跟标量元素之间的运算一样。 -要明白Python是如何利用与标量值类似的语法进行批次计算,我先引入NumPy,然后生成一个包含随机数据的小数组: +要明白 Python 是如何利用与标量值类似的语法进行批次计算,我先引入 NumPy,然后生成一个包含随机数据的小数组: ```python In [12]: import numpy as np @@ -83,11 +83,11 @@ array([[-0.4094, 0.9579, -1.0389], [-1.1115, 3.9316, 2.7868]]) ``` -第一个例子中,所有的元素都乘以10。第二个例子中,每个元素都与自身相加。 +第一个例子中,所有的元素都乘以 10。第二个例子中,每个元素都与自身相加。 ->笔记:在本章及全书中,我会使用标准的NumPy惯用法``import numpy as np``。你当然也可以在代码中使用``from numpy import *``,但不建议这么做。``numpy``的命名空间很大,包含许多函数,其中一些的名字与Python的内置函数重名(比如min和max)。 +>笔记:在本章及全书中,我会使用标准的 NumPy 惯用法``import numpy as np``。你当然也可以在代码中使用``from numpy import *``,但不建议这么做。``numpy``的命名空间很大,包含许多函数,其中一些的名字与 Python 的内置函数重名(比如 min 和 max)。 -ndarray是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象): +ndarray 是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个 shape(一个表示各维度大小的元组)和一个 dtype(一个用于说明数组数据类型的对象): ```python In [17]: data.shape Out[17]: (2, 3) @@ -96,12 +96,12 @@ In [18]: data.dtype Out[18]: dtype('float64') ``` -本章将会介绍NumPy数组的基本用法,这对于本书后面各章的理解基本够用。虽然大多数数据分析工作不需要深入理解NumPy,但是精通面向数组的编程和思维方式是成为Python科学计算牛人的一大关键步骤。 +本章将会介绍 NumPy 数组的基本用法,这对于本书后面各章的理解基本够用。虽然大多数数据分析工作不需要深入理解 NumPy,但是精通面向数组的编程和思维方式是成为 Python 科学计算牛人的一大关键步骤。 ->笔记:当你在本书中看到“数组”、“NumPy数组”、"ndarray"时,基本上都指的是同一样东西,即ndarray对象。 +>笔记:当你在本书中看到“数组”、“NumPy 数组”、"ndarray"时,基本上都指的是同一样东西,即 ndarray 对象。 -## 创建ndarray -创建数组最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。以一个列表的转换为例: +## 创建 ndarray +创建数组最简单的办法就是使用 array 函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的 NumPy 数组。以一个列表的转换为例: ```python In [19]: data1 = [6, 7.5, 8, 0, 1] @@ -123,7 +123,7 @@ array([[1, 2, 3, 4], [5, 6, 7, 8]]) ``` -因为data2是列表的列表,NumPy数组arr2的两个维度的shape是从data2引入的。可以用属性ndim和shape验证: +因为 data2 是列表的列表,NumPy 数组 arr2 的两个维度的 shape 是从 data2 引入的。可以用属性 ndim 和 shape 验证: ```python In [25]: arr2.ndim Out[25]: 2 @@ -132,7 +132,7 @@ In [26]: arr2.shape Out[26]: (2, 4) ``` -除非特别说明(稍后将会详细介绍),np.array会尝试为新建的这个数组推断出一个较为合适的数据类型。数据类型保存在一个特殊的dtype对象中。比如说,在上面的两个例子中,我们有: +除非特别说明(稍后将会详细介绍),np.array 会尝试为新建的这个数组推断出一个较为合适的数据类型。数据类型保存在一个特殊的 dtype 对象中。比如说,在上面的两个例子中,我们有: ```python In [27]: arr1.dtype Out[27]: dtype('float64') @@ -141,7 +141,7 @@ In [28]: arr2.dtype Out[28]: dtype('int64') ``` -除np.array之外,还有一些函数也可以新建数组。比如,zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可: +除 np.array 之外,还有一些函数也可以新建数组。比如,zeros 和 ones 分别可以创建指定长度或形状的全 0 或全 1 数组。empty 可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可: ```python In [29]: np.zeros(10) Out[29]: array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) @@ -162,20 +162,20 @@ array([[[ 0., 0.], [ 0., 0.]]]) ``` ->注意:认为np.empty会返回全0数组的想法是不安全的。很多情况下(如前所示),它返回的都是一些未初始化的垃圾值。 +>注意:认为 np.empty 会返回全 0 数组的想法是不安全的。很多情况下(如前所示),它返回的都是一些未初始化的垃圾值。 -arange是Python内置函数range的数组版: +arange 是 Python 内置函数 range 的数组版: ```python In [32]: np.arange(15) Out[32]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) ``` -表4-1列出了一些数组创建函数。由于NumPy关注的是数值计算,因此,如果没有特别指定,数据类型基本都是float64(浮点数)。 +表 4-1 列出了一些数组创建函数。由于 NumPy 关注的是数值计算,因此,如果没有特别指定,数据类型基本都是 float64(浮点数)。 -![表4-1 数组创建函数](img/7178691-78ab11f67e7077a6.png) +![表 4-1 数组创建函数](img/7178691-78ab11f67e7077a6.png) -## ndarray的数据类型 -dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息: +## ndarray 的数据类型 +dtype(数据类型)是一个特殊的对象,它含有 ndarray 将一块内存解释为特定数据类型所需的信息: ```python In [33]: arr1 = np.array([1, 2, 3], dtype=np.float64) @@ -188,15 +188,15 @@ In [36]: arr2.dtype Out[36]: dtype('int32') ``` -dtype是NumPy灵活交互其它系统的源泉之一。多数情况下,它们直接映射到相应的机器表示,这使得“读写磁盘上的二进制数据流”以及“集成低级语言代码(如C、Fortran)”等工作变得更加简单。数值型dtype的命名方式相同:一个类型名(如float或int),后面跟一个用于表示各元素位长的数字。标准的双精度浮点值(即Python中的float对象)需要占用8字节(即64位)。因此,该类型在NumPy中就记作float64。表4-2列出了NumPy所支持的全部数据类型。 +dtype 是 NumPy 灵活交互其它系统的源泉之一。多数情况下,它们直接映射到相应的机器表示,这使得“读写磁盘上的二进制数据流”以及“集成低级语言代码(如 C、Fortran)”等工作变得更加简单。数值型 dtype 的命名方式相同:一个类型名(如 float 或 int),后面跟一个用于表示各元素位长的数字。标准的双精度浮点值(即 Python 中的 float 对象)需要占用 8 字节(即 64 位)。因此,该类型在 NumPy 中就记作 float64。表 4-2 列出了 NumPy 所支持的全部数据类型。 ->笔记:记不住这些NumPy的dtype也没关系,新手更是如此。通常只需要知道你所处理的数据的大致类型是浮点数、复数、整数、布尔值、字符串,还是普通的Python对象即可。当你需要控制数据在内存和磁盘中的存储方式时(尤其是对大数据集),那就得了解如何控制存储类型。 +>笔记:记不住这些 NumPy 的 dtype 也没关系,新手更是如此。通常只需要知道你所处理的数据的大致类型是浮点数、复数、整数、布尔值、字符串,还是普通的 Python 对象即可。当你需要控制数据在内存和磁盘中的存储方式时(尤其是对大数据集),那就得了解如何控制存储类型。 ![](img/7178691-2f2d7406a8bc076c.png) ![](img/7178691-5cc31115615737b7.png) -你可以通过ndarray的astype方法明确地将一个数组从一个dtype转换成另一个dtype: +你可以通过 ndarray 的 astype 方法明确地将一个数组从一个 dtype 转换成另一个 dtype: ```python In [37]: arr = np.array([1, 2, 3, 4, 5]) @@ -220,7 +220,7 @@ In [43]: arr.astype(np.int32) Out[43]: array([ 3, -1, -2, 0, 12, 10], dtype=int32) ``` -如果某字符串数组表示的全是数字,也可以用astype将其转换为数值形式: +如果某字符串数组表示的全是数字,也可以用 astype 将其转换为数值形式: ```python In [44]: numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_) @@ -228,11 +228,11 @@ In [45]: numeric_strings.astype(float) Out[45]: array([ 1.25, -9.6 , 42. ]) ``` ->注意:使用numpy.string_类型时,一定要小心,因为NumPy的字符串数据是大小固定的,发生截取时,不会发出警告。pandas提供了更多非数值数据的便利的处理方法。 +>注意:使用 numpy.string_ 类型时,一定要小心,因为 NumPy 的字符串数据是大小固定的,发生截取时,不会发出警告。pandas 提供了更多非数值数据的便利的处理方法。 -如果转换过程因为某种原因而失败了(比如某个不能被转换为float64的字符串),就会引发一个ValueError。这里,我比较懒,写的是float而不是np.float64;NumPy很聪明,它会将Python类型映射到等价的dtype上。 +如果转换过程因为某种原因而失败了(比如某个不能被转换为 float64 的字符串),就会引发一个 ValueError。这里,我比较懒,写的是 float 而不是 np.float64;NumPy 很聪明,它会将 Python 类型映射到等价的 dtype 上。 -数组的dtype还有另一个属性: +数组的 dtype 还有另一个属性: ```python In [46]: int_array = np.arange(10) @@ -242,7 +242,7 @@ In [48]: int_array.astype(calibers.dtype) Out[48]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]) ``` -你还可以用简洁的类型代码来表示dtype: +你还可以用简洁的类型代码来表示 dtype: ```python In [49]: empty_uint32 = np.empty(8, dtype='u4') @@ -252,10 +252,10 @@ array([ 0, 1075314688, 0, 1075707904, 0, 1075838976, 0, 1072693248], dtype=uint32) ``` ->笔记:调用astype总会创建一个新的数组(一个数据的备份),即使新的dtype与旧的dtype相同。 +>笔记:调用 astype 总会创建一个新的数组(一个数据的备份),即使新的 dtype 与旧的 dtype 相同。 -## NumPy数组的运算 -数组很重要,因为它使你不用编写循环即可对数据执行批量运算。NumPy用户称其为矢量化(vectorization)。大小相等的数组之间的任何算术运算都会将运算应用到元素级: +## NumPy 数组的运算 +数组很重要,因为它使你不用编写循环即可对数据执行批量运算。NumPy 用户称其为矢量化(vectorization)。大小相等的数组之间的任何算术运算都会将运算应用到元素级: ```python In [51]: arr = np.array([[1., 2., 3.], [4., 5., 6.]]) @@ -303,10 +303,10 @@ array([[False, True, False], [ True, False, True]], dtype=bool) ``` -不同大小的数组之间的运算叫做广播(broadcasting),将在附录A中对其进行详细讨论。本书的内容不需要对广播机制有多深的理解。 +不同大小的数组之间的运算叫做广播(broadcasting),将在附录 A 中对其进行详细讨论。本书的内容不需要对广播机制有多深的理解。 ## 基本的索引和切片 -NumPy数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有很多。一维数组很简单。从表面上看,它们跟Python列表的功能差不多: +NumPy 数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有很多。一维数组很简单。从表面上看,它们跟 Python 列表的功能差不多: ```python In [60]: arr = np.arange(10) @@ -325,9 +325,9 @@ In [65]: arr Out[65]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9]) ``` -如上所示,当你将一个标量值赋值给一个切片时(如arr[5:8]=12),该值会自动传播(也就说后面将会讲到的“广播”)到整个选区。跟列表最重要的区别在于,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。 +如上所示,当你将一个标量值赋值给一个切片时(如 arr[5:8]=12),该值会自动传播(也就说后面将会讲到的“广播”)到整个选区。跟列表最重要的区别在于,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。 -作为例子,先创建一个arr的切片: +作为例子,先创建一个 arr 的切片: ```python In [66]: arr_slice = arr[5:8] @@ -335,7 +335,7 @@ In [67]: arr_slice Out[67]: array([12, 12, 12]) ``` -现在,当我修稿arr_slice中的值,变动也会体现在原始数组arr中: +现在,当我修稿 arr_slice 中的值,变动也会体现在原始数组 arr 中: ```python In [68]: arr_slice[1] = 12345 @@ -352,9 +352,9 @@ In [71]: arr Out[71]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9]) ``` -如果你刚开始接触NumPy,可能会对此感到惊讶(尤其是当你曾经用过其他热衷于复制数组数据的编程语言)。由于NumPy的设计目的是处理大数据,所以你可以想象一下,假如NumPy坚持要将数据复制来复制去的话会产生何等的性能和内存问题。 +如果你刚开始接触 NumPy,可能会对此感到惊讶(尤其是当你曾经用过其他热衷于复制数组数据的编程语言)。由于 NumPy 的设计目的是处理大数据,所以你可以想象一下,假如 NumPy 坚持要将数据复制来复制去的话会产生何等的性能和内存问题。 ->注意:如果你想要得到的是ndarray切片的一份副本而非视图,就需要明确地进行复制操作,例如``arr[5:8].copy()``。 +>注意:如果你想要得到的是 ndarray 切片的一份副本而非视图,就需要明确地进行复制操作,例如``arr[5:8].copy()``。 对于高维度数组,能做的事情更多。在一个二维数组中,各索引位置上的元素不再是标量而是一维数组: ```python @@ -373,11 +373,11 @@ In [75]: arr2d[0, 2] Out[75]: 3 ``` -图4-1说明了二维数组的索引方式。轴0作为行,轴1作为列。 +图 4-1 说明了二维数组的索引方式。轴 0 作为行,轴 1 作为列。 -![图4-1 NumPy数组中的元素索引](img/7178691-0a641536f73f560e.png) +![图 4-1 NumPy 数组中的元素索引](img/7178691-0a641536f73f560e.png) -在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray(它含有高一级维度上的所有数据)。因此,在2×2×3数组arr3d中: +在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的 ndarray(它含有高一级维度上的所有数据)。因此,在 2×2×3 数组 arr3d 中: ```python In [76]: arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]) @@ -389,7 +389,7 @@ array([[[ 1, 2, 3], [10, 11, 12]]]) ``` -arr3d[0]是一个2×3数组: +arr3d[0]是一个 2×3 数组: ```python In [78]: arr3d[0] Out[78]: @@ -397,7 +397,7 @@ array([[1, 2, 3], [4, 5, 6]]) ``` -标量值和数组都可以被赋值给arr3d[0]: +标量值和数组都可以被赋值给 arr3d[0]: ```python In [79]: old_values = arr3d[0].copy() @@ -442,7 +442,7 @@ Out[87]: array([7, 8, 9]) 注意,在上面所有这些选取数组子集的例子中,返回的数组都是视图。 ## 切片索引 -ndarray的切片语法跟Python列表这样的一维对象差不多: +ndarray 的切片语法跟 Python 列表这样的一维对象差不多: ```python In [88]: arr Out[88]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9]) @@ -451,7 +451,7 @@ In [89]: arr[1:6] Out[89]: array([ 1, 2, 3, 4, 64]) ``` -对于之前的二维数组arr2d,其切片方式稍显不同: +对于之前的二维数组 arr2d,其切片方式稍显不同: ```python In [90]: arr2d Out[90]: @@ -465,7 +465,7 @@ array([[1, 2, 3], [4, 5, 6]]) ``` -可以看出,它是沿着第0轴(即第一个轴)切片的。也就是说,切片是沿着一个轴向选取元素的。表达式arr2d[:2]可以被认为是“选取arr2d的前两行”。 +可以看出,它是沿着第 0 轴(即第一个轴)切片的。也就是说,切片是沿着一个轴向选取元素的。表达式 arr2d[:2]可以被认为是“选取 arr2d 的前两行”。 你可以一次传入多个切片,就像传入多个索引那样: ```python @@ -489,7 +489,7 @@ In [94]: arr2d[:2, 2] Out[94]: array([3, 6]) ``` -图4-2对此进行了说明。注意,“只有冒号”表示选取整个轴,因此你可以像下面这样只对高维轴进行切片: +图 4-2 对此进行了说明。注意,“只有冒号”表示选取整个轴,因此你可以像下面这样只对高维轴进行切片: ```python In [95]: arr2d[:, :1] Out[95]: @@ -498,7 +498,7 @@ array([[1], [7]]) ``` -![图4-2 二维数组切片](img/7178691-9da32d2f4629c304.png) +![图 4-2 二维数组切片](img/7178691-9da32d2f4629c304.png) 自然,对切片表达式的赋值操作也会被扩散到整个选区: @@ -513,7 +513,7 @@ array([[1, 0, 0], ``` ## 布尔型索引 -来看这样一个例子,假设我们有一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。在这里,我将使用numpy.random中的randn函数生成一些正态分布的随机数据: +来看这样一个例子,假设我们有一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。在这里,我将使用 numpy.random 中的 randn 函数生成一些正态分布的随机数据: ```python In [98]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe']) @@ -535,7 +535,7 @@ array([[ 0.0929, 0.2817, 0.769 , 1.2464], [-0.7135, -0.8312, -2.3702, -1.8608]]) ``` -假设每个名字都对应data数组中的一行,而我们想要选出对应于名字"Bob"的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对names和字符串"Bob"的比较运算将会产生一个布尔型数组: +假设每个名字都对应 data 数组中的一行,而我们想要选出对应于名字"Bob"的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对 names 和字符串"Bob"的比较运算将会产生一个布尔型数组: ```python In [102]: names == 'Bob' Out[102]: array([ True, False, False, True, False, False, False], dtype=bool) @@ -614,9 +614,9 @@ array([[ 0.0929, 0.2817, 0.769 , 1.2464], 通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回一模一样的数组也是如此。 ->注意:Python关键字and和or在布尔型数组中无效。要使用&与|。 +>注意:Python 关键字 and 和 or 在布尔型数组中无效。要使用&与|。 -通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0,我们只需: +通过布尔型数组设置值是一种经常用到的手段。为了将 data 中的所有负值都设置为 0,我们只需: ```python In [113]: data[data < 0] = 0 @@ -646,10 +646,10 @@ array([[ 7. , 7. , 7. , 7. ], [ 0. , 0. , 0. , 0. ]]) ``` -后面会看到,这类二维数据的操作也可以用pandas方便的来做。 +后面会看到,这类二维数据的操作也可以用 pandas 方便的来做。 ## 花式索引 -花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。假设我们有一个8×4数组: +花式索引(Fancy indexing)是一个 NumPy 术语,它指的是利用整数数组进行索引。假设我们有一个 8×4 数组: ```python In [117]: arr = np.empty((8, 4)) @@ -668,7 +668,7 @@ array([[ 0., 0., 0., 0.], [ 7., 7., 7., 7.]]) ``` -为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可: +为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或 ndarray 即可: ```python In [120]: arr[[4, 3, 0, 6]] Out[120]: @@ -706,7 +706,7 @@ In [124]: arr[[1, 5, 7, 2], [0, 3, 1, 2]] Out[124]: array([ 4, 23, 29, 10]) ``` -附录A中会详细介绍reshape方法。 +附录 A 中会详细介绍 reshape 方法。 最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。无论数组是多少维的,花式索引总是一维的。 @@ -723,7 +723,7 @@ array([[ 4, 7, 5, 6], 记住,花式索引跟切片不一样,它总是将数据复制到新数组中。 ## 数组转置和轴对换 -转置是重塑的一种特殊形式,它返回的是源数据的视图(不会进行任何复制操作)。数组不仅有transpose方法,还有一个特殊的T属性: +转置是重塑的一种特殊形式,它返回的是源数据的视图(不会进行任何复制操作)。数组不仅有 transpose 方法,还有一个特殊的 T 属性: ```python In [126]: arr = np.arange(15).reshape((3, 5)) @@ -742,7 +742,7 @@ array([[ 0, 5, 10], [ 4, 9, 14]]) ``` -在进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积: +在进行矩阵计算时,经常需要用到该操作,比如利用 np.dot 计算矩阵内积: ```python In [129]: arr = np.random.randn(6, 3) @@ -762,7 +762,7 @@ array([[ 9.2291, 0.9394, 4.948 ], [ 4.948 , -1.3622, 4.3437]]) ``` -对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子): +对于高维数组,transpose 需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子): ```python In [132]: arr = np.arange(16).reshape((2, 2, 4)) @@ -783,7 +783,7 @@ array([[[ 0, 1, 2, 3], 这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。 -简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray还有一个swapaxes方法,它需要接受一对轴编号: +简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray 还有一个 swapaxes 方法,它需要接受一对轴编号: ```python In [135]: arr Out[135]: @@ -804,12 +804,12 @@ array([[[ 0, 4], [11, 15]]]) ``` -swapaxes也是返回源数据的视图(不会进行任何复制操作)。 +swapaxes 也是返回源数据的视图(不会进行任何复制操作)。 # 4.2 通用函数:快速的元素级数组函数 -通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。 +通用函数(即 ufunc)是一种对 ndarray 中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。 -许多ufunc都是简单的元素级变体,如sqrt和exp: +许多 ufunc 都是简单的元素级变体,如 sqrt 和 exp: ```python In [137]: arr = np.arange(10) @@ -827,7 +827,7 @@ array([ 1. , 2.7183, 7.3891, 20.0855, 54.5982, 148.4132, 403.4288, 1096.6332, 2980.958 , 8103.0839]) ``` -这些都是一元(unary)ufunc。另外一些(如add或maximum)接受2个数组(因此也叫二元(binary)ufunc),并返回一个结果数组: +这些都是一元(unary)ufunc。另外一些(如 add 或 maximum)接受 2 个数组(因此也叫二元(binary)ufunc),并返回一个结果数组: ```python In [141]: x = np.random.randn(8) @@ -849,9 +849,9 @@ array([ 0.8626, 1.0048, 1.3272, 0.6702, 0.853 , 0.0222, 0.7584, -0.6605]) ``` -这里,numpy.maximum计算了x和y中元素级别最大的元素。 +这里,numpy.maximum 计算了 x 和 y 中元素级别最大的元素。 -虽然并不常见,但有些ufunc的确可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,它会返回浮点数数组的小数和整数部分: +虽然并不常见,但有些 ufunc 的确可以返回多个数组。modf 就是一个例子,它是 Python 内置函数 divmod 的矢量化版本,它会返回浮点数数组的小数和整数部分: ```python In [146]: arr = np.random.randn(7) * 5 @@ -868,7 +868,7 @@ In [150]: whole_part Out[150]: array([-3., -6., -6., 5., 3., 3., 5.]) ``` -Ufuncs可以接受一个out可选参数,这样就能在数组原地进行操作: +Ufuncs 可以接受一个 out 可选参数,这样就能在数组原地进行操作: ```python In [151]: arr Out[151]: array([-3.2623, -6.0915, -6.663 , 5.3731, 3.6182, 3.45 , 5.0077]) @@ -883,7 +883,7 @@ In [154]: arr Out[154]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378]) ``` -表4-3和表4-4分别列出了一些一元和二元ufunc。 +表 4-3 和表 4-4 分别列出了一些一元和二元 ufunc。 ![](img/7178691-1d494e73b61c7ced.png) @@ -896,9 +896,9 @@ Out[154]: array([ nan, nan, nan, 2.318 , 1.9022, 1.8574, 2.2378]) ![](img/7178691-236dba83b6a420cc.png) # 4.3 利用数组进行数据处理 -NumPy数组使你可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环)。用数组表达式代替循环的做法,通常被称为矢量化。一般来说,矢量化数组运算要比等价的纯Python方式快上一两个数量级(甚至更多),尤其是各种数值计算。在后面内容中(见附录A)我将介绍广播,这是一种针对矢量化计算的强大手段。 +NumPy 数组使你可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环)。用数组表达式代替循环的做法,通常被称为矢量化。一般来说,矢量化数组运算要比等价的纯 Python 方式快上一两个数量级(甚至更多),尤其是各种数值计算。在后面内容中(见附录 A)我将介绍广播,这是一种针对矢量化计算的强大手段。 -作为简单的例子,假设我们想要在一组值(网格型)上计算函数``sqrt(x^2+y^2)``。np.meshgrid函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对): +作为简单的例子,假设我们想要在一组值(网格型)上计算函数``sqrt(x^2+y^2)``。np.meshgrid 函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对): ```python In [155]: points = np.arange(-5, 5, 0.01) # 1000 equally spaced points @@ -929,7 +929,7 @@ array([[ 7.0711, 7.064 , 7.0569, ..., 7.0499, 7.0569, 7.064 ], [ 7.064 , 7.0569, 7.0499, ..., 7.0428, 7.0499, 7.0569]]) ``` -作为第9章的先导,我用matplotlib创建了这个二维数组的可视化: +作为第 9 章的先导,我用 matplotlib 创建了这个二维数组的可视化: ```python In [160]: import matplotlib.pyplot as plt @@ -940,12 +940,12 @@ In [162]: plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values") Out[162]: ``` -见图4-3。这张图是用matplotlib的imshow函数创建的。 +见图 4-3。这张图是用 matplotlib 的 imshow 函数创建的。 -![图4-3 根据网格对函数求值的结果](img/7178691-3b22000d4cd38650.png) +![图 4-3 根据网格对函数求值的结果](img/7178691-3b22000d4cd38650.png) ## 将条件逻辑表述为数组运算 -numpy.where函数是三元表达式x if condition else y的矢量化版本。假设我们有一个布尔数组和两个值数组: +numpy.where 函数是三元表达式 x if condition else y 的矢量化版本。假设我们有一个布尔数组和两个值数组: ```python In [165]: xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5]) @@ -954,7 +954,7 @@ In [166]: yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5]) In [167]: cond = np.array([True, False, True, True, False]) ``` -假设我们想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。列表推导式的写法应该如下所示: +假设我们想要根据 cond 中的值选取 xarr 和 yarr 的值:当 cond 中的值为 True 时,选取 xarr 的值,否则从 yarr 中选取。列表推导式的写法应该如下所示: ```python In [168]: result = [(x if c else y) .....: for x, y, c in zip(xarr, yarr, cond)] @@ -963,7 +963,7 @@ In [169]: result Out[169]: [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5] ``` -这有几个问题。第一,它对大数组的处理速度不是很快(因为所有工作都是由纯Python完成的)。第二,无法用于多维数组。若使用np.where,则可以将该功能写得非常简洁: +这有几个问题。第一,它对大数组的处理速度不是很快(因为所有工作都是由纯 Python 完成的)。第二,无法用于多维数组。若使用 np.where,则可以将该功能写得非常简洁: ```python In [170]: result = np.where(cond, xarr, yarr) @@ -971,7 +971,7 @@ In [171]: result Out[171]: array([ 1.1, 2.2, 1.3, 1.4, 2.5]) ``` -np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where通常用于根据另一个数组而产生一个新的数组。假设有一个由随机数据组成的矩阵,你希望将所有正值替换为2,将所有负值替换为-2。若利用np.where,则会非常简单: +np.where 的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where 通常用于根据另一个数组而产生一个新的数组。假设有一个由随机数据组成的矩阵,你希望将所有正值替换为 2,将所有负值替换为-2。若利用 np.where,则会非常简单: ```python In [172]: arr = np.random.randn(4, 4) @@ -997,7 +997,7 @@ array([[-2, -2, -2, -2], [ 2, -2, 2, 2]]) ``` -使用np.where,可以将标量和数组结合起来。例如,我可用常数2替换arr中所有正的值: +使用 np.where,可以将标量和数组结合起来。例如,我可用常数 2 替换 arr 中所有正的值: ```python In [176]: np.where(arr > 0, 2, arr) # set only positive values to 2 Out[176]: @@ -1007,10 +1007,10 @@ array([[-0.5031, -0.6223, -0.9212, -0.7262], [ 2. , -0.1316, 2. , 2. ]]) ``` -传递给where的数组大小可以不相等,甚至可以是标量值。 +传递给 where 的数组大小可以不相等,甚至可以是标量值。 ## 数学和统计方法 -可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级NumPy函数使用。 +可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean 以及标准差 std 等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级 NumPy 函数使用。 这里,我生成了一些正态分布随机数据,然后做了聚类统计: ```python @@ -1034,7 +1034,7 @@ In [181]: arr.sum() Out[181]: 3.9214102239996507 ``` -mean和sum这类的函数可以接受一个axis选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组: +mean 和 sum 这类的函数可以接受一个 axis 选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组: ```python In [182]: arr.mean(axis=1) Out[182]: array([ 1.022 , 0.1875, -0.502 , -0.0881, 0.3611]) @@ -1045,7 +1045,7 @@ Out[183]: array([ 3.1693, -2.6345, 2.2381, 1.1486]) 这里,arr.mean(1)是“计算行的平均值”,arr.sum(0)是“计算每列的和”。 -其他如cumsum和cumprod之类的方法则不聚合,而是产生一个由中间结果组成的数组: +其他如 cumsum 和 cumprod 之类的方法则不聚合,而是产生一个由中间结果组成的数组: ```python In [184]: arr = np.array([0, 1, 2, 3, 4, 5, 6, 7]) @@ -1053,7 +1053,7 @@ In [185]: arr.cumsum() Out[185]: array([ 0, 1, 3, 6, 10, 15, 21, 28]) ``` -在多维数组中,累加函数(如cumsum)返回的是同样大小的数组,但是会根据每个低维的切片沿着标记轴计算部分聚类: +在多维数组中,累加函数(如 cumsum)返回的是同样大小的数组,但是会根据每个低维的切片沿着标记轴计算部分聚类: ```python In [186]: arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) @@ -1076,14 +1076,14 @@ array([[ 0, 0, 0], [ 6, 42, 336]]) ``` -表4-5列出了全部的基本数组统计方法。后续章节中有很多例子都会用到这些方法。 +表 4-5 列出了全部的基本数组统计方法。后续章节中有很多例子都会用到这些方法。 ![](img/7178691-a6c6df3ca8e0b98e.png) ![](img/7178691-866fcde885b1d357.png) ## 用于布尔型数组的方法 -在上面这些方法中,布尔值会被强制转换为1(True)和0(False)。因此,sum经常被用来对布尔型数组中的True值计数: +在上面这些方法中,布尔值会被强制转换为 1(True)和 0(False)。因此,sum 经常被用来对布尔型数组中的 True 值计数: ```python In [190]: arr = np.random.randn(100) @@ -1091,7 +1091,7 @@ In [191]: (arr > 0).sum() # Number of positive values Out[191]: 42 ``` -另外还有两个方法any和all,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True: +另外还有两个方法 any 和 all,它们对布尔型数组非常有用。any 用于测试数组中是否存在一个或多个 True,而 all 则检查数组中所有值是否都是 True: ```python In [192]: bools = np.array([False, False, True, False]) @@ -1102,10 +1102,10 @@ In [194]: bools.all() Out[194]: False ``` -这两个方法也能用于非布尔型数组,所有非0元素将会被当做True。 +这两个方法也能用于非布尔型数组,所有非 0 元素将会被当做 True。 ## 排序 -跟Python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序: +跟 Python 内置的列表类型一样,NumPy 数组也可以通过 sort 方法就地排序: ```python In [195]: arr = np.random.randn(6) @@ -1118,7 +1118,7 @@ In [198]: arr Out[198]: array([-0.8469, -0.4938, -0.1357, 0.6095, 1.24 , 1.43 ]) ``` -多维数组可以在任何一个轴向上进行排序,只需将轴编号传给sort即可: +多维数组可以在任何一个轴向上进行排序,只需将轴编号传给 sort 即可: ```python In [199]: arr = np.random.randn(5, 3) @@ -1141,7 +1141,7 @@ array([[-0.2555, 0.6033, 1.2636], [-1.7415, -0.8948, 0.218 ]]) ``` -顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值: +顶级方法 np.sort 返回的是数组的已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值: ```python In [203]: large_arr = np.random.randn(1000) @@ -1151,10 +1151,10 @@ In [205]: large_arr[int(0.05 * len(large_arr))] # 5% quantile Out[205]: -1.5311513550102103 ``` -更多关于NumPy排序方法以及诸如间接排序之类的高级技术,请参阅附录A。在pandas中还可以找到一些其他跟排序有关的数据操作(比如根据一列或多列对表格型数据进行排序)。 +更多关于 NumPy 排序方法以及诸如间接排序之类的高级技术,请参阅附录 A。在 pandas 中还可以找到一些其他跟排序有关的数据操作(比如根据一列或多列对表格型数据进行排序)。 ## 唯一化以及其它的集合逻辑 -NumPy提供了一些针对一维ndarray的基本集合运算。最常用的可能要数np.unique了,它用于找出数组中的唯一值并返回已排序的结果: +NumPy 提供了一些针对一维 ndarray 的基本集合运算。最常用的可能要数 np.unique 了,它用于找出数组中的唯一值并返回已排序的结果: ```python In [206]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe']) @@ -1169,13 +1169,13 @@ In [209]: np.unique(ints) Out[209]: array([1, 2, 3, 4]) ``` -拿跟np.unique等价的纯Python代码来对比一下: +拿跟 np.unique 等价的纯 Python 代码来对比一下: ```python In [210]: sorted(set(names)) Out[210]: ['Bob', 'Joe', 'Will'] ``` -另一个函数np.in1d用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组: +另一个函数 np.in1d 用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组: ```python In [211]: values = np.array([6, 0, 0, 3, 2, 5, 6]) @@ -1183,31 +1183,31 @@ In [212]: np.in1d(values, [2, 3, 6]) Out[212]: array([ True, False, False, True, True, False, True], dtype=bool) ``` -NumPy中的集合函数请参见表4-6。 +NumPy 中的集合函数请参见表 4-6。 ![](img/7178691-80e85ae6b9c89ada.png) # 4.4 用于数组的文件输入输出 -NumPy能够读写磁盘上的文本数据或二进制数据。这一小节只讨论NumPy的内置二进制格式,因为更多的用户会使用pandas或其它工具加载文本或表格数据(见第6章)。 +NumPy 能够读写磁盘上的文本数据或二进制数据。这一小节只讨论 NumPy 的内置二进制格式,因为更多的用户会使用 pandas 或其它工具加载文本或表格数据(见第 6 章)。 -np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的: +np.save 和 np.load 是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy 的文件中的: ```python In [213]: arr = np.arange(10) In [214]: np.save('some_array', arr) ``` -如果文件路径末尾没有扩展名.npy,则该扩展名会被自动加上。然后就可以通过np.load读取磁盘上的数组: +如果文件路径末尾没有扩展名.npy,则该扩展名会被自动加上。然后就可以通过 np.load 读取磁盘上的数组: ```python In [215]: np.load('some_array.npy') Out[215]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) ``` -通过np.savez可以将多个数组保存到一个未压缩文件中,将数组以关键字参数的形式传入即可: +通过 np.savez 可以将多个数组保存到一个未压缩文件中,将数组以关键字参数的形式传入即可: ```python In [216]: np.savez('array_archive.npz', a=arr, b=arr) ``` -加载.npz文件时,你会得到一个类似字典的对象,该对象会对各个数组进行延迟加载: +加载.npz 文件时,你会得到一个类似字典的对象,该对象会对各个数组进行延迟加载: ```python In [217]: arch = np.load('array_archive.npz') @@ -1215,13 +1215,13 @@ In [218]: arch['b'] Out[218]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) ``` -如果要将数据压缩,可以使用numpy.savez_compressed: +如果要将数据压缩,可以使用 numpy.savez_compressed: ```python In [219]: np.savez_compressed('arrays_compressed.npz', a=arr, b=arr) ``` # 4.5 线性代数 -线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。不像某些语言(如MATLAB),通过*对两个二维数组相乘得到的是一个元素级的积,而不是一个矩阵点积。因此,NumPy提供了一个用于矩阵乘法的dot函数(既是一个数组方法也是numpy命名空间中的一个函数): +线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。不像某些语言(如 MATLAB),通过*对两个二维数组相乘得到的是一个元素级的积,而不是一个矩阵点积。因此,NumPy 提供了一个用于矩阵乘法的 dot 函数(既是一个数组方法也是 numpy 命名空间中的一个函数): ```python In [223]: x = np.array([[1., 2., 3.], [4., 5., 6.]]) @@ -1244,7 +1244,7 @@ array([[ 28., 64.], [ 67., 181.]]) ``` -x.dot(y)等价于np.dot(x, y): +x.dot(y)等价于 np.dot(x, y): ```python In [228]: np.dot(x, y) Out[228]: @@ -1258,13 +1258,13 @@ In [229]: np.dot(x, np.ones(3)) Out[229]: array([ 6., 15.]) ``` -@符(类似Python 3.5)也可以用作中缀运算符,进行矩阵乘法: +@符(类似 Python 3.5)也可以用作中缀运算符,进行矩阵乘法: ```python In [230]: x @ np.ones(3) Out[230]: array([ 6., 15.]) ``` -numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。它们跟MATLAB和R等语言所使用的是相同的行业标准线性代数库,如BLAS、LAPACK、Intel MKL(Math Kernel Library,可能有,取决于你的NumPy版本)等: +numpy.linalg 中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。它们跟 MATLAB 和 R 等语言所使用的是相同的行业标准线性代数库,如 BLAS、LAPACK、Intel MKL(Math Kernel Library,可能有,取决于你的 NumPy 版本)等: ```python In [231]: from numpy.linalg import inv, qr @@ -1299,14 +1299,14 @@ array([[-1.6914, 4.38 , 0.1757, 0.4075, -0.7838], [ 0. , 0. , 0. , 0. , 0.0002]]) ``` -表达式X.T.dot(X)计算X和它的转置X.T的点积。 +表达式 X.T.dot(X)计算 X 和它的转置 X.T 的点积。 -表4-7中列出了一些最常用的线性代数函数。 +表 4-7 中列出了一些最常用的线性代数函数。 ![](img/7178691-dcdb66e49e5f70ea.png) # 4.6 伪随机数生成 -numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。例如,你可以用normal来得到一个标准正态分布的4×4样本数组: +numpy.random 模块对 Python 内置的 random 进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。例如,你可以用 normal 来得到一个标准正态分布的 4×4 样本数组: ```python In [238]: samples = np.random.normal(size=(4, 4)) @@ -1318,7 +1318,7 @@ array([[ 0.5732, 0.1933, 0.4429, 1.2796], [ 0.1525, 0.9435, -1.0953, -0.144 ]]) ``` -而Python内置的random模块则只能一次生成一个样本值。从下面的测试结果中可以看出,如果需要产生大量样本值,numpy.random快了不止一个数量级: +而 Python 内置的 random 模块则只能一次生成一个样本值。从下面的测试结果中可以看出,如果需要产生大量样本值,numpy.random 快了不止一个数量级: ```python In [240]: from random import normalvariate @@ -1331,12 +1331,12 @@ In [243]: %timeit np.random.normal(size=N) 61.7 ms +- 1.32 ms per loop (mean +- std. dev. of 7 runs, 10 loops each) ``` -我们说这些都是伪随机数,是因为它们都是通过算法基于随机数生成器种子,在确定性的条件下生成的。你可以用NumPy的np.random.seed更改随机数生成种子: +我们说这些都是伪随机数,是因为它们都是通过算法基于随机数生成器种子,在确定性的条件下生成的。你可以用 NumPy 的 np.random.seed 更改随机数生成种子: ```python In [244]: np.random.seed(1234) ``` -numpy.random的数据生成函数使用了全局的随机种子。要避免全局状态,你可以使用numpy.random.RandomState,创建一个与其它隔离的随机数生成器: +numpy.random 的数据生成函数使用了全局的随机种子。要避免全局状态,你可以使用 numpy.random.RandomState,创建一个与其它隔离的随机数生成器: ```python In [245]: rng = np.random.RandomState(1234) @@ -1346,16 +1346,16 @@ array([ 0.4714, -1.191 , 1.4327, -0.3127, -0.7206, 0.8872, 0.8596, -0.6365, 0.0157, -2.2427]) ``` -表4-8列出了numpy.random中的部分函数。在下一节中,我将给出一些利用这些函数一次性生成大量样本值的范例。 +表 4-8 列出了 numpy.random 中的部分函数。在下一节中,我将给出一些利用这些函数一次性生成大量样本值的范例。 ![](img/7178691-97ba09c96dab93a2.png) ![](img/7178691-6ed04fae3d1178e2.png) # 4.7 示例:随机漫步 -我们通过模拟随机漫步来说明如何运用数组运算。先来看一个简单的随机漫步的例子:从0开始,步长1和-1出现的概率相等。 +我们通过模拟随机漫步来说明如何运用数组运算。先来看一个简单的随机漫步的例子:从 0 开始,步长 1 和-1 出现的概率相等。 -下面是一个通过内置的random模块以纯Python的方式实现1000步的随机漫步: +下面是一个通过内置的 random 模块以纯 Python 的方式实现 1000 步的随机漫步: ```python In [247]: import random .....: position = 0 @@ -1368,14 +1368,14 @@ In [247]: import random .....: ``` -图4-4是根据前100个随机漫步值生成的折线图: +图 4-4 是根据前 100 个随机漫步值生成的折线图: ```python In [249]: plt.plot(walk[:100]) ``` -![图4-4 简单的随机漫步](img/7178691-0833621694f6dda0.png) +![图 4-4 简单的随机漫步](img/7178691-0833621694f6dda0.png) -不难看出,这其实就是随机漫步中各步的累计和,可以用一个数组运算来实现。因此,我用np.random模块一次性随机产生1000个“掷硬币”结果(即两个数中任选一个),将其分别设置为1或-1,然后计算累计和: +不难看出,这其实就是随机漫步中各步的累计和,可以用一个数组运算来实现。因此,我用 np.random 模块一次性随机产生 1000 个“掷硬币”结果(即两个数中任选一个),将其分别设置为 1 或-1,然后计算累计和: ```python In [251]: nsteps = 1000 @@ -1395,16 +1395,16 @@ In [256]: walk.max() Out[256]: 31 ``` -现在来看一个复杂点的统计任务——首次穿越时间,即随机漫步过程中第一次到达某个特定值的时间。假设我们想要知道本次随机漫步需要多久才能距离初始0点至少10步远(任一方向均可)。np.abs(walk)>=10可以得到一个布尔型数组,它表示的是距离是否达到或超过10,而我们想要知道的是第一个10或-10的索引。可以用argmax来解决这个问题,它返回的是该布尔型数组第一个最大值的索引(True就是最大值): +现在来看一个复杂点的统计任务——首次穿越时间,即随机漫步过程中第一次到达某个特定值的时间。假设我们想要知道本次随机漫步需要多久才能距离初始 0 点至少 10 步远(任一方向均可)。np.abs(walk)>=10 可以得到一个布尔型数组,它表示的是距离是否达到或超过 10,而我们想要知道的是第一个 10 或-10 的索引。可以用 argmax 来解决这个问题,它返回的是该布尔型数组第一个最大值的索引(True 就是最大值): ```python In [257]: (np.abs(walk) >= 10).argmax() Out[257]: 37 ``` -注意,这里使用argmax并不是很高效,因为它无论如何都会对数组进行完全扫描。在本例中,只要发现了一个True,那我们就知道它是个最大值了。 +注意,这里使用 argmax 并不是很高效,因为它无论如何都会对数组进行完全扫描。在本例中,只要发现了一个 True,那我们就知道它是个最大值了。 ## 一次模拟多个随机漫步 -如果你希望模拟多个随机漫步过程(比如5000个),只需对上面的代码做一点点修改即可生成所有的随机漫步过程。只要给numpy.random的函数传入一个二元元组就可以产生一个二维数组,然后我们就可以一次性计算5000个随机漫步过程(一行一个)的累计和了: +如果你希望模拟多个随机漫步过程(比如 5000 个),只需对上面的代码做一点点修改即可生成所有的随机漫步过程。只要给 numpy.random 的函数传入一个二元元组就可以产生一个二维数组,然后我们就可以一次性计算 5000 个随机漫步过程(一行一个)的累计和了: ```python In [258]: nwalks = 5000 @@ -1436,7 +1436,7 @@ In [265]: walks.min() Out[265]: -133 ``` -得到这些数据之后,我们来计算30或-30的最小穿越时间。这里稍微复杂些,因为不是5000个过程都到达了30。我们可以用any方法来对此进行检查: +得到这些数据之后,我们来计算 30 或-30 的最小穿越时间。这里稍微复杂些,因为不是 5000 个过程都到达了 30。我们可以用 any 方法来对此进行检查: ```python In [266]: hits30 = (np.abs(walks) >= 30).any(1) @@ -1447,7 +1447,7 @@ In [268]: hits30.sum() # Number that hit 30 or -30 Out[268]: 3410 ``` -然后我们利用这个布尔型数组选出那些穿越了30(绝对值)的随机漫步(行),并调用argmax在轴1上获取穿越时间: +然后我们利用这个布尔型数组选出那些穿越了 30(绝对值)的随机漫步(行),并调用 argmax 在轴 1 上获取穿越时间: ```python In [269]: crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1) @@ -1455,11 +1455,11 @@ In [270]: crossing_times.mean() Out[270]: 498.88973607038122 ``` -请尝试用其他分布方式得到漫步数据。只需使用不同的随机数生成函数即可,如normal用于生成指定均值和标准差的正态分布数据: +请尝试用其他分布方式得到漫步数据。只需使用不同的随机数生成函数即可,如 normal 用于生成指定均值和标准差的正态分布数据: ```python In [271]: steps = np.random.normal(loc=0, scale=0.25, .....: size=(nwalks, nsteps)) ``` # 4.8 结论 -虽然本书剩下的章节大部分是用pandas规整数据,我们还是会用到相似的基于数组的计算。在附录A中,我们会深入挖掘NumPy的特点,进一步学习数组的技巧。 +虽然本书剩下的章节大部分是用 pandas 规整数据,我们还是会用到相似的基于数组的计算。在附录 A 中,我们会深入挖掘 NumPy 的特点,进一步学习数组的技巧。 diff --git a/docs/5.md b/docs/5.md index e87a54a..7a73dc2 100644 --- a/docs/5.md +++ b/docs/5.md @@ -1,26 +1,26 @@ # 第 5 章 pandas 入门 -pandas是本书后续内容的首选库。它含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。pandas经常和其它工具一同使用,如数值计算工具NumPy和SciPy,分析库statsmodels和scikit-learn,和数据可视化库matplotlib。pandas是基于NumPy数组构建的,特别是基于数组的函数和不使用for循环的数据处理。 +pandas 是本书后续内容的首选库。它含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。pandas 经常和其它工具一同使用,如数值计算工具 NumPy 和 SciPy,分析库 statsmodels 和 scikit-learn,和数据可视化库 matplotlib。pandas 是基于 NumPy 数组构建的,特别是基于数组的函数和不使用 for 循环的数据处理。 -虽然pandas采用了大量的NumPy编码风格,但二者最大的不同是pandas是专门为处理表格和混杂数据设计的。而NumPy更适合处理统一的数值数组数据。 +虽然 pandas 采用了大量的 NumPy 编码风格,但二者最大的不同是 pandas 是专门为处理表格和混杂数据设计的。而 NumPy 更适合处理统一的数值数组数据。 -自从2010年pandas开源以来,pandas逐渐成长为一个非常大的库,应用于许多真实案例。开发者社区已经有了800个独立的贡献者,他们在解决日常数据问题的同时为这个项目提供贡献。 +自从 2010 年 pandas 开源以来,pandas 逐渐成长为一个非常大的库,应用于许多真实案例。开发者社区已经有了 800 个独立的贡献者,他们在解决日常数据问题的同时为这个项目提供贡献。 -在本书后续部分中,我将使用下面这样的pandas引入约定: +在本书后续部分中,我将使用下面这样的 pandas 引入约定: ```python In [1]: import pandas as pd ``` -因此,只要你在代码中看到pd.,就得想到这是pandas。因为Series和DataFrame用的次数非常多,所以将其引入本地命名空间中会更方便: +因此,只要你在代码中看到 pd.,就得想到这是 pandas。因为 Series 和 DataFrame 用的次数非常多,所以将其引入本地命名空间中会更方便: ```python In [2]: from pandas import Series, DataFrame ``` -# 5.1 pandas的数据结构介绍 -要使用pandas,你首先就得熟悉它的两个主要数据结构:Series和DataFrame。虽然它们并不能解决所有问题,但它们为大多数应用提供了一种可靠的、易于使用的基础。 +# 5.1 pandas 的数据结构介绍 +要使用 pandas,你首先就得熟悉它的两个主要数据结构:Series 和 DataFrame。虽然它们并不能解决所有问题,但它们为大多数应用提供了一种可靠的、易于使用的基础。 ## Series -Series是一种类似于一维数组的对象,它由一组数据(各种NumPy数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的Series: +Series 是一种类似于一维数组的对象,它由一组数据(各种 NumPy 数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的 Series: ```python In [11]: obj = pd.Series([4, 7, -5, 3]) @@ -33,7 +33,7 @@ Out[12]: dtype: int64 ``` -Series的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个0到N-1(N为数据的长度)的整数型索引。你可以通过Series 的values和index属性获取其数组表示形式和索引对象: +Series 的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个 0 到 N-1(N 为数据的长度)的整数型索引。你可以通过 Series 的 values 和 index 属性获取其数组表示形式和索引对象: ```python In [13]: obj.values Out[13]: array([ 4, 7, -5, 3]) @@ -42,7 +42,7 @@ In [14]: obj.index # like range(4) Out[14]: RangeIndex(start=0, stop=4, step=1) ``` -通常,我们希望所创建的Series带有一个可以对各个数据点进行标记的索引: +通常,我们希望所创建的 Series 带有一个可以对各个数据点进行标记的索引: ```python In [15]: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c']) @@ -58,7 +58,7 @@ In [17]: obj2.index Out[17]: Index(['d', 'b', 'a', 'c'], dtype='object') ``` -与普通NumPy数组相比,你可以通过索引的方式选取Series中的单个或一组值: +与普通 NumPy 数组相比,你可以通过索引的方式选取 Series 中的单个或一组值: ```python In [18]: obj2['a'] Out[18]: -5 @@ -75,7 +75,7 @@ dtype: int64 ['c', 'a', 'd']是索引列表,即使它包含的是字符串而不是整数。 -使用NumPy函数或类似NumPy的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值的链接: +使用 NumPy 函数或类似 NumPy 的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值的链接: ```python In [21]: obj2[obj2 > 0] Out[21]: @@ -101,7 +101,7 @@ c 20.085537 dtype: float64 ``` -还可以将Series看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中: +还可以将 Series 看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中: ```python In [24]: 'b' in obj2 Out[24]: True @@ -110,7 +110,7 @@ In [25]: 'e' in obj2 Out[25]: False ``` -如果数据被存放在一个Python字典中,也可以直接通过这个字典来创建Series: +如果数据被存放在一个 Python 字典中,也可以直接通过这个字典来创建 Series: ```python In [26]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000} @@ -125,7 +125,7 @@ Utah 5000 dtype: int64 ``` -如果只传入一个字典,则结果Series中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序: +如果只传入一个字典,则结果 Series 中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序: ```python In [29]: states = ['California', 'Ohio', 'Oregon', 'Texas'] @@ -140,9 +140,9 @@ Texas 71000.0 dtype: float64 ``` -在这个例子中,sdata中跟states索引相匹配的那3个值会被找出来并放到相应的位置上,但由于"California"所对应的sdata值找不到,所以其结果就为NaN(即“非数字”(not a number),在pandas中,它用于表示缺失或NA值)。因为‘Utah’不在states中,它被从结果中除去。 +在这个例子中,sdata 中跟 states 索引相匹配的那 3 个值会被找出来并放到相应的位置上,但由于"California"所对应的 sdata 值找不到,所以其结果就为 NaN(即“非数字”(not a number),在 pandas 中,它用于表示缺失或 NA 值)。因为‘Utah’不在 states 中,它被从结果中除去。 -我将使用缺失(missing)或NA表示缺失数据。pandas的isnull和notnull函数可用于检测缺失数据: +我将使用缺失(missing)或 NA 表示缺失数据。pandas 的 isnull 和 notnull 函数可用于检测缺失数据: ```python In [32]: pd.isnull(obj4) Out[32]: @@ -161,7 +161,7 @@ Texas True dtype: bool ``` -Series也有类似的实例方法: +Series 也有类似的实例方法: ```python In [34]: obj4.isnull() Out[34]: @@ -172,9 +172,9 @@ Texas False dtype: bool ``` -我将在第7章详细讲解如何处理缺失数据。 +我将在第 7 章详细讲解如何处理缺失数据。 -对于许多应用而言,Series最重要的一个功能是,它会根据运算的索引标签自动对齐数据: +对于许多应用而言,Series 最重要的一个功能是,它会根据运算的索引标签自动对齐数据: ```python In [35]: obj3 Out[35]: @@ -202,9 +202,9 @@ Utah NaN dtype: float64 ``` -数据对齐功能将在后面详细讲解。如果你使用过数据库,你可以认为是类似join的操作。 +数据对齐功能将在后面详细讲解。如果你使用过数据库,你可以认为是类似 join 的操作。 -Series对象本身及其索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切: +Series 对象本身及其索引都有一个 name 属性,该属性跟 pandas 其他的关键功能关系非常密切: ```python In [38]: obj4.name = 'population' @@ -220,7 +220,7 @@ Texas 71000.0 Name: population, dtype: float64 ``` -Series的索引可以通过赋值的方式就地修改: +Series 的索引可以通过赋值的方式就地修改: ```python In [41]: obj Out[41]: @@ -242,11 +242,11 @@ dtype: int64 ``` ## DataFrame -DataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典(共用同一个索引)。DataFrame中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。有关DataFrame内部的技术细节远远超出了本书所讨论的范围。 +DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame 既有行索引也有列索引,它可以被看做由 Series 组成的字典(共用同一个索引)。DataFrame 中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。有关 DataFrame 内部的技术细节远远超出了本书所讨论的范围。 ->笔记:虽然DataFrame是以二维结构保存数据的,但你仍然可以轻松地将其表示为更高维度的数据(层次化索引的表格型结构,这是pandas中许多高级数据处理功能的关键要素,我们会在第8章讨论这个问题)。 +>笔记:虽然 DataFrame 是以二维结构保存数据的,但你仍然可以轻松地将其表示为更高维度的数据(层次化索引的表格型结构,这是 pandas 中许多高级数据处理功能的关键要素,我们会在第 8 章讨论这个问题)。 -建DataFrame的办法有很多,最常用的一种是直接传入一个由等长列表或NumPy数组组成的字典: +建 DataFrame 的办法有很多,最常用的一种是直接传入一个由等长列表或 NumPy 数组组成的字典: ```python data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], @@ -254,7 +254,7 @@ data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], frame = pd.DataFrame(data) ``` -结果DataFrame会自动加上索引(跟Series一样),且全部列会被有序排列: +结果 DataFrame 会自动加上索引(跟 Series 一样),且全部列会被有序排列: ```python In [45]: frame Out[45]: @@ -267,9 +267,9 @@ Out[45]: 5 3.2 Nevada 2003 ``` -如果你使用的是Jupyter notebook,pandas DataFrame对象会以对浏览器友好的HTML表格的方式呈现。 +如果你使用的是 Jupyter notebook,pandas DataFrame 对象会以对浏览器友好的 HTML 表格的方式呈现。 -对于特别大的DataFrame,head方法会选取前五行: +对于特别大的 DataFrame,head 方法会选取前五行: ```python In [46]: frame.head() Out[46]: @@ -281,7 +281,7 @@ Out[46]: 4 2.9 Nevada 2002 ``` -如果指定了列序列,则DataFrame的列就会按照指定顺序进行排列: +如果指定了列序列,则 DataFrame 的列就会按照指定顺序进行排列: ```python In [47]: pd.DataFrame(data, columns=['year', 'state', 'pop']) Out[47]: @@ -314,7 +314,7 @@ In [50]: frame2.columns Out[50]: Index(['year', 'state', 'pop', 'debt'], dtype='object') ``` -通过类似字典标记的方式或属性的方式,可以将DataFrame的列获取为一个Series: +通过类似字典标记的方式或属性的方式,可以将 DataFrame 的列获取为一个 Series: ```python In [51]: frame2['state'] Out[51]: @@ -337,12 +337,12 @@ six 2003 Name: year, dtype: int64 ``` ->笔记:IPython提供了类似属性的访问(即frame2.year)和tab补全。 -frame2[column]适用于任何列的名,但是frame2.column只有在列名是一个合理的Python变量名时才适用。 +>笔记:IPython 提供了类似属性的访问(即 frame2.year)和 tab 补全。 +frame2[column]适用于任何列的名,但是 frame2.column 只有在列名是一个合理的 Python 变量名时才适用。 -注意,返回的Series拥有原DataFrame相同的索引,且其name属性也已经被相应地设置好了。 +注意,返回的 Series 拥有原 DataFrame 相同的索引,且其 name 属性也已经被相应地设置好了。 -行也可以通过位置或名称的方式进行获取,比如用loc属性(稍后将对此进行详细讲解): +行也可以通过位置或名称的方式进行获取,比如用 loc 属性(稍后将对此进行详细讲解): ```python In [53]: frame2.loc['three'] Out[53]: @@ -380,7 +380,7 @@ five 2002 Nevada 2.9 4.0 six 2003 Nevada 3.2 5.0 ``` -将列表或数组赋值给某个列时,其长度必须跟DataFrame的长度相匹配。如果赋值的是一个Series,就会精确匹配DataFrame的索引,所有的空位都将被填上缺失值: +将列表或数组赋值给某个列时,其长度必须跟 DataFrame 的长度相匹配。如果赋值的是一个 Series,就会精确匹配 DataFrame 的索引,所有的空位都将被填上缺失值: ```python In [58]: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five']) @@ -397,9 +397,9 @@ five 2002 Nevada 2.9 -1.7 six 2003 Nevada 3.2 NaN ``` -为不存在的列赋值会创建出一个新列。关键字del用于删除列。 +为不存在的列赋值会创建出一个新列。关键字 del 用于删除列。 -作为del的例子,我先添加一个新的布尔值的列,state是否为'Ohio': +作为 del 的例子,我先添加一个新的布尔值的列,state 是否为'Ohio': ```python In [61]: frame2['eastern'] = frame2.state == 'Ohio' @@ -414,9 +414,9 @@ five 2002 Nevada 2.9 -1.7 False six 2003 Nevada 3.2 NaN False ``` ->注意:不能用frame2.eastern创建新的列。 +>注意:不能用 frame2.eastern 创建新的列。 -del方法可以用来删除这列: +del 方法可以用来删除这列: ```python In [63]: del frame2['eastern'] @@ -424,7 +424,7 @@ In [64]: frame2.columns Out[64]: Index(['year', 'state', 'pop', 'debt'], dtype='object') ``` ->注意:通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的Series所做的任何就地修改全都会反映到源DataFrame上。通过Series的copy方法即可指定复制列。 +>注意:通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的 Series 所做的任何就地修改全都会反映到源 DataFrame 上。通过 Series 的 copy 方法即可指定复制列。 另一种常见的数据形式是嵌套字典: ```python @@ -432,7 +432,7 @@ In [65]: pop = {'Nevada': {2001: 2.4, 2002: 2.9}, ....: 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}} ``` -如果嵌套字典传给DataFrame,pandas就会被解释为:外层字典的键作为列,内层键则作为行索引: +如果嵌套字典传给 DataFrame,pandas 就会被解释为:外层字典的键作为列,内层键则作为行索引: ```python In [66]: frame3 = pd.DataFrame(pop) @@ -444,7 +444,7 @@ Out[67]: 2002 2.9 3.6 ``` -你也可以使用类似NumPy数组的方法,对DataFrame进行转置(交换行和列): +你也可以使用类似 NumPy 数组的方法,对 DataFrame 进行转置(交换行和列): ```python In [68]: frame3.T Out[68]: @@ -463,7 +463,7 @@ Out[69]: 2003 NaN NaN ``` -由Series组成的字典差不多也是一样的用法: +由 Series 组成的字典差不多也是一样的用法: ```python In [70]: pdata = {'Ohio': frame3['Ohio'][:-1], ....: 'Nevada': frame3['Nevada'][:2]} @@ -475,11 +475,11 @@ Out[71]: 2001 2.4 1.7 ``` -表5-1列出了DataFrame构造函数所能接受的各种数据。 +表 5-1 列出了 DataFrame 构造函数所能接受的各种数据。 ![](img/7178691-106835b28c0cea5a.png) -如果设置了DataFrame的index和columns的name属性,则这些信息也会被显示出来: +如果设置了 DataFrame 的 index 和 columns 的 name 属性,则这些信息也会被显示出来: ```python In [72]: frame3.index.name = 'year'; frame3.columns.name = 'state' @@ -492,7 +492,7 @@ year 2002 2.9 3.6 ``` -跟Series一样,values属性也会以二维ndarray的形式返回DataFrame中的数据: +跟 Series 一样,values 属性也会以二维 ndarray 的形式返回 DataFrame 中的数据: ```python In [74]: frame3.values Out[74]: @@ -501,7 +501,7 @@ array([[ nan, 1.5], [ 2.9, 3.6]]) ``` -如果DataFrame各列的数据类型不同,则值数组的dtype就会选用能兼容所有列的数据类型: +如果 DataFrame 各列的数据类型不同,则值数组的 dtype 就会选用能兼容所有列的数据类型: ```python In [75]: frame2.values Out[75]: @@ -514,7 +514,7 @@ array([[2000, 'Ohio', 1.5, nan], ``` ## 索引对象 -pandas的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建Series或DataFrame时,所用到的任何数组或其他序列的标签都会被转换成一个Index: +pandas 的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建 Series 或 DataFrame 时,所用到的任何数组或其他序列的标签都会被转换成一个 Index: ```python In [76]: obj = pd.Series(range(3), index=['a', 'b', 'c']) @@ -527,12 +527,12 @@ In [79]: index[1:] Out[79]: Index(['b', 'c'], dtype='object') ``` -Index对象是不可变的,因此用户不能对其进行修改: +Index 对象是不可变的,因此用户不能对其进行修改: ```python index[1] = 'd' # TypeError ``` -不可变可以使Index对象在多个数据结构之间安全共享: +不可变可以使 Index 对象在多个数据结构之间安全共享: ```python In [80]: labels = pd.Index(np.arange(3)) @@ -552,9 +552,9 @@ In [84]: obj2.index is labels Out[84]: True ``` ->注意:虽然用户不需要经常使用Index的功能,但是因为一些操作会生成包含被索引化的数据,理解它们的工作原理是很重要的。 +>注意:虽然用户不需要经常使用 Index 的功能,但是因为一些操作会生成包含被索引化的数据,理解它们的工作原理是很重要的。 -除了类似于数组,Index的功能也类似一个固定大小的集合: +除了类似于数组,Index 的功能也类似一个固定大小的集合: ```python In [85]: frame3 Out[85]: @@ -573,7 +573,7 @@ In [88]: 2003 in frame3.index Out[88]: False ``` -与python的集合不同,pandas的Index可以包含重复的标签: +与 python 的集合不同,pandas 的 Index 可以包含重复的标签: ```python In [89]: dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar']) @@ -583,15 +583,15 @@ Out[90]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object') 选择重复的标签,会显示所有的结果。 -每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表5-2列出了这些函数。 +每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表 5-2 列出了这些函数。 ![](img/7178691-5499d14f0e2cd639.jpg) # 5.2 基本功能 -本节中,我将介绍操作Series和DataFrame中的数据的基本手段。后续章节将更加深入地挖掘pandas在数据分析和处理方面的功能。本书不是pandas库的详尽文档,主要关注的是最重要的功能,那些不大常用的内容(也就是那些更深奥的内容)就交给你自己去摸索吧。 +本节中,我将介绍操作 Series 和 DataFrame 中的数据的基本手段。后续章节将更加深入地挖掘 pandas 在数据分析和处理方面的功能。本书不是 pandas 库的详尽文档,主要关注的是最重要的功能,那些不大常用的内容(也就是那些更深奥的内容)就交给你自己去摸索吧。 ## 重新索引 -pandas对象的一个重要方法是reindex,其作用是创建一个新对象,它的数据符合新的索引。看下面的例子: +pandas 对象的一个重要方法是 reindex,其作用是创建一个新对象,它的数据符合新的索引。看下面的例子: ```python In [91]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c']) @@ -604,7 +604,7 @@ c 3.6 dtype: float64 ``` -用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值: +用该 Series 的 reindex 将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值: ```python In [93]: obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e']) @@ -618,7 +618,7 @@ e NaN dtype: float64 ``` -对于时间序列这样的有序数据,重新索引时可能需要做一些插值处理。method选项即可达到此目的,例如,使用ffill可以实现前向值填充: +对于时间序列这样的有序数据,重新索引时可能需要做一些插值处理。method 选项即可达到此目的,例如,使用 ffill 可以实现前向值填充: ```python In [95]: obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4]) @@ -640,7 +640,7 @@ Out[97]: dtype: object ``` -借助DataFrame,reindex可以修改(行)索引和列。只传递一个序列时,会重新索引结果的行: +借助 DataFrame,reindex 可以修改(行)索引和列。只传递一个序列时,会重新索引结果的行: ```python In [98]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)), ....: index=['a', 'c', 'd'], @@ -664,7 +664,7 @@ c 3.0 4.0 5.0 d 6.0 7.0 8.0 ``` -列可以用columns关键字重新索引: +列可以用 columns 关键字重新索引: ```python In [102]: states = ['Texas', 'Utah', 'California'] @@ -676,12 +676,12 @@ c 4 NaN 5 d 7 NaN 8 ``` -表5-3列出了reindex函数的各参数及说明。 +表 5-3 列出了 reindex 函数的各参数及说明。 ![](img/7178691-efa3dbd4b83c61ec.jpg) ## 丢弃指定轴上的项 -丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑,所以drop方法返回的是一个在指定轴上删除了指定值的新对象: +丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑,所以 drop 方法返回的是一个在指定轴上删除了指定值的新对象: ```python In [105]: obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e']) @@ -712,7 +712,7 @@ e 4.0 dtype: float64 ``` -对于DataFrame,可以删除任意轴上的索引值。为了演示,先新建一个DataFrame例子: +对于 DataFrame,可以删除任意轴上的索引值。为了演示,先新建一个 DataFrame 例子: ```python In [110]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), .....: index=['Ohio', 'Colorado', 'Utah', 'New York'], @@ -727,7 +727,7 @@ Utah 8 9 10 11 New York 12 13 14 15 ``` -用标签序列调用drop会从行标签(axis 0)删除值: +用标签序列调用 drop 会从行标签(axis 0)删除值: ```python In [112]: data.drop(['Colorado', 'Ohio']) Out[112]: @@ -736,7 +736,7 @@ Utah 8 9 10 11 New York 12 13 14 15 ``` -通过传递axis=1或axis='columns'可以删除列的值: +通过传递 axis=1 或 axis='columns'可以删除列的值: ```python In [113]: data.drop('two', axis=1) Out[113]: @@ -755,7 +755,7 @@ Utah 8 10 New York 12 14 ``` -许多函数,如drop,会修改Series或DataFrame的大小或形状,可以就地修改对象,不会返回新的对象: +许多函数,如 drop,会修改 Series 或 DataFrame 的大小或形状,可以就地修改对象,不会返回新的对象: ```python In [115]: obj.drop('c', inplace=True) @@ -768,10 +768,10 @@ e 4.0 dtype: float64 ``` -小心使用inplace,它会销毁所有被删除的数据。 +小心使用 inplace,它会销毁所有被删除的数据。 ## 索引、选取和过滤 -Series索引(obj[...])的工作方式类似于NumPy数组的索引,只不过Series的索引值不只是整数。下面是几个例子: +Series 索引(obj[...])的工作方式类似于 NumPy 数组的索引,只不过 Series 的索引值不只是整数。下面是几个例子: ```python In [117]: obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd']) @@ -815,7 +815,7 @@ b 1.0 dtype: float64 ``` -利用标签的切片运算与普通的Python切片运算不同,其末端是包含的: +利用标签的切片运算与普通的 Python 切片运算不同,其末端是包含的: ```python In [125]: obj['b':'c'] Out[125]: @@ -824,7 +824,7 @@ c 2.0 dtype: float64 ``` -用切片可以对Series的相应部分进行设置: +用切片可以对 Series 的相应部分进行设置: ```python In [126]: obj['b':'c'] = 5 @@ -837,7 +837,7 @@ d 3.0 dtype: float64 ``` -用一个值或序列对DataFrame进行索引其实就是获取一个或多个列: +用一个值或序列对 DataFrame 进行索引其实就是获取一个或多个列: ```python In [128]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), .....: index=['Ohio', 'Colorado', 'Utah', 'New York'], @@ -884,9 +884,9 @@ Utah 8 9 10 11 New York 12 13 14 15 ``` -选取行的语法data[:2]十分方便。向[ ]传递单一的元素或列表,就可选择列。 +选取行的语法 data[:2]十分方便。向[ ]传递单一的元素或列表,就可选择列。 -另一种用法是通过布尔型DataFrame(比如下面这个由标量比较运算得出的)进行索引: +另一种用法是通过布尔型 DataFrame(比如下面这个由标量比较运算得出的)进行索引: ```python In [134]: data < 5 Out[134]: @@ -907,10 +907,10 @@ Utah 8 9 10 11 New York 12 13 14 15 ``` -这使得DataFrame的语法与NumPy二维数组的语法很像。 +这使得 DataFrame 的语法与 NumPy 二维数组的语法很像。 -## 用loc和iloc进行选取 -对于DataFrame的行的标签索引,我引入了特殊的标签运算符loc和iloc。它们可以让你用类似NumPy的标记,使用轴标签(loc)或整数索引(iloc),从DataFrame选择行和列的子集。 +## 用 loc 和 iloc 进行选取 +对于 DataFrame 的行的标签索引,我引入了特殊的标签运算符 loc 和 iloc。它们可以让你用类似 NumPy 的标记,使用轴标签(loc)或整数索引(iloc),从 DataFrame 选择行和列的子集。 作为一个初步示例,让我们通过标签选择一行和多列: ```python @@ -921,7 +921,7 @@ three 6 Name: Colorado, dtype: int64 ``` -然后用iloc和整数进行选取: +然后用 iloc 和整数进行选取: ```python In [138]: data.iloc[2, [3, 0, 1]] Out[138]: @@ -962,22 +962,22 @@ Utah 8 9 10 New York 12 13 14 ``` -所以,在pandas中,有多个方法可以选取和重新组合数据。对于DataFrame,表5-4进行了总结。后面会看到,还有更多的方法进行层级化索引。 +所以,在 pandas 中,有多个方法可以选取和重新组合数据。对于 DataFrame,表 5-4 进行了总结。后面会看到,还有更多的方法进行层级化索引。 ->笔记:在一开始设计pandas时,我觉得用frame[:, col]选取列过于繁琐(也容易出错),因为列的选择是非常常见的操作。我做了些取舍,将花式索引的功能(标签和整数)放到了ix运算符中。在实践中,这会导致许多边缘情况,数据的轴标签是整数,所以pandas团队决定创造loc和iloc运算符分别处理严格基于标签和整数的索引。 -ix运算符仍然可用,但并不推荐。 +>笔记:在一开始设计 pandas 时,我觉得用 frame[:, col]选取列过于繁琐(也容易出错),因为列的选择是非常常见的操作。我做了些取舍,将花式索引的功能(标签和整数)放到了 ix 运算符中。在实践中,这会导致许多边缘情况,数据的轴标签是整数,所以 pandas 团队决定创造 loc 和 iloc 运算符分别处理严格基于标签和整数的索引。 +ix 运算符仍然可用,但并不推荐。 -![表5-4 DataFrame的索引选项](img/7178691-64354f2ab777bd8c.png) +![表 5-4 DataFrame 的索引选项](img/7178691-64354f2ab777bd8c.png) ## 整数索引 -处理整数索引的pandas对象常常难住新手,因为它与Python内置的列表和元组的索引语法不同。例如,你可能不认为下面的代码会出错: +处理整数索引的 pandas 对象常常难住新手,因为它与 Python 内置的列表和元组的索引语法不同。例如,你可能不认为下面的代码会出错: ```python ser = pd.Series(np.arange(3.)) ser ser[-1] ``` -这里,pandas可以勉强进行整数索引,但是会导致小bug。我们有包含0,1,2的索引,但是引入用户想要的东西(基于标签或位置的索引)很难: +这里,pandas 可以勉强进行整数索引,但是会导致小 bug。我们有包含 0,1,2 的索引,但是引入用户想要的东西(基于标签或位置的索引)很难: ```python In [144]: ser Out[144]: @@ -995,7 +995,7 @@ In [146]: ser2[-1] Out[146]: 2.0 ``` -为了进行统一,如果轴索引含有整数,数据选取总会使用标签。为了更准确,请使用loc(标签)或iloc(整数): +为了进行统一,如果轴索引含有整数,数据选取总会使用标签。为了更准确,请使用 loc(标签)或 iloc(整数): ```python In [147]: ser[:1] Out[147]: @@ -1015,7 +1015,7 @@ dtype: float64 ``` ## 算术运算和数据对齐 -pandas最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。对于有数据库经验的用户,这就像在索引标签上进行自动外连接。看一个简单的例子: +pandas 最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。对于有数据库经验的用户,这就像在索引标签上进行自动外连接。看一个简单的例子: ```python In [150]: s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e']) @@ -1053,9 +1053,9 @@ g NaN dtype: float64 ``` -自动的数据对齐操作在不重叠的索引处引入了NA值。缺失值会在算术运算过程中传播。 +自动的数据对齐操作在不重叠的索引处引入了 NA 值。缺失值会在算术运算过程中传播。 -对于DataFrame,对齐操作会同时发生在行和列上: +对于 DataFrame,对齐操作会同时发生在行和列上: ```python In [155]: df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'), .....: index=['Ohio', 'Texas', 'Colorado']) @@ -1079,7 +1079,7 @@ Texas 6.0 7.0 8.0 Oregon 9.0 10.0 11.0 ``` -把它们相加后将会返回一个新的DataFrame,其索引和列为原来那两个DataFrame的并集: +把它们相加后将会返回一个新的 DataFrame,其索引和列为原来那两个 DataFrame 的并集: ```python In [159]: df1 + df2 Out[159]: @@ -1091,9 +1091,9 @@ Texas 9.0 NaN 12.0 NaN Utah NaN NaN NaN NaN ``` -因为'c'和'e'列均不在两个DataFrame对象中,在结果中以缺省值呈现。行也是同样。 +因为'c'和'e'列均不在两个 DataFrame 对象中,在结果中以缺省值呈现。行也是同样。 -如果DataFrame对象相加,没有共用的列或行标签,结果都会是空: +如果 DataFrame 对象相加,没有共用的列或行标签,结果都会是空: ```python In [160]: df1 = pd.DataFrame({'A': [1, 2]}) @@ -1119,7 +1119,7 @@ Out[164]: ``` ## 在算术方法中填充值 -在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如0): +在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如 0): ```python In [165]: df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)), .....: columns=list('abcd')) @@ -1145,7 +1145,7 @@ Out[169]: 3 15.0 16.0 17.0 18.0 19.0 ``` -将它们相加时,没有重叠的位置就会产生NA值: +将它们相加时,没有重叠的位置就会产生 NA 值: ```python In [170]: df1 + df2 Out[170]: @@ -1156,7 +1156,7 @@ Out[170]: 3 NaN NaN NaN NaN NaN ``` -使用df1的add方法,传入df2以及一个fill_value参数: +使用 df1 的 add 方法,传入 df2 以及一个 fill_value 参数: ```python In [171]: df1.add(df2, fill_value=0) Out[171]: @@ -1167,7 +1167,7 @@ Out[171]: 3 15.0 16.0 17.0 18.0 19.0 ``` -表5-5列出了Series和DataFrame的算术方法。它们每个都有一个副本,以字母r开头,它会翻转参数。因此这两个语句是等价的: +表 5-5 列出了 Series 和 DataFrame 的算术方法。它们每个都有一个副本,以字母 r 开头,它会翻转参数。因此这两个语句是等价的: ```python In [172]: 1 / df1 Out[172]: @@ -1184,9 +1184,9 @@ Out[173]: 2 0.125000 0.111111 0.100000 0.090909 ``` -![表5-5 灵活的算术方法](img/7178691-16857a1021f98d1f.png) +![表 5-5 灵活的算术方法](img/7178691-16857a1021f98d1f.png) -与此类似,在对Series或DataFrame重新索引时,也可以指定一个填充值: +与此类似,在对 Series 或 DataFrame 重新索引时,也可以指定一个填充值: ```python In [174]: df1.reindex(columns=df2.columns, fill_value=0) Out[174]: @@ -1196,8 +1196,8 @@ Out[174]: 2 8.0 9.0 10.0 11.0 0 ``` -## DataFrame和Series之间的运算 -跟不同维度的NumPy数组一样,DataFrame和Series之间算术运算也是有明确规定的。先来看一个具有启发性的例子,计算一个二维数组与其某行之间的差: +## DataFrame 和 Series 之间的运算 +跟不同维度的 NumPy 数组一样,DataFrame 和 Series 之间算术运算也是有明确规定的。先来看一个具有启发性的例子,计算一个二维数组与其某行之间的差: ```python In [175]: arr = np.arange(12.).reshape((3, 4)) @@ -1217,7 +1217,7 @@ array([[ 0., 0., 0., 0.], [ 8., 8., 8., 8.]]) ``` -当我们从arr减去arr[0],每一行都会执行这个操作。这就叫做广播(broadcasting),附录A将对此进行详细讲解。DataFrame和Series之间的运算差不多也是如此: +当我们从 arr 减去 arr[0],每一行都会执行这个操作。这就叫做广播(broadcasting),附录 A 将对此进行详细讲解。DataFrame 和 Series 之间的运算差不多也是如此: ```python In [179]: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), .....: columns=list('bde'), @@ -1241,7 +1241,7 @@ e 2.0 Name: Utah, dtype: float64 ``` -默认情况下,DataFrame和Series之间的算术运算会将Series的索引匹配到DataFrame的列,然后沿着行一直向下广播: +默认情况下,DataFrame 和 Series 之间的算术运算会将 Series 的索引匹配到 DataFrame 的列,然后沿着行一直向下广播: ```python In [183]: frame - series Out[183]: @@ -1252,7 +1252,7 @@ Texas 6.0 6.0 6.0 Oregon 9.0 9.0 9.0 ``` -如果某个索引值在DataFrame的列或Series的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集: +如果某个索引值在 DataFrame 的列或 Series 的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集: ```python In [184]: series2 = pd.Series(range(3), index=['b', 'e', 'f']) @@ -1294,10 +1294,10 @@ Texas -1.0 0.0 1.0 Oregon -1.0 0.0 1.0 ``` -传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配DataFrame的行索引(axis='index' or axis=0)并进行广播。 +传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配 DataFrame 的行索引(axis='index' or axis=0)并进行广播。 ## 函数应用和映射 -NumPy的ufuncs(元素级数组方法)也可用于操作pandas对象: +NumPy 的 ufuncs(元素级数组方法)也可用于操作 pandas 对象: ```python In [190]: frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'), .....: index=['Utah', 'Ohio', 'Texas', 'Oregon']) @@ -1319,7 +1319,7 @@ Texas 0.092908 0.281746 0.769023 Oregon 1.246435 1.007189 1.296221 ``` -另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame的apply方法即可实现此功能: +另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。DataFrame 的 apply 方法即可实现此功能: ```python In [193]: f = lambda x: x.max() - x.min() @@ -1331,9 +1331,9 @@ e 2.689627 dtype: float64 ``` -这里的函数f,计算了一个Series的最大值和最小值的差,在frame的每列都执行了一次。结果是一个Series,使用frame的列作为索引。 +这里的函数 f,计算了一个 Series 的最大值和最小值的差,在 frame 的每列都执行了一次。结果是一个 Series,使用 frame 的列作为索引。 -如果传递axis='columns'到apply,这个函数会在每行执行: +如果传递 axis='columns'到 apply,这个函数会在每行执行: ```python In [195]: frame.apply(f, axis='columns') Out[195]: @@ -1344,9 +1344,9 @@ Oregon 2.542656 dtype: float64 ``` -许多最为常见的数组统计功能都被实现成DataFrame的方法(如sum和mean),因此无需使用apply方法。 +许多最为常见的数组统计功能都被实现成 DataFrame 的方法(如 sum 和 mean),因此无需使用 apply 方法。 -传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的Series: +传递到 apply 的函数不是必须返回一个标量,还可以返回由多个值组成的 Series: ```python In [196]: def f(x): .....: return pd.Series([x.min(), x.max()], index=['min', 'max']) @@ -1358,7 +1358,7 @@ min -0.555730 0.281746 -1.296221 max 1.246435 1.965781 1.393406 ``` -元素级的Python函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串,使用applymap即可: +元素级的 Python 函数也是可以用的。假如你想得到 frame 中各个浮点值的格式化字符串,使用 applymap 即可: ```python In [198]: format = lambda x: '%.2f' % x @@ -1371,7 +1371,7 @@ Texas 0.09 0.28 0.77 Oregon 1.25 1.01 -1.30 ``` -之所以叫做applymap,是因为Series有一个用于应用元素级函数的map方法: +之所以叫做 applymap,是因为 Series 有一个用于应用元素级函数的 map 方法: ```python In [200]: frame['e'].map(format) Out[200]: @@ -1383,7 +1383,7 @@ Name: e, dtype: object ``` ## 排序和排名 -根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用sort_index方法,它将返回一个已排序的新对象: +根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用 sort_index 方法,它将返回一个已排序的新对象: ```python In [201]: obj = pd.Series(range(4), index=['d', 'a', 'b', 'c']) @@ -1396,7 +1396,7 @@ d 0 dtype: int64 ``` -对于DataFrame,则可以根据任意一个轴上的索引进行排序: +对于 DataFrame,则可以根据任意一个轴上的索引进行排序: ```python In [203]: frame = pd.DataFrame(np.arange(8).reshape((2, 4)), .....: index=['three', 'one'], @@ -1424,7 +1424,7 @@ three 0 3 2 1 one 4 7 6 5 ``` -若要按值对Series进行排序,可使用其sort_values方法: +若要按值对 Series 进行排序,可使用其 sort_values 方法: ```python In [207]: obj = pd.Series([4, 7, -3, 2]) @@ -1437,7 +1437,7 @@ Out[208]: dtype: int64 ``` -在排序时,任何缺失值默认都会被放到Series的末尾: +在排序时,任何缺失值默认都会被放到 Series 的末尾: ```python In [209]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2]) @@ -1452,7 +1452,7 @@ Out[210]: dtype: float64 ``` -当排序一个DataFrame时,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_values的by选项即可达到该目的: +当排序一个 DataFrame 时,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给 sort_values 的 by 选项即可达到该目的: ```python In [211]: frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]}) @@ -1484,7 +1484,7 @@ Out[214]: 1 1 7 ``` -排名会从1开始一直到数组中有效数据的数量。接下来介绍Series和DataFrame的rank方法。默认情况下,rank是通过“为各组分配一个平均排名”的方式破坏平级关系的: +排名会从 1 开始一直到数组中有效数据的数量。接下来介绍 Series 和 DataFrame 的 rank 方法。默认情况下,rank 是通过“为各组分配一个平均排名”的方式破坏平级关系的: ```python In [215]: obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) In [216]: obj.rank() @@ -1513,7 +1513,7 @@ Out[217]: dtype: float64 ``` -这里,条目0和2没有使用平均排名6.5,它们被设成了6和7,因为数据中标签0位于标签2的前面。 +这里,条目 0 和 2 没有使用平均排名 6.5,它们被设成了 6 和 7,因为数据中标签 0 位于标签 2 的前面。 你也可以按降序进行排名: ```python @@ -1530,7 +1530,7 @@ Out[218]: dtype: float64 ``` -表5-6列出了所有用于破坏平级关系的method选项。DataFrame可以在行或列上计算排名: +表 5-6 列出了所有用于破坏平级关系的 method 选项。DataFrame 可以在行或列上计算排名: ```python In [219]: frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], .....: 'c': [-2, 5, 8, -2.5]}) @@ -1552,10 +1552,10 @@ Out[221]: 3 2.0 3.0 1.0 ``` -![表5-6 排名时用于破坏平级关系的方法](img/7178691-7edfab5b4a147581.png) +![表 5-6 排名时用于破坏平级关系的方法](img/7178691-7edfab5b4a147581.png) ## 带有重复标签的轴索引 -直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然许多pandas函数(如reindex)都要求标签唯一,但这并不是强制性的。我们来看看下面这个简单的带有重复索引值的Series: +直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然许多 pandas 函数(如 reindex)都要求标签唯一,但这并不是强制性的。我们来看看下面这个简单的带有重复索引值的 Series: ```python In [222]: obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c']) @@ -1569,13 +1569,13 @@ c 4 dtype: int64 ``` -索引的is_unique属性可以告诉你它的值是否是唯一的: +索引的 is_unique 属性可以告诉你它的值是否是唯一的: ```python In [224]: obj.index.is_unique Out[224]: False ``` -对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个Series;而对应单个值的,则返回一个标量值: +对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个 Series;而对应单个值的,则返回一个标量值: ```python In [225]: obj['a'] Out[225]: @@ -1589,7 +1589,7 @@ Out[226]: 4 这样会使代码变复杂,因为索引的输出类型会根据标签是否有重复发生变化。 -对DataFrame的行进行索引时也是如此: +对 DataFrame 的行进行索引时也是如此: ```python In [227]: df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b']) @@ -1609,7 +1609,7 @@ b 0.476985 3.248944 -1.021228 ``` # 5.3 汇总和计算描述统计 -pandas对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从Series中提取单个值(如sum或mean)或从DataFrame的行或列中提取一个Series。跟对应的NumPy数组方法相比,它们都是基于没有缺失数据的假设而构建的。看一个简单的DataFrame: +pandas 对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从 Series 中提取单个值(如 sum 或 mean)或从 DataFrame 的行或列中提取一个 Series。跟对应的 NumPy 数组方法相比,它们都是基于没有缺失数据的假设而构建的。看一个简单的 DataFrame: ```python In [230]: df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5], .....: [np.nan, np.nan], [0.75, -1.3]], @@ -1625,7 +1625,7 @@ c NaN NaN d 0.75 -1.3 ``` -调用DataFrame的sum方法将会返回一个含有列的和的Series: +调用 DataFrame 的 sum 方法将会返回一个含有列的和的 Series: ```python In [232]: df.sum() Out[232]: @@ -1634,7 +1634,7 @@ two -5.80 dtype: float64 ``` -传入axis='columns'或axis=1将会按行进行求和运算: +传入 axis='columns'或 axis=1 将会按行进行求和运算: ```python In [233]: df.sum(axis=1) Out[233]: @@ -1644,7 +1644,7 @@ c     NaN d   -0.55 ``` -NA值会自动被排除,除非整个切片(这里指的是行或列)都是NA。通过skipna选项可以禁用该功能: +NA 值会自动被排除,除非整个切片(这里指的是行或列)都是 NA。通过 skipna 选项可以禁用该功能: ```python In [234]: df.mean(axis='columns', skipna=False) Out[234]: @@ -1655,11 +1655,11 @@ d -0.275 dtype: float64 ``` -表5-7列出了这些约简方法的常用选项。 +表 5-7 列出了这些约简方法的常用选项。 ![](img/7178691-af35e3809278410e.jpg) -有些方法(如idxmin和idxmax)返回的是间接统计(比如达到最小值或最大值的索引): +有些方法(如 idxmin 和 idxmax)返回的是间接统计(比如达到最小值或最大值的索引): ```python In [235]: df.idxmax() Out[235]: @@ -1679,7 +1679,7 @@ c NaN NaN d 9.25 -5.8 ``` -还有一种方法,它既不是约简型也不是累计型。describe就是一个例子,它用于一次性产生多个汇总统计: +还有一种方法,它既不是约简型也不是累计型。describe 就是一个例子,它用于一次性产生多个汇总统计: ```python In [237]: df.describe() Out[237]: @@ -1694,7 +1694,7 @@ min 0.750000 -4.500000 max 7.100000 -1.300000 ``` -对于非数值型数据,describe会产生另外一种汇总统计: +对于非数值型数据,describe 会产生另外一种汇总统计: ```python In [238]: obj = pd.Series(['a', 'a', 'b', 'c'] * 4) @@ -1707,17 +1707,17 @@ freq 8 dtype: object ``` -表5-8列出了所有与描述统计相关的方法。 +表 5-8 列出了所有与描述统计相关的方法。 ![](img/7178691-11fa967f658ac314.jpg) ## 相关系数与协方差 -有些汇总统计(如相关系数和协方差)是通过参数对计算出来的。我们来看几个DataFrame,它们的数据来自Yahoo!Finance的股票价格和成交量,使用的是pandas-datareader包(可以用conda或pip安装): +有些汇总统计(如相关系数和协方差)是通过参数对计算出来的。我们来看几个 DataFrame,它们的数据来自 Yahoo!Finance 的股票价格和成交量,使用的是 pandas-datareader 包(可以用 conda 或 pip 安装): ```python conda install pandas-datareader ``` -我使用pandas_datareader模块下载了一些股票数据: +我使用 pandas_datareader 模块下载了一些股票数据: ```python import pandas_datareader.data as web all_data = {ticker: web.get_data_yahoo(ticker) @@ -1729,9 +1729,9 @@ volume = pd.DataFrame({ticker: data['Volume'] for ticker, data in all_data.items()}) ``` ->注意:此时Yahoo! Finance已经不存在了,因为2017年Yahoo!被Verizon收购了。参阅pandas-datareader文档,可以学习最新的功能。 +>注意:此时 Yahoo! Finance 已经不存在了,因为 2017 年 Yahoo!被 Verizon 收购了。参阅 pandas-datareader 文档,可以学习最新的功能。 -现在计算价格的百分数变化,时间序列的操作会在第11章介绍: +现在计算价格的百分数变化,时间序列的操作会在第 11 章介绍: ```python In [242]: returns = price.pct_change() @@ -1746,7 +1746,7 @@ Date 2016-10-21 -0.003930 0.003011 -0.012474 0.042096 ``` -Series的corr方法用于计算两个Series中重叠的、非NA的、按索引对齐的值的相关系数。与此类似,cov用于计算协方差: +Series 的 corr 方法用于计算两个 Series 中重叠的、非 NA 的、按索引对齐的值的相关系数。与此类似,cov 用于计算协方差: ```python In [244]: returns['MSFT'].corr(returns['IBM']) Out[244]: 0.49976361144151144 @@ -1755,13 +1755,13 @@ In [245]: returns['MSFT'].cov(returns['IBM']) Out[245]: 8.8706554797035462e-05 ``` -因为MSTF是一个合理的Python属性,我们还可以用更简洁的语法选择列: +因为 MSTF 是一个合理的 Python 属性,我们还可以用更简洁的语法选择列: ```python In [246]: returns.MSFT.corr(returns.IBM) Out[246]: 0.49976361144151144 ``` -另一方面,DataFrame的corr和cov方法将以DataFrame的形式分别返回完整的相关系数或协方差矩阵: +另一方面,DataFrame 的 corr 和 cov 方法将以 DataFrame 的形式分别返回完整的相关系数或协方差矩阵: ```python In [247]: returns.corr() Out[247]: @@ -1780,7 +1780,7 @@ IBM 0.000078 0.000078 0.000146 0.000089 MSFT 0.000095 0.000108 0.000089 0.000215 ``` -利用DataFrame的corrwith方法,你可以计算其列或行跟另一个Series或DataFrame之间的相关系数。传入一个Series将会返回一个相关系数值Series(针对各列进行计算): +利用 DataFrame 的 corrwith 方法,你可以计算其列或行跟另一个 Series 或 DataFrame 之间的相关系数。传入一个 Series 将会返回一个相关系数值 Series(针对各列进行计算): ```python In [249]: returns.corrwith(returns.IBM) Out[249]: @@ -1791,7 +1791,7 @@ MSFT 0.499764 dtype: float64 ``` -传入一个DataFrame则会计算按列名配对的相关系数。这里,我计算百分比变化与成交量的相关系数: +传入一个 DataFrame 则会计算按列名配对的相关系数。这里,我计算百分比变化与成交量的相关系数: ```python In [250]: returns.corrwith(volume) Out[250]: @@ -1802,15 +1802,15 @@ MSFT -0.092950 dtype: float64 ``` -传入axis='columns'即可按行进行计算。无论如何,在计算相关系数之前,所有的数据项都会按标签对齐。 +传入 axis='columns'即可按行进行计算。无论如何,在计算相关系数之前,所有的数据项都会按标签对齐。 ## 唯一值、值计数以及成员资格 -还有一类方法可以从一维Series的值中抽取信息。看下面的例子: +还有一类方法可以从一维 Series 的值中抽取信息。看下面的例子: ```python In [251]: obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c']) ``` -第一个函数是unique,它可以得到Series中的唯一值数组: +第一个函数是 unique,它可以得到 Series 中的唯一值数组: ```python In [252]: uniques = obj.unique() @@ -1818,7 +1818,7 @@ In [253]: uniques Out[253]: array(['c', 'a', 'd', 'b'], dtype=object) ``` -返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。相似的,value_counts用于计算一个Series中各值出现的频率: +返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。相似的,value_counts 用于计算一个 Series 中各值出现的频率: ```python In [254]: obj.value_counts() Out[254]: @@ -1829,7 +1829,7 @@ d 1 dtype: int64 ``` -为了便于查看,结果Series是按值频率降序排列的。value_counts还是一个顶级pandas方法,可用于任何数组或序列: +为了便于查看,结果 Series 是按值频率降序排列的。value_counts 还是一个顶级 pandas 方法,可用于任何数组或序列: ```python In [255]: pd.value_counts(obj.values, sort=False) Out[255]: @@ -1840,7 +1840,7 @@ d 1 dtype: int64 ``` -isin用于判断矢量化集合的成员资格,可用于过滤Series中或DataFrame列中数据的子集: +isin 用于判断矢量化集合的成员资格,可用于过滤 Series 中或 DataFrame 列中数据的子集: ```python In [256]: obj Out[256]: @@ -1880,7 +1880,7 @@ Out[259]: dtype: object ``` -与isin类似的是Index.get_indexer方法,它可以给你一个索引数组,从可能包含重复值的数组到另一个不同值的数组: +与 isin 类似的是 Index.get_indexer 方法,它可以给你一个索引数组,从可能包含重复值的数组到另一个不同值的数组: ```python In [260]: to_match = pd.Series(['c', 'a', 'b', 'b', 'c', 'a']) @@ -1890,11 +1890,11 @@ In [262]: pd.Index(unique_vals).get_indexer(to_match) Out[262]: array([0, 2, 1, 1, 0, 2]) ``` -表5-9给出了这几个方法的一些参考信息。 +表 5-9 给出了这几个方法的一些参考信息。 -![表5-9 唯一值、值计数、成员资格方法](img/7178691-b53c4a9d65a2db32.png) +![表 5-9 唯一值、值计数、成员资格方法](img/7178691-b53c4a9d65a2db32.png) -有时,你可能希望得到DataFrame中多个相关列的一张柱状图。例如: +有时,你可能希望得到 DataFrame 中多个相关列的一张柱状图。例如: ```python In [263]: data = pd.DataFrame({'Qu1': [1, 3, 4, 3, 4], .....: 'Qu2': [2, 3, 1, 2, 3], @@ -1910,7 +1910,7 @@ Out[264]: 4 4 3 4 ``` -将pandas.value_counts传给该DataFrame的apply函数,就会出现: +将 pandas.value_counts 传给该 DataFrame 的 apply 函数,就会出现: ```python In [265]: result = data.apply(pd.value_counts).fillna(0) @@ -1927,6 +1927,6 @@ Out[266]: 这里,结果中的行标签是所有列的唯一值。后面的频率值是每个列中这些值的相应计数。 # 5.4 总结 -在下一章,我们将讨论用pandas读取(或加载)和写入数据集的工具。 +在下一章,我们将讨论用 pandas 读取(或加载)和写入数据集的工具。 -之后,我们将更深入地研究使用pandas进行数据清洗、规整、分析和可视化工具。 +之后,我们将更深入地研究使用 pandas 进行数据清洗、规整、分析和可视化工具。 diff --git a/docs/6.md b/docs/6.md index 1c9e320..a8850d4 100644 --- a/docs/6.md +++ b/docs/6.md @@ -1,25 +1,25 @@ # 第 6 章 数据加载、存储与文件格式 -访问数据是使用本书所介绍的这些工具的第一步。我会着重介绍pandas的数据输入与输出,虽然别的库中也有不少以此为目的的工具。 +访问数据是使用本书所介绍的这些工具的第一步。我会着重介绍 pandas 的数据输入与输出,虽然别的库中也有不少以此为目的的工具。 -输入输出通常可以划分为几个大类:读取文本文件和其他更高效的磁盘存储格式,加载数据库中的数据,利用Web API操作网络资源。 +输入输出通常可以划分为几个大类:读取文本文件和其他更高效的磁盘存储格式,加载数据库中的数据,利用 Web API 操作网络资源。 # 6.1 读写文本格式的数据 -pandas提供了一些用于将表格型数据读取为DataFrame对象的函数。表6-1对它们进行了总结,其中read_csv和read_table可能会是你今后用得最多的。 +pandas 提供了一些用于将表格型数据读取为 DataFrame 对象的函数。表 6-1 对它们进行了总结,其中 read_csv 和 read_table 可能会是你今后用得最多的。 -![表6-1 pandas中的解析函数](img/7178691-958f849e6067b19b.png) +![表 6-1 pandas 中的解析函数](img/7178691-958f849e6067b19b.png) -我将大致介绍一下这些函数在将文本数据转换为DataFrame时所用到的一些技术。这些函数的选项可以划分为以下几个大类: +我将大致介绍一下这些函数在将文本数据转换为 DataFrame 时所用到的一些技术。这些函数的选项可以划分为以下几个大类: -- 索引:将一个或多个列当做返回的DataFrame处理,以及是否从文件、用户获取列名。 +- 索引:将一个或多个列当做返回的 DataFrame 处理,以及是否从文件、用户获取列名。 - 类型推断和数据转换:包括用户定义值的转换、和自定义的缺失值标记列表等。 - 日期解析:包括组合功能,比如将分散在多个列中的日期时间信息组合成结果中的单个列。 - 迭代:支持对大文件进行逐块迭代。 - 不规整数据问题:跳过一些行、页脚、注释或其他一些不重要的东西(比如由成千上万个逗号隔开的数值数据)。 -因为工作中实际碰到的数据可能十分混乱,一些数据加载函数(尤其是read_csv)的选项逐渐变得复杂起来。面对不同的参数,感到头痛很正常(read_csv有超过50个参数)。pandas文档有这些参数的例子,如果你感到阅读某个文件很难,可以通过相似的足够多的例子找到正确的参数。 +因为工作中实际碰到的数据可能十分混乱,一些数据加载函数(尤其是 read_csv)的选项逐渐变得复杂起来。面对不同的参数,感到头痛很正常(read_csv 有超过 50 个参数)。pandas 文档有这些参数的例子,如果你感到阅读某个文件很难,可以通过相似的足够多的例子找到正确的参数。 -其中一些函数,比如pandas.read_csv,有类型推断功能,因为列数据的类型不属于数据类型。也就是说,你不需要指定列的类型到底是数值、整数、布尔值,还是字符串。其它的数据格式,如HDF5、Feather和msgpack,会在格式中存储数据类型。 +其中一些函数,比如 pandas.read_csv,有类型推断功能,因为列数据的类型不属于数据类型。也就是说,你不需要指定列的类型到底是数值、整数、布尔值,还是字符串。其它的数据格式,如 HDF5、Feather 和 msgpack,会在格式中存储数据类型。 日期和其他自定义类型的处理需要多花点工夫才行。首先我们来看一个以逗号分隔的(CSV)文本文件: ```python @@ -30,9 +30,9 @@ a,b,c,d,message 9,10,11,12,foo ``` ->笔记:这里,我用的是Unix的cat shell命令将文件的原始内容打印到屏幕上。如果你用的是Windows,你可以使用type达到同样的效果。 +>笔记:这里,我用的是 Unix 的 cat shell 命令将文件的原始内容打印到屏幕上。如果你用的是 Windows,你可以使用 type 达到同样的效果。 -由于该文件以逗号分隔,所以我们可以使用read_csv将其读入一个DataFrame: +由于该文件以逗号分隔,所以我们可以使用 read_csv 将其读入一个 DataFrame: ```python In [9]: df = pd.read_csv('examples/ex1.csv') @@ -44,7 +44,7 @@ Out[10]: 2 9 10 11 12 foo ``` -我们还可以使用read_table,并指定分隔符: +我们还可以使用 read_table,并指定分隔符: ```python In [11]: pd.read_table('examples/ex1.csv', sep=',') Out[11]: @@ -62,7 +62,7 @@ In [12]: !cat examples/ex2.csv 9,10,11,12,foo ``` -读入该文件的办法有两个。你可以让pandas为其分配默认的列名,也可以自己定义列名: +读入该文件的办法有两个。你可以让 pandas 为其分配默认的列名,也可以自己定义列名: ```python In [13]: pd.read_csv('examples/ex2.csv', header=None) Out[13]: @@ -79,7 +79,7 @@ Out[14]: 2 9 10 11 12 foo ``` -假设你希望将message列做成DataFrame的索引。你可以明确表示要将该列放到索引4的位置上,也可以通过index_col参数指定"message": +假设你希望将 message 列做成 DataFrame 的索引。你可以明确表示要将该列放到索引 4 的位置上,也可以通过 index_col 参数指定"message": ```python In [15]: names = ['a', 'b', 'c', 'd', 'message'] @@ -133,7 +133,7 @@ Out[20]: 'ddd -0.871858 -0.348382 1.100491\n'] ``` -虽然可以手动对数据进行规整,这里的字段是被数量不同的空白字符间隔开的。这种情况下,你可以传递一个正则表达式作为read_table的分隔符。可以用正则表达式表达为\s+,于是有: +虽然可以手动对数据进行规整,这里的字段是被数量不同的空白字符间隔开的。这种情况下,你可以传递一个正则表达式作为 read_table 的分隔符。可以用正则表达式表达为\s+,于是有: ```python In [21]: result = pd.read_table('examples/ex3.txt', sep='\s+') @@ -146,9 +146,9 @@ ccc -0.264273 -0.386314 -0.217601 ddd -0.871858 -0.348382 1.100491 ``` -这里,由于列名比数据行的数量少,所以read_table推断第一列应该是DataFrame的索引。 +这里,由于列名比数据行的数量少,所以 read_table 推断第一列应该是 DataFrame 的索引。 -这些解析器函数还有许多参数可以帮助你处理各种各样的异形文件格式(表6-2列出了一些)。比如说,你可以用skiprows跳过文件的第一行、第三行和第四行: +这些解析器函数还有许多参数可以帮助你处理各种各样的异形文件格式(表 6-2 列出了一些)。比如说,你可以用 skiprows 跳过文件的第一行、第三行和第四行: ```python In [23]: !cat examples/ex4.csv # hey! @@ -166,7 +166,7 @@ Out[24]: 2 9 10 11 12 foo ``` -缺失值处理是文件解析任务中的一个重要组成部分。缺失数据经常是要么没有(空字符串),要么用某个标记值表示。默认情况下,pandas会用一组经常出现的标记值进行识别,比如NA及NULL: +缺失值处理是文件解析任务中的一个重要组成部分。缺失数据经常是要么没有(空字符串),要么用某个标记值表示。默认情况下,pandas 会用一组经常出现的标记值进行识别,比如 NA 及 NULL: ```python In [25]: !cat examples/ex5.csv something,a,b,c,d,message @@ -190,7 +190,7 @@ Out[28]: 2 False False False False False False ``` -na_values可以用一个列表或集合的字符串表示缺失值: +na_values 可以用一个列表或集合的字符串表示缺失值: ```python In [29]: result = pd.read_csv('examples/ex5.csv', na_values=['NULL']) @@ -202,7 +202,7 @@ Out[30]: 2 three 9 10 11.0 12 foo ``` -字典的各列可以使用不同的NA标记值: +字典的各列可以使用不同的 NA 标记值: ```python In [31]: sentinels = {'message': ['foo', 'NA'], 'something': ['two']} @@ -214,7 +214,7 @@ something a b c d message 2 three 9 10 11.0 12 NaN ``` -表6-2列出了pandas.read_csv和pandas.read_table常用的选项。 +表 6-2 列出了 pandas.read_csv 和 pandas.read_table 常用的选项。 ![](img/7178691-082daf4a00ed9494.png) @@ -225,7 +225,7 @@ something a b c d message ## 逐块读取文本文件 在处理很大的文件时,或找出大文件中的参数集以便于后续处理时,你可能只想读取文件的一小部分或逐块对文件进行迭代。 -在看大文件之前,我们先设置pandas显示地更紧些: +在看大文件之前,我们先设置 pandas 显示地更紧些: ```python In [33]: pd.options.display.max_rows = 10 ``` @@ -252,7 +252,7 @@ Out[35]: If you want to only read a small ``` -如果只想读取几行(避免读取整个文件),通过nrows进行指定即可: +如果只想读取几行(避免读取整个文件),通过 nrows 进行指定即可: ```python In [36]: pd.read_csv('examples/ex6.csv', nrows=5) Out[36]: @@ -264,7 +264,7 @@ Out[36]: 4 0.354628 -0.133116 0.283763 -0.837063 Q ``` -要逐块读取文件,可以指定chunksize(行数): +要逐块读取文件,可以指定 chunksize(行数): ```python In [874]: chunker = pd.read_csv('ch06/ex6.csv', chunksize=1000) @@ -272,7 +272,7 @@ In [875]: chunker Out[875]:  ``` -read_csv所返回的这个TextParser对象使你可以根据chunksize对文件进行逐块迭代。比如说,我们可以迭代处理ex6.csv,将值计数聚合到"key"列中,如下所示: +read_csv 所返回的这个 TextParser 对象使你可以根据 chunksize 对文件进行逐块迭代。比如说,我们可以迭代处理 ex6.csv,将值计数聚合到"key"列中,如下所示: ```python chunker = pd.read_csv('examples/ex6.csv', chunksize=1000) @@ -300,10 +300,10 @@ H 330.0 dtype: float64 ``` -TextParser还有一个get_chunk方法,它使你可以读取任意大小的块。 +TextParser 还有一个 get_chunk 方法,它使你可以读取任意大小的块。 ## 将数据写出到文本格式 -数据也可以被输出为分隔符格式的文本。我们再来看看之前读过的一个CSV文件: +数据也可以被输出为分隔符格式的文本。我们再来看看之前读过的一个 CSV 文件: ```python In [41]: data = pd.read_csv('examples/ex5.csv') @@ -315,7 +315,7 @@ Out[42]: 2 three 9 10 11.0 12 foo ``` -利用DataFrame的to_csv方法,我们可以将数据写到一个以逗号分隔的文件中: +利用 DataFrame 的 to_csv 方法,我们可以将数据写到一个以逗号分隔的文件中: ```python In [43]: data.to_csv('examples/out.csv') @@ -326,7 +326,7 @@ In [44]: !cat examples/out.csv 2,three,9,10,11.0,12,foo ``` -当然,还可以使用其他分隔符(由于这里直接写出到sys.stdout,所以仅仅是打印出文本结果而已): +当然,还可以使用其他分隔符(由于这里直接写出到 sys.stdout,所以仅仅是打印出文本结果而已): ```python In [45]: import sys @@ -363,7 +363,7 @@ a,b,c 9,10,11.0 ``` -Series也有一个to_csv方法: +Series 也有一个 to_csv 方法: ```python In [50]: dates = pd.date_range('1/1/2000', periods=7) @@ -382,7 +382,7 @@ In [53]: !cat examples/tseries.csv ``` ## 处理分隔符格式 -大部分存储在磁盘上的表格型数据都能用pandas.read_table进行加载。然而,有时还是需要做一些手工处理。由于接收到含有畸形行的文件而使read_table出毛病的情况并不少见。为了说明这些基本工具,看看下面这个简单的CSV文件: +大部分存储在磁盘上的表格型数据都能用 pandas.read_table 进行加载。然而,有时还是需要做一些手工处理。由于接收到含有畸形行的文件而使 read_table 出毛病的情况并不少见。为了说明这些基本工具,看看下面这个简单的 CSV 文件: ```python In [54]: !cat examples/ex7.csv "a","b","c" @@ -390,7 +390,7 @@ In [54]: !cat examples/ex7.csv "1","2","3" ``` -对于任何单字符分隔符文件,可以直接使用Python内置的csv模块。将任意已打开的文件或文件型的对象传给csv.reader: +对于任何单字符分隔符文件,可以直接使用 Python 内置的 csv 模块。将任意已打开的文件或文件型的对象传给 csv.reader: ```python import csv f = open('examples/ex7.csv') @@ -398,7 +398,7 @@ f = open('examples/ex7.csv') reader = csv.reader(f) ``` -对这个reader进行迭代将会为每行产生一个元组(并移除了所有的引号):对这个reader进行迭代将会为每行产生一个元组(并移除了所有的引号): +对这个 reader 进行迭代将会为每行产生一个元组(并移除了所有的引号):对这个 reader 进行迭代将会为每行产生一个元组(并移除了所有的引号): ```python In [56]: for line in reader: ....: print(line) @@ -418,7 +418,7 @@ In [57]: with open('examples/ex7.csv') as f: In [58]: header, values = lines[0], lines[1:] ``` -然后,我们可以用字典构造式和zip(*values),后者将行转置为列,创建数据列的字典: +然后,我们可以用字典构造式和 zip(*values),后者将行转置为列,创建数据列的字典: ```python In [59]: data_dict = {h: v for h, v in zip(header, zip(*values))} @@ -426,7 +426,7 @@ In [60]: data_dict Out[60]: {'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')} ``` -CSV文件的形式有很多。只需定义csv.Dialect的一个子类即可定义出新格式(如专门的分隔符、字符串引用约定、行结束符等): +CSV 文件的形式有很多。只需定义 csv.Dialect 的一个子类即可定义出新格式(如专门的分隔符、字符串引用约定、行结束符等): ```python class my_dialect(csv.Dialect): lineterminator = '\n' @@ -436,18 +436,18 @@ class my_dialect(csv.Dialect): reader = csv.reader(f, dialect=my_dialect) ``` -各个CSV语支的参数也可以用关键字的形式提供给csv.reader,而无需定义子类: +各个 CSV 语支的参数也可以用关键字的形式提供给 csv.reader,而无需定义子类: ```python reader = csv.reader(f, delimiter='|') ``` -可用的选项(csv.Dialect的属性)及其功能如表6-3所示。 +可用的选项(csv.Dialect 的属性)及其功能如表 6-3 所示。 ![](img/7178691-7a1cee622459072b.png) ->笔记:对于那些使用复杂分隔符或多字符分隔符的文件,csv模块就无能为力了。这种情况下,你就只能使用字符串的split方法或正则表达式方法re.split进行行拆分和其他整理工作了。 +>笔记:对于那些使用复杂分隔符或多字符分隔符的文件,csv 模块就无能为力了。这种情况下,你就只能使用字符串的 split 方法或正则表达式方法 re.split 进行行拆分和其他整理工作了。 -要手工输出分隔符文件,你可以使用csv.writer。它接受一个已打开且可写的文件对象以及跟csv.reader相同的那些语支和格式化选项: +要手工输出分隔符文件,你可以使用 csv.writer。它接受一个已打开且可写的文件对象以及跟 csv.reader 相同的那些语支和格式化选项: ```python with open('mydata.csv', 'w') as f: writer = csv.writer(f, dialect=my_dialect) @@ -457,8 +457,8 @@ with open('mydata.csv', 'w') as f: writer.writerow(('7', '8', '9')) ``` -## JSON数据 -JSON(JavaScript Object Notation的简称)已经成为通过HTTP请求在Web浏览器和其他应用程序之间发送数据的标准格式之一。它是一种比表格型文本格式(如CSV)灵活得多的数据格式。下面是一个例子: +## JSON 数据 +JSON(JavaScript Object Notation 的简称)已经成为通过 HTTP 请求在 Web 浏览器和其他应用程序之间发送数据的标准格式之一。它是一种比表格型文本格式(如 CSV)灵活得多的数据格式。下面是一个例子: ```python obj = """ {"name": "Wes", @@ -470,7 +470,7 @@ obj = """ } """ ``` -除其空值null和一些其他的细微差别(如列表末尾不允许存在多余的逗号)之外,JSON非常接近于有效的Python代码。基本类型有对象(字典)、数组(列表)、字符串、数值、布尔值以及null。对象中所有的键都必须是字符串。许多Python库都可以读写JSON数据。我将使用json,因为它是构建于Python标准库中的。通过json.loads即可将JSON字符串转换成Python形式: +除其空值 null 和一些其他的细微差别(如列表末尾不允许存在多余的逗号)之外,JSON 非常接近于有效的 Python 代码。基本类型有对象(字典)、数组(列表)、字符串、数值、布尔值以及 null。对象中所有的键都必须是字符串。许多 Python 库都可以读写 JSON 数据。我将使用 json,因为它是构建于 Python 标准库中的。通过 json.loads 即可将 JSON 字符串转换成 Python 形式: ```python In [62]: import json @@ -485,12 +485,12 @@ Out[64]: {'age': 38, 'name': 'Katie', 'pets': ['Sixes', 'Stache', 'Cisco']}]} ``` -json.dumps则将Python对象转换成JSON格式: +json.dumps 则将 Python 对象转换成 JSON 格式: ```python In [65]: asjson = json.dumps(result) ``` -如何将(一个或一组)JSON对象转换为DataFrame或其他便于分析的数据结构就由你决定了。最简单方便的方式是:向DataFrame构造器传入一个字典的列表(就是原先的JSON对象),并选取数据字段的子集: +如何将(一个或一组)JSON 对象转换为 DataFrame 或其他便于分析的数据结构就由你决定了。最简单方便的方式是:向 DataFrame 构造器传入一个字典的列表(就是原先的 JSON 对象),并选取数据字段的子集: ```python In [66]: siblings = pd.DataFrame(result['siblings'], columns=['name', 'age']) @@ -501,7 +501,7 @@ Out[67]: 1 Katie 38 ``` -pandas.read_json可以自动将特别格式的JSON数据集转换为Series或DataFrame。例如: +pandas.read_json 可以自动将特别格式的 JSON 数据集转换为 Series 或 DataFrame。例如: ```python In [68]: !cat examples/example.json [{"a": 1, "b": 2, "c": 3}, @@ -509,7 +509,7 @@ In [68]: !cat examples/example.json {"a": 7, "b": 8, "c": 9}] ``` -pandas.read_json的默认选项假设JSON数组中的每个对象是表格中的一行: +pandas.read_json 的默认选项假设 JSON 数组中的每个对象是表格中的一行: ```python In [69]: data = pd.read_json('examples/example.json') @@ -521,9 +521,9 @@ Out[70]: 2 7 8 9 ``` -第7章中关于USDA Food Database的那个例子进一步讲解了JSON数据的读取和处理(包括嵌套记录)。 +第 7 章中关于 USDA Food Database 的那个例子进一步讲解了 JSON 数据的读取和处理(包括嵌套记录)。 -如果你需要将数据从pandas输出到JSON,可以使用to_json方法: +如果你需要将数据从 pandas 输出到 JSON,可以使用 to_json 方法: ```python In [71]: print(data.to_json()) {"a":{"0":1,"1":4,"2":7},"b":{"0":2,"1":5,"2":8},"c":{"0":3,"1":6,"2":9}} @@ -532,19 +532,19 @@ In [72]: print(data.to_json(orient='records')) [{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}] ``` -## XML和HTML:Web信息收集 +## XML 和 HTML:Web 信息收集 -Python有许多可以读写常见的HTML和XML格式数据的库,包括lxml、Beautiful Soup和html5lib。lxml的速度比较快,但其它的库处理有误的HTML或XML文件更好。 +Python 有许多可以读写常见的 HTML 和 XML 格式数据的库,包括 lxml、Beautiful Soup 和 html5lib。lxml 的速度比较快,但其它的库处理有误的 HTML 或 XML 文件更好。 -pandas有一个内置的功能,read_html,它可以使用lxml和Beautiful Soup自动将HTML文件中的表格解析为DataFrame对象。为了进行展示,我从美国联邦存款保险公司下载了一个HTML文件(pandas文档中也使用过),它记录了银行倒闭的情况。首先,你需要安装read_html用到的库: +pandas 有一个内置的功能,read_html,它可以使用 lxml 和 Beautiful Soup 自动将 HTML 文件中的表格解析为 DataFrame 对象。为了进行展示,我从美国联邦存款保险公司下载了一个 HTML 文件(pandas 文档中也使用过),它记录了银行倒闭的情况。首先,你需要安装 read_html 用到的库: ``` conda install lxml pip install beautifulsoup4 html5lib ``` -如果你用的不是conda,可以使用``pip install lxml``。 +如果你用的不是 conda,可以使用``pip install lxml``。 -pandas.read_html有一些选项,默认条件下,它会搜索、尝试解析标签内的的表格数据。结果是一个列表的DataFrame对象: +pandas.read_html 有一些选项,默认条件下,它会搜索、尝试解析
标签内的的表格数据。结果是一个列表的 DataFrame 对象: ```python In [73]: tables = pd.read_html('examples/fdic_failed_bank_list.html') @@ -569,7 +569,7 @@ Out[76]: 4 First-Citizens Bank & Trust Company March 11, 2016 June 16, 2016 ``` -因为failures有许多列,pandas插入了一个换行符\。 +因为 failures 有许多列,pandas 插入了一个换行符\。 这里,我们可以做一些数据清洗和分析(后面章节会进一步讲解),比如计算按年份计算倒闭的银行数: ```python @@ -591,12 +591,12 @@ Out[78]: Name: Closing Date, Length: 15, dtype: int64 ``` -## 利用lxml.objectify解析XML -XML(Extensible Markup Language)是另一种常见的支持分层、嵌套数据以及元数据的结构化数据格式。本书所使用的这些文件实际上来自于一个很大的XML文档。 +## 利用 lxml.objectify 解析 XML +XML(Extensible Markup Language)是另一种常见的支持分层、嵌套数据以及元数据的结构化数据格式。本书所使用的这些文件实际上来自于一个很大的 XML 文档。 -前面,我介绍了pandas.read_html函数,它可以使用lxml或Beautiful Soup从HTML解析数据。XML和HTML的结构很相似,但XML更为通用。这里,我会用一个例子演示如何利用lxml从XML格式解析数据。 +前面,我介绍了 pandas.read_html 函数,它可以使用 lxml 或 Beautiful Soup 从 HTML 解析数据。XML 和 HTML 的结构很相似,但 XML 更为通用。这里,我会用一个例子演示如何利用 lxml 从 XML 格式解析数据。 -纽约大都会运输署发布了一些有关其公交和列车服务的数据资料(http://www.mta.info/developers/download.html)。这里,我们将看看包含在一组XML文件中的运行情况数据。每项列车或公交服务都有各自的文件(如Metro-North Railroad的文件是Performance_MNR.xml),其中每条XML记录就是一条月度数据,如下所示: +纽约大都会运输署发布了一些有关其公交和列车服务的数据资料(http://www.mta.info/developers/download.html)。这里,我们将看看包含在一组 XML 文件中的运行情况数据。每项列车或公交服务都有各自的文件(如 Metro-North Railroad 的文件是 Performance_MNR.xml),其中每条 XML 记录就是一条月度数据,如下所示: ```xml 373889 @@ -621,7 +621,7 @@ XML(Extensible Markup Language)是另一种常见的支持分层、嵌套数 ``` -我们先用lxml.objectify解析该文件,然后通过getroot得到该XML文件的根节点的引用: +我们先用 lxml.objectify 解析该文件,然后通过 getroot 得到该 XML 文件的根节点的引用: ```python from lxml import objectify @@ -630,7 +630,7 @@ parsed = objectify.parse(open(path)) root = parsed.getroot() ``` -root.INDICATOR返回一个用于产生各个XML元素的生成器。对于每条记录,我们可以用标记名(如YTD_ACTUAL)和数据值填充一个字典(排除几个标记): +root.INDICATOR 返回一个用于产生各个XML 元素的生成器。对于每条记录,我们可以用标记名(如 YTD_ACTUAL)和数据值填充一个字典(排除几个标记): ```python data = [] @@ -646,7 +646,7 @@ for elt in root.INDICATOR: data.append(el_data) ``` -最后,将这组字典转换为一个DataFrame: +最后,将这组字典转换为一个 DataFrame: ```python In [81]: perf = pd.DataFrame(data) @@ -657,14 +657,14 @@ Columns: [] Index: [] ``` -XML数据可以比本例复杂得多。每个标记都可以有元数据。看看下面这个HTML的链接标签(它也算是一段有效的XML): +XML 数据可以比本例复杂得多。每个标记都可以有元数据。看看下面这个 HTML 的链接标签(它也算是一段有效的 XML): ```python from io import StringIO tag = 'Google' root = objectify.parse(StringIO(tag)).getroot() ``` -现在就可以访问标签或链接文本中的任何字段了(如href): +现在就可以访问标签或链接文本中的任何字段了(如 href): ```python In [84]: root Out[84]: @@ -678,7 +678,7 @@ Out[86]: 'Google' # 6.2 二进制数据格式 -实现数据的高效二进制格式存储最简单的办法之一是使用Python内置的pickle序列化。pandas对象都有一个用于将数据以pickle格式保存到磁盘上的to_pickle方法: +实现数据的高效二进制格式存储最简单的办法之一是使用 Python 内置的 pickle 序列化。pandas 对象都有一个用于将数据以 pickle 格式保存到磁盘上的 to_pickle 方法: ```python In [87]: frame = pd.read_csv('examples/ex1.csv') @@ -692,7 +692,7 @@ Out[88]: In [89]: frame.to_pickle('examples/frame_pickle') ``` -你可以通过pickle直接读取被pickle化的数据,或是使用更为方便的pandas.read_pickle: +你可以通过 pickle 直接读取被 pickle 化的数据,或是使用更为方便的 pandas.read_pickle: ```python In [90]: pd.read_pickle('examples/frame_pickle') Out[90]: @@ -702,18 +702,18 @@ Out[90]: 2 9 10 11 12 foo ``` ->注意:pickle仅建议用于短期存储格式。其原因是很难保证该格式永远是稳定的;今天pickle的对象可能无法被后续版本的库unpickle出来。虽然我尽力保证这种事情不会发生在pandas中,但是今后的某个时候说不定还是得“打破”该pickle格式。 +>注意:pickle 仅建议用于短期存储格式。其原因是很难保证该格式永远是稳定的;今天 pickle 的对象可能无法被后续版本的库 unpickle 出来。虽然我尽力保证这种事情不会发生在 pandas 中,但是今后的某个时候说不定还是得“打破”该 pickle 格式。 -pandas内置支持两个二进制数据格式:HDF5和MessagePack。下一节,我会给出几个HDF5的例子,但我建议你尝试下不同的文件格式,看看它们的速度以及是否适合你的分析工作。pandas或NumPy数据的其它存储格式有: +pandas 内置支持两个二进制数据格式:HDF5 和 MessagePack。下一节,我会给出几个 HDF5 的例子,但我建议你尝试下不同的文件格式,看看它们的速度以及是否适合你的分析工作。pandas 或 NumPy 数据的其它存储格式有: -- bcolz:一种可压缩的列存储二进制格式,基于Blosc压缩库。 -- Feather:我与R语言社区的Hadley Wickham设计的一种跨语言的列存储文件格式。Feather使用了Apache Arrow的列式内存格式。 +- bcolz:一种可压缩的列存储二进制格式,基于 Blosc 压缩库。 +- Feather:我与 R 语言社区的 Hadley Wickham 设计的一种跨语言的列存储文件格式。Feather 使用了 Apache Arrow 的列式内存格式。 -## 使用HDF5格式 +## 使用 HDF5 格式 -HDF5是一种存储大规模科学数组数据的非常好的文件格式。它可以被作为C标准库,带有许多语言的接口,如Java、Python和MATLAB等。HDF5中的HDF指的是层次型数据格式(hierarchical data format)。每个HDF5文件都含有一个文件系统式的节点结构,它使你能够存储多个数据集并支持元数据。与其他简单格式相比,HDF5支持多种压缩器的即时压缩,还能更高效地存储重复模式数据。对于那些非常大的无法直接放入内存的数据集,HDF5就是不错的选择,因为它可以高效地分块读写。 +HDF5 是一种存储大规模科学数组数据的非常好的文件格式。它可以被作为 C 标准库,带有许多语言的接口,如 Java、Python 和 MATLAB 等。HDF5 中的 HDF 指的是层次型数据格式(hierarchical data format)。每个 HDF5 文件都含有一个文件系统式的节点结构,它使你能够存储多个数据集并支持元数据。与其他简单格式相比,HDF5 支持多种压缩器的即时压缩,还能更高效地存储重复模式数据。对于那些非常大的无法直接放入内存的数据集,HDF5 就是不错的选择,因为它可以高效地分块读写。 -虽然可以用PyTables或h5py库直接访问HDF5文件,pandas提供了更为高级的接口,可以简化存储Series和DataFrame对象。HDFStore类可以像字典一样,处理低级的细节: +虽然可以用 PyTables 或 h5py 库直接访问 HDF5 文件,pandas 提供了更为高级的接口,可以简化存储 Series 和 DataFrame 对象。HDFStore 类可以像字典一样,处理低级的细节: ```python In [92]: frame = pd.DataFrame({'a': np.random.randn(100)}) @@ -737,7 +737,7 @@ File path: mydata.h5 [index]) ``` -HDF5文件中的对象可以通过与字典一样的API进行获取: +HDF5 文件中的对象可以通过与字典一样的 API 进行获取: ```python In [97]: store['obj1'] Out[97]: @@ -756,7 +756,7 @@ Out[97]: [100 rows x 1 columns] ``` -HDFStore支持两种存储模式,'fixed'和'table'。后者通常会更慢,但是支持使用特殊语法进行查询操作: +HDFStore 支持两种存储模式,'fixed'和'table'。后者通常会更慢,但是支持使用特殊语法进行查询操作: ```python In [98]: store.put('obj2', frame, format='table') @@ -773,9 +773,9 @@ Out[99]: In [100]: store.close() ``` -put是store['obj2'] = frame方法的显示版本,允许我们设置其它的选项,比如格式。 +put 是 store['obj2'] = frame 方法的显示版本,允许我们设置其它的选项,比如格式。 -pandas.read_hdf函数可以快捷使用这些工具: +pandas.read_hdf 函数可以快捷使用这些工具: ```python In [101]: frame.to_hdf('mydata.h5', 'obj3', format='table') @@ -789,22 +789,22 @@ Out[102]: 4 1.965781 ``` ->笔记:如果你要处理的数据位于远程服务器,比如Amazon S3或HDFS,使用专门为分布式存储(比如Apache Parquet)的二进制格式也许更加合适。Python的Parquet和其它存储格式还在不断的发展之中,所以这本书中没有涉及。 +>笔记:如果你要处理的数据位于远程服务器,比如 Amazon S3 或 HDFS,使用专门为分布式存储(比如 Apache Parquet)的二进制格式也许更加合适。Python 的 Parquet 和其它存储格式还在不断的发展之中,所以这本书中没有涉及。 -如果需要本地处理海量数据,我建议你好好研究一下PyTables和h5py,看看它们能满足你的哪些需求。。由于许多数据分析问题都是IO密集型(而不是CPU密集型),利用HDF5这样的工具能显著提升应用程序的效率。 +如果需要本地处理海量数据,我建议你好好研究一下 PyTables 和 h5py,看看它们能满足你的哪些需求。。由于许多数据分析问题都是 IO 密集型(而不是 CPU 密集型),利用 HDF5 这样的工具能显著提升应用程序的效率。 ->注意:HDF5不是数据库。它最适合用作“一次写多次读”的数据集。虽然数据可以在任何时候被添加到文件中,但如果同时发生多个写操作,文件就可能会被破坏。 +>注意:HDF5 不是数据库。它最适合用作“一次写多次读”的数据集。虽然数据可以在任何时候被添加到文件中,但如果同时发生多个写操作,文件就可能会被破坏。 -## 读取Microsoft Excel文件 +## 读取 Microsoft Excel 文件 -pandas的ExcelFile类或pandas.read_excel函数支持读取存储在Excel 2003(或更高版本)中的表格型数据。这两个工具分别使用扩展包xlrd和openpyxl读取XLS和XLSX文件。你可以用pip或conda安装它们。 +pandas 的 ExcelFile 类或 pandas.read_excel 函数支持读取存储在 Excel 2003(或更高版本)中的表格型数据。这两个工具分别使用扩展包 xlrd 和 openpyxl 读取 XLS 和 XLSX 文件。你可以用 pip 或 conda 安装它们。 -要使用ExcelFile,通过传递xls或xlsx路径创建一个实例: +要使用 ExcelFile,通过传递 xls 或 xlsx 路径创建一个实例: ```python In [104]: xlsx = pd.ExcelFile('examples/ex1.xlsx') ``` -存储在表单中的数据可以read_excel读取到DataFrame(原书这里写的是用parse解析,但代码中用的是read_excel,是个笔误:只换了代码,没有改文字): +存储在表单中的数据可以 read_excel 读取到 DataFrame(原书这里写的是用 parse 解析,但代码中用的是 read_excel,是个笔误:只换了代码,没有改文字): ```python In [105]: pd.read_excel(xlsx, 'Sheet1') Out[105]: @@ -814,7 +814,7 @@ Out[105]: 2 9 10 11 12 foo ``` -如果要读取一个文件中的多个表单,创建ExcelFile会更快,但你也可以将文件名传递到pandas.read_excel: +如果要读取一个文件中的多个表单,创建 ExcelFile 会更快,但你也可以将文件名传递到 pandas.read_excel: ```python In [106]: frame = pd.read_excel('examples/ex1.xlsx', 'Sheet1') @@ -826,7 +826,7 @@ Out[107]: 2 9 10 11 12 foo ``` -如果要将pandas数据写入为Excel格式,你必须首先创建一个ExcelWriter,然后使用pandas对象的to_excel方法将数据写入到其中: +如果要将 pandas 数据写入为 Excel 格式,你必须首先创建一个 ExcelWriter,然后使用 pandas 对象的 to_excel 方法将数据写入到其中: ```python In [108]: writer = pd.ExcelWriter('examples/ex2.xlsx') @@ -835,15 +835,15 @@ In [109]: frame.to_excel(writer, 'Sheet1') In [110]: writer.save() ``` -你还可以不使用ExcelWriter,而是传递文件的路径到to_excel: +你还可以不使用 ExcelWriter,而是传递文件的路径到 to_excel: ```python In [111]: frame.to_excel('examples/ex2.xlsx') ``` -# 6.3 Web APIs交互 -许多网站都有一些通过JSON或其他格式提供数据的公共API。通过Python访问这些API的办法有不少。一个简单易用的办法(推荐)是requests包(http://docs.python-requests.org)。 +# 6.3 Web APIs 交互 +许多网站都有一些通过 JSON 或其他格式提供数据的公共 API。通过 Python 访问这些 API 的办法有不少。一个简单易用的办法(推荐)是 requests 包(http://docs.python-requests.org)。 -为了搜索最新的30个GitHub上的pandas主题,我们可以发一个HTTP GET请求,使用requests扩展库: +为了搜索最新的 30 个 GitHub 上的 pandas 主题,我们可以发一个 HTTP GET 请求,使用 requests 扩展库: ```python In [113]: import requests @@ -855,7 +855,7 @@ In [116]: resp Out[116]: ``` -响应对象的json方法会返回一个包含被解析过的JSON字典,加载到一个Python对象中: +响应对象的 json 方法会返回一个包含被解析过的 JSON 字典,加载到一个 Python 对象中: ```python In [117]: data = resp.json() @@ -863,7 +863,7 @@ In [118]: data[0]['title'] Out[118]: 'Period does not round down for frequencies less that 1 hour' ``` -data中的每个元素都是一个包含所有GitHub主题页数据(不包含评论)的字典。我们可以直接传递数据到DataFrame,并提取感兴趣的字段: +data 中的每个元素都是一个包含所有 GitHub 主题页数据(不包含评论)的字典。我们可以直接传递数据到 DataFrame,并提取感兴趣的字段: ```python In [119]: issues = pd.DataFrame(data, columns=['number', 'title', .....: 'labels', 'state']) @@ -897,13 +897,13 @@ Out[120]: [30 rows x 4 columns] ``` -花费一些精力,你就可以创建一些更高级的常见的Web API的接口,返回DataFrame对象,方便进行分析。 +花费一些精力,你就可以创建一些更高级的常见的 Web API 的接口,返回 DataFrame 对象,方便进行分析。 # 6.4 数据库交互 -在商业场景下,大多数数据可能不是存储在文本或Excel文件中。基于SQL的关系型数据库(如SQL Server、PostgreSQL和MySQL等)使用非常广泛,其它一些数据库也很流行。数据库的选择通常取决于性能、数据完整性以及应用程序的伸缩性需求。 +在商业场景下,大多数数据可能不是存储在文本或 Excel 文件中。基于 SQL 的关系型数据库(如 SQL Server、PostgreSQL 和 MySQL 等)使用非常广泛,其它一些数据库也很流行。数据库的选择通常取决于性能、数据完整性以及应用程序的伸缩性需求。 -将数据从SQL加载到DataFrame的过程很简单,此外pandas还有一些能够简化该过程的函数。例如,我将使用SQLite数据库(通过Python内置的sqlite3驱动器): +将数据从 SQL 加载到 DataFrame 的过程很简单,此外 pandas 还有一些能够简化该过程的函数。例如,我将使用 SQLite 数据库(通过 Python 内置的 sqlite3 驱动器): ```python In [121]: import sqlite3 @@ -933,7 +933,7 @@ In [128]: con.executemany(stmt, data) Out[128]: ``` -从表中选取数据时,大部分Python SQL驱动器(PyODBC、psycopg2、MySQLdb、pymssql等)都会返回一个元组列表: +从表中选取数据时,大部分 Python SQL 驱动器(PyODBC、psycopg2、MySQLdb、pymssql 等)都会返回一个元组列表: ```python In [130]: cursor = con.execute('select * from test') @@ -946,7 +946,7 @@ Out[132]: ('Sacramento', 'California', 1.7, 5)] ``` -你可以将这个元组列表传给DataFrame构造器,但还需要列名(位于光标的description属性中): +你可以将这个元组列表传给 DataFrame 构造器,但还需要列名(位于光标的 description 属性中): ```python In [133]: cursor.description Out[133]: @@ -963,7 +963,7 @@ Out[134]: 2 Sacramento California 1.70 5 ``` -这种数据规整操作相当多,你肯定不想每查一次数据库就重写一次。[SQLAlchemy项目](http://www.sqlalchemy.org/)是一个流行的Python SQL工具,它抽象出了SQL数据库中的许多常见差异。pandas有一个read_sql函数,可以让你轻松的从SQLAlchemy连接读取数据。这里,我们用SQLAlchemy连接SQLite数据库,并从之前创建的表读取数据: +这种数据规整操作相当多,你肯定不想每查一次数据库就重写一次。[SQLAlchemy 项目](http://www.sqlalchemy.org/)是一个流行的 Python SQL 工具,它抽象出了 SQL 数据库中的许多常见差异。pandas 有一个 read_sql 函数,可以让你轻松的从 SQLAlchemy 连接读取数据。这里,我们用 SQLAlchemy 连接 SQLite 数据库,并从之前创建的表读取数据: ```python In [135]: import sqlalchemy as sqla diff --git a/docs/7.md b/docs/7.md index bbb01aa..a21f3d8 100644 --- a/docs/7.md +++ b/docs/7.md @@ -1,15 +1,15 @@ # 第 7 章 数据清洗和准备 -在数据分析和建模的过程中,相当多的时间要用在数据准备上:加载、清理、转换以及重塑。这些工作会占到分析师时间的80%或更多。有时,存储在文件和数据库中的数据的格式不适合某个特定的任务。许多研究者都选择使用通用编程语言(如Python、Perl、R或Java)或UNIX文本处理工具(如sed或awk)对数据格式进行专门处理。幸运的是,pandas和内置的Python标准库提供了一组高级的、灵活的、快速的工具,可以让你轻松地将数据规整为想要的格式。 +在数据分析和建模的过程中,相当多的时间要用在数据准备上:加载、清理、转换以及重塑。这些工作会占到分析师时间的 80%或更多。有时,存储在文件和数据库中的数据的格式不适合某个特定的任务。许多研究者都选择使用通用编程语言(如 Python、Perl、R 或 Java)或 UNIX 文本处理工具(如 sed 或 awk)对数据格式进行专门处理。幸运的是,pandas 和内置的 Python 标准库提供了一组高级的、灵活的、快速的工具,可以让你轻松地将数据规整为想要的格式。 -如果你发现了一种本书或pandas库中没有的数据操作方式,请在邮件列表或GitHub网站上提出。实际上,pandas的许多设计和实现都是由真实应用的需求所驱动的。 +如果你发现了一种本书或 pandas 库中没有的数据操作方式,请在邮件列表或 GitHub 网站上提出。实际上,pandas 的许多设计和实现都是由真实应用的需求所驱动的。 在本章中,我会讨论处理缺失数据、重复数据、字符串操作和其它分析数据转换的工具。下一章,我会关注于用多种方法合并、重塑数据集。 # 7.1 处理缺失数据 -在许多数据分析工作中,缺失数据是经常发生的。pandas的目标之一就是尽量轻松地处理缺失数据。例如,pandas对象的所有描述性统计默认都不包括缺失数据。 +在许多数据分析工作中,缺失数据是经常发生的。pandas 的目标之一就是尽量轻松地处理缺失数据。例如,pandas 对象的所有描述性统计默认都不包括缺失数据。 -缺失数据在pandas中呈现的方式有些不完美,但对于大多数用户可以保证功能正常。对于数值数据,pandas使用浮点值NaN(Not a Number)表示缺失数据。我们称其为哨兵值,可以方便的检测出来: +缺失数据在 pandas 中呈现的方式有些不完美,但对于大多数用户可以保证功能正常。对于数值数据,pandas 使用浮点值 NaN(Not a Number)表示缺失数据。我们称其为哨兵值,可以方便的检测出来: ```python In [10]: string_data = pd.Series(['aardvark', 'artichoke', np.nan, 'avocado']) @@ -30,9 +30,9 @@ Out[12]: dtype: bool ``` -在pandas中,我们采用了R语言中的惯用法,即将缺失值表示为NA,它表示不可用not available。在统计应用中,NA数据可能是不存在的数据或者虽然存在,但是没有观察到(例如,数据采集中发生了问题)。当进行数据清洗以进行分析时,最好直接对缺失数据进行分析,以判断数据采集的问题或缺失数据可能导致的偏差。 +在 pandas 中,我们采用了 R 语言中的惯用法,即将缺失值表示为 NA,它表示不可用 not available。在统计应用中,NA 数据可能是不存在的数据或者虽然存在,但是没有观察到(例如,数据采集中发生了问题)。当进行数据清洗以进行分析时,最好直接对缺失数据进行分析,以判断数据采集的问题或缺失数据可能导致的偏差。 -Python内置的None值在对象数组中也可以作为NA: +Python 内置的 None 值在对象数组中也可以作为 NA: ```python In [13]: string_data[0] = None @@ -45,12 +45,12 @@ Out[14]: dtype: bool ``` -pandas项目中还在不断优化内部细节以更好处理缺失数据,像用户API功能,例如pandas.isnull,去除了许多恼人的细节。表7-1列出了一些关于缺失数据处理的函数。 +pandas 项目中还在不断优化内部细节以更好处理缺失数据,像用户 API 功能,例如 pandas.isnull,去除了许多恼人的细节。表 7-1 列出了一些关于缺失数据处理的函数。 -![表7-1 NA处理方法](img/7178691-1a0f73e5bb26ea21.png) +![表 7-1 NA 处理方法](img/7178691-1a0f73e5bb26ea21.png) ## 滤除缺失数据 -过滤掉缺失数据的办法有很多种。你可以通过pandas.isnull或布尔索引的手工方法,但dropna可能会更实用一些。对于一个Series,dropna返回一个仅含非空数据和索引值的Series: +过滤掉缺失数据的办法有很多种。你可以通过 pandas.isnull 或布尔索引的手工方法,但 dropna 可能会更实用一些。对于一个 Series,dropna 返回一个仅含非空数据和索引值的 Series: ```python In [15]: from numpy import nan as NA @@ -74,7 +74,7 @@ Out[18]: dtype: float64 ``` -而对于DataFrame对象,事情就有点复杂了。你可能希望丢弃全NA或含有NA的行或列。dropna默认丢弃任何含有缺失值的行: +而对于 DataFrame 对象,事情就有点复杂了。你可能希望丢弃全 NA 或含有 NA 的行或列。dropna 默认丢弃任何含有缺失值的行: ```python In [19]: data = pd.DataFrame([[1., 6.5, 3.], [1., NA, NA], ....: [NA, NA, NA], [NA, 6.5, 3.]]) @@ -95,7 +95,7 @@ Out[22]: 0 1.0 6.5 3.0 ``` -传入how='all'将只丢弃全为NA的那些行: +传入 how='all'将只丢弃全为 NA 的那些行: ```python In [23]: data.dropna(how='all') Out[23]: @@ -105,7 +105,7 @@ Out[23]: 3 NaN 6.5 3.0 ``` -用这种方式丢弃列,只需传入axis=1即可: +用这种方式丢弃列,只需传入 axis=1 即可: ```python In [24]: data[4] = NA @@ -126,7 +126,7 @@ Out[26]: 3 NaN 6.5 3.0 ``` -另一个滤除DataFrame行的问题涉及时间序列数据。假设你只想留下一部分观测数据,可以用thresh参数实现此目的: +另一个滤除 DataFrame 行的问题涉及时间序列数据。假设你只想留下一部分观测数据,可以用 thresh 参数实现此目的: ```python In [27]: df = pd.DataFrame(np.random.randn(7, 3)) @@ -163,7 +163,7 @@ Out[32]: ``` ## 填充缺失数据 -你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填补那些“空洞”。对于大多数情况而言,fillna方法是最主要的函数。通过一个常数调用fillna就会将缺失值替换为那个常数值: +你可能不想滤除缺失数据(有可能会丢弃跟它有关的其他数据),而是希望通过其他方式填补那些“空洞”。对于大多数情况而言,fillna 方法是最主要的函数。通过一个常数调用 fillna 就会将缺失值替换为那个常数值: ```python In [33]: df.fillna(0) Out[33]: @@ -177,7 +177,7 @@ Out[33]: 6 1.669025 -0.438570 -0.539741 ``` -若是通过一个字典调用fillna,就可以实现对不同的列填充不同的值: +若是通过一个字典调用 fillna,就可以实现对不同的列填充不同的值: ```python In [34]: df.fillna({1: 0.5, 2: 0}) Out[34]: @@ -191,7 +191,7 @@ Out[34]: 6 1.669025 -0.438570 -0.539741 ``` -fillna默认会返回新对象,但也可以对现有对象进行就地修改: +fillna 默认会返回新对象,但也可以对现有对象进行就地修改: ```python In [35]: _ = df.fillna(0, inplace=True) @@ -207,7 +207,7 @@ Out[36]: 6 1.669025 -0.438570 -0.539741 ``` -对reindexing有效的那些插值方法也可用于fillna: +对 reindexing 有效的那些插值方法也可用于 fillna: ```python In [37]: df = pd.DataFrame(np.random.randn(6, 3)) @@ -246,7 +246,7 @@ Out[42]: 5 -1.265934 NaN -2.370232 ``` -只要有些创新,你就可以利用fillna实现许多别的功能。比如说,你可以传入Series的平均值或中位数: +只要有些创新,你就可以利用 fillna 实现许多别的功能。比如说,你可以传入 Series 的平均值或中位数: ```python In [43]: data = pd.Series([1., NA, 3.5, NA, 7]) @@ -259,18 +259,18 @@ Out[44]: 4 7.000000 dtype: float64 ``` -表7-2列出了fillna的参考。 +表 7-2 列出了 fillna 的参考。 ![](img/7178691-0bf235386a64c3b5.png) -![fillna函数参数](img/7178691-4edd39e68f4dc530.png) +![fillna 函数参数](img/7178691-4edd39e68f4dc530.png) # 7.2 数据转换 本章到目前为止介绍的都是数据的重排。另一类重要操作则是过滤、清理以及其他的转换工作。 ## 移除重复数据 -DataFrame中出现重复行有多种原因。下面就是一个例子: +DataFrame 中出现重复行有多种原因。下面就是一个例子: ```python In [45]: data = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], ....: 'k2': [1, 1, 2, 3, 3, 4, 4]}) @@ -287,7 +287,7 @@ Out[46]: 6 two 4 ``` -DataFrame的duplicated方法返回一个布尔型Series,表示各行是否是重复行(前面出现过的行): +DataFrame 的 duplicated 方法返回一个布尔型 Series,表示各行是否是重复行(前面出现过的行): ```python In [47]: data.duplicated() Out[47]: @@ -301,7 +301,7 @@ Out[47]: dtype: bool ``` -还有一个与此相关的drop_duplicates方法,它会返回一个DataFrame,重复的数组会标为False: +还有一个与此相关的 drop_duplicates 方法,它会返回一个 DataFrame,重复的数组会标为 False: ```python In [48]: data.drop_duplicates() Out[48]: @@ -314,7 +314,7 @@ Out[48]: 5 two 4 ``` -这两个方法默认会判断全部列,你也可以指定部分列进行重复项判断。假设我们还有一列值,且只希望根据k1列过滤重复项: +这两个方法默认会判断全部列,你也可以指定部分列进行重复项判断。假设我们还有一列值,且只希望根据 k1 列过滤重复项: ```python In [49]: data['v1'] = range(7) @@ -325,7 +325,7 @@ Out[50]: 1 two 1 1 ``` -duplicated和drop_duplicates默认保留的是第一个出现的值组合。传入keep='last'则保留最后一个: +duplicated 和 drop_duplicates 默认保留的是第一个出现的值组合。传入 keep='last'则保留最后一个: ```python In [51]: data.drop_duplicates(['k1', 'k2'], keep='last') Out[51]: @@ -339,7 +339,7 @@ Out[51]: ``` ## 利用函数或映射进行数据转换 -对于许多数据集,你可能希望根据数组、Series或DataFrame列中的值来实现转换工作。我们来看看下面这组有关肉类的数据: +对于许多数据集,你可能希望根据数组、Series 或 DataFrame 列中的值来实现转换工作。我们来看看下面这组有关肉类的数据: ```python In [52]: data = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon', ....: 'Pastrami', 'corned beef', 'Bacon', @@ -372,7 +372,7 @@ meat_to_animal = { } ``` -Series的map方法可以接受一个函数或含有映射关系的字典型对象,但是这里有一个小问题,即有些肉类的首字母大写了,而另一些则没有。因此,我们还需要使用Series的str.lower方法,将各个值转换为小写: +Series 的 map 方法可以接受一个函数或含有映射关系的字典型对象,但是这里有一个小问题,即有些肉类的首字母大写了,而另一些则没有。因此,我们还需要使用 Series 的 str.lower 方法,将各个值转换为小写: ```python In [55]: lowercased = data['food'].str.lower() @@ -421,10 +421,10 @@ Out[59]: Name: food, dtype: object ``` -使用map是一种实现元素级转换以及其他数据清理工作的便捷方式。 +使用 map 是一种实现元素级转换以及其他数据清理工作的便捷方式。 ## 替换值 -利用fillna方法填充缺失数据可以看做值替换的一种特殊情况。前面已经看到,map可用于修改对象的数据子集,而replace则提供了一种实现该功能的更简单、更灵活的方式。我们来看看下面这个Series: +利用 fillna 方法填充缺失数据可以看做值替换的一种特殊情况。前面已经看到,map 可用于修改对象的数据子集,而 replace 则提供了一种实现该功能的更简单、更灵活的方式。我们来看看下面这个 Series: ```python In [60]: data = pd.Series([1., -999., 2., -999., -1000., 3.]) @@ -438,7 +438,7 @@ Out[61]: 5 3.0 ``` --999这个值可能是一个表示缺失数据的标记值。要将其替换为pandas能够理解的NA值,我们可以利用replace来产生一个新的Series(除非传入inplace=True): +-999 这个值可能是一个表示缺失数据的标记值。要将其替换为 pandas 能够理解的 NA 值,我们可以利用 replace 来产生一个新的 Series(除非传入 inplace=True): ```python In [62]: data.replace(-999, np.nan) Out[62]: @@ -490,17 +490,17 @@ Out[65]: dtype: float64 ``` ->笔记:data.replace方法与data.str.replace不同,后者做的是字符串的元素级替换。我们会在后面学习Series的字符串方法。 +>笔记:data.replace 方法与 data.str.replace 不同,后者做的是字符串的元素级替换。我们会在后面学习 Series 的字符串方法。 ## 重命名轴索引 -跟Series中的值一样,轴标签也可以通过函数或映射进行转换,从而得到一个新的不同标签的对象。轴还可以被就地修改,而无需新建一个数据结构。接下来看看下面这个简单的例子: +跟 Series 中的值一样,轴标签也可以通过函数或映射进行转换,从而得到一个新的不同标签的对象。轴还可以被就地修改,而无需新建一个数据结构。接下来看看下面这个简单的例子: ```python In [66]: data = pd.DataFrame(np.arange(12).reshape((3, 4)), ....: index=['Ohio', 'Colorado', 'New York'], ....: columns=['one', 'two', 'three', 'four']) ``` -跟Series一样,轴索引也有一个map方法: +跟 Series 一样,轴索引也有一个 map 方法: ```python In [67]: transform = lambda x: x[:4].upper() @@ -508,7 +508,7 @@ In [68]: data.index.map(transform) Out[68]: Index(['OHIO', 'COLO', 'NEW '], dtype='object') ``` -你可以将其赋值给index,这样就可以对DataFrame进行就地修改: +你可以将其赋值给 index,这样就可以对 DataFrame 进行就地修改: ```python In [69]: data.index = data.index.map(transform) @@ -520,7 +520,7 @@ COLO 4 5 6 7 NEW 8 9 10 11 ``` -如果想要创建数据集的转换版(而不是修改原始数据),比较实用的方法是rename: +如果想要创建数据集的转换版(而不是修改原始数据),比较实用的方法是 rename: ```python In [71]: data.rename(index=str.title, columns=str.upper) Out[71]: @@ -530,7 +530,7 @@ Colo 4 5 6 7 New 8 9 10 11 ``` -特别说明一下,rename可以结合字典型对象实现对部分轴标签的更新: +特别说明一下,rename 可以结合字典型对象实现对部分轴标签的更新: ```python In [72]: data.rename(index={'OHIO': 'INDIANA'}, ....: columns={'three': 'peekaboo'}) @@ -541,7 +541,7 @@ COLO 4 5 6 7 NEW 8 9 10 11 ``` -rename可以实现复制DataFrame并对其索引和列标签进行赋值。如果希望就地修改某个数据集,传入inplace=True即可: +rename 可以实现复制 DataFrame 并对其索引和列标签进行赋值。如果希望就地修改某个数据集,传入 inplace=True 即可: ```python In [73]: data.rename(index={'OHIO': 'INDIANA'}, inplace=True) @@ -559,7 +559,7 @@ NEW 8 9 10 11 In [75]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32] ``` -接下来将这些数据划分为“18到25”、“26到35”、“35到60”以及“60以上”几个面元。要实现该功能,你需要使用pandas的cut函数: +接下来将这些数据划分为“18 到 25”、“26 到 35”、“35 到 60”以及“60 以上”几个面元。要实现该功能,你需要使用 pandas 的 cut 函数: ```python In [76]: bins = [18, 25, 35, 60, 100] @@ -572,7 +572,7 @@ Length: 12 Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]] ``` -pandas返回的是一个特殊的Categorical对象。结果展示了pandas.cut划分的面元。你可以将其看做一组表示面元名称的字符串。它的底层含有一个表示不同分类名称的类型数组,以及一个codes属性中的年龄数据的标签: +pandas 返回的是一个特殊的 Categorical 对象。结果展示了 pandas.cut 划分的面元。你可以将其看做一组表示面元名称的字符串。它的底层含有一个表示不同分类名称的类型数组,以及一个 codes 属性中的年龄数据的标签: ```python In [79]: cats.codes Out[79]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8) @@ -592,9 +592,9 @@ Out[81]: dtype: int64 ``` -pd.value_counts(cats)是pandas.cut结果的面元计数。 +pd.value_counts(cats)是 pandas.cut 结果的面元计数。 -跟“区间”的数学符号一样,圆括号表示开端,而方括号则表示闭端(包括)。哪边是闭端可以通过right=False进行修改: +跟“区间”的数学符号一样,圆括号表示开端,而方括号则表示闭端(包括)。哪边是闭端可以通过 right=False 进行修改: ```python In [82]: pd.cut(ages, [18, 26, 36, 61, 100], right=False) Out[82]: @@ -604,7 +604,7 @@ Length: 12 Categories (4, interval[int64]): [[18, 26) < [26, 36) < [36, 61) < [61, 100)] ``` -你可 以通过传递一个列表或数组到labels,设置自己的面元名称: +你可 以通过传递一个列表或数组到 labels,设置自己的面元名称: ```python In [83]: group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior'] @@ -616,7 +616,7 @@ Length: 12 Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior] ``` -如果向cut传入的是面元的数量而不是确切的面元边界,则它会根据数据的最小值和最大值计算等长面元。下面这个例子中,我们将一些均匀分布的数据分成四组: +如果向 cut 传入的是面元的数量而不是确切的面元边界,则它会根据数据的最小值和最大值计算等长面元。下面这个例子中,我们将一些均匀分布的数据分成四组: ```python In [85]: data = np.random.rand(20) @@ -629,9 +629,9 @@ Categories (4, interval[float64]): [(0.12, 0.34] < (0.34, 0.55] < (0.55, 0.76] < (0.76, 0.97]] ``` -选项precision=2,限定小数只有两位。 +选项 precision=2,限定小数只有两位。 -qcut是一个非常类似于cut的函数,它可以根据样本分位数对数据进行面元划分。根据数据的分布情况,cut可能无法使各个面元中含有相同数量的数据点。而qcut由于使用的是样本分位数,因此可以得到大小基本相等的面元: +qcut 是一个非常类似于 cut 的函数,它可以根据样本分位数对数据进行面元划分。根据数据的分布情况,cut 可能无法使各个面元中含有相同数量的数据点。而 qcut 由于使用的是样本分位数,因此可以得到大小基本相等的面元: ```python In [87]: data = np.random.randn(1000) # Normally distributed @@ -656,7 +656,7 @@ Out[90]: dtype: int64 ``` -与cut类似,你也可以传递自定义的分位数(0到1之间的数值,包含端点): +与 cut 类似,你也可以传递自定义的分位数(0 到 1 之间的数值,包含端点): ```python In [91]: pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.]) Out[91]: @@ -669,10 +669,10 @@ Categories (4, interval[float64]): [(-2.95, -1.187] < (-1.187, -0.0265] < (-0.02 (1.286, 3.928]] ``` -本章稍后在讲解聚合和分组运算时会再次用到cut和qcut,因为这两个离散化函数对分位和分组分析非常重要。 +本章稍后在讲解聚合和分组运算时会再次用到 cut 和 qcut,因为这两个离散化函数对分位和分组分析非常重要。 ## 检测和过滤异常值 -过滤或变换异常值(outlier)在很大程度上就是运用数组运算。来看一个含有正态分布数据的DataFrame: +过滤或变换异常值(outlier)在很大程度上就是运用数组运算。来看一个含有正态分布数据的 DataFrame: ```python In [92]: data = pd.DataFrame(np.random.randn(1000, 4)) @@ -689,7 +689,7 @@ min -3.645860 -3.184377 -3.745356 -3.428254 max 2.653656 3.525865 2.735527 3.366626 ``` -假设你想要找出某列中绝对值大小超过3的值: +假设你想要找出某列中绝对值大小超过 3 的值: ```python In [94]: col = data[2] @@ -700,7 +700,7 @@ Out[95]: Name: 2, dtype: float64 ``` -要选出全部含有“超过3或-3的值”的行,你可以在布尔型DataFrame中使用any方法: +要选出全部含有“超过 3 或-3 的值”的行,你可以在布尔型 DataFrame 中使用 any 方法: ```python In [96]: data[(np.abs(data) > 3).any(1)] Out[96]: @@ -717,7 +717,7 @@ Out[96]: 803 -3.645860 0.255475 -0.549574 -1.907459 ``` -根据这些条件,就可以对值进行设置。下面的代码可以将值限制在区间-3到3以内: +根据这些条件,就可以对值进行设置。下面的代码可以将值限制在区间-3 到 3 以内: ```python In [97]: data[np.abs(data) > 3] = np.sign(data) * 3 @@ -734,7 +734,7 @@ min -3.000000 -3.000000 -3.000000 -3.000000 max 2.653656 3.000000 2.735527 3.000000 ``` -根据数据的值是正还是负,np.sign(data)可以生成1和-1: +根据数据的值是正还是负,np.sign(data)可以生成 1 和-1: ```python In [99]: np.sign(data).head() Out[99]: @@ -747,7 +747,7 @@ Out[99]: ``` ## 排列和随机采样 -利用numpy.random.permutation函数可以轻松实现对Series或DataFrame的列的排列工作(permuting,随机重排序)。通过需要排列的轴的长度调用permutation,可产生一个表示新顺序的整数数组: +利用 numpy.random.permutation 函数可以轻松实现对 Series 或 DataFrame 的列的排列工作(permuting,随机重排序)。通过需要排列的轴的长度调用 permutation,可产生一个表示新顺序的整数数组: ```python In [100]: df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4))) @@ -757,7 +757,7 @@ In [102]: sampler Out[102]: array([3, 1, 4, 2, 0]) ``` -然后就可以在基于iloc的索引操作或take函数中使用该数组了: +然后就可以在基于 iloc 的索引操作或 take 函数中使用该数组了: ```python In [103]: df Out[103]: @@ -778,7 +778,7 @@ Out[104]: 0 0 1 2 3 ``` -如果不想用替换的方式选取随机子集,可以在Series和DataFrame上使用sample方法: +如果不想用替换的方式选取随机子集,可以在 Series 和 DataFrame 上使用 sample 方法: ```python In [105]: df.sample(n=3) Out[105]: @@ -788,7 +788,7 @@ Out[105]: 2 8 9 10 11 ``` -要通过替换的方式产生样本(允许重复选择),可以传递replace=True到sample: +要通过替换的方式产生样本(允许重复选择),可以传递 replace=True 到 sample: ```python In [106]: choices = pd.Series([5, 7, -1, 6, 4]) @@ -812,7 +812,7 @@ dtype: int64 ## 计算指标/哑变量 另一种常用于统计建模或机器学习的转换方式是:将分类变量(categorical variable)转换为“哑变量”或“指标矩阵”。 -如果DataFrame的某一列中含有k个不同的值,则可以派生出一个k列矩阵或DataFrame(其值全为1和0)。pandas有一个get_dummies函数可以实现该功能(其实自己动手做一个也不难)。使用之前的一个DataFrame例子: +如果 DataFrame 的某一列中含有 k 个不同的值,则可以派生出一个 k 列矩阵或 DataFrame(其值全为 1 和 0)。pandas 有一个 get_dummies 函数可以实现该功能(其实自己动手做一个也不难)。使用之前的一个 DataFrame 例子: ```python In [109]: df = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'], .....: 'data1': range(6)}) @@ -828,7 +828,7 @@ Out[110]: 5 0 1 0 ``` -有时候,你可能想给指标DataFrame的列加上一个前缀,以便能够跟其他数据进行合并。get_dummies的prefix参数可以实现该功能: +有时候,你可能想给指标 DataFrame 的列加上一个前缀,以便能够跟其他数据进行合并。get_dummies 的 prefix 参数可以实现该功能: ```python In [111]: dummies = pd.get_dummies(df['key'], prefix='key') @@ -845,7 +845,7 @@ Out[113]: 5 5 0 1 0 ``` -如果DataFrame中的某行同属于多个分类,则事情就会有点复杂。看一下MovieLens 1M数据集,14章会更深入地研究它: +如果 DataFrame 中的某行同属于多个分类,则事情就会有点复杂。看一下 MovieLens 1M 数据集,14 章会更深入地研究它: ```python In [114]: mnames = ['movie_id', 'title', 'genres'] @@ -868,7 +868,7 @@ Action 9 10 GoldenEye (1995) Action|Adventure|Thriller ``` -要为每个genre添加指标变量就需要做一些数据规整操作。首先,我们从数据集中抽取出不同的genre值: +要为每个 genre 添加指标变量就需要做一些数据规整操作。首先,我们从数据集中抽取出不同的 genre 值: ```python In [117]: all_genres = [] @@ -888,14 +888,14 @@ array(['Animation', "Children's", 'Comedy', 'Adventure', 'Fantasy', 'Western'], dtype=object) ``` -构建指标DataFrame的方法之一是从一个全零DataFrame开始: +构建指标 DataFrame 的方法之一是从一个全零 DataFrame 开始: ```python In [121]: zero_matrix = np.zeros((len(movies), len(genres))) In [122]: dummies = pd.DataFrame(zero_matrix, columns=genres) ``` -现在,迭代每一部电影,并将dummies各行的条目设为1。要这么做,我们使用dummies.columns来计算每个类型的列索引: +现在,迭代每一部电影,并将 dummies 各行的条目设为 1。要这么做,我们使用 dummies.columns 来计算每个类型的列索引: ```python In [123]: gen = movies.genres[0] @@ -906,7 +906,7 @@ In [125]: dummies.columns.get_indexer(gen.split('|')) Out[125]: array([0, 1, 2]) ``` -然后,根据索引,使用.iloc设定值: +然后,根据索引,使用.iloc 设定值: ```python In [126]: for i, gen in enumerate(movies.genres): .....: indices = dummies.columns.get_indexer(gen.split('|')) @@ -914,7 +914,7 @@ In [126]: for i, gen in enumerate(movies.genres): .....: ``` -然后,和以前一样,再将其与movies合并起来: +然后,和以前一样,再将其与 movies 合并起来: ```python In [127]: movies_windic = movies.join(dummies.add_prefix('Genre_')) @@ -944,9 +944,9 @@ Genre_Western 0 Name: 0, Length: 21, dtype: object ``` ->笔记:对于很大的数据,用这种方式构建多成员指标变量就会变得非常慢。最好使用更低级的函数,将其写入NumPy数组,然后结果包装在DataFrame中。 +>笔记:对于很大的数据,用这种方式构建多成员指标变量就会变得非常慢。最好使用更低级的函数,将其写入 NumPy 数组,然后结果包装在 DataFrame 中。 -一个对统计应用有用的秘诀是:结合get_dummies和诸如cut之类的离散化函数: +一个对统计应用有用的秘诀是:结合 get_dummies 和诸如 cut 之类的离散化函数: ```python In [129]: np.random.seed(12345) @@ -974,22 +974,22 @@ Out[133]: 9 0 0 0 1 0 ``` -我们用numpy.random.seed,使这个例子具有确定性。本书后面会介绍pandas.get_dummies。 +我们用 numpy.random.seed,使这个例子具有确定性。本书后面会介绍 pandas.get_dummies。 # 7.3 字符串操作 -Python能够成为流行的数据处理语言,部分原因是其简单易用的字符串和文本处理功能。大部分文本运算都直接做成了字符串对象的内置方法。对于更为复杂的模式匹配和文本操作,则可能需要用到正则表达式。pandas对此进行了加强,它使你能够对整组数据应用字符串表达式和正则表达式,而且能处理烦人的缺失数据。 +Python 能够成为流行的数据处理语言,部分原因是其简单易用的字符串和文本处理功能。大部分文本运算都直接做成了字符串对象的内置方法。对于更为复杂的模式匹配和文本操作,则可能需要用到正则表达式。pandas 对此进行了加强,它使你能够对整组数据应用字符串表达式和正则表达式,而且能处理烦人的缺失数据。 ## 字符串对象方法 -对于许多字符串处理和脚本应用,内置的字符串方法已经能够满足要求了。例如,以逗号分隔的字符串可以用split拆分成数段: +对于许多字符串处理和脚本应用,内置的字符串方法已经能够满足要求了。例如,以逗号分隔的字符串可以用 split 拆分成数段: ```python In [134]: val = 'a,b, guido' In [135]: val.split(',') Out[135]: ['a', 'b', ' guido'] ``` -split常常与strip一起使用,以去除空白符(包括换行符): +split 常常与 strip 一起使用,以去除空白符(包括换行符): ```python In [136]: pieces = [x.strip() for x in val.split(',')] @@ -1005,13 +1005,13 @@ In [139]: first + '::' + second + '::' + third Out[139]: 'a::b::guido' ``` -但这种方式并不是很实用。一种更快更符合Python风格的方式是,向字符串"::"的join方法传入一个列表或元组: +但这种方式并不是很实用。一种更快更符合 Python 风格的方式是,向字符串"::"的 join 方法传入一个列表或元组: ```python In [140]: '::'.join(pieces) Out[140]: 'a::b::guido' ``` -其它方法关注的是子串定位。检测子串的最佳方式是利用Python的in关键字,还可以使用index和find: +其它方法关注的是子串定位。检测子串的最佳方式是利用 Python 的 in 关键字,还可以使用 index 和 find: ```python In [141]: 'guido' in val Out[141]: True @@ -1023,7 +1023,7 @@ In [143]: val.find(':') Out[143]: -1 ``` -注意find和index的区别:如果找不到字符串,index将会引发一个异常(而不是返回-1): +注意 find 和 index 的区别:如果找不到字符串,index 将会引发一个异常(而不是返回-1): ```python In [144]: val.index(':') --------------------------------------------------------------------------- @@ -1033,13 +1033,13 @@ ValueError Traceback (most recent call last) ValueError: substring not found ``` -与此相关,count可以返回指定子串的出现次数: +与此相关,count 可以返回指定子串的出现次数: ```python In [145]: val.count(',') Out[145]: 2 ``` -replace用于将指定模式替换为另一个模式。通过传入空字符串,它也常常用于删除模式: +replace 用于将指定模式替换为另一个模式。通过传入空字符串,它也常常用于删除模式: ```python In [146]: val.replace(',', '::') Out[146]: 'a::b:: guido' @@ -1048,7 +1048,7 @@ In [147]: val.replace(',', '') Out[147]: 'ab guido' ``` -表7-3列出了Python内置的字符串方法。 +表 7-3 列出了 Python 内置的字符串方法。 这些运算大部分都能使用正则表达式实现(马上就会看到)。 @@ -1060,11 +1060,11 @@ casefold 将字符转换为小写,并将任何特定区域的变量字符 ## 正则表达式 -正则表达式提供了一种灵活的在文本中搜索或匹配(通常比前者复杂)字符串模式的方式。正则表达式,常称作regex,是根据正则表达式语言编写的字符串。Python内置的re模块负责对字符串应用正则表达式。我将通过一些例子说明其使用方法。 +正则表达式提供了一种灵活的在文本中搜索或匹配(通常比前者复杂)字符串模式的方式。正则表达式,常称作 regex,是根据正则表达式语言编写的字符串。Python 内置的 re 模块负责对字符串应用正则表达式。我将通过一些例子说明其使用方法。 >笔记:正则表达式的编写技巧可以自成一章,超出了本书的范围。从网上和其它书可以找到许多非常不错的教程和参考资料。 -re模块的函数可以分为三个大类:模式匹配、替换以及拆分。当然,它们之间是相辅相成的。一个regex描述了需要在文本中定位的一个模式,它可以用于许多目的。我们先来看一个简单的例子:假设我想要拆分一个字符串,分隔符为数量不定的一组空白符(制表符、空格、换行符等)。描述一个或多个空白符的regex是\s+: +re 模块的函数可以分为三个大类:模式匹配、替换以及拆分。当然,它们之间是相辅相成的。一个 regex 描述了需要在文本中定位的一个模式,它可以用于许多目的。我们先来看一个简单的例子:假设我想要拆分一个字符串,分隔符为数量不定的一组空白符(制表符、空格、换行符等)。描述一个或多个空白符的 regex 是\s+: ```python In [148]: import re @@ -1074,7 +1074,7 @@ In [150]: re.split('\s+', text) Out[150]: ['foo', 'bar', 'baz', 'qux'] ``` -调用re.split('\s+',text)时,正则表达式会先被编译,然后再在text上调用其split方法。你可以用re.compile自己编译regex以得到一个可重用的regex对象: +调用 re.split('\s+',text)时,正则表达式会先被编译,然后再在 text 上调用其 split 方法。你可以用 re.compile 自己编译 regex 以得到一个可重用的 regex 对象: ```python In [151]: regex = re.compile('\s+') @@ -1082,17 +1082,17 @@ In [152]: regex.split(text) Out[152]: ['foo', 'bar', 'baz', 'qux'] ``` -如果只希望得到匹配regex的所有模式,则可以使用findall方法: +如果只希望得到匹配 regex 的所有模式,则可以使用 findall 方法: ```python In [153]: regex.findall(text) Out[153]: [' ', '\t ', ' \t'] ``` ->笔记:如果想避免正则表达式中不需要的转义(\),则可以使用原始字符串字面量如r'C:\x'(也可以编写其等价式'C:\\x')。 +>笔记:如果想避免正则表达式中不需要的转义(\),则可以使用原始字符串字面量如 r'C:\x'(也可以编写其等价式'C:\\x')。 -如果打算对许多字符串应用同一条正则表达式,强烈建议通过re.compile创建regex对象。这样将可以节省大量的CPU时间。 +如果打算对许多字符串应用同一条正则表达式,强烈建议通过 re.compile 创建 regex 对象。这样将可以节省大量的 CPU 时间。 -match和search跟findall功能类似。findall返回的是字符串中所有的匹配项,而search则只返回第一个匹配项。match更加严格,它只匹配字符串的首部。来看一个小例子,假设我们有一段文本以及一条能够识别大部分电子邮件地址的正则表达式: +match 和 search 跟 findall 功能类似。findall 返回的是字符串中所有的匹配项,而 search 则只返回第一个匹配项。match 更加严格,它只匹配字符串的首部。来看一个小例子,假设我们有一段文本以及一条能够识别大部分电子邮件地址的正则表达式: ```python text = """Dave dave@google.com Steve steve@gmail.com @@ -1105,7 +1105,7 @@ pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}' regex = re.compile(pattern, flags=re.IGNORECASE) ``` -对text使用findall将得到一组电子邮件地址: +对 text 使用 findall 将得到一组电子邮件地址: ```python In [155]: regex.findall(text) Out[155]: @@ -1115,7 +1115,7 @@ Out[155]: 'ryan@yahoo.com'] ``` -search返回的是文本中第一个电子邮件地址(以特殊的匹配项对象形式返回)。对于上面那个regex,匹配项对象只能告诉我们模式在原字符串中的起始和结束位置: +search 返回的是文本中第一个电子邮件地址(以特殊的匹配项对象形式返回)。对于上面那个 regex,匹配项对象只能告诉我们模式在原字符串中的起始和结束位置: ```python In [156]: m = regex.search(text) @@ -1126,13 +1126,13 @@ In [158]: text[m.start():m.end()] Out[158]: 'dave@google.com' ``` -regex.match则将返回None,因为它只匹配出现在字符串开头的模式: +regex.match 则将返回 None,因为它只匹配出现在字符串开头的模式: ```python In [159]: print(regex.match(text)) None ``` -相关的,sub方法可以将匹配到的模式替换为指定字符串,并返回所得到的新字符串: +相关的,sub 方法可以将匹配到的模式替换为指定字符串,并返回所得到的新字符串: ```python In [160]: print(regex.sub('REDACTED', text)) Dave REDACTED @@ -1141,14 +1141,14 @@ Rob REDACTED Ryan REDACTED ``` -假设你不仅想要找出电子邮件地址,还想将各个地址分成3个部分:用户名、域名以及域后缀。要实现此功能,只需将待分段的模式的各部分用圆括号包起来即可: +假设你不仅想要找出电子邮件地址,还想将各个地址分成 3 个部分:用户名、域名以及域后缀。要实现此功能,只需将待分段的模式的各部分用圆括号包起来即可: ```python In [161]: pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})' In [162]: regex = re.compile(pattern, flags=re.IGNORECASE) ``` -由这种修改过的正则表达式所产生的匹配项对象,可以通过其groups方法返回一个由模式各段组成的元组: +由这种修改过的正则表达式所产生的匹配项对象,可以通过其 groups 方法返回一个由模式各段组成的元组: ```python In [163]: m = regex.match('wesm@bright.net') @@ -1156,7 +1156,7 @@ In [164]: m.groups() Out[164]: ('wesm', 'bright', 'net') ``` -对于带有分组功能的模式,findall会返回一个元组列表: +对于带有分组功能的模式,findall 会返回一个元组列表: ```python In [165]: regex.findall(text) Out[165]: @@ -1166,7 +1166,7 @@ Out[165]: ('ryan', 'yahoo', 'com')] ``` -sub还能通过诸如\1、\2之类的特殊符号访问各匹配项中的分组。符号\1对应第一个匹配的组,\2对应第二个匹配的组,以此类推: +sub 还能通过诸如\1、\2 之类的特殊符号访问各匹配项中的分组。符号\1 对应第一个匹配的组,\2 对应第二个匹配的组,以此类推: ```python In [166]: print(regex.sub(r'Username: \1, Domain: \2, Suffix: \3', text)) Dave Username: dave, Domain: google, Suffix: com @@ -1175,11 +1175,11 @@ Rob Username: rob, Domain: gmail, Suffix: com Ryan Username: ryan, Domain: yahoo, Suffix: com ``` -Python中还有许多的正则表达式,但大部分都超出了本书的范围。表7-4是一个简要概括。 +Python 中还有许多的正则表达式,但大部分都超出了本书的范围。表 7-4 是一个简要概括。 ![](img/7178691-efbb80a793759fc0.png) -## pandas的矢量化字符串函数 +## pandas 的矢量化字符串函数 清理待分析的散乱数据时,常常需要做一些字符串规整化工作。更为复杂的情况是,含有字符串的列有时还含有缺失数据: ```python @@ -1205,7 +1205,7 @@ Wes True dtype: bool ``` -通过data.map,所有字符串和正则表达式方法都能被应用于(传入lambda表达式或其他函数)各个值,但是如果存在NA(null)就会报错。为了解决这个问题,Series有一些能够跳过NA值的面向数组方法,进行字符串操作。通过Series的str属性即可访问这些方法。例如,我们可以通过str.contains检查各个电子邮件地址是否含有"gmail": +通过 data.map,所有字符串和正则表达式方法都能被应用于(传入 lambda 表达式或其他函数)各个值,但是如果存在 NA(null)就会报错。为了解决这个问题,Series 有一些能够跳过 NA 值的面向数组方法,进行字符串操作。通过 Series 的 str 属性即可访问这些方法。例如,我们可以通过 str.contains 检查各个电子邮件地址是否含有"gmail": ```python In [171]: data.str.contains('gmail') Out[171]: @@ -1216,7 +1216,7 @@ Wes NaN dtype: object ``` -也可以使用正则表达式,还可以加上任意re选项(如IGNORECASE): +也可以使用正则表达式,还可以加上任意 re 选项(如 IGNORECASE): ```python In [172]: pattern Out[172]: '([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\\.([A-Z]{2,4})' @@ -1230,7 +1230,7 @@ Wes NaN dtype: object ``` -有两个办法可以实现矢量化的元素获取操作:要么使用str.get,要么在str属性上使用索引: +有两个办法可以实现矢量化的元素获取操作:要么使用 str.get,要么在 str 属性上使用索引: ```python In [174]: matches = data.str.match(pattern, flags=re.IGNORECASE) @@ -1273,11 +1273,11 @@ Wes NaN dtype: object ``` -表7-5介绍了更多的pandas字符串方法。 +表 7-5 介绍了更多的 pandas 字符串方法。 -![表7-5 部分矢量化字符串方法](img/7178691-a634364ed6d5d5c5.png) +![表 7-5 部分矢量化字符串方法](img/7178691-a634364ed6d5d5c5.png) # 7.4 总结 -高效的数据准备可以让你将更多的时间用于数据分析,花较少的时间用于准备工作,这样就可以极大地提高生产力。我们在本章中学习了许多工具,但覆盖并不全面。下一章,我们会学习pandas的聚合与分组。 +高效的数据准备可以让你将更多的时间用于数据分析,花较少的时间用于准备工作,这样就可以极大地提高生产力。我们在本章中学习了许多工具,但覆盖并不全面。下一章,我们会学习 pandas 的聚合与分组。 diff --git a/docs/8.md b/docs/8.md index 38d617a..5d9d54f 100644 --- a/docs/8.md +++ b/docs/8.md @@ -2,11 +2,11 @@ 在许多应用中,数据可能分散在许多文件或数据库中,存储的形式也不利于分析。本章关注可以聚合、合并、重塑数据的方法。 -首先,我会介绍pandas的层次化索引,它广泛用于以上操作。然后,我深入介绍了一些特殊的数据操作。在第14章,你可以看到这些工具的多种应用。 +首先,我会介绍 pandas 的层次化索引,它广泛用于以上操作。然后,我深入介绍了一些特殊的数据操作。在第 14 章,你可以看到这些工具的多种应用。 # 8.1 层次化索引 -层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引: +层次化索引(hierarchical indexing)是 pandas 的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个 Series,并用一个由列表或数组组成的列表作为索引: ```python In [9]: data = pd.Series(np.random.randn(9), ...: index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'], @@ -26,7 +26,7 @@ d 2 0.281746 dtype: float64 ``` -看到的结果是经过美化的带有MultiIndex索引的Series的格式。索引之间的“间隔”表示“直接使用上面的标签”: +看到的结果是经过美化的带有 MultiIndex 索引的 Series 的格式。索引之间的“间隔”表示“直接使用上面的标签”: ```python In [11]: data.index Out[11]: @@ -69,7 +69,7 @@ d 0.281746 dtype: float64 ``` -层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色。例如,可以通过unstack方法将这段数据重新安排到一个DataFrame中: +层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色。例如,可以通过 unstack 方法将这段数据重新安排到一个 DataFrame 中: ```python In [16]: data.unstack() Out[16]: @@ -80,7 +80,7 @@ c 1.393406 0.092908 NaN d NaN 0.281746 0.769023 ``` -unstack的逆运算是stack: +unstack 的逆运算是 stack: ```python In [17]: data.unstack().stack() Out[17]: @@ -96,9 +96,9 @@ d 2 0.281746 dtype: float64 ``` -stack和unstack将在本章后面详细讲解。 +stack 和 unstack 将在本章后面详细讲解。 -对于一个DataFrame,每条轴都可以有分层索引: +对于一个 DataFrame,每条轴都可以有分层索引: ```python In [18]: frame = pd.DataFrame(np.arange(12).reshape((4, 3)), ....: index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], @@ -115,7 +115,7 @@ b 1 6 7 8 2 9 10 11 ``` -各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中: +各层都可以有名字(可以是字符串,也可以是别的 Python 对象)。如果指定了名称,它们就会显示在控制台输出中: ```python In [20]: frame.index.names = ['key1', 'key2'] @@ -132,7 +132,7 @@ b 1 6 7 8 2 9 10 11 ``` ->注意:小心区分索引名state、color与行标签。 +>注意:小心区分索引名 state、color 与行标签。 有了部分列索引,因此可以轻松选取列分组: ```python @@ -146,7 +146,7 @@ b 1 6 7 2 9 10 ``` -可以单独创建MultiIndex然后复用。上面那个DataFrame中的(带有分级名称)列可以这样创建: +可以单独创建 MultiIndex 然后复用。上面那个 DataFrame 中的(带有分级名称)列可以这样创建: ```python MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']], names=['state', 'color']) @@ -154,7 +154,7 @@ MultiIndex.from_arrays([['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']] ## 重排与分级排序 -有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化): +有时,你需要重新调整某条轴上各级别的顺序,或根据指定级别上的值对数据进行排序。swaplevel 接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化): ```python In [24]: frame.swaplevel('key1', 'key2') Out[24]: @@ -167,7 +167,7 @@ key2 key1 2 b 9 10 11 ``` -而sort_index则根据单个级别中的值对数据进行排序。交换级别时,常常也会用到sort_index,这样最终结果就是按照指定顺序进行字母排序了: +而 sort_index 则根据单个级别中的值对数据进行排序。交换级别时,常常也会用到 sort_index,这样最终结果就是按照指定顺序进行字母排序了: ```python In [25]: frame.sort_index(level=1) Out[25]: @@ -192,7 +192,7 @@ key2 key1 ## 根据级别汇总统计 -许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。再以上面那个DataFrame为例,我们可以根据行或列上的级别来进行求和: +许多对 DataFrame 和 Series 的描述和汇总统计都有一个 level 选项,它用于指定在某条轴上求和的级别。再以上面那个 DataFrame 为例,我们可以根据行或列上的级别来进行求和: ```python In [27]: frame.sum(level='key2') Out[27]: @@ -212,11 +212,11 @@ b 1 14 7 2 20 10 ``` -这其实是利用了pandas的groupby功能,本书稍后将对其进行详细讲解。 +这其实是利用了 pandas 的 groupby 功能,本书稍后将对其进行详细讲解。 -## 使用DataFrame的列进行索引 +## 使用 DataFrame 的列进行索引 -人们经常想要将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成DataFrame的列。以下面这个DataFrame为例: +人们经常想要将 DataFrame 的一个或多个列当做行索引来用,或者可能希望将行索引变成 DataFrame 的列。以下面这个 DataFrame 为例: ```python In [29]: frame = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1), ....: 'c': ['one', 'one', 'one', 'two', 'two', @@ -235,7 +235,7 @@ Out[30]: 6 6 1 two 3 ``` -DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame: +DataFrame 的 set_index 函数会将其一个或多个列转换为行索引,并创建一个新的 DataFrame: ```python In [31]: frame2 = frame.set_index(['c', 'd']) @@ -252,7 +252,7 @@ two 0 3 4 3 6 1 ``` -默认情况下,那些列会从DataFrame中移除,但也可以将其保留下来: +默认情况下,那些列会从 DataFrame 中移除,但也可以将其保留下来: ```python In [33]: frame.set_index(['c', 'd'], drop=False) Out[33]: @@ -267,7 +267,7 @@ two 0 3 4 two 0 3 6 1 two 3 ``` -reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面: +reset_index 的功能跟 set_index 刚好相反,层次化索引的级别会被转移到列里面: ```python In [34]: frame2.reset_index() Out[34]: @@ -283,17 +283,17 @@ c d a b # 8.2 合并数据集 -pandas对象中的数据可以通过一些方式进行合并: +pandas 对象中的数据可以通过一些方式进行合并: -- pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。SQL或其他关系型数据库的用户对此应该会比较熟悉,因为它实现的就是数据库的join操作。 -- pandas.concat可以沿着一条轴将多个对象堆叠到一起。 -- 实例方法combine_first可以将重复数据拼接在一起,用一个对象中的值填充另一个对象中的缺失值。 +- pandas.merge 可根据一个或多个键将不同 DataFrame 中的行连接起来。SQL 或其他关系型数据库的用户对此应该会比较熟悉,因为它实现的就是数据库的 join 操作。 +- pandas.concat 可以沿着一条轴将多个对象堆叠到一起。 +- 实例方法 combine_first 可以将重复数据拼接在一起,用一个对象中的值填充另一个对象中的缺失值。 我将分别对它们进行讲解,并给出一些例子。本书剩余部分的示例中将经常用到它们。 -##数据库风格的DataFrame合并 +##数据库风格的 DataFrame 合并 -数据集的合并(merge)或连接(join)运算是通过一个或多个键将行连接起来的。这些运算是关系型数据库(基于SQL)的核心。pandas的merge函数是对数据应用这些算法的主要切入点。 +数据集的合并(merge)或连接(join)运算是通过一个或多个键将行连接起来的。这些运算是关系型数据库(基于 SQL)的核心。pandas 的 merge 函数是对数据应用这些算法的主要切入点。 以一个简单的例子开始: ```python @@ -322,7 +322,7 @@ Out[38]: 2 2 d ``` -这是一种多对一的合并。df1中的数据有多个被标记为a和b的行,而df2中key列的每个值则仅对应一行。对这些对象调用merge即可得到: +这是一种多对一的合并。df1 中的数据有多个被标记为 a 和 b 的行,而 df2 中 key 列的每个值则仅对应一行。对这些对象调用 merge 即可得到: ```python In [39]: pd.merge(df1, df2) Out[39]: @@ -335,7 +335,7 @@ Out[39]: 5 5 a 0 ``` -注意,我并没有指明要用哪个列进行连接。如果没有指定,merge就会将重叠列的列名当做键。不过,最好明确指定一下: +注意,我并没有指明要用哪个列进行连接。如果没有指定,merge 就会将重叠列的列名当做键。不过,最好明确指定一下: ```python In [40]: pd.merge(df1, df2, on='key') Out[40]: @@ -367,7 +367,7 @@ Out[43]: 5 5 a 0 a ``` -可能你已经注意到了,结果里面c和d以及与之相关的数据消失了。默认情况下,merge做的是“内连接”;结果中的键是交集。其他方式还有"left"、"right"以及"outer"。外连接求取的是键的并集,组合了左连接和右连接的效果: +可能你已经注意到了,结果里面 c 和 d 以及与之相关的数据消失了。默认情况下,merge 做的是“内连接”;结果中的键是交集。其他方式还有"left"、"right"以及"outer"。外连接求取的是键的并集,组合了左连接和右连接的效果: ```python In [44]: pd.merge(df1, df2, how='outer') Out[44]: @@ -382,9 +382,9 @@ Out[44]: 7 NaN d 2.0 ``` -表8-1对这些选项进行了总结。 +表 8-1 对这些选项进行了总结。 -![表8-1 不同的连接类型](img/7178691-e49b3341f4a3c90e.png) +![表 8-1 不同的连接类型](img/7178691-e49b3341f4a3c90e.png) 多对多的合并有些不直观。看下面的例子: @@ -430,7 +430,7 @@ Out[49]: 10 5 b 3.0 ``` -多对多连接产生的是行的笛卡尔积。由于左边的DataFrame有3个"b"行,右边的有2个,所以最终结果中就有6个"b"行。连接方式只影响出现在结果中的不同的键的值: +多对多连接产生的是行的笛卡尔积。由于左边的 DataFrame 有 3 个"b"行,右边的有 2 个,所以最终结果中就有 6 个"b"行。连接方式只影响出现在结果中的不同的键的值: ```python In [50]: pd.merge(df1, df2, how='inner') Out[50]: @@ -469,9 +469,9 @@ Out[53]: 结果中会出现哪些键组合取决于所选的合并方式,你可以这样来理解:多个键形成一系列元组,并将其当做单个连接键(当然,实际上并不是这么回事)。 ->注意:在进行列-列连接时,DataFrame对象中的索引会被丢弃。 +>注意:在进行列-列连接时,DataFrame 对象中的索引会被丢弃。 -对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(查看前面介绍的重命名轴标签),但merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串: +对于合并运算需要考虑的最后一个问题是对重复列名的处理。虽然你可以手工处理列名重叠的问题(查看前面介绍的重命名轴标签),但 merge 有一个更实用的 suffixes 选项,用于指定附加到左右两个 DataFrame 对象的重叠列名上的字符串: ```python In [54]: pd.merge(left, right, on='key1') Out[54]: @@ -494,19 +494,19 @@ Out[55]: 5 bar one 3 two 7 ``` -merge的参数请参见表8-2。使用DataFrame的行索引合并是下一节的主题。 +merge 的参数请参见表 8-2。使用 DataFrame 的行索引合并是下一节的主题。 -表8-2 merge函数的参数 +表 8-2 merge 函数的参数 ![](img/7178691-35ca716a4f1b8475.png) ![](img/7178691-c86672e733ceccd9.png) -indicator 添加特殊的列_merge,它可以指明每个行的来源,它的值有left_only、right_only或both,根据每行的合并数据的来源。 +indicator 添加特殊的列 _merge,它可以指明每个行的来源,它的值有 left_only、right_only 或 both,根据每行的合并数据的来源。 ## 索引上的合并 -有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传入left_index=True或right_index=True(或两个都传)以说明索引应该被用作连接键: +有时候,DataFrame 中的连接键位于其索引中。在这种情况下,你可以传入 left_index=True 或 right_index=True(或两个都传)以说明索引应该被用作连接键: ```python In [56]: left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'], ....: 'value': range(6)}) @@ -540,7 +540,7 @@ Out[60]: 4 b 4 7.0 ``` -由于默认的merge方法是求取连接键的交集,因此你可以通过外连接的方式得到它们的并集: +由于默认的 merge 方法是求取连接键的交集,因此你可以通过外连接的方式得到它们的并集: ```python In [61]: pd.merge(left1, right1, left_on='key', right_index=True, how='outer') Out[61]: @@ -586,7 +586,7 @@ Ohio 2000 4 5 2002 10 11 ``` -这种情况下,你必须以列表的形式指明用作合并键的多个列(注意用how='outer'对重复索引值的处理): +这种情况下,你必须以列表的形式指明用作合并键的多个列(注意用 how='outer'对重复索引值的处理): ```python In [66]: pd.merge(lefth, righth, left_on=['key1', 'key2'], right_index=True) Out[66]: @@ -645,7 +645,7 @@ d NaN NaN 11.0 12.0 e 5.0 6.0 13.0 14.0 ``` -DataFrame还有一个便捷的join实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的DataFrame对象,但要求没有重叠的列。在上面那个例子中,我们可以编写: +DataFrame 还有一个便捷的 join 实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的 DataFrame 对象,但要求没有重叠的列。在上面那个例子中,我们可以编写: ```python In [73]: left2.join(right2, how='outer') Out[73]: @@ -657,7 +657,7 @@ d NaN NaN 11.0 12.0 e 5.0 6.0 13.0 14.0 ``` -因为一些历史版本的遗留原因,DataFrame的join方法默认使用的是左连接,保留左边表的行索引。它还支持在调用的DataFrame的列上,连接传递的DataFrame索引: +因为一些历史版本的遗留原因,DataFrame 的 join 方法默认使用的是左连接,保留左边表的行索引。它还支持在调用的 DataFrame 的列上,连接传递的 DataFrame 索引: ```python In [74]: left1.join(right1, on='key') Out[74]: @@ -670,7 +670,7 @@ Out[74]: 5 c 5 NaN ``` -最后,对于简单的索引合并,你还可以向join传入一组DataFrame,下一节会介绍更为通用的concat函数,也能实现此功能: +最后,对于简单的索引合并,你还可以向 join 传入一组 DataFrame,下一节会介绍更为通用的 concat 函数,也能实现此功能: ```python In [75]: another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]], ....: index=['a', 'c', 'e', 'f'], @@ -705,7 +705,7 @@ f NaN NaN NaN NaN 16.0 17.0 ## 轴向连接 -另一种数据合并运算也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。NumPy的concatenation函数可以用NumPy数组来做: +另一种数据合并运算也被称作连接(concatenation)、绑定(binding)或堆叠(stacking)。NumPy 的 concatenation 函数可以用 NumPy 数组来做: ```python In [79]: arr = np.arange(12).reshape((3, 4)) @@ -722,13 +722,13 @@ array([[ 0, 1, 2, 3, 0, 1, 2, 3], [ 8, 9, 10, 11, 8, 9, 10, 11]]) ``` -对于pandas对象(如Series和DataFrame),带有标签的轴使你能够进一步推广数组的连接运算。具体点说,你还需要考虑以下这些东西: +对于 pandas 对象(如 Series 和 DataFrame),带有标签的轴使你能够进一步推广数组的连接运算。具体点说,你还需要考虑以下这些东西: - 如果对象在其它轴上的索引不同,我们应该合并这些轴的不同元素还是只使用交集? - 连接的数据集是否需要在结果对象中可识别? -- 连接轴中保存的数据是否需要保留?许多情况下,DataFrame默认的整数标签最好在连接时删掉。 +- 连接轴中保存的数据是否需要保留?许多情况下,DataFrame 默认的整数标签最好在连接时删掉。 -pandas的concat函数提供了一种能够解决这些问题的可靠方式。我将给出一些例子来讲解其使用方式。假设有三个没有重叠索引的Series: +pandas 的 concat 函数提供了一种能够解决这些问题的可靠方式。我将给出一些例子来讲解其使用方式。假设有三个没有重叠索引的 Series: ```python In [82]: s1 = pd.Series([0, 1], index=['a', 'b']) @@ -737,7 +737,7 @@ In [83]: s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e']) In [84]: s3 = pd.Series([5, 6], index=['f', 'g']) ``` -对这些对象调用concat可以将值和索引粘合在一起: +对这些对象调用 concat 可以将值和索引粘合在一起: ```python In [85]: pd.concat([s1, s2, s3]) Out[85]: @@ -751,7 +751,7 @@ g 6 dtype: int64 ``` -默认情况下,concat是在axis=0上工作的,最终产生一个新的Series。如果传入axis=1,则结果就会变成一个DataFrame(axis=1是列): +默认情况下,concat 是在 axis=0 上工作的,最终产生一个新的 Series。如果传入 axis=1,则结果就会变成一个 DataFrame(axis=1 是列): ```python In [86]: pd.concat([s1, s2, s3], axis=1) Out[86]: @@ -765,7 +765,7 @@ f NaN NaN 5.0 g NaN NaN 6.0 ``` -这种情况下,另外的轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。传入join='inner'即可得到它们的交集: +这种情况下,另外的轴上没有重叠,从索引的有序并集(外连接)上就可以看出来。传入 join='inner'即可得到它们的交集: ```python In [87]: s4 = pd.concat([s1, s3]) @@ -792,9 +792,9 @@ a 0 0 b 1 1 ``` -在这个例子中,f和g标签消失了,是因为使用的是join='inner'选项。 +在这个例子中,f 和 g 标签消失了,是因为使用的是 join='inner'选项。 -你可以通过join_axes指定要在其它轴上使用的索引: +你可以通过 join_axes 指定要在其它轴上使用的索引: ```python In [91]: pd.concat([s1, s4], axis=1, join_axes=[['a', 'c', 'b', 'e']]) Out[91]: @@ -805,7 +805,7 @@ b 1.0 1.0 e NaN NaN ``` -不过有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引。使用keys参数即可达到这个目的: +不过有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引。使用 keys 参数即可达到这个目的: ```python In [92]: result = pd.concat([s1, s1, s3], keys=['one','two', 'three']) @@ -827,7 +827,7 @@ two 0.0 1.0 NaN NaN three NaN NaN 5.0 6.0 ``` -如果沿着axis=1对Series进行合并,则keys就会成为DataFrame的列头: +如果沿着 axis=1 对 Series 进行合并,则 keys 就会成为 DataFrame 的列头: ```python In [95]: pd.concat([s1, s2, s3], axis=1, keys=['one','two', 'three']) Out[95]: @@ -841,7 +841,7 @@ f NaN NaN 5.0 g NaN NaN 6.0 ``` -同样的逻辑也适用于DataFrame对象: +同样的逻辑也适用于 DataFrame 对象: ```python In [96]: df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'], ....: columns=['one', 'two']) @@ -871,7 +871,7 @@ b 2 3 NaN NaN c 4 5 7.0 8.0 ``` -如果传入的不是列表而是一个字典,则字典的键就会被当做keys选项的值: +如果传入的不是列表而是一个字典,则字典的键就会被当做 keys 选项的值: ```python In [101]: pd.concat({'level1': df1, 'level2': df2}, axis=1) @@ -883,7 +883,7 @@ b 2 3 NaN NaN c 4 5 7.0 8.0 ``` -此外还有两个用于管理层次化索引创建方式的参数(参见表8-3)。举个例子,我们可以用names参数命名创建的轴级别: +此外还有两个用于管理层次化索引创建方式的参数(参见表 8-3)。举个例子,我们可以用 names 参数命名创建的轴级别: ```python In [102]: pd.concat([df1, df2], axis=1, keys=['level1', 'level2'], .....: names=['upper', 'lower']) @@ -895,7 +895,7 @@ b 2 3 NaN NaN c 4 5 7.0 8.0 ``` -最后一个关于DataFrame的问题是,DataFrame的行索引不包含任何相关数据: +最后一个关于 DataFrame 的问题是,DataFrame 的行索引不包含任何相关数据: ```python In [103]: df1 = pd.DataFrame(np.random.randn(3, 4), columns=['a', 'b', 'c', 'd']) @@ -915,7 +915,7 @@ Out[106]: 1 -0.577087 0.124121 0.302614 ``` -在这种情况下,传入ignore_index=True即可: +在这种情况下,传入 ignore_index=True 即可: ```python In [107]: pd.concat([df1, df2], ignore_index=True) Out[107]: @@ -927,11 +927,11 @@ Out[107]: 4 0.302614 -0.577087 NaN 0.124121 ``` -![表8-3 concat函数的参数](img/7178691-339436563b519415.png) +![表 8-3 concat 函数的参数](img/7178691-339436563b519415.png) ## 合并重叠数据 -还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,你可能有索引全部或部分重叠的两个数据集。举个有启发性的例子,我们使用NumPy的where函数,它表示一种等价于面向数组的if-else: +还有一种数据组合问题不能用简单的合并(merge)或连接(concatenation)运算来处理。比如说,你可能有索引全部或部分重叠的两个数据集。举个有启发性的例子,我们使用 NumPy 的 where 函数,它表示一种等价于面向数组的 if-else: ```python In [108]: a = pd.Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan], .....: index=['f', 'e', 'd', 'c', 'b', 'a']) @@ -965,7 +965,7 @@ In [113]: np.where(pd.isnull(a), b, a) Out[113]: array([ 0. , 2.5, 2. , 3.5, 4.5, nan]) ``` -Series有一个combine_first方法,实现的也是一样的功能,还带有pandas的数据对齐: +Series 有一个 combine_first 方法,实现的也是一样的功能,还带有 pandas 的数据对齐: ```python In [114]: b[:-2].combine_first(a[2:]) Out[114]: @@ -978,7 +978,7 @@ f 0.0 dtype: float64 ``` -对于DataFrame,combine_first自然也会在列上做同样的事情,因此你可以将其看做:用传递对象中的数据为调用对象的缺失数据“打补丁”: +对于 DataFrame,combine_first 自然也会在列上做同样的事情,因此你可以将其看做:用传递对象中的数据为调用对象的缺失数据“打补丁”: ```python In [115]: df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan], .....: 'b': [np.nan, 2., np.nan, 6.], @@ -1020,12 +1020,12 @@ Out[119]: ## 重塑层次化索引 -层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二: +层次化索引为 DataFrame 数据的重排任务提供了一种具有良好一致性的方式。主要功能有二: - stack:将数据的列“旋转”为行。 - unstack:将数据的行“旋转”为列。 -我将通过一系列的范例来讲解这些操作。接下来看一个简单的DataFrame,其中的行列索引均为字符串数组: +我将通过一系列的范例来讲解这些操作。接下来看一个简单的 DataFrame,其中的行列索引均为字符串数组: ```python In [120]: data = pd.DataFrame(np.arange(6).reshape((2, 3)), .....: index=pd.Index(['Ohio','Colorado'], name='state'), @@ -1040,7 +1040,7 @@ Ohio 0 1 2 Colorado 3 4 5 ``` -对该数据使用stack方法即可将列转换为行,得到一个Series: +对该数据使用 stack 方法即可将列转换为行,得到一个 Series: ```python In [122]: result = data.stack() @@ -1056,7 +1056,7 @@ Colorado one 3 dtype: int64 ``` -对于一个层次化索引的Series,你可以用unstack将其重排为一个DataFrame: +对于一个层次化索引的 Series,你可以用 unstack 将其重排为一个 DataFrame: ```python In [124]: result.unstack() Out[124]: @@ -1066,7 +1066,7 @@ Ohio 0 1 2 Colorado 3 4 5 ``` -默认情况下,unstack操作的是最内层(stack也是如此)。传入分层级别的编号或名称即可对其它级别进行unstack操作: +默认情况下,unstack 操作的是最内层(stack 也是如此)。传入分层级别的编号或名称即可对其它级别进行 unstack 操作: ```python In [125]: result.unstack(0) Out[125]: @@ -1085,7 +1085,7 @@ two 1 4 three 2 5 ``` -如果不是所有的级别值都能在各分组中找到的话,则unstack操作可能会引入缺失数据: +如果不是所有的级别值都能在各分组中找到的话,则 unstack 操作可能会引入缺失数据: ```python In [127]: s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd']) @@ -1111,7 +1111,7 @@ one 0.0 1.0 2.0 3.0 NaN two NaN NaN 4.0 5.0 6.0 ``` -stack默认会滤除缺失数据,因此该运算是可逆的: +stack 默认会滤除缺失数据,因此该运算是可逆的: ```python In [132]: data2.unstack() Out[132]: @@ -1145,7 +1145,7 @@ two a NaN dtype: float64 ``` -在对DataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别: +在对 DataFrame 进行 unstack 操作时,作为旋转轴的级别将会成为结果中的最低级别: ```python In [135]: df = pd.DataFrame({'left': result, 'right': result + 5}, .....: columns=pd.Index(['left', 'right'], name='side')) @@ -1171,7 +1171,7 @@ two 1 4 6 9 three 2 5 7 10 ``` -当调用stack,我们可以指明轴的名字: +当调用 stack,我们可以指明轴的名字: ```python In [138]: df.unstack('state').stack('side') Out[138]: @@ -1187,7 +1187,7 @@ three left 5 2 ## 将“长格式”旋转为“宽格式” -多个时间序列数据通常是以所谓的“长格式”(long)或“堆叠格式”(stacked)存储在数据库和CSV中的。我们先加载一些示例数据,做一些时间序列规整和数据清洗: +多个时间序列数据通常是以所谓的“长格式”(long)或“堆叠格式”(stacked)存储在数据库和 CSV 中的。我们先加载一些示例数据,做一些时间序列规整和数据清洗: ```python In [139]: data = pd.read_csv('examples/macrodata.csv') @@ -1218,9 +1218,9 @@ In [144]: data.index = periods.to_timestamp('D', 'end') In [145]: ldata = data.stack().reset_index().rename(columns={0: 'value'}) ``` -这就是多个时间序列(或者其它带有两个或多个键的可观察数据,这里,我们的键是date和item)的长格式。表中的每行代表一次观察。 +这就是多个时间序列(或者其它带有两个或多个键的可观察数据,这里,我们的键是 date 和 item)的长格式。表中的每行代表一次观察。 -关系型数据库(如MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加,item列中的值的种类能够增加。在前面的例子中,date和item通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。有的情况下,使用这样的数据会很麻烦,你可能会更喜欢DataFrame,不同的item值分别形成一列,date列中的时间戳则用作索引。DataFrame的pivot方法完全可以实现这个转换: +关系型数据库(如 MySQL)中的数据经常都是这样存储的,因为固定架构(即列名和数据类型)有一个好处:随着表中数据的添加,item 列中的值的种类能够增加。在前面的例子中,date 和 item 通常就是主键(用关系型数据库的说法),不仅提供了关系完整性,而且提供了更为简单的查询支持。有的情况下,使用这样的数据会很麻烦,你可能会更喜欢 DataFrame,不同的 item 值分别形成一列,date 列中的时间戳则用作索引。DataFrame 的 pivot 方法完全可以实现这个转换: ```python In [147]: pivoted = ldata.pivot('date', 'item', 'value') @@ -1252,7 +1252,7 @@ date [203 rows x 3 columns] ``` -前两个传递的值分别用作行和列索引,最后一个可选值则是用于填充DataFrame的数据列。假设有两个需要同时重塑的数据列: +前两个传递的值分别用作行和列索引,最后一个可选值则是用于填充 DataFrame 的数据列。假设有两个需要同时重塑的数据列: ```python In [149]: ldata['value2'] = np.random.randn(len(ldata)) @@ -1271,7 +1271,7 @@ Out[150]: 9 1959-12-31 realgdp 2785.204 -1.265934 ``` -如果忽略最后一个参数,得到的DataFrame就会带有层次化的列: +如果忽略最后一个参数,得到的 DataFrame 就会带有层次化的列: ```python In [151]: pivoted = ldata.pivot('date', 'item') @@ -1297,7 +1297,7 @@ date 1960-03-31 2.31 2847.699 5.2 ``` -注意,pivot其实就是用set_index创建层次化索引,再用unstack重塑: +注意,pivot 其实就是用 set_index 创建层次化索引,再用 unstack 重塑: ```python In [154]: unstacked = ldata.set_index(['date', 'item']).unstack('item') @@ -1317,7 +1317,7 @@ date ## 将“宽格式”旋转为“长格式” -旋转DataFrame的逆运算是pandas.melt。它不是将一列转换到多个新的DataFrame,而是合并多个列成为一个,产生一个比输入长的DataFrame。看一个例子: +旋转 DataFrame 的逆运算是 pandas.melt。它不是将一列转换到多个新的 DataFrame,而是合并多个列成为一个,产生一个比输入长的 DataFrame。看一个例子: ```python In [157]: df = pd.DataFrame({'key': ['foo', 'bar', 'baz'], .....: 'A': [1, 2, 3], @@ -1332,7 +1332,7 @@ Out[158]: 2 3 6 9 baz ``` -key列可能是分组指标,其它的列是数据值。当使用pandas.melt,我们必须指明哪些列是分组指标。下面使用key作为唯一的分组指标: +key 列可能是分组指标,其它的列是数据值。当使用 pandas.melt,我们必须指明哪些列是分组指标。下面使用 key 作为唯一的分组指标: ```python In [159]: melted = pd.melt(df, ['key']) @@ -1350,7 +1350,7 @@ Out[160]: 8 baz C 9 ``` -使用pivot,可以重塑回原来的样子: +使用 pivot,可以重塑回原来的样子: ```python In [161]: reshaped = melted.pivot('key', 'variable', 'value') @@ -1363,7 +1363,7 @@ baz 3 6 9 foo 1 4 7 ``` -因为pivot的结果从列创建了一个索引,用作行标签,我们可以使用reset_index将数据移回列: +因为 pivot 的结果从列创建了一个索引,用作行标签,我们可以使用 reset_index 将数据移回列: ```python In [163]: reshaped.reset_index() Out[163]: @@ -1386,7 +1386,7 @@ Out[164]: 5 baz B 6 ``` -pandas.melt也可以不用分组指标: +pandas.melt 也可以不用分组指标: ```python In [165]: pd.melt(df, value_vars=['A', 'B', 'C']) Out[165]: @@ -1417,4 +1417,4 @@ Out[166]: #8.4 总结 -现在你已经掌握了pandas数据导入、清洗、重塑,我们可以进一步学习matplotlib数据可视化。我们在稍后会回到pandas,学习更高级的分析。 +现在你已经掌握了 pandas 数据导入、清洗、重塑,我们可以进一步学习 matplotlib 数据可视化。我们在稍后会回到 pandas,学习更高级的分析。 diff --git a/docs/9.md b/docs/9.md index 9a23f2f..5f4fb59 100644 --- a/docs/9.md +++ b/docs/9.md @@ -1,25 +1,25 @@ # 第 9 章 绘图和可视化 -信息可视化(也叫绘图)是数据分析中最重要的工作之一。它可能是探索过程的一部分,例如,帮助我们找出异常值、必要的数据转换、得出有关模型的idea等。另外,做一个可交互的数据可视化也许是工作的最终目标。Python有许多库进行静态或动态的数据可视化,但我这里重要关注于matplotlib(http://matplotlib.org/)和基于它的库。 +信息可视化(也叫绘图)是数据分析中最重要的工作之一。它可能是探索过程的一部分,例如,帮助我们找出异常值、必要的数据转换、得出有关模型的 idea 等。另外,做一个可交互的数据可视化也许是工作的最终目标。Python 有许多库进行静态或动态的数据可视化,但我这里重要关注于 matplotlib(http://matplotlib.org/)和基于它的库。 -matplotlib是一个用于创建出版质量图表的桌面绘图包(主要是2D方面)。该项目是由John Hunter于2002年启动的,其目的是为Python构建一个MATLAB式的绘图接口。matplotlib和IPython社区进行合作,简化了从IPython shell(包括现在的Jupyter notebook)进行交互式绘图。matplotlib支持各种操作系统上许多不同的GUI后端,而且还能将图片导出为各种常见的矢量(vector)和光栅(raster)图:PDF、SVG、JPG、PNG、BMP、GIF等。除了几张,本书中的大部分图都是用它生成的。 +matplotlib 是一个用于创建出版质量图表的桌面绘图包(主要是 2D 方面)。该项目是由 John Hunter 于 2002 年启动的,其目的是为 Python 构建一个 MATLAB 式的绘图接口。matplotlib 和 IPython 社区进行合作,简化了从 IPython shell(包括现在的 Jupyter notebook)进行交互式绘图。matplotlib 支持各种操作系统上许多不同的 GUI 后端,而且还能将图片导出为各种常见的矢量(vector)和光栅(raster)图:PDF、SVG、JPG、PNG、BMP、GIF 等。除了几张,本书中的大部分图都是用它生成的。 -随着时间的发展,matplotlib衍生出了多个数据可视化的工具集,它们使用matplotlib作为底层。其中之一是seaborn(http://seaborn.pydata.org/),本章后面会学习它。 +随着时间的发展,matplotlib 衍生出了多个数据可视化的工具集,它们使用 matplotlib 作为底层。其中之一是 seaborn(http://seaborn.pydata.org/),本章后面会学习它。 -学习本章代码案例的最简单方法是在Jupyter notebook进行交互式绘图。在Jupyter notebook中执行下面的语句: +学习本章代码案例的最简单方法是在 Jupyter notebook 进行交互式绘图。在 Jupyter notebook 中执行下面的语句: ```python %matplotlib notebook ``` -# 9.1 matplotlib API入门 +# 9.1 matplotlib API 入门 - matplotlib的通常引入约定是: + matplotlib 的通常引入约定是: ```python In [11]: import matplotlib.pyplot as plt ``` -在Jupyter中运行%matplotlib notebook(或在IPython中运行%matplotlib),就可以创建一个简单的图形。如果一切设置正确,会看到图9-1: +在 Jupyter 中运行%matplotlib notebook(或在 IPython 中运行%matplotlib),就可以创建一个简单的图形。如果一切设置正确,会看到图 9-1: ```python In [12]: import numpy as np @@ -32,29 +32,29 @@ Out[14]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [15]: plt.plot(data) ``` -![图9-1 简单的线图](img/7178691-7032e333a6ecdd37.png) +![图 9-1 简单的线图](img/7178691-7032e333a6ecdd37.png) -虽然seaborn这样的库和pandas的内置绘图函数能够处理许多普通的绘图任务,但如果需要自定义一些高级功能的话就必须学习matplotlib API。 +虽然 seaborn 这样的库和 pandas 的内置绘图函数能够处理许多普通的绘图任务,但如果需要自定义一些高级功能的话就必须学习 matplotlib API。 ->笔记:虽然本书没有详细地讨论matplotlib的各种功能,但足以将你引入门。matplotlib的示例库和文档是学习高级特性的最好资源。 +>笔记:虽然本书没有详细地讨论 matplotlib 的各种功能,但足以将你引入门。matplotlib 的示例库和文档是学习高级特性的最好资源。 -## Figure和Subplot +## Figure 和 Subplot -matplotlib的图像都位于Figure对象中。你可以用plt.figure创建一个新的Figure: +matplotlib 的图像都位于 Figure 对象中。你可以用 plt.figure 创建一个新的 Figure: ```python In [16]: fig = plt.figure() ``` -如果用的是IPython,这时会弹出一个空窗口,但在Jupyter中,必须再输入更多命令才能看到。plt.figure有一些选项,特别是figsize,它用于确保当图片保存到磁盘时具有一定的大小和纵横比。 +如果用的是 IPython,这时会弹出一个空窗口,但在 Jupyter 中,必须再输入更多命令才能看到。plt.figure 有一些选项,特别是 figsize,它用于确保当图片保存到磁盘时具有一定的大小和纵横比。 -不能通过空Figure绘图。必须用add_subplot创建一个或多个subplot才行: +不能通过空 Figure 绘图。必须用 add_subplot 创建一个或多个 subplot 才行: ```python In [17]: ax1 = fig.add_subplot(2, 2, 1) ``` -这条代码的意思是:图像应该是2×2的(即最多4张图),且当前选中的是4个subplot中的第一个(编号从1开始)。如果再把后面两个subplot也创建出来,最终得到的图像如图9-2所示: +这条代码的意思是:图像应该是 2×2 的(即最多 4 张图),且当前选中的是 4 个 subplot 中的第一个(编号从 1 开始)。如果再把后面两个 subplot 也创建出来,最终得到的图像如图 9-2 所示: ```python In [18]: ax2 = fig.add_subplot(2, 2, 2) @@ -62,9 +62,9 @@ In [18]: ax2 = fig.add_subplot(2, 2, 2) In [19]: ax3 = fig.add_subplot(2, 2, 3) ``` -![图9-2 带有三个subplot的Figure](img/7178691-b8cff158e64eae74.png) +![图 9-2 带有三个 subplot 的 Figure](img/7178691-b8cff158e64eae74.png) ->提示:使用Jupyter notebook有一点不同,即每个小窗重新执行后,图形会被重置。因此,对于复杂的图形,,你必须将所有的绘图命令存在一个小窗里。 +>提示:使用 Jupyter notebook 有一点不同,即每个小窗重新执行后,图形会被重置。因此,对于复杂的图形,,你必须将所有的绘图命令存在一个小窗里。 这里,我们运行同一个小窗里的所有命令: @@ -75,15 +75,15 @@ ax2 = fig.add_subplot(2, 2, 2) ax3 = fig.add_subplot(2, 2, 3) ``` -如果这时执行一条绘图命令(如plt.plot([1.5, 3.5, -2, 1.6])),matplotlib就会在最后一个用过的subplot(如果没有则创建一个)上进行绘制,隐藏创建figure和subplot的过程。因此,如果我们执行下列命令,你就会得到如图9-3所示的结果: +如果这时执行一条绘图命令(如 plt.plot([1.5, 3.5, -2, 1.6])),matplotlib 就会在最后一个用过的 subplot(如果没有则创建一个)上进行绘制,隐藏创建 figure 和 subplot 的过程。因此,如果我们执行下列命令,你就会得到如图 9-3 所示的结果: ```python In [20]: plt.plot(np.random.randn(50).cumsum(), 'k--') ``` -![图9-3 绘制一次之后的图像](img/7178691-7bcbd5e56fdbbd92.png) +![图 9-3 绘制一次之后的图像](img/7178691-7bcbd5e56fdbbd92.png) -"k--"是一个线型选项,用于告诉matplotlib绘制黑色虚线图。上面那些由fig.add_subplot所返回的对象是AxesSubplot对象,直接调用它们的实例方法就可以在其它空着的格子里面画图了,如图9-4所示: +"k--"是一个线型选项,用于告诉 matplotlib 绘制黑色虚线图。上面那些由 fig.add_subplot 所返回的对象是 AxesSubplot 对象,直接调用它们的实例方法就可以在其它空着的格子里面画图了,如图 9-4 所示: ```python In [21]: ax1.hist(np.random.randn(100), bins=20, color='k', alpha=0.3) @@ -91,11 +91,11 @@ In [21]: ax1.hist(np.random.randn(100), bins=20, color='k', alpha=0.3) In [22]: ax2.scatter(np.arange(30), np.arange(30) + 3 * np.random.randn(30)) ``` -![图9-4 继续绘制两次之后的图像](img/7178691-2297bcaf355db24c.png) +![图 9-4 继续绘制两次之后的图像](img/7178691-2297bcaf355db24c.png) -你可以在matplotlib的文档中找到各种图表类型。 +你可以在 matplotlib 的文档中找到各种图表类型。 -创建包含subplot网格的figure是一个非常常见的任务,matplotlib有一个更为方便的方法plt.subplots,它可以创建一个新的Figure,并返回一个含有已创建的subplot对象的NumPy数组: +创建包含 subplot 网格的 figure 是一个非常常见的任务,matplotlib 有一个更为方便的方法 plt.subplots,它可以创建一个新的 Figure,并返回一个含有已创建的 subplot 对象的 NumPy 数组: ```python In [24]: fig, axes = plt.subplots(2, 3) @@ -111,20 +111,20 @@ array([[, =object) ``` -这是非常实用的,因为可以轻松地对axes数组进行索引,就好像是一个二维数组一样,例如axes[0,1]。你还可以通过sharex和sharey指定subplot应该具有相同的X轴或Y轴。在比较相同范围的数据时,这也是非常实用的,否则,matplotlib会自动缩放各图表的界限。有关该方法的更多信息,请参见表9-1。 +这是非常实用的,因为可以轻松地对 axes 数组进行索引,就好像是一个二维数组一样,例如 axes[0,1]。你还可以通过 sharex 和 sharey 指定 subplot 应该具有相同的 X 轴或 Y 轴。在比较相同范围的数据时,这也是非常实用的,否则,matplotlib 会自动缩放各图表的界限。有关该方法的更多信息,请参见表 9-1。 -![表9-1 pyplot.subplots的选项](img/7178691-88bb55faca7d01ba.png) +![表 9-1 pyplot.subplots 的选项](img/7178691-88bb55faca7d01ba.png) -## 调整subplot周围的间距 +## 调整 subplot 周围的间距 -默认情况下,matplotlib会在subplot外围留下一定的边距,并在subplot之间留下一定的间距。间距跟图像的高度和宽度有关,因此,如果你调整了图像大小(不管是编程还是手工),间距也会自动调整。利用Figure的subplots_adjust方法可以轻而易举地修改间距,此外,它也是个顶级函数: +默认情况下,matplotlib 会在 subplot 外围留下一定的边距,并在 subplot 之间留下一定的间距。间距跟图像的高度和宽度有关,因此,如果你调整了图像大小(不管是编程还是手工),间距也会自动调整。利用 Figure 的 subplots_adjust 方法可以轻而易举地修改间距,此外,它也是个顶级函数: ```python subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None) ``` -wspace和hspace用于控制宽度和高度的百分比,可以用作subplot之间的间距。下面是一个简单的例子,其中我将间距收缩到了0(如图9-5所示): +wspace 和 hspace 用于控制宽度和高度的百分比,可以用作 subplot 之间的间距。下面是一个简单的例子,其中我将间距收缩到了 0(如图 9-5 所示): ```python fig, axes = plt.subplots(2, 2, sharex=True, sharey=True) @@ -134,13 +134,13 @@ for i in range(2): plt.subplots_adjust(wspace=0, hspace=0) ``` -![图9-5 各subplot之间没有间距](img/7178691-80be7ffc3dec88a5.png) +![图 9-5 各 subplot 之间没有间距](img/7178691-80be7ffc3dec88a5.png) -不难看出,其中的轴标签重叠了。matplotlib不会检查标签是否重叠,所以对于这种情况,你只能自己设定刻度位置和刻度标签。后面几节将会详细介绍该内容。 +不难看出,其中的轴标签重叠了。matplotlib 不会检查标签是否重叠,所以对于这种情况,你只能自己设定刻度位置和刻度标签。后面几节将会详细介绍该内容。 ## 颜色、标记和线型 -matplotlib的plot函数接受一组X和Y坐标,还可以接受一个表示颜色和线型的字符串缩写。例如,要根据x和y绘制绿色虚线,你可以执行如下代码: +matplotlib 的 plot 函数接受一组 X 和 Y 坐标,还可以接受一个表示颜色和线型的字符串缩写。例如,要根据 x 和 y 绘制绿色虚线,你可以执行如下代码: ```python ax.plot(x, y, 'g--') @@ -152,9 +152,9 @@ ax.plot(x, y, 'g--') ax.plot(x, y, linestyle='--', color='g') ``` -常用的颜色可以使用颜色缩写,你也可以指定颜色码(例如,'#CECECE')。你可以通过查看plot的文档字符串查看所有线型的合集(在IPython和Jupyter中使用plot?)。 +常用的颜色可以使用颜色缩写,你也可以指定颜色码(例如,'#CECECE')。你可以通过查看 plot 的文档字符串查看所有线型的合集(在 IPython 和 Jupyter 中使用 plot?)。 -线图可以使用标记强调数据点。因为matplotlib可以创建连续线图,在点之间进行插值,因此有时可能不太容易看出真实数据点的位置。标记也可以放到格式字符串中,但标记类型和线型必须放在颜色后面(见图9-6): +线图可以使用标记强调数据点。因为 matplotlib 可以创建连续线图,在点之间进行插值,因此有时可能不太容易看出真实数据点的位置。标记也可以放到格式字符串中,但标记类型和线型必须放在颜色后面(见图 9-6): ```python In [30]: from numpy.random import randn @@ -162,7 +162,7 @@ In [30]: from numpy.random import randn In [31]: plt.plot(randn(30).cumsum(), 'ko--') ``` -![图9-6 带有标记的线型图示例](img/7178691-404d816f3e1d6621.png) +![图 9-6 带有标记的线型图示例](img/7178691-404d816f3e1d6621.png) 还可以将其写成更为明确的形式: @@ -170,7 +170,7 @@ In [31]: plt.plot(randn(30).cumsum(), 'ko--') plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o') ``` -在线型图中,非实际数据点默认是按线性方式插值的。可以通过drawstyle选项修改(见图9-7): +在线型图中,非实际数据点默认是按线性方式插值的。可以通过 drawstyle 选项修改(见图 9-7): ```python In [33]: data = np.random.randn(30).cumsum() @@ -184,26 +184,26 @@ Out[35]: [] In [36]: plt.legend(loc='best') ``` -![图9-7 不同drawstyle选项的线型图](img/7178691-3ec7642e1a592f08.png) +![图 9-7 不同 drawstyle 选项的线型图](img/7178691-3ec7642e1a592f08.png) -你可能注意到运行上面代码时有输出。matplotlib会返回引用了新添加的子组件的对象。大多数时候,你可以放心地忽略这些输出。这里,因为我们传递了label参数到plot,我们可以创建一个plot图例,指明每条使用plt.legend的线。 +你可能注意到运行上面代码时有输出。matplotlib 会返回引用了新添加的子组件的对象。大多数时候,你可以放心地忽略这些输出。这里,因为我们传递了 label 参数到 plot,我们可以创建一个 plot 图例,指明每条使用 plt.legend 的线。 ->笔记:你必须调用plt.legend(或使用ax.legend,如果引用了轴的话)来创建图例,无论你绘图时是否传递label标签选项。 +>笔记:你必须调用 plt.legend(或使用 ax.legend,如果引用了轴的话)来创建图例,无论你绘图时是否传递 label 标签选项。 ## 刻度、标签和图例 -对于大多数的图表装饰项,其主要实现方式有二:使用过程型的pyplot接口(例如,matplotlib.pyplot)以及更为面向对象的原生matplotlib API。 +对于大多数的图表装饰项,其主要实现方式有二:使用过程型的 pyplot 接口(例如,matplotlib.pyplot)以及更为面向对象的原生 matplotlib API。 -pyplot接口的设计目的就是交互式使用,含有诸如xlim、xticks和xticklabels之类的方法。它们分别控制图表的范围、刻度位置、刻度标签等。其使用方式有以下两种: +pyplot 接口的设计目的就是交互式使用,含有诸如 xlim、xticks 和 xticklabels 之类的方法。它们分别控制图表的范围、刻度位置、刻度标签等。其使用方式有以下两种: -- 调用时不带参数,则返回当前的参数值(例如,plt.xlim()返回当前的X轴绘图范围)。 -- 调用时带参数,则设置参数值(例如,plt.xlim([0,10])会将X轴的范围设置为0到10)。 +- 调用时不带参数,则返回当前的参数值(例如,plt.xlim()返回当前的 X 轴绘图范围)。 +- 调用时带参数,则设置参数值(例如,plt.xlim([0,10])会将 X 轴的范围设置为 0 到 10)。 -所有这些方法都是对当前或最近创建的AxesSubplot起作用的。它们各自对应subplot对象上的两个方法,以xlim为例,就是ax.get_xlim和ax.set_xlim。我更喜欢使用subplot的实例方法(因为我喜欢明确的事情,而且在处理多个subplot时这样也更清楚一些)。当然你完全可以选择自己觉得方便的那个。 +所有这些方法都是对当前或最近创建的 AxesSubplot 起作用的。它们各自对应 subplot 对象上的两个方法,以 xlim 为例,就是 ax.get_xlim 和 ax.set_xlim。我更喜欢使用 subplot 的实例方法(因为我喜欢明确的事情,而且在处理多个 subplot 时这样也更清楚一些)。当然你完全可以选择自己觉得方便的那个。 ## 设置标题、轴标签、刻度以及刻度标签 -为了说明自定义轴,我将创建一个简单的图像并绘制一段随机漫步(如图9-8所示): +为了说明自定义轴,我将创建一个简单的图像并绘制一段随机漫步(如图 9-8 所示): ```python In [37]: fig = plt.figure() @@ -213,9 +213,9 @@ In [38]: ax = fig.add_subplot(1, 1, 1) In [39]: ax.plot(np.random.randn(1000).cumsum()) ``` -![图9-8 用于演示xticks的简单线型图(带有标签)](img/7178691-caf9300dacb61fa4.png) +![图 9-8 用于演示 xticks 的简单线型图(带有标签)](img/7178691-caf9300dacb61fa4.png) -要改变x轴刻度,最简单的办法是使用set_xticks和set_xticklabels。前者告诉matplotlib要将刻度放在数据范围中的哪些位置,默认情况下,这些位置也就是刻度标签。但我们可以通过set_xticklabels将任何其他的值用作标签: +要改变 x 轴刻度,最简单的办法是使用 set_xticks 和 set_xticklabels。前者告诉 matplotlib 要将刻度放在数据范围中的哪些位置,默认情况下,这些位置也就是刻度标签。但我们可以通过 set_xticklabels 将任何其他的值用作标签: ```python In [40]: ticks = ax.set_xticks([0, 250, 500, 750, 1000]) @@ -224,7 +224,7 @@ In [41]: labels = ax.set_xticklabels(['one', 'two', 'three', 'four', 'five'], ....: rotation=30, fontsize='small') ``` -rotation选项设定x刻度标签倾斜30度。最后,再用set_xlabel为X轴设置一个名称,并用set_title设置一个标题(见图9-9的结果): +rotation 选项设定 x 刻度标签倾斜 30 度。最后,再用 set_xlabel 为 X 轴设置一个名称,并用 set_title 设置一个标题(见图 9-9 的结果): ```python In [42]: ax.set_title('My first matplotlib plot') @@ -233,9 +233,9 @@ Out[42]: In [43]: ax.set_xlabel('Stages') ``` -![图9-9 用于演示xticks的简单线型图](img/7178691-741f968323bd818f.png) +![图 9-9 用于演示 xticks 的简单线型图](img/7178691-741f968323bd818f.png) -Y轴的修改方式与此类似,只需将上述代码中的x替换为y即可。轴的类有集合方法,可以批量设定绘图选项。前面的例子,也可以写为: +Y 轴的修改方式与此类似,只需将上述代码中的 x 替换为 y 即可。轴的类有集合方法,可以批量设定绘图选项。前面的例子,也可以写为: ```python props = { @@ -247,7 +247,7 @@ ax.set(**props) ## 添加图例 -图例(legend)是另一种用于标识图表元素的重要工具。添加图例的方式有多种。最简单的是在添加subplot的时候传入label参数: +图例(legend)是另一种用于标识图表元素的重要工具。添加图例的方式有多种。最简单的是在添加 subplot 的时候传入 label 参数: ```python In [44]: from numpy.random import randn @@ -264,28 +264,28 @@ In [48]: ax.plot(randn(1000).cumsum(), 'k.', label='three') Out[48]: [] ``` -在此之后,你可以调用ax.legend()或plt.legend()来自动创建图例(结果见图9-10): +在此之后,你可以调用 ax.legend()或 plt.legend()来自动创建图例(结果见图 9-10): ```python In [49]: ax.legend(loc='best') ``` -![图9-10 带有三条线以及图例的简单线型图](img/7178691-651ff89750c0a89b.png) +![图 9-10 带有三条线以及图例的简单线型图](img/7178691-651ff89750c0a89b.png) -legend方法有几个其它的loc位置参数选项。请查看文档字符串(使用ax.legend?)。 +legend 方法有几个其它的 loc 位置参数选项。请查看文档字符串(使用 ax.legend?)。 -loc告诉matplotlib要将图例放在哪。如果你不是吹毛求疵的话,"best"是不错的选择,因为它会选择最不碍事的位置。要从图例中去除一个或多个元素,不传入label或传入label='_nolegend_'即可。(中文第一版这里把best错写成了beat) +loc 告诉 matplotlib 要将图例放在哪。如果你不是吹毛求疵的话,"best"是不错的选择,因为它会选择最不碍事的位置。要从图例中去除一个或多个元素,不传入 label 或传入 label='_nolegend_'即可。(中文第一版这里把 best 错写成了 beat) -## 注解以及在Subplot上绘图 +## 注解以及在 Subplot 上绘图 -除标准的绘图类型,你可能还希望绘制一些子集的注解,可能是文本、箭头或其他图形等。注解和文字可以通过text、arrow和annotate函数进行添加。text可以将文本绘制在图表的指定坐标(x,y),还可以加上一些自定义格式: +除标准的绘图类型,你可能还希望绘制一些子集的注解,可能是文本、箭头或其他图形等。注解和文字可以通过 text、arrow 和 annotate 函数进行添加。text 可以将文本绘制在图表的指定坐标(x,y),还可以加上一些自定义格式: ```python ax.text(x, y, 'Hello world!', family='monospace', fontsize=10) ``` -注解中可以既含有文本也含有箭头。例如,我们根据最近的标准普尔500指数价格(来自Yahoo!Finance)绘制一张曲线图,并标出2008年到2009年金融危机期间的一些重要日期。你可以在Jupyter notebook的一个小窗中试验这段代码(图9-11是结果): +注解中可以既含有文本也含有箭头。例如,我们根据最近的标准普尔 500 指数价格(来自 Yahoo!Finance)绘制一张曲线图,并标出 2008 年到 2009 年金融危机期间的一些重要日期。你可以在 Jupyter notebook 的一个小窗中试验这段代码(图 9-11 是结果): ```python from datetime import datetime @@ -318,15 +318,15 @@ ax.set_ylim([600, 1800]) ax.set_title('Important dates in the 2008-2009 financial crisis') ``` -![图9-11 2008-2009年金融危机期间的重要日期](img/7178691-3127eaa51f5e4c2c.png) +![图 9-11 2008-2009 年金融危机期间的重要日期](img/7178691-3127eaa51f5e4c2c.png) -这张图中有几个重要的点要强调:ax.annotate方法可以在指定的x和y坐标轴绘制标签。我们使用set_xlim和set_ylim人工设定起始和结束边界,而不使用matplotlib的默认方法。最后,用ax.set_title添加图标标题。 +这张图中有几个重要的点要强调:ax.annotate 方法可以在指定的 x 和 y 坐标轴绘制标签。我们使用 set_xlim 和 set_ylim 人工设定起始和结束边界,而不使用 matplotlib 的默认方法。最后,用 ax.set_title 添加图标标题。 -更多有关注解的示例,请访问matplotlib的在线示例库。 +更多有关注解的示例,请访问 matplotlib 的在线示例库。 -图形的绘制要麻烦一些。matplotlib有一些表示常见图形的对象。这些对象被称为块(patch)。其中有些(如Rectangle和Circle),可以在matplotlib.pyplot中找到,但完整集合位于matplotlib.patches。 +图形的绘制要麻烦一些。matplotlib 有一些表示常见图形的对象。这些对象被称为块(patch)。其中有些(如 Rectangle 和 Circle),可以在 matplotlib.pyplot 中找到,但完整集合位于 matplotlib.patches。 -要在图表中添加一个图形,你需要创建一个块对象shp,然后通过ax.add_patch(shp)将其添加到subplot中(如图9-12所示): +要在图表中添加一个图形,你需要创建一个块对象 shp,然后通过 ax.add_patch(shp)将其添加到 subplot 中(如图 9-12 所示): ```python fig = plt.figure() @@ -342,26 +342,26 @@ ax.add_patch(circ) ax.add_patch(pgon) ``` -![图9-12 由三个块图形组成的图](img/7178691-1f8a3d7a3a02d7d8.png) +![图 9-12 由三个块图形组成的图](img/7178691-1f8a3d7a3a02d7d8.png) -如果查看许多常见图表对象的具体实现代码,你就会发现它们其实就是由块patch组装而成的。 +如果查看许多常见图表对象的具体实现代码,你就会发现它们其实就是由块 patch 组装而成的。 ## 将图表保存到文件 -利用plt.savefig可以将当前图表保存到文件。该方法相当于Figure对象的实例方法savefig。例如,要将图表保存为SVG文件,你只需输入: +利用 plt.savefig 可以将当前图表保存到文件。该方法相当于 Figure 对象的实例方法 savefig。例如,要将图表保存为 SVG 文件,你只需输入: ```python plt.savefig('figpath.svg') ``` -文件类型是通过文件扩展名推断出来的。因此,如果你使用的是.pdf,就会得到一个PDF文件。我在发布图片时最常用到两个重要的选项是dpi(控制“每英寸点数”分辨率)和bbox_inches(可以剪除当前图表周围的空白部分)。要得到一张带有最小白边且分辨率为400DPI的PNG图片,你可以: +文件类型是通过文件扩展名推断出来的。因此,如果你使用的是.pdf,就会得到一个 PDF 文件。我在发布图片时最常用到两个重要的选项是 dpi(控制“每英寸点数”分辨率)和 bbox_inches(可以剪除当前图表周围的空白部分)。要得到一张带有最小白边且分辨率为 400DPI 的 PNG 图片,你可以: ```python plt.savefig('figpath.png', dpi=400, bbox_inches='tight') ``` -savefig并非一定要写入磁盘,也可以写入任何文件型的对象,比如BytesIO: +savefig 并非一定要写入磁盘,也可以写入任何文件型的对象,比如 BytesIO: ```python from io import BytesIO @@ -370,19 +370,19 @@ plt.savefig(buffer) plot_data = buffer.getvalue() ``` -表9-2列出了savefig的其它选项。 +表 9-2 列出了 savefig 的其它选项。 -![表9-2 Figure.savefig的选项](img/7178691-4bee796bf7262423.png) +![表 9-2 Figure.savefig 的选项](img/7178691-4bee796bf7262423.png) -## matplotlib配置 +## matplotlib 配置 -matplotlib自带一些配色方案,以及为生成出版质量的图片而设定的默认配置信息。幸运的是,几乎所有默认行为都能通过一组全局参数进行自定义,它们可以管理图像大小、subplot边距、配色方案、字体大小、网格类型等。一种Python编程方式配置系统的方法是使用rc方法。例如,要将全局的图像默认大小设置为10×10,你可以执行: +matplotlib 自带一些配色方案,以及为生成出版质量的图片而设定的默认配置信息。幸运的是,几乎所有默认行为都能通过一组全局参数进行自定义,它们可以管理图像大小、subplot 边距、配色方案、字体大小、网格类型等。一种 Python 编程方式配置系统的方法是使用 rc 方法。例如,要将全局的图像默认大小设置为 10×10,你可以执行: ```python plt.rc('figure', figsize=(10, 10)) ``` -rc的第一个参数是希望自定义的对象,如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等。其后可以跟上一系列的关键字参数。一个简单的办法是将这些选项写成一个字典: +rc 的第一个参数是希望自定义的对象,如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等。其后可以跟上一系列的关键字参数。一个简单的办法是将这些选项写成一个字典: ```python font_options = {'family' : 'monospace', @@ -391,21 +391,21 @@ font_options = {'family' : 'monospace', plt.rc('font', **font_options) ``` -要了解全部的自定义选项,请查阅matplotlib的配置文件matplotlibrc(位于matplotlib/mpl-data目录中)。如果对该文件进行了自定义,并将其放在你自己的.matplotlibrc目录中,则每次使用matplotlib时就会加载该文件。 +要了解全部的自定义选项,请查阅 matplotlib 的配置文件 matplotlibrc(位于 matplotlib/mpl-data 目录中)。如果对该文件进行了自定义,并将其放在你自己的.matplotlibrc 目录中,则每次使用 matplotlib 时就会加载该文件。 -下一节,我们会看到,seaborn包有若干内置的绘图主题或类型,它们使用了matplotlib的内部配置。 +下一节,我们会看到,seaborn 包有若干内置的绘图主题或类型,它们使用了 matplotlib 的内部配置。 -# 9.2 使用pandas和seaborn绘图 +# 9.2 使用 pandas 和 seaborn 绘图 -matplotlib实际上是一种比较低级的工具。要绘制一张图表,你组装一些基本组件就行:数据展示(即图表类型:线型图、柱状图、盒形图、散布图、等值线图等)、图例、标题、刻度标签以及其他注解型信息。 +matplotlib 实际上是一种比较低级的工具。要绘制一张图表,你组装一些基本组件就行:数据展示(即图表类型:线型图、柱状图、盒形图、散布图、等值线图等)、图例、标题、刻度标签以及其他注解型信息。 -在pandas中,我们有多列数据,还有行和列标签。pandas自身就有内置的方法,用于简化从DataFrame和Series绘制图形。另一个库seaborn(https://seaborn.pydata.org/),由Michael Waskom创建的静态图形库。Seaborn简化了许多常见可视类型的创建。 +在 pandas 中,我们有多列数据,还有行和列标签。pandas 自身就有内置的方法,用于简化从 DataFrame 和 Series 绘制图形。另一个库 seaborn(https://seaborn.pydata.org/),由 Michael Waskom 创建的静态图形库。Seaborn 简化了许多常见可视类型的创建。 ->提示:引入seaborn会修改matplotlib默认的颜色方案和绘图类型,以提高可读性和美观度。即使你不使用seaborn API,你可能也会引入seaborn,作为提高美观度和绘制常见matplotlib图形的简化方法。 +>提示:引入 seaborn 会修改 matplotlib 默认的颜色方案和绘图类型,以提高可读性和美观度。即使你不使用 seaborn API,你可能也会引入 seaborn,作为提高美观度和绘制常见 matplotlib 图形的简化方法。 ## 线型图 -Series和DataFrame都有一个用于生成各类图表的plot方法。默认情况下,它们所生成的是线型图(如图9-13所示): +Series 和 DataFrame 都有一个用于生成各类图表的 plot 方法。默认情况下,它们所生成的是线型图(如图 9-13 所示): ```python In [60]: s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10)) @@ -413,18 +413,18 @@ In [60]: s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10) In [61]: s.plot() ``` -![图9-13 简单的Series图表示例](img/7178691-f28e5ab2ac94c7a2.png) +![图 9-13 简单的 Series 图表示例](img/7178691-f28e5ab2ac94c7a2.png) -该Series对象的索引会被传给matplotlib,并用以绘制X轴。可以通过use_index=False禁用该功能。X轴的刻度和界限可以通过xticks和xlim选项进行调节,Y轴就用yticks和ylim。plot参数的完整列表请参见表9-3。我只会讲解其中几个,剩下的就留给读者自己去研究了。 +该 Series 对象的索引会被传给 matplotlib,并用以绘制 X 轴。可以通过 use_index=False 禁用该功能。X 轴的刻度和界限可以通过 xticks 和 xlim 选项进行调节,Y 轴就用 yticks 和 ylim。plot 参数的完整列表请参见表 9-3。我只会讲解其中几个,剩下的就留给读者自己去研究了。 ![](img/7178691-6d9fbf863c09370a.png) -![表9-3 Series.plot方法的参数](img/7178691-44e50562aeb5eb49.png) +![表 9-3 Series.plot 方法的参数](img/7178691-44e50562aeb5eb49.png) -pandas的大部分绘图方法都有一个可选的ax参数,它可以是一个matplotlib的subplot对象。这使你能够在网格布局中更为灵活地处理subplot的位置。 +pandas 的大部分绘图方法都有一个可选的 ax 参数,它可以是一个 matplotlib 的 subplot 对象。这使你能够在网格布局中更为灵活地处理 subplot 的位置。 -DataFrame的plot方法会在一个subplot中为各列绘制一条线,并自动创建图例(如图9-14所示): +DataFrame 的 plot 方法会在一个 subplot 中为各列绘制一条线,并自动创建图例(如图 9-14 所示): ```python In [62]: df = pd.DataFrame(np.random.randn(10, 4).cumsum(0), ....: columns=['A', 'B', 'C', 'D'], @@ -433,21 +433,21 @@ In [62]: df = pd.DataFrame(np.random.randn(10, 4).cumsum(0), In [63]: df.plot() ``` -![图9-14 简单的DataFrame绘图](img/7178691-a1234d5e5ee41a40.png) +![图 9-14 简单的 DataFrame 绘图](img/7178691-a1234d5e5ee41a40.png) -plot属性包含一批不同绘图类型的方法。例如,df.plot()等价于df.plot.line()。后面会学习这些方法。 +plot 属性包含一批不同绘图类型的方法。例如,df.plot()等价于 df.plot.line()。后面会学习这些方法。 ->笔记:plot的其他关键字参数会被传给相应的matplotlib绘图函数,所以要更深入地自定义图表,就必须学习更多有关matplotlib API的知识。 +>笔记:plot 的其他关键字参数会被传给相应的 matplotlib 绘图函数,所以要更深入地自定义图表,就必须学习更多有关 matplotlib API 的知识。 -DataFrame还有一些用于对列进行灵活处理的选项,例如,是要将所有列都绘制到一个subplot中还是创建各自的subplot。详细信息请参见表9-4。 +DataFrame 还有一些用于对列进行灵活处理的选项,例如,是要将所有列都绘制到一个 subplot 中还是创建各自的 subplot。详细信息请参见表 9-4。 -![表9-4 专用于DataFrame的plot参数](img/7178691-96651ecaa90f1c68.png) +![表 9-4 专用于 DataFrame 的 plot 参数](img/7178691-96651ecaa90f1c68.png) ->注意: 有关时间序列的绘图,请见第11章。 +>注意: 有关时间序列的绘图,请见第 11 章。 ## 柱状图 -plot.bar()和plot.barh()分别绘制水平和垂直的柱状图。这时,Series和DataFrame的索引将会被用作X(bar)或Y(barh)刻度(如图9-15所示): +plot.bar()和 plot.barh()分别绘制水平和垂直的柱状图。这时,Series 和 DataFrame 的索引将会被用作 X(bar)或 Y(barh)刻度(如图 9-15 所示): ```python In [64]: fig, axes = plt.subplots(2, 1) @@ -460,9 +460,9 @@ Out[66]: In [67]: data.plot.barh(ax=axes[1], color='k', alpha=0.7) ``` -![图9-15 水平和垂直的柱状图](img/7178691-cd54c7ccfa3f0687.png) +![图 9-15 水平和垂直的柱状图](img/7178691-cd54c7ccfa3f0687.png) -color='k'和alpha=0.7设定了图形的颜色为黑色,并使用部分的填充透明度。对于DataFrame,柱状图会将每一行的值分为一组,并排显示,如图9-16所示: +color='k'和 alpha=0.7 设定了图形的颜色为黑色,并使用部分的填充透明度。对于 DataFrame,柱状图会将每一行的值分为一组,并排显示,如图 9-16 所示: ```python In [69]: df = pd.DataFrame(np.random.rand(6, 4), @@ -482,21 +482,21 @@ six 0.601648 0.478576 0.205690 0.560547 In [71]: df.plot.bar() ``` -![图9-16 DataFrame的柱状图](img/7178691-bfc141acb37d99b5.png) +![图 9-16 DataFrame 的柱状图](img/7178691-bfc141acb37d99b5.png) -注意,DataFrame各列的名称"Genus"被用作了图例的标题。 +注意,DataFrame 各列的名称"Genus"被用作了图例的标题。 -设置stacked=True即可为DataFrame生成堆积柱状图,这样每行的值就会被堆积在一起(如图9-17所示): +设置 stacked=True 即可为 DataFrame 生成堆积柱状图,这样每行的值就会被堆积在一起(如图 9-17 所示): ```python In [73]: df.plot.barh(stacked=True, alpha=0.5) ``` -![图9-17 DataFrame的堆积柱状图](img/7178691-c19e4246eb897978.png) +![图 9-17 DataFrame 的堆积柱状图](img/7178691-c19e4246eb897978.png) ->笔记:柱状图有一个非常不错的用法:利用value_counts图形化显示Series中各值的出现频率,比如s.value_counts().plot.bar()。 +>笔记:柱状图有一个非常不错的用法:利用 value_counts 图形化显示 Series 中各值的出现频率,比如 s.value_counts().plot.bar()。 -再以本书前面用过的那个有关小费的数据集为例,假设我们想要做一张堆积柱状图以展示每天各种聚会规模的数据点的百分比。我用read_csv将数据加载进来,然后根据日期和聚会规模创建一张交叉表: +再以本书前面用过的那个有关小费的数据集为例,假设我们想要做一张堆积柱状图以展示每天各种聚会规模的数据点的百分比。我用 read_csv 将数据加载进来,然后根据日期和聚会规模创建一张交叉表: ```python In [75]: tips = pd.read_csv('examples/tips.csv') @@ -516,7 +516,7 @@ Thur 1 48 4 5 1 3 In [78]: party_counts = party_counts.loc[:, 2:5] ``` -然后进行规格化,使得各行的和为1,并生成图表(如图9-18所示): +然后进行规格化,使得各行的和为 1,并生成图表(如图 9-18 所示): ```python # Normalize to sum to 1 @@ -534,11 +534,11 @@ Thur 0.827586 0.068966 0.086207 0.017241 In [81]: party_pcts.plot.bar() ``` -![图9-18 每天各种聚会规模的比例](img/7178691-2918f67936823834.png) +![图 9-18 每天各种聚会规模的比例](img/7178691-2918f67936823834.png) 于是,通过该数据集就可以看出,聚会规模在周末会变大。 -对于在绘制一个图形之前,需要进行合计的数据,使用seaborn可以减少工作量。用seaborn来看每天的小费比例(图9-19是结果): +对于在绘制一个图形之前,需要进行合计的数据,使用 seaborn 可以减少工作量。用 seaborn 来看每天的小费比例(图 9-19 是结果): ```python In [83]: import seaborn as sns @@ -557,19 +557,19 @@ Out[85]: In [86]: sns.barplot(x='tip_pct', y='day', data=tips, orient='h') ``` -![图9-19 小费的每日比例,带有误差条](img/7178691-c33e8b3add99904b.png) +![图 9-19 小费的每日比例,带有误差条](img/7178691-c33e8b3add99904b.png) -seaborn的绘制函数使用data参数,它可能是pandas的DataFrame。其它的参数是关于列的名字。因为一天的每个值有多次观察,柱状图的值是tip_pct的平均值。绘制在柱状图上的黑线代表95%置信区间(可以通过可选参数配置)。 +seaborn 的绘制函数使用 data 参数,它可能是 pandas 的 DataFrame。其它的参数是关于列的名字。因为一天的每个值有多次观察,柱状图的值是 tip_pct 的平均值。绘制在柱状图上的黑线代表 95%置信区间(可以通过可选参数配置)。 -seaborn.barplot有颜色选项,使我们能够通过一个额外的值设置(见图9-20): +seaborn.barplot 有颜色选项,使我们能够通过一个额外的值设置(见图 9-20): ```python In [88]: sns.barplot(x='tip_pct', y='day', hue='time', data=tips, orient='h') ``` -![图9-20 根据天和时间的小费比例](img/7178691-06abe2f070222115.png) +![图 9-20 根据天和时间的小费比例](img/7178691-06abe2f070222115.png) -注意,seaborn已经自动修改了图形的美观度:默认调色板,图形背景和网格线的颜色。你可以用seaborn.set在不同的图形外观之间切换: +注意,seaborn 已经自动修改了图形的美观度:默认调色板,图形背景和网格线的颜色。你可以用 seaborn.set 在不同的图形外观之间切换: ```python In [90]: sns.set(style="whitegrid") @@ -577,21 +577,21 @@ In [90]: sns.set(style="whitegrid") ## 直方图和密度图 -直方图(histogram)是一种可以对值频率进行离散化显示的柱状图。数据点被拆分到离散的、间隔均匀的面元中,绘制的是各面元中数据点的数量。再以前面那个小费数据为例,通过在Series使用plot.hist方法,我们可以生成一张“小费占消费总额百分比”的直方图(如图9-21所示): +直方图(histogram)是一种可以对值频率进行离散化显示的柱状图。数据点被拆分到离散的、间隔均匀的面元中,绘制的是各面元中数据点的数量。再以前面那个小费数据为例,通过在 Series 使用 plot.hist 方法,我们可以生成一张“小费占消费总额百分比”的直方图(如图 9-21 所示): ```python In [92]: tips['tip_pct'].plot.hist(bins=50) ``` -![图9-21 小费百分比的直方图](img/7178691-255279376f7649a3.png) +![图 9-21 小费百分比的直方图](img/7178691-255279376f7649a3.png) -与此相关的一种图表类型是密度图,它是通过计算“可能会产生观测数据的连续概率分布的估计”而产生的。一般的过程是将该分布近似为一组核(即诸如正态分布之类的较为简单的分布)。因此,密度图也被称作KDE(Kernel Density Estimate,核密度估计)图。使用plot.kde和标准混合正态分布估计即可生成一张密度图(见图9-22): +与此相关的一种图表类型是密度图,它是通过计算“可能会产生观测数据的连续概率分布的估计”而产生的。一般的过程是将该分布近似为一组核(即诸如正态分布之类的较为简单的分布)。因此,密度图也被称作 KDE(Kernel Density Estimate,核密度估计)图。使用 plot.kde 和标准混合正态分布估计即可生成一张密度图(见图 9-22): ```python In [94]: tips['tip_pct'].plot.density() ``` -![图9-22 小费百分比的密度图](img/7178691-ee929d033159516a.png) +![图 9-22 小费百分比的密度图](img/7178691-ee929d033159516a.png) -seaborn的distplot方法绘制直方图和密度图更加简单,还可以同时画出直方图和连续密度估计图。作为例子,考虑一个双峰分布,由两个不同的标准正态分布组成(见图9-23): +seaborn 的 distplot 方法绘制直方图和密度图更加简单,还可以同时画出直方图和连续密度估计图。作为例子,考虑一个双峰分布,由两个不同的标准正态分布组成(见图 9-23): ```python In [96]: comp1 = np.random.normal(0, 1, size=200) @@ -603,11 +603,11 @@ In [98]: values = pd.Series(np.concatenate([comp1, comp2])) In [99]: sns.distplot(values, bins=100, color='k') ``` -![图9-23 标准混合密度估计的标准直方图](img/7178691-975f04d750c4efe2.png) +![图 9-23 标准混合密度估计的标准直方图](img/7178691-975f04d750c4efe2.png) ## 散布图或点图 -点图或散布图是观察两个一维数据序列之间的关系的有效手段。在下面这个例子中,我加载了来自statsmodels项目的macrodata数据集,选择了几个变量,然后计算对数差: +点图或散布图是观察两个一维数据序列之间的关系的有效手段。在下面这个例子中,我加载了来自 statsmodels 项目的 macrodata 数据集,选择了几个变量,然后计算对数差: ```python In [100]: macro = pd.read_csv('examples/macrodata.csv') @@ -626,7 +626,7 @@ Out[103]: 202 0.008894 0.012202 -0.405465 0.042560 ``` -然后可以使用seaborn的regplot方法,它可以做一个散布图,并加上一条线性回归的线(见图9-24): +然后可以使用 seaborn 的 regplot 方法,它可以做一个散布图,并加上一条线性回归的线(见图 9-24): ```python In [105]: sns.regplot('m1', 'unemp', data=trans_data) @@ -635,27 +635,27 @@ Out[105]: In [106]: plt.title('Changes in log %s versus log %s' % ('m1', 'unemp')) ``` -![图9-24 seaborn的回归/散布图](img/7178691-2133d20739478a80.png) +![图 9-24 seaborn 的回归/散布图](img/7178691-2133d20739478a80.png) -在探索式数据分析工作中,同时观察一组变量的散布图是很有意义的,这也被称为散布图矩阵(scatter plot matrix)。纯手工创建这样的图表很费工夫,所以seaborn提供了一个便捷的pairplot函数,它支持在对角线上放置每个变量的直方图或密度估计(见图9-25): +在探索式数据分析工作中,同时观察一组变量的散布图是很有意义的,这也被称为散布图矩阵(scatter plot matrix)。纯手工创建这样的图表很费工夫,所以 seaborn 提供了一个便捷的 pairplot 函数,它支持在对角线上放置每个变量的直方图或密度估计(见图 9-25): ```python In [107]: sns.pairplot(trans_data, diag_kind='kde', plot_kws={'alpha': 0.2}) ``` -![图9-25 statsmodels macro data的散布图矩阵](img/7178691-20aa530a44e06f61.png) +![图 9-25 statsmodels macro data 的散布图矩阵](img/7178691-20aa530a44e06f61.png) -你可能注意到了plot_kws参数。它可以让我们传递配置选项到非对角线元素上的图形使用。对于更详细的配置选项,可以查阅seaborn.pairplot文档字符串。 +你可能注意到了 plot_kws 参数。它可以让我们传递配置选项到非对角线元素上的图形使用。对于更详细的配置选项,可以查阅 seaborn.pairplot 文档字符串。 ##分面网格(facet grid)和类型数据 -要是数据集有额外的分组维度呢?有多个分类变量的数据可视化的一种方法是使用小面网格。seaborn有一个有用的内置函数factorplot,可以简化制作多种分面图(见图9-26): +要是数据集有额外的分组维度呢?有多个分类变量的数据可视化的一种方法是使用小面网格。seaborn 有一个有用的内置函数 factorplot,可以简化制作多种分面图(见图 9-26): ```python In [108]: sns.factorplot(x='day', y='tip_pct', hue='time', col='smoker', .....: kind='bar', data=tips[tips.tip_pct < 1]) ``` -![图9-26 按照天/时间/吸烟者的小费百分比](img/7178691-737ba19a0cbdd46f.png) +![图 9-26 按照天/时间/吸烟者的小费百分比](img/7178691-737ba19a0cbdd46f.png) 除了在分面中用不同的颜色按时间分组,我们还可以通过给每个时间值添加一行来扩展分面网格: @@ -665,26 +665,26 @@ In [109]: sns.factorplot(x='day', y='tip_pct', row='time', .....: kind='bar', data=tips[tips.tip_pct < 1]) ``` -![图9-27 按天的tip_pct,通过time/smoker分面](img/7178691-4e52192441c609f7.png) +![图 9-27 按天的 tip_pct,通过 time/smoker 分面](img/7178691-4e52192441c609f7.png) -factorplot支持其它的绘图类型,你可能会用到。例如,盒图(它可以显示中位数,四分位数,和异常值)就是一个有用的可视化类型(见图9-28): +factorplot 支持其它的绘图类型,你可能会用到。例如,盒图(它可以显示中位数,四分位数,和异常值)就是一个有用的可视化类型(见图 9-28): ```python In [110]: sns.factorplot(x='tip_pct', y='day', kind='box', .....: data=tips[tips.tip_pct < 0.5]) ``` -![图9-28 按天的tip_pct的盒图](img/7178691-356fb27a7c658920.png) +![图 9-28 按天的 tip_pct 的盒图](img/7178691-356fb27a7c658920.png) -使用更通用的seaborn.FacetGrid类,你可以创建自己的分面网格。请查阅seaborn的文档(https://seaborn.pydata.org/)。 +使用更通用的 seaborn.FacetGrid 类,你可以创建自己的分面网格。请查阅 seaborn 的文档(https://seaborn.pydata.org/)。 -# 9.3 其它的Python可视化工具 -与其它开源库类似,Python创建图形的方式非常多(根本罗列不完)。自从2010年,许多开发工作都集中在创建交互式图形以便在Web上发布。利用工具如Boken(https://bokeh.pydata.org/en/latest/)和Plotly(https://github.com/plotly/plotly.py),现在可以创建动态交互图形,用于网页浏览器。 +# 9.3 其它的 Python 可视化工具 +与其它开源库类似,Python 创建图形的方式非常多(根本罗列不完)。自从 2010 年,许多开发工作都集中在创建交互式图形以便在 Web 上发布。利用工具如 Boken(https://bokeh.pydata.org/en/latest/)和 Plotly(https://github.com/plotly/plotly.py),现在可以创建动态交互图形,用于网页浏览器。 -对于创建用于打印或网页的静态图形,我建议默认使用matplotlib和附加的库,比如pandas和seaborn。对于其它数据可视化要求,学习其它的可用工具可能是有用的。我鼓励你探索绘图的生态系统,因为它将持续发展。 +对于创建用于打印或网页的静态图形,我建议默认使用 matplotlib 和附加的库,比如 pandas 和 seaborn。对于其它数据可视化要求,学习其它的可用工具可能是有用的。我鼓励你探索绘图的生态系统,因为它将持续发展。 # 9.4 总结 -本章的目的是熟悉一些基本的数据可视化操作,使用pandas,matplotlib,和seaborn。如果视觉显示数据分析的结果对你的工作很重要,我鼓励你寻求更多的资源来了解更高效的数据可视化。这是一个活跃的研究领域,你可以通过在线和纸质的形式学习许多优秀的资源。 +本章的目的是熟悉一些基本的数据可视化操作,使用 pandas,matplotlib,和 seaborn。如果视觉显示数据分析的结果对你的工作很重要,我鼓励你寻求更多的资源来了解更高效的数据可视化。这是一个活跃的研究领域,你可以通过在线和纸质的形式学习许多优秀的资源。 -下一章,我们将重点放在pandas的数据聚合和分组操作上。 +下一章,我们将重点放在 pandas 的数据聚合和分组操作上。 diff --git a/docs/a.md b/docs/a.md index fbb1859..6e03971 100644 --- a/docs/a.md +++ b/docs/a.md @@ -1,42 +1,42 @@ # 附录 A NumPy 高级应用 -在这篇附录中,我会深入NumPy库的数组计算。这会包括ndarray更内部的细节,和更高级的数组操作和算法。 +在这篇附录中,我会深入 NumPy 库的数组计算。这会包括 ndarray 更内部的细节,和更高级的数组操作和算法。 本章包括了一些杂乱的章节,不需要仔细研究。 -# A.1 ndarray对象的内部机理 +# A.1 ndarray 对象的内部机理 -NumPy的ndarray提供了一种将同质数据块(可以是连续或跨越)解释为多维数组对象的方式。正如你之前所看到的那样,数据类型(dtype)决定了数据的解释方式,比如浮点数、整数、布尔值等。 +NumPy 的 ndarray 提供了一种将同质数据块(可以是连续或跨越)解释为多维数组对象的方式。正如你之前所看到的那样,数据类型(dtype)决定了数据的解释方式,比如浮点数、整数、布尔值等。 -ndarray如此强大的部分原因是所有数组对象都是数据块的一个跨度视图(strided view)。你可能想知道数组视图arr[::2,::-1]不复制任何数据的原因是什么。简单地说,ndarray不只是一块内存和一个dtype,它还有跨度信息,这使得数组能以各种步幅(step size)在内存中移动。更准确地讲,ndarray内部由以下内容组成: +ndarray 如此强大的部分原因是所有数组对象都是数据块的一个跨度视图(strided view)。你可能想知道数组视图 arr[::2,::-1]不复制任何数据的原因是什么。简单地说,ndarray 不只是一块内存和一个 dtype,它还有跨度信息,这使得数组能以各种步幅(step size)在内存中移动。更准确地讲,ndarray 内部由以下内容组成: - 一个指向数据(内存或内存映射文件中的一块数据)的指针。 -- 数据类型或dtype,描述在数组中的固定大小值的格子。 +- 数据类型或 dtype,描述在数组中的固定大小值的格子。 - 一个表示数组形状(shape)的元组。 - 一个跨度元组(stride),其中的整数指的是为了前进到当前维度下一个元素需要“跨过”的字节数。 -图A-1简单地说明了ndarray的内部结构。 +图 A-1 简单地说明了 ndarray 的内部结构。 -![图A-1 Numpy的ndarray对象](img/7178691-43452f2f413e5094.png) +![图 A-1 Numpy 的 ndarray 对象](img/7178691-43452f2f413e5094.png) -例如,一个10×5的数组,其形状为(10,5): +例如,一个 10×5 的数组,其形状为(10,5): ```python In [10]: np.ones((10, 5)).shape Out[10]: (10, 5) ``` -一个典型的(C顺序,稍后将详细讲解)3×4×5的float64(8个字节)数组,其跨度为(160,40,8) —— 知道跨度是非常有用的,通常,跨度在一个轴上越大,沿这个轴进行计算的开销就越大: +一个典型的(C 顺序,稍后将详细讲解)3×4×5 的 float64(8 个字节)数组,其跨度为(160,40,8) —— 知道跨度是非常有用的,通常,跨度在一个轴上越大,沿这个轴进行计算的开销就越大: ```python In [11]: np.ones((3, 4, 5), dtype=np.float64).strides Out[11]: (160, 40, 8) ``` -虽然NumPy用户很少会对数组的跨度信息感兴趣,但它们却是构建非复制式数组视图的重要因素。跨度甚至可以是负数,这样会使数组在内存中后向移动,比如在切片obj[::-1]或obj[:,::-1]中就是这样的。 +虽然 NumPy 用户很少会对数组的跨度信息感兴趣,但它们却是构建非复制式数组视图的重要因素。跨度甚至可以是负数,这样会使数组在内存中后向移动,比如在切片 obj[::-1]或 obj[:,::-1]中就是这样的。 -## NumPy数据类型体系 +## NumPy 数据类型体系 -你可能偶尔需要检查数组中所包含的是否是整数、浮点数、字符串或Python对象。因为浮点数的种类很多(从float16到float128),判断dtype是否属于某个大类的工作非常繁琐。幸运的是,dtype都有一个超类(比如np.integer和np.floating),它们可以跟np.issubdtype函数结合使用: +你可能偶尔需要检查数组中所包含的是否是整数、浮点数、字符串或 Python 对象。因为浮点数的种类很多(从 float16 到 float128),判断 dtype 是否属于某个大类的工作非常繁琐。幸运的是,dtype 都有一个超类(比如 np.integer 和 np.floating),它们可以跟 np.issubdtype 函数结合使用: ```python In [12]: ints = np.ones(10, dtype=np.uint16) @@ -49,7 +49,7 @@ In [15]: np.issubdtype(floats.dtype, np.floating) Out[15]: True ``` -调用dtype的mro方法即可查看其所有的父类: +调用 dtype 的 mro 方法即可查看其所有的父类: ```python In [16]: np.float64.mro() Out[16]: @@ -68,17 +68,17 @@ In [17]: np.issubdtype(ints.dtype, np.number) Out[17]: True ``` -大部分NumPy用户完全不需要了解这些知识,但是这些知识偶尔还是能派上用场的。图A-2说明了dtype体系以及父子类关系。 +大部分 NumPy 用户完全不需要了解这些知识,但是这些知识偶尔还是能派上用场的。图 A-2 说明了 dtype 体系以及父子类关系。 -![图A-2 NumPy的dtype体系](img/7178691-b8996bf943a06ab9.png) +![图 A-2 NumPy 的 dtype 体系](img/7178691-b8996bf943a06ab9.png) # A.2 高级数组操作 -除花式索引、切片、布尔条件取子集等操作之外,数组的操作方式还有很多。虽然pandas中的高级函数可以处理数据分析工作中的许多重型任务,但有时你还是需要编写一些在现有库中找不到的数据算法。 +除花式索引、切片、布尔条件取子集等操作之外,数组的操作方式还有很多。虽然 pandas 中的高级函数可以处理数据分析工作中的许多重型任务,但有时你还是需要编写一些在现有库中找不到的数据算法。 ## 数组重塑 -多数情况下,你可以无需复制任何数据,就将数组从一个形状转换为另一个形状。只需向数组的实例方法reshape传入一个表示新形状的元组即可实现该目的。例如,假设有一个一维数组,我们希望将其重新排列为一个矩阵(结果见图A-3): +多数情况下,你可以无需复制任何数据,就将数组从一个形状转换为另一个形状。只需向数组的实例方法 reshape 传入一个表示新形状的元组即可实现该目的。例如,假设有一个一维数组,我们希望将其重新排列为一个矩阵(结果见图 A-3): ```python In [18]: arr = np.arange(8) @@ -93,7 +93,7 @@ array([[0, 1], [6, 7]]) ``` -![图A-3 按C顺序(按行)和按Fortran顺序(按列)进行重塑](img/7178691-95bbca6d8d04e4c7.png) +![图 A-3 按 C 顺序(按行)和按 Fortran 顺序(按列)进行重塑](img/7178691-95bbca6d8d04e4c7.png) 多维数组也能被重塑: ```python @@ -116,7 +116,7 @@ array([[ 0, 1, 2], [12, 13, 14]]) ``` -与reshape将一维数组转换为多维数组的运算过程相反的运算通常称为扁平化(flattening)或散开(raveling): +与 reshape 将一维数组转换为多维数组的运算过程相反的运算通常称为扁平化(flattening)或散开(raveling): ```python In [27]: arr = np.arange(15).reshape((5, 3)) @@ -132,21 +132,21 @@ In [29]: arr.ravel() Out[29]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) ``` -如果结果中的值与原始数组相同,ravel不会产生源数据的副本。flatten方法的行为类似于ravel,只不过它总是返回数据的副本: +如果结果中的值与原始数组相同,ravel 不会产生源数据的副本。flatten 方法的行为类似于 ravel,只不过它总是返回数据的副本: ```python In [30]: arr.flatten() Out[30]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) ``` -数组可以被重塑或散开为别的顺序。这对NumPy新手来说是一个比较微妙的问题,所以在下一小节中我们将专门讲解这个问题。 +数组可以被重塑或散开为别的顺序。这对 NumPy 新手来说是一个比较微妙的问题,所以在下一小节中我们将专门讲解这个问题。 -## C和Fortran顺序 +## C 和 Fortran 顺序 -NumPy允许你更为灵活地控制数据在内存中的布局。默认情况下,NumPy数组是按行优先顺序创建的。在空间方面,这就意味着,对于一个二维数组,每行中的数据项是被存放在相邻内存位置上的。另一种顺序是列优先顺序,它意味着每列中的数据项是被存放在相邻内存位置上的。 +NumPy 允许你更为灵活地控制数据在内存中的布局。默认情况下,NumPy 数组是按行优先顺序创建的。在空间方面,这就意味着,对于一个二维数组,每行中的数据项是被存放在相邻内存位置上的。另一种顺序是列优先顺序,它意味着每列中的数据项是被存放在相邻内存位置上的。 -由于一些历史原因,行和列优先顺序又分别称为C和Fortran顺序。在FORTRAN 77中,矩阵全都是列优先的。 +由于一些历史原因,行和列优先顺序又分别称为 C 和 Fortran 顺序。在 FORTRAN 77 中,矩阵全都是列优先的。 -像reshape和reval这样的函数,都可以接受一个表示数组数据存放顺序的order参数。一般可以是'C'或'F'(还有'A'和'K'等不常用的选项,具体请参考NumPy的文档)。图A-3对此进行了说明: +像 reshape 和 reval 这样的函数,都可以接受一个表示数组数据存放顺序的 order 参数。一般可以是'C'或'F'(还有'A'和'K'等不常用的选项,具体请参考 NumPy 的文档)。图 A-3 对此进行了说明: ```python In [31]: arr = np.arange(12).reshape((3, 4)) @@ -163,17 +163,17 @@ In [34]: arr.ravel('F') Out[34]: array([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11]) ``` -![图A-3 按C(行优先)或Fortran(列优先)顺序进行重塑](img/7178691-f486e7c41d7e0eec.png) +![图 A-3 按 C(行优先)或 Fortran(列优先)顺序进行重塑](img/7178691-f486e7c41d7e0eec.png) -二维或更高维数组的重塑过程比较令人费解(见图A-3)。C和Fortran顺序的关键区别就是维度的行进顺序: +二维或更高维数组的重塑过程比较令人费解(见图 A-3)。C 和 Fortran 顺序的关键区别就是维度的行进顺序: -- C/行优先顺序:先经过更高的维度(例如,轴1会先于轴0被处理)。 -- Fortran/列优先顺序:后经过更高的维度(例如,轴0会先于轴1被处理)。 +- C/行优先顺序:先经过更高的维度(例如,轴 1 会先于轴 0 被处理)。 +- Fortran/列优先顺序:后经过更高的维度(例如,轴 0 会先于轴 1 被处理)。 ## 数组的合并和拆分 -numpy.concatenate可以按指定轴将一个由数组组成的序列(如元组、列表等)连接到一起: +numpy.concatenate 可以按指定轴将一个由数组组成的序列(如元组、列表等)连接到一起: ```python In [35]: arr1 = np.array([[1, 2, 3], [4, 5, 6]]) @@ -192,7 +192,7 @@ array([[ 1, 2, 3, 7, 8, 9], [ 4, 5, 6, 10, 11, 12]]) ``` -对于常见的连接操作,NumPy提供了一些比较方便的方法(如vstack和hstack)。因此,上面的运算还可以表达为: +对于常见的连接操作,NumPy 提供了一些比较方便的方法(如 vstack 和 hstack)。因此,上面的运算还可以表达为: ```python In [39]: np.vstack((arr1, arr2)) Out[39]: @@ -207,7 +207,7 @@ array([[ 1, 2, 3, 7, 8, 9], [ 4, 5, 6, 10, 11, 12]]) ``` -与此相反,split用于将一个数组沿指定轴拆分为多个数组: +与此相反,split 用于将一个数组沿指定轴拆分为多个数组: ```python In [41]: arr = np.random.randn(5, 2) @@ -234,15 +234,15 @@ array([[ 0.0929, 0.2817], [ 0.769 , 1.2464]]) ``` -传入到np.split的值[1,3]指示在哪个索引处分割数组。 +传入到 np.split 的值[1,3]指示在哪个索引处分割数组。 -表A-1中列出了所有关于数组连接和拆分的函数,其中有些是专门为了方便常见的连接运算而提供的。 +表 A-1 中列出了所有关于数组连接和拆分的函数,其中有些是专门为了方便常见的连接运算而提供的。 -![表A-1 数组连接函数](img/7178691-c597246722a6bb01.png) +![表 A-1 数组连接函数](img/7178691-c597246722a6bb01.png) -## 堆叠辅助类:r_和c_ +## 堆叠辅助类:r_ 和 c_ -NumPy命名空间中有两个特殊的对象——r_和c_,它们可以使数组的堆叠操作更为简洁: +NumPy 命名空间中有两个特殊的对象——r_ 和 c_,它们可以使数组的堆叠操作更为简洁: ```python In [47]: arr = np.arange(6) @@ -280,11 +280,11 @@ array([[ 1, -10], [ 5, -6]]) ``` -r_和c_的具体功能请参考其文档。 +r_ 和 c_ 的具体功能请参考其文档。 -## 元素的重复操作:tile和repeat +## 元素的重复操作:tile 和 repeat -对数组进行重复以产生更大数组的工具主要是repeat和tile这两个函数。repeat会将数组中的各个元素重复一定次数,从而产生一个更大的数组: +对数组进行重复以产生更大数组的工具主要是 repeat 和 tile 这两个函数。repeat 会将数组中的各个元素重复一定次数,从而产生一个更大的数组: ```python In [53]: arr = np.arange(3) @@ -295,7 +295,7 @@ In [55]: arr.repeat(3) Out[55]: array([0, 0, 0, 1, 1, 1, 2, 2, 2]) ``` ->笔记:跟其他流行的数组编程语言(如MATLAB)不同,NumPy中很少需要对数组进行重复(replicate)。这主要是因为广播(broadcasting,我们将在下一节中讲解该技术)能更好地满足该需求。 +>笔记:跟其他流行的数组编程语言(如 MATLAB)不同,NumPy 中很少需要对数组进行重复(replicate)。这主要是因为广播(broadcasting,我们将在下一节中讲解该技术)能更好地满足该需求。 默认情况下,如果传入的是一个整数,则各元素就都会重复那么多次。如果传入的是一组整数,则各元素就可以重复不同的次数: ```python @@ -336,7 +336,7 @@ array([[-2.0016, -2.0016, -0.3718, -0.3718, -0.3718], [ 1.669 , 1.669 , -0.4386, -0.4386, -0.4386]]) ``` -tile的功能是沿指定轴向堆叠数组的副本。你可以形象地将其想象成“铺瓷砖”: +tile 的功能是沿指定轴向堆叠数组的副本。你可以形象地将其想象成“铺瓷砖”: ```python In [62]: arr Out[62]: @@ -373,9 +373,9 @@ array([[-2.0016, -0.3718, -2.0016, -0.3718], [ 1.669 , -0.4386, 1.669 , -0.4386]]) ``` -## 花式索引的等价函数:take和put +## 花式索引的等价函数:take 和 put -在第4章中我们讲过,获取和设置数组子集的一个办法是通过整数数组使用花式索引: +在第 4 章中我们讲过,获取和设置数组子集的一个办法是通过整数数组使用花式索引: ```python In [67]: arr = np.arange(10) * 100 @@ -385,7 +385,7 @@ In [69]: arr[inds] Out[69]: array([700, 100, 200, 600]) ``` -ndarray还有其它方法用于获取单个轴向上的选区: +ndarray 还有其它方法用于获取单个轴向上的选区: ```python In [70]: arr.take(inds) Out[70]: array([700, 100, 200, 600]) @@ -401,7 +401,7 @@ In [74]: arr Out[74]: array([ 0, 41, 42, 300, 400, 500, 43, 40, 800, 900]) ``` -要在其它轴上使用take,只需传入axis关键字即可: +要在其它轴上使用 take,只需传入 axis 关键字即可: ```python In [75]: inds = [2, 0, 2, 1] @@ -418,7 +418,7 @@ array([[ 3.2489, -0.5397, 3.2489, 0.477 ], [ 0.3026, -0.5771, 0.3026, 0.1241]]) ``` -put不接受axis参数,它只会在数组的扁平化版本(一维,C顺序)上进行索引。因此,在需要用其他轴向的索引设置元素时,最好还是使用花式索引。 +put 不接受 axis 参数,它只会在数组的扁平化版本(一维,C 顺序)上进行索引。因此,在需要用其他轴向的索引设置元素时,最好还是使用花式索引。 # A.3 广播 @@ -433,7 +433,7 @@ In [81]: arr * 4 Out[81]: array([ 0, 4, 8, 12, 16]) ``` -这里我们说:在这个乘法运算中,标量值4被广播到了其他所有的元素上。 +这里我们说:在这个乘法运算中,标量值 4 被广播到了其他所有的元素上。 看一个例子,我们可以通过减去列平均值的方式对数组的每一列进行距平化处理。这个问题解决起来非常简单: ```python @@ -455,16 +455,16 @@ In [86]: demeaned.mean(0) Out[86]: array([-0., 0., -0.]) ``` -图A-4形象地展示了该过程。用广播的方式对行进行距平化处理会稍微麻烦一些。幸运的是,只要遵循一定的规则,低维度的值是可以被广播到数组的任意维度的(比如对二维数组各列减去行平均值)。 +图 A-4 形象地展示了该过程。用广播的方式对行进行距平化处理会稍微麻烦一些。幸运的是,只要遵循一定的规则,低维度的值是可以被广播到数组的任意维度的(比如对二维数组各列减去行平均值)。 -![图A-4 一维数组在轴0上的广播](img/7178691-6aaf022ab88452a9.png) +![图 A-4 一维数组在轴 0 上的广播](img/7178691-6aaf022ab88452a9.png) 于是就得到了: ![](img/7178691-fcaba8455960862a.png) -虽然我是一名经验丰富的NumPy老手,但经常还是得停下来画张图并想想广播的原则。再来看一下最后那个例子,假设你希望对各行减去那个平均值。由于arr.mean(0)的长度为3,所以它可以在0轴向上进行广播:因为arr的后缘维度是3,所以它们是兼容的。根据该原则,要在1轴向上做减法(即各行减去行平均值),较小的那个数组的形状必须是(4,1): +虽然我是一名经验丰富的 NumPy 老手,但经常还是得停下来画张图并想想广播的原则。再来看一下最后那个例子,假设你希望对各行减去那个平均值。由于 arr.mean(0)的长度为 3,所以它可以在 0 轴向上进行广播:因为 arr 的后缘维度是 3,所以它们是兼容的。根据该原则,要在 1 轴向上做减法(即各行减去行平均值),较小的那个数组的形状必须是(4,1): ```python In [87]: arr Out[87]: @@ -491,13 +491,13 @@ In [92]: demeaned.mean(1) Out[92]: array([ 0., -0., 0., 0.]) ``` -图A-5说明了该运算的过程。 +图 A-5 说明了该运算的过程。 -![图A-5 二维数组在轴1上的广播](img/7178691-9b0310d6773c3d38.png) +![图 A-5 二维数组在轴 1 上的广播](img/7178691-9b0310d6773c3d38.png) -图A-6展示了另外一种情况,这次是在一个三维数组上沿0轴向加上一个二维数组。 +图 A-6 展示了另外一种情况,这次是在一个三维数组上沿 0 轴向加上一个二维数组。 -![图A-6 三维数组在轴0上的广播](img/7178691-965eb28b60046cd9.png) +![图 A-6 三维数组在轴 0 上的广播](img/7178691-965eb28b60046cd9.png) ## 沿其它轴向广播 @@ -511,7 +511,7 @@ ValueError Traceback (most recent call last) ValueError: operands could not be broadcast together with shapes (4,3) (4,) ``` -人们经常需要通过算术运算过程将较低维度的数组在除0轴以外的其他轴向上广播。根据广播的原则,较小数组的“广播维”必须为1。在上面那个行距平化的例子中,这就意味着要将行平均值的形状变成(4,1)而不是(4,): +人们经常需要通过算术运算过程将较低维度的数组在除 0 轴以外的其他轴向上广播。根据广播的原则,较小数组的“广播维”必须为 1。在上面那个行距平化的例子中,这就意味着要将行平均值的形状变成(4,1)而不是(4,): ```python In [94]: arr - arr.mean(1).reshape((4, 1)) Out[94]: @@ -521,11 +521,11 @@ array([[-0.2095, 1.1334, -0.9239], [ 0.3234, -0.8599, 0.5365]]) ``` -对于三维的情况,在三维中的任何一维上广播其实也就是将数据重塑为兼容的形状而已。图A-7说明了要在三维数组各维度上广播的形状需求。 +对于三维的情况,在三维中的任何一维上广播其实也就是将数据重塑为兼容的形状而已。图 A-7 说明了要在三维数组各维度上广播的形状需求。 -![图A-7:能在该三维数组上广播的二维数组的形状](img/7178691-b40936aab8e757d0.png) +![图 A-7:能在该三维数组上广播的二维数组的形状](img/7178691-b40936aab8e757d0.png) -于是就有了一个非常普遍的问题(尤其是在通用算法中),即专门为了广播而添加一个长度为1的新轴。虽然reshape是一个办法,但插入轴需要构造一个表示新形状的元组。这是一个很郁闷的过程。因此,NumPy数组提供了一种通过索引机制插入轴的特殊语法。下面这段代码通过特殊的np.newaxis属性以及“全”切片来插入新轴: +于是就有了一个非常普遍的问题(尤其是在通用算法中),即专门为了广播而添加一个长度为 1 的新轴。虽然 reshape 是一个办法,但插入轴需要构造一个表示新形状的元组。这是一个很郁闷的过程。因此,NumPy 数组提供了一种通过索引机制插入轴的特殊语法。下面这段代码通过特殊的 np.newaxis 属性以及“全”切片来插入新轴: ```python In [95]: arr = np.zeros((4, 4)) @@ -546,7 +546,7 @@ In [100]: arr_1d[np.newaxis, :] Out[100]: array([[-2.3594, -0.1995, -1.542 ]]) ``` -因此,如果我们有一个三维数组,并希望对轴2进行距平化,那么只需要编写下面这样的代码就可以了: +因此,如果我们有一个三维数组,并希望对轴 2 进行距平化,那么只需要编写下面这样的代码就可以了: ```python In [101]: arr = np.random.randn(3, 4, 5) @@ -619,15 +619,15 @@ array([[-1.37 , -1.37 , -1.37 ], [ 1.6 , 1.6 , 1.6 ]]) ``` -# A.4 ufunc高级应用 +# A.4 ufunc 高级应用 -虽然许多NumPy用户只会用到通用函数所提供的快速的元素级运算,但通用函数实际上还有一些高级用法能使我们丢开循环而编写出更为简洁的代码。 +虽然许多 NumPy 用户只会用到通用函数所提供的快速的元素级运算,但通用函数实际上还有一些高级用法能使我们丢开循环而编写出更为简洁的代码。 -## ufunc实例方法 +## ufunc 实例方法 -NumPy的各个二元ufunc都有一些用于执行特定矢量化运算的特殊方法。表A-2汇总了这些方法,下面我将通过几个具体的例子对它们进行说明。 +NumPy 的各个二元 ufunc 都有一些用于执行特定矢量化运算的特殊方法。表 A-2 汇总了这些方法,下面我将通过几个具体的例子对它们进行说明。 -reduce接受一个数组参数,并通过一系列的二元运算对其值进行聚合(可指明轴向)。例如,我们可以用np.add.reduce对数组中各个元素进行求和: +reduce 接受一个数组参数,并通过一系列的二元运算对其值进行聚合(可指明轴向)。例如,我们可以用 np.add.reduce 对数组中各个元素进行求和: ```python In [115]: arr = np.arange(10) @@ -638,7 +638,7 @@ In [117]: arr.sum() Out[117]: 45 ``` -起始值取决于ufunc(对于add的情况,就是0)。如果设置了轴号,约简运算就会沿该轴向执行。这就使你能用一种比较简洁的方式得到某些问题的答案。在下面这个例子中,我们用np.logical_and检查数组各行中的值是否是有序的: +起始值取决于 ufunc(对于 add 的情况,就是 0)。如果设置了轴号,约简运算就会沿该轴向执行。这就使你能用一种比较简洁的方式得到某些问题的答案。在下面这个例子中,我们用 np.logical_and 检查数组各行中的值是否是有序的: ```python In [118]: np.random.seed(12346) # for reproducibility @@ -658,9 +658,9 @@ In [122]: np.logical_and.reduce(arr[:, :-1] < arr[:, 1:], axis=1) Out[122]: array([ True, False, True, False, True], dtype=bool) ``` -注意,logical_and.reduce跟all方法是等价的。 +注意,logical_and.reduce 跟 all 方法是等价的。 -ccumulate跟reduce的关系就像cumsum跟sum的关系那样。它产生一个跟原数组大小相同的中间“累计”值数组: +ccumulate 跟 reduce 的关系就像 cumsum 跟 sum 的关系那样。它产生一个跟原数组大小相同的中间“累计”值数组: ```python In [123]: arr = np.arange(15).reshape((3, 5)) @@ -671,7 +671,7 @@ array([[ 0, 1, 3, 6, 10], [10, 21, 33, 46, 60]]) ``` -outer用于计算两个数组的叉积: +outer 用于计算两个数组的叉积: ```python In [125]: arr = np.arange(3).repeat([1, 2, 2]) @@ -687,7 +687,7 @@ array([[0, 0, 0, 0, 0], [0, 2, 4, 6, 8]]) ``` -outer输出结果的维度是两个输入数据的维度之和: +outer 输出结果的维度是两个输入数据的维度之和: ```python In [128]: x, y = np.random.randn(3, 4), np.random.randn(5) @@ -697,7 +697,7 @@ In [130]: result.shape Out[130]: (3, 4, 5) ``` -最后一个方法reduceat用于计算“局部约简”,其实就是一个对数据各切片进行聚合的groupby运算。它接受一组用于指示如何对值进行拆分和聚合的“面元边界”: +最后一个方法 reduceat 用于计算“局部约简”,其实就是一个对数据各切片进行聚合的 groupby 运算。它接受一组用于指示如何对值进行拆分和聚合的“面元边界”: ```python In [131]: arr = np.arange(10) @@ -705,7 +705,7 @@ In [132]: np.add.reduceat(arr, [0, 5, 8]) Out[132]: array([10, 18, 17]) ``` -最终结果是在arr[0:5]、arr[5:8]以及arr[8:]上执行的约简。跟其他方法一样,这里也可以传入一个axis参数: +最终结果是在 arr[0:5]、arr[5:8]以及 arr[8:]上执行的约简。跟其他方法一样,这里也可以传入一个 axis 参数: ```python In [133]: arr = np.multiply.outer(np.arange(4), np.arange(5)) @@ -724,16 +724,16 @@ array([[ 0, 0, 0], [ 3, 15, 12]]) ``` -表A-2总结了部分的ufunc方法。 +表 A-2 总结了部分的 ufunc 方法。 -![表A ufunc方法](img/7178691-c997bd45000f7b72.png) +![表 A ufunc 方法](img/7178691-c997bd45000f7b72.png) -## 编写新的ufunc +## 编写新的 ufunc -有多种方法可以让你编写自己的NumPy ufuncs。最常见的是使用NumPy C API,但它超越了本书的范围。在本节,我们讲纯粹的Python ufunc。 +有多种方法可以让你编写自己的 NumPy ufuncs。最常见的是使用 NumPy C API,但它超越了本书的范围。在本节,我们讲纯粹的 Python ufunc。 -numpy.frompyfunc接受一个Python函数以及两个分别表示输入输出参数数量的参数。例如,下面是一个能够实现元素级加法的简单函数: +numpy.frompyfunc 接受一个 Python 函数以及两个分别表示输入输出参数数量的参数。例如,下面是一个能够实现元素级加法的简单函数: ```python In [136]: def add_elements(x, y): .....: return x + y @@ -744,7 +744,7 @@ In [138]: add_them(np.arange(8), np.arange(8)) Out[138]: array([0, 2, 4, 6, 8, 10, 12, 14], dtype=object) ``` -用frompyfunc创建的函数总是返回Python对象数组,这一点很不方便。幸运的是,还有另一个办法,即numpy.vectorize。虽然没有frompyfunc那么强大,但可以让你指定输出类型: +用 frompyfunc 创建的函数总是返回 Python 对象数组,这一点很不方便。幸运的是,还有另一个办法,即 numpy.vectorize。虽然没有 frompyfunc 那么强大,但可以让你指定输出类型: ```python In [139]: add_them = np.vectorize(add_elements, otypes=[np.float64]) @@ -752,7 +752,7 @@ In [140]: add_them(np.arange(8), np.arange(8)) Out[140]: array([ 0., 2., 4., 6., 8., 10., 12., 14.]) ``` -虽然这两个函数提供了一种创建ufunc型函数的手段,但它们非常慢,因为它们在计算每个元素时都要执行一次Python函数调用,这就会比NumPy自带的基于C的ufunc慢很多: +虽然这两个函数提供了一种创建 ufunc 型函数的手段,但它们非常慢,因为它们在计算每个元素时都要执行一次 Python 函数调用,这就会比 NumPy 自带的基于 C 的 ufunc 慢很多: ```python In [141]: arr = np.random.randn(10000) @@ -763,11 +763,11 @@ In [143]: %timeit np.add(arr, arr) 6.89 us +- 504 ns per loop (mean +- std. dev. of 7 runs, 100000 loops each) ``` -本章的后面,我会介绍使用Numba(http://numba.pydata.org/),创建快速Python ufuncs。 +本章的后面,我会介绍使用 Numba(http://numba.pydata.org/),创建快速 Python ufuncs。 # A.5 结构化和记录式数组 -你可能已经注意到了,到目前为止我们所讨论的ndarray都是一种同质数据容器,也就是说,在它所表示的内存块中,各元素占用的字节数相同(具体根据dtype而定)。从表面上看,它似乎不能用于表示异质或表格型的数据。结构化数组是一种特殊的ndarray,其中的各个元素可以被看做C语言中的结构体(struct,这就是“结构化”的由来)或SQL表中带有多个命名字段的行: +你可能已经注意到了,到目前为止我们所讨论的 ndarray 都是一种同质数据容器,也就是说,在它所表示的内存块中,各元素占用的字节数相同(具体根据 dtype 而定)。从表面上看,它似乎不能用于表示异质或表格型的数据。结构化数组是一种特殊的 ndarray,其中的各个元素可以被看做 C 语言中的结构体(struct,这就是“结构化”的由来)或 SQL 表中带有多个命名字段的行: ```python In [144]: dtype = [('x', np.float64), ('y', np.int32)] @@ -779,7 +779,7 @@ array([( 1.5 , 6), ( 3.1416, -2)], dtype=[('x', ' ``` -刚开始使用lexsort的时候可能会比较容易头晕,这是因为键的应用顺序是从最后一个传入的算起的。不难看出,last_name是先于first_name被应用的。 +刚开始使用 lexsort 的时候可能会比较容易头晕,这是因为键的应用顺序是从最后一个传入的算起的。不难看出,last_name 是先于 first_name 被应用的。 ->笔记:Series和DataFrame的sort_index以及Series的order方法就是通过这些函数的变体(它们还必须考虑缺失值)实现的。 +>笔记:Series 和 DataFrame 的 sort_index 以及 Series 的 order 方法就是通过这些函数的变体(它们还必须考虑缺失值)实现的。 ## 其他排序算法 @@ -996,13 +996,13 @@ array(['1:first', '1:second', '1:third', '2:first', '2:second'], dtype='警告:当处理非常大的数据集时,要记住IPython的输入和输出的历史会造成被引用的对象不被垃圾回收(释放内存),即使你使用del关键字从交互命名空间删除变量。在这种情况下,小心使用xdel %和%reset可以帮助你避免陷入内存问题。 +>警告:当处理非常大的数据集时,要记住 IPython 的输入和输出的历史会造成被引用的对象不被垃圾回收(释放内存),即使你使用 del 关键字从交互命名空间删除变量。在这种情况下,小心使用 xdel %和%reset 可以帮助你避免陷入内存问题。 # B.2 与操作系统交互 -IPython的另一个功能是无缝连接文件系统和操作系统。这意味着,在同时做其它事时,无需退出IPython,就可以像Windows或Unix使用命令行操作,包括shell命令、更改目录、用Python对象(列表或字符串)存储结果。它还有简单的命令别名和目录书签功能。 +IPython 的另一个功能是无缝连接文件系统和操作系统。这意味着,在同时做其它事时,无需退出 IPython,就可以像 Windows 或 Unix 使用命令行操作,包括 shell 命令、更改目录、用 Python 对象(列表或字符串)存储结果。它还有简单的命令别名和目录书签功能。 -表B-1总结了调用shell命令的魔术函数和语法。我会在下面几节介绍这些功能。 +表 B-1 总结了调用 shell 命令的魔术函数和语法。我会在下面几节介绍这些功能。 -![表B-1 IPython系统相关命令](img/7178691-4da7ee14be2da211.png) +![表 B-1 IPython 系统相关命令](img/7178691-4da7ee14be2da211.png) -## Shell命令和别名 -用叹号开始一行,是告诉IPython执行叹号后面的所有内容。这意味着你可以删除文件(取决于操作系统,用rm或del)、改变目录或执行任何其他命令。 +## Shell 命令和别名 +用叹号开始一行,是告诉 IPython 执行叹号后面的所有内容。这意味着你可以删除文件(取决于操作系统,用 rm 或 del)、改变目录或执行任何其他命令。 -通过给变量加上叹号,你可以在一个变量中存储命令的控制台输出。例如,在我联网的基于Linux的主机上,我可以获得IP地址为Python变量: +通过给变量加上叹号,你可以在一个变量中存储命令的控制台输出。例如,在我联网的基于 Linux 的主机上,我可以获得 IP 地址为 Python 变量: ```python In [1]: ip_info = !ifconfig wlan0 | grep "inet " @@ -85,9 +85,9 @@ In [2]: ip_info[0].strip() Out[2]: 'inet addr:10.0.0.11 Bcast:10.0.0.255 Mask:255.255.255.0' ``` -返回的Python对象ip_info实际上是一个自定义的列表类型,它包含着多种版本的控制台输出。 +返回的 Python 对象 ip_info 实际上是一个自定义的列表类型,它包含着多种版本的控制台输出。 -当使用!,IPython还可以替换定义在当前环境的Python值。要这么做,可以在变量名前面加上$符号: +当使用!,IPython 还可以替换定义在当前环境的 Python 值。要这么做,可以在变量名前面加上$符号: ```python In [3]: foo = 'test*' @@ -95,7 +95,7 @@ In [4]: !ls $foo test4.py test.py test.xml ``` -%alias魔术函数可以自定义shell命令的快捷方式。看一个简单的例子: +%alias 魔术函数可以自定义 shell 命令的快捷方式。看一个简单的例子: ```python In [1]: %alias ll ls -l @@ -121,38 +121,38 @@ In [559]: test_alias macrodata.csv spx.csv tips.csv ``` -当session结束,你定义的别名就会失效。要创建恒久的别名,需要使用配置。 +当 session 结束,你定义的别名就会失效。要创建恒久的别名,需要使用配置。 ## 目录书签系统 -IPython有一个简单的目录书签系统,可以让你保存常用目录的别名,这样在跳来跳去的时候会非常方便。例如,假设你想创建一个书签,指向本书的补充内容: +IPython 有一个简单的目录书签系统,可以让你保存常用目录的别名,这样在跳来跳去的时候会非常方便。例如,假设你想创建一个书签,指向本书的补充内容: ```python In [6]: %bookmark py4da /home/wesm/code/pydata-book ``` -这么做之后,当使用%cd魔术命令,就可以使用定义的书签: +这么做之后,当使用%cd 魔术命令,就可以使用定义的书签: ```python In [7]: cd py4da (bookmark:py4da) -> /home/wesm/code/pydata-book /home/wesm/code/pydata-book ``` -如果书签的名字,与当前工作目录的一个目录重名,你可以使用-b标志来覆写,使用书签的位置。使用%bookmark的-l选项,可以列出所有的书签: +如果书签的名字,与当前工作目录的一个目录重名,你可以使用-b 标志来覆写,使用书签的位置。使用%bookmark 的-l 选项,可以列出所有的书签: ```python In [8]: %bookmark -l Current bookmarks: py4da -> /home/wesm/code/pydata-book-source ``` -书签,和别名不同,在session之间是保持的。 +书签,和别名不同,在 session 之间是保持的。 # B.3 软件开发工具 -除了作为优秀的交互式计算和数据探索环境,IPython也是有效的Python软件开发工具。在数据分析中,最重要的是要有正确的代码。幸运的是,IPython紧密集成了和加强了Python内置的pdb调试器。第二,需要快速的代码。对于这点,IPython有易于使用的代码计时和分析工具。我会详细介绍这些工具。 +除了作为优秀的交互式计算和数据探索环境,IPython 也是有效的 Python 软件开发工具。在数据分析中,最重要的是要有正确的代码。幸运的是,IPython 紧密集成了和加强了 Python 内置的 pdb 调试器。第二,需要快速的代码。对于这点,IPython 有易于使用的代码计时和分析工具。我会详细介绍这些工具。 ## 交互调试器 -IPython的调试器用tab补全、语法增强、逐行异常追踪增强了pdb。调试代码的最佳时间就是刚刚发生错误。异常发生之后就输入%debug,就启动了调试器,进入抛出异常的堆栈框架: +IPython 的调试器用 tab 补全、语法增强、逐行异常追踪增强了 pdb。调试代码的最佳时间就是刚刚发生错误。异常发生之后就输入%debug,就启动了调试器,进入抛出异常的堆栈框架: ```python In [2]: run examples/ipython_bug.py @@ -188,7 +188,7 @@ In [3]: %debug ipdb> ``` -一旦进入调试器,你就可以执行任意的Python代码,在每个堆栈框架中检查所有的对象和数据(解释器会保持它们活跃)。默认是从错误发生的最低级开始。通过u(up)和d(down),你可以在不同等级的堆栈踪迹切换: +一旦进入调试器,你就可以执行任意的 Python 代码,在每个堆栈框架中检查所有的对象和数据(解释器会保持它们活跃)。默认是从错误发生的最低级开始。通过 u(up)和 d(down),你可以在不同等级的堆栈踪迹切换: ```python ipdb> u > /home/wesm/code/pydata-book/examples/ipython_bug.py(13)calling_things() @@ -197,9 +197,9 @@ ipdb> u 14 ``` -执行%pdb命令,可以在发生任何异常时让IPython自动启动调试器,许多用户会发现这个功能非常好用。 +执行%pdb 命令,可以在发生任何异常时让 IPython 自动启动调试器,许多用户会发现这个功能非常好用。 -用调试器帮助开发代码也很容易,特别是当你希望设置断点或在函数和脚本间移动,以检查每个阶段的状态。有多种方法可以实现。第一种是使用%run和-d,它会在执行传入脚本的任何代码之前调用调试器。你必须马上按s(step)以进入脚本: +用调试器帮助开发代码也很容易,特别是当你希望设置断点或在函数和脚本间移动,以检查每个阶段的状态。有多种方法可以实现。第一种是使用%run 和-d,它会在执行传入脚本的任何代码之前调用调试器。你必须马上按 s(step)以进入脚本: ```python In [5]: run -d examples/ipython_bug.py Breakpoint 1 at /home/wesm/code/pydata-book/examples/ipython_bug.py:1 @@ -214,7 +214,7 @@ ipdb> s 3 b = 6 ``` -然后,你就可以决定如何工作。例如,在前面的异常,我们可以设置一个断点,就在调用works_fine之前,然后运行脚本,在遇到断点时按c(continue): +然后,你就可以决定如何工作。例如,在前面的异常,我们可以设置一个断点,就在调用 works_fine 之前,然后运行脚本,在遇到断点时按 c(continue): ```python ipdb> b 12 ipdb> c @@ -224,7 +224,7 @@ ipdb> c 13 throws_an_exception() ``` -这时,你可以step进入works_fine(),或通过按n(next)执行works_fine(),进入下一行: +这时,你可以 step 进入 works_fine(),或通过按 n(next)执行 works_fine(),进入下一行: ```python ipdb> n > /home/wesm/code/pydata-book/examples/ipython_bug.py(13)calling_things() @@ -233,7 +233,7 @@ ipdb> n 14 ``` -然后,我们可以进入throws_an_exception,到达发生错误的一行,查看变量。注意,调试器的命令是在变量名之前,在变量名前面加叹号!可以查看内容: +然后,我们可以进入 throws_an_exception,到达发生错误的一行,查看变量。注意,调试器的命令是在变量名之前,在变量名前面加叹号!可以查看内容: ```python ipdb> s --Call-- @@ -266,13 +266,13 @@ ipdb> !b 6 ``` -提高使用交互式调试器的熟练度需要练习和经验。表B-2,列出了所有调试器命令。如果你习惯了IDE,你可能觉得终端的调试器在一开始会不顺手,但会觉得越来越好用。一些Python的IDEs有很好的GUI调试器,选择顺手的就好。 +提高使用交互式调试器的熟练度需要练习和经验。表 B-2,列出了所有调试器命令。如果你习惯了 IDE,你可能觉得终端的调试器在一开始会不顺手,但会觉得越来越好用。一些 Python 的 IDEs 有很好的 GUI 调试器,选择顺手的就好。 -![表B-2 IPython调试器命令](img/7178691-90a4b17e20b5b03a.png) +![表 B-2 IPython 调试器命令](img/7178691-90a4b17e20b5b03a.png) ## 使用调试器的其它方式 -还有一些其它工作可以用到调试器。第一个是使用特殊的set_trace函数(根据pdb.set_trace命名的),这是一个简装的断点。还有两种方法是你可能想用的(像我一样,将其添加到IPython的配置): +还有一些其它工作可以用到调试器。第一个是使用特殊的 set_trace 函数(根据 pdb.set_trace 命名的),这是一个简装的断点。还有两种方法是你可能想用的(像我一样,将其添加到 IPython 的配置): ```python from IPython.core.debugger import Pdb @@ -283,7 +283,7 @@ def debug(f, *args, **kwargs): pdb = Pdb(color_scheme='Linux') return pdb.runcall(f, *args, **kwargs) ``` -第一个函数set_trace非常简单。如果你想暂时停下来进行仔细检查(比如发生异常之前),可以在代码的任何位置使用set_trace: +第一个函数 set_trace 非常简单。如果你想暂时停下来进行仔细检查(比如发生异常之前),可以在代码的任何位置使用 set_trace: ```python In [7]: run examples/ipython_bug.py > /home/wesm/code/pydata-book/examples/ipython_bug.py(16)calling_things() @@ -292,16 +292,16 @@ In [7]: run examples/ipython_bug.py 17 ``` -按c(continue)可以让代码继续正常行进。 +按 c(continue)可以让代码继续正常行进。 -我们刚看的debug函数,可以让你方便的在调用任何函数时使用调试器。假设我们写了一个下面的函数,想逐步分析它的逻辑: +我们刚看的 debug 函数,可以让你方便的在调用任何函数时使用调试器。假设我们写了一个下面的函数,想逐步分析它的逻辑: ```python def f(x, y, z=1): tmp = x + y return tmp / z ``` -普通地使用f,就会像f(1, 2, z=3)。而要想进入f,将f作为第一个参数传递给debug,再将位置和关键词参数传递给f: +普通地使用 f,就会像 f(1, 2, z=3)。而要想进入 f,将 f 作为第一个参数传递给 debug,再将位置和关键词参数传递给 f: ```python In [6]: debug(f, 1, 2, z=3) > (2)f() @@ -314,7 +314,7 @@ ipdb> 这两个简单方法节省了我平时的大量时间。 -最后,调试器可以和%run一起使用。脚本通过运行%run -d,就可以直接进入调试器,随意设置断点并启动脚本: +最后,调试器可以和%run 一起使用。脚本通过运行%run -d,就可以直接进入调试器,随意设置断点并启动脚本: ```python In [1]: %run -d examples/ipython_bug.py Breakpoint 1 at /home/wesm/code/pydata-book/examples/ipython_bug.py:1 @@ -324,7 +324,7 @@ NOTE: Enter 'c' at the ipdb> prompt to start your script. ipdb> ``` -加上-b和行号,可以预设一个断点: +加上-b 和行号,可以预设一个断点: ```python In [2]: %run -d -b2 examples/ipython_bug.py @@ -343,9 +343,9 @@ ipdb> ## 代码计时:%time 和 %timeit -对于大型和长时间运行的数据分析应用,你可能希望测量不同组件或单独函数调用语句的执行时间。你可能想知道哪个函数占用的时间最长。幸运的是,IPython可以让你开发和测试代码时,很容易地获得这些信息。 +对于大型和长时间运行的数据分析应用,你可能希望测量不同组件或单独函数调用语句的执行时间。你可能想知道哪个函数占用的时间最长。幸运的是,IPython 可以让你开发和测试代码时,很容易地获得这些信息。 -手动用time模块和它的函数time.clock和time.time给代码计时,既单调又重复,因为必须要写一些无趣的模板化代码: +手动用 time 模块和它的函数 time.clock 和 time.time 给代码计时,既单调又重复,因为必须要写一些无趣的模板化代码: ```python import time start = time.time() @@ -354,9 +354,9 @@ for i in range(iterations): elapsed_per = (time.time() - start) / iterations ``` -因为这是一个很普通的操作,IPython有两个魔术函数,%time和%timeit,可以自动化这个过程。 +因为这是一个很普通的操作,IPython 有两个魔术函数,%time 和%timeit,可以自动化这个过程。 -%time会运行一次语句,报告总共的执行时间。假设我们有一个大的字符串列表,我们想比较不同的可以挑选出特定开头字符串的方法。这里有一个含有600000字符串的列表,和两个方法,用以选出foo开头的字符串: +%time 会运行一次语句,报告总共的执行时间。假设我们有一个大的字符串列表,我们想比较不同的可以挑选出特定开头字符串的方法。这里有一个含有 600000 字符串的列表,和两个方法,用以选出 foo 开头的字符串: ```python # a very large list of strings strings = ['foo', 'foobar', 'baz', 'qux', @@ -367,7 +367,7 @@ method1 = [x for x in strings if x.startswith('foo')] method2 = [x for x in strings if x[:3] == 'foo'] ``` -看起来它们的性能应该是同级别的,但事实呢?用%time进行一下测量: +看起来它们的性能应该是同级别的,但事实呢?用%time 进行一下测量: ```python In [561]: %time method1 = [x for x in strings if x.startswith('foo')] CPU times: user 0.19 s, sys: 0.00 s, total: 0.19 s @@ -378,7 +378,7 @@ CPU times: user 0.09 s, sys: 0.00 s, total: 0.09 s Wall time: 0.09 s ``` -Wall time(wall-clock time的简写)是主要关注的。第一个方法是第二个方法的两倍多,但是这种测量方法并不准确。如果用%time多次测量,你就会发现结果是变化的。要想更准确,可以使用%timeit魔术函数。给出任意一条语句,它能多次运行这条语句以得到一个更为准确的时间: +Wall time(wall-clock time 的简写)是主要关注的。第一个方法是第二个方法的两倍多,但是这种测量方法并不准确。如果用%time 多次测量,你就会发现结果是变化的。要想更准确,可以使用%timeit 魔术函数。给出任意一条语句,它能多次运行这条语句以得到一个更为准确的时间: ```python In [563]: %timeit [x for x in strings if x.startswith('foo')] 10 loops, best of 3: 159 ms per loop @@ -387,9 +387,9 @@ In [564]: %timeit [x for x in strings if x[:3] == 'foo'] 10 loops, best of 3: 59.3 ms per loop ``` -这个例子说明了解Python标准库、NumPy、pandas和其它库的性能是很有价值的。在大型数据分析中,这些毫秒的时间就会累积起来! +这个例子说明了解 Python 标准库、NumPy、pandas 和其它库的性能是很有价值的。在大型数据分析中,这些毫秒的时间就会累积起来! -%timeit特别适合分析执行时间短的语句和函数,即使是微秒或纳秒。这些时间可能看起来毫不重要,但是一个20微秒的函数执行1百万次就比一个5微秒的函数长15秒。在上一个例子中,我们可以直接比较两个字符串操作,以了解它们的性能特点: +%timeit 特别适合分析执行时间短的语句和函数,即使是微秒或纳秒。这些时间可能看起来毫不重要,但是一个 20 微秒的函数执行 1 百万次就比一个 5 微秒的函数长 15 秒。在上一个例子中,我们可以直接比较两个字符串操作,以了解它们的性能特点: ```python In [565]: x = 'foobar' @@ -402,11 +402,11 @@ In [568]: %timeit x[:3] == y 10000000 loops, best of 3: 147 ns per loop ``` -## 基础分析:%prun和%run -p +## 基础分析:%prun 和%run -p -分析代码与代码计时关系很紧密,除了它关注的是“时间花在了哪里”。Python主要的分析工具是cProfile模块,它并不局限于IPython。cProfile会执行一个程序或任意的代码块,并会跟踪每个函数执行的时间。 +分析代码与代码计时关系很紧密,除了它关注的是“时间花在了哪里”。Python 主要的分析工具是 cProfile 模块,它并不局限于 IPython。cProfile 会执行一个程序或任意的代码块,并会跟踪每个函数执行的时间。 -使用cProfile的通常方式是在命令行中运行一整段程序,输出每个函数的累积时间。假设我们有一个简单的在循环中进行线型代数运算的脚本(计算一系列的100×100矩阵的最大绝对特征值): +使用 cProfile 的通常方式是在命令行中运行一整段程序,输出每个函数的累积时间。假设我们有一个简单的在循环中进行线型代数运算的脚本(计算一系列的 100×100 矩阵的最大绝对特征值): ```python import numpy as np from numpy.linalg import eigvals @@ -423,12 +423,12 @@ some_results = run_experiment() print 'Largest one we saw: %s' % np.max(some_results) ``` -你可以用cProfile运行这个脚本,使用下面的命令行: +你可以用 cProfile 运行这个脚本,使用下面的命令行: ``` python -m cProfile cprof_example.py ``` -运行之后,你会发现输出是按函数名排序的。这样要看出谁耗费的时间多有点困难,最好用-s指定排序: +运行之后,你会发现输出是按函数名排序的。这样要看出谁耗费的时间多有点困难,最好用-s 指定排序: ```python $ python -m cProfile -s cumulative cprof_example.py Largest one we saw: 11.923204422 @@ -454,9 +454,9 @@ ncalls tottime percall cumtime percall filename:lineno(function) 100 0.003 0.000 0.005 0.000 linalg.py:162(_assertFinite) ``` -只显示出前15行。扫描cumtime列,可以容易地看出每个函数用了多少时间。如果一个函数调用了其它函数,计时并不会停止。cProfile会记录每个函数的起始和结束时间,使用它们进行计时。 +只显示出前 15 行。扫描 cumtime 列,可以容易地看出每个函数用了多少时间。如果一个函数调用了其它函数,计时并不会停止。cProfile 会记录每个函数的起始和结束时间,使用它们进行计时。 -除了在命令行中使用,cProfile也可以在程序中使用,分析任意代码块,而不必运行新进程。Ipython的%prun和%run -p,有便捷的接口实现这个功能。%prun使用类似cProfile的命令行选项,但是可以分析任意Python语句,而不用整个py文件: +除了在命令行中使用,cProfile 也可以在程序中使用,分析任意代码块,而不必运行新进程。Ipython 的%prun 和%run -p,有便捷的接口实现这个功能。%prun 使用类似 cProfile 的命令行选项,但是可以分析任意 Python 语句,而不用整个 py 文件: ```python In [4]: %prun -l 7 -s cumulative run_experiment() 4203 function calls in 0.643 seconds @@ -473,15 +473,15 @@ ncalls tottime percall cumtime percall filename:lineno(function) 200 0.002 0.000 0.002 0.000 {method 'all' of 'numpy.ndarray'} ``` -相似的,调用``%run -p -s cumulative cprof_example.py``有和命令行相似的作用,只是你不用离开Ipython。 +相似的,调用``%run -p -s cumulative cprof_example.py``有和命令行相似的作用,只是你不用离开 Ipython。 -在Jupyter notebook中,你可以使用%%prun魔术方法(两个%)来分析一整段代码。这会弹出一个带有分析输出的独立窗口。便于快速回答一些问题,比如“为什么这段代码用了这么长时间”? +在 Jupyter notebook 中,你可以使用%%prun 魔术方法(两个%)来分析一整段代码。这会弹出一个带有分析输出的独立窗口。便于快速回答一些问题,比如“为什么这段代码用了这么长时间”? -使用IPython或Jupyter,还有一些其它工具可以让分析工作更便于理解。其中之一是SnakeViz(https://github.com/jiffyclub/snakeviz/),它会使用d3.js产生一个分析结果的交互可视化界面。 +使用 IPython 或 Jupyter,还有一些其它工具可以让分析工作更便于理解。其中之一是 SnakeViz(https://github.com/jiffyclub/snakeviz/),它会使用 d3.js 产生一个分析结果的交互可视化界面。 ## 逐行分析函数 -有些情况下,用%prun(或其它基于cProfile的分析方法)得到的信息,不能获得函数执行时间的整个过程,或者结果过于复杂,加上函数名,很难进行解读。对于这种情况,有一个小库叫做line_profiler(可以通过PyPI或包管理工具获得)。它包含IPython插件,可以启用一个新的魔术函数%lprun,可以对一个函数或多个函数进行逐行分析。你可以通过修改IPython配置(查看IPython文档或本章后面的配置小节)加入下面这行,启用这个插件: +有些情况下,用%prun(或其它基于 cProfile 的分析方法)得到的信息,不能获得函数执行时间的整个过程,或者结果过于复杂,加上函数名,很难进行解读。对于这种情况,有一个小库叫做 line_profiler(可以通过 PyPI 或包管理工具获得)。它包含 IPython 插件,可以启用一个新的魔术函数%lprun,可以对一个函数或多个函数进行逐行分析。你可以通过修改 IPython 配置(查看 IPython 文档或本章后面的配置小节)加入下面这行,启用这个插件: ```python # A list of dotted module names of IPython extensions to load. c.TerminalIPythonApp.extensions = ['line_profiler'] @@ -492,7 +492,7 @@ c.TerminalIPythonApp.extensions = ['line_profiler'] %load_ext line_profiler ``` -line_profiler也可以在程序中使用(查看完整文档),但是在IPython中使用是最为强大的。假设你有一个带有下面代码的模块prof_mod,做一些NumPy数组操作: +line_profiler 也可以在程序中使用(查看完整文档),但是在 IPython 中使用是最为强大的。假设你有一个带有下面代码的模块 prof_mod,做一些 NumPy 数组操作: ```python from numpy.random import randn @@ -507,7 +507,7 @@ def call_function(): return add_and_sum(x, y) ``` -如果想了解add_and_sum函数的性能,%prun可以给出下面内容: +如果想了解 add_and_sum 函数的性能,%prun 可以给出下面内容: ```python In [569]: %run prof_mod @@ -524,12 +524,12 @@ In [572]: %prun add_and_sum(x, y) 1 0.003 0.003 0.049 0.049 :1() ``` -上面的做法启发性不大。激活了IPython插件line_profiler,新的命令%lprun就能用了。使用中的不同点是,我们必须告诉%lprun要分析的函数是哪个。语法是: +上面的做法启发性不大。激活了 IPython 插件 line_profiler,新的命令%lprun 就能用了。使用中的不同点是,我们必须告诉%lprun 要分析的函数是哪个。语法是: ```python %lprun -f func1 -f func2 statement_to_profile ``` -我们想分析add_and_sum,运行: +我们想分析 add_and_sum,运行: ```python In [573]: %lprun -f add_and_sum add_and_sum(x, y) Timer unit: 1e-06 s @@ -544,7 +544,7 @@ Line # Hits Time Per Hit % Time Line Contents 6 1 1 1.0 0.0 return summed ``` -这样就容易诠释了。我们分析了和代码语句中一样的函数。看之前的模块代码,我们可以调用call_function并对它和add_and_sum进行分析,得到一个完整的代码性能概括: +这样就容易诠释了。我们分析了和代码语句中一样的函数。看之前的模块代码,我们可以调用 call_function 并对它和 add_and_sum 进行分析,得到一个完整的代码性能概括: ```python In [574]: %lprun -f add_and_sum -f call_function call_function() Timer unit: 1e-06 s @@ -570,17 +570,17 @@ Line # Hits Time Per Hit % Time Line Contents 我的经验是用%prun (cProfile)进行宏观分析,%lprun (line_profiler)做微观分析。最好对这两个工具都了解清楚。 ->笔记:使用%lprun必须要指明函数名的原因是追踪每行的执行时间的损耗过多。追踪无用的函数会显著地改变结果。 +>笔记:使用%lprun 必须要指明函数名的原因是追踪每行的执行时间的损耗过多。追踪无用的函数会显著地改变结果。 -# B.4 使用IPython高效开发的技巧 +# B.4 使用 IPython 高效开发的技巧 方便快捷地写代码、调试和使用是每个人的目标。除了代码风格,流程细节(比如代码重载)也需要一些调整。 -因此,这一节的内容更像是门艺术而不是科学,还需要你不断的试验,以达成高效。最终,你要能结构优化代码,并且能省时省力地检查程序或函数的结果。我发现用IPython设计的软件比起命令行,要更适合工作。尤其是当发生错误时,你需要检查自己或别人写的数月或数年前写的代码的错误。 +因此,这一节的内容更像是门艺术而不是科学,还需要你不断的试验,以达成高效。最终,你要能结构优化代码,并且能省时省力地检查程序或函数的结果。我发现用 IPython 设计的软件比起命令行,要更适合工作。尤其是当发生错误时,你需要检查自己或别人写的数月或数年前写的代码的错误。 ## 重载模块依赖 -在Python中,当你输入import some_lib,some_lib中的代码就会被执行,所有的变量、函数和定义的引入,就会被存入到新创建的some_lib模块命名空间。当下一次输入some_lib,就会得到一个已存在的模块命名空间的引用。潜在的问题是当你%run一个脚本,它依赖于另一个模块,而这个模块做过修改,就会产生问题。假设我在test_script.py中有如下代码: +在 Python 中,当你输入 import some_lib,some_lib 中的代码就会被执行,所有的变量、函数和定义的引入,就会被存入到新创建的 some_lib 模块命名空间。当下一次输入 some_lib,就会得到一个已存在的模块命名空间的引用。潜在的问题是当你%run 一个脚本,它依赖于另一个模块,而这个模块做过修改,就会产生问题。假设我在 test_script.py 中有如下代码: ```python import some_lib @@ -589,7 +589,7 @@ y = [1, 2, 3, 4] result = some_lib.get_answer(x, y) ``` -如果你运行过了%run test_script.py,然后修改了some_lib.py,下一次再执行%run test_script.py,还会得到旧版本的some_lib.py,这是因为Python模块系统的“一次加载”机制。这一点区分了Python和其它数据分析环境,比如MATLAB,它会自动传播代码修改。解决这个问题,有多种方法。第一种是在标准库importlib模块中使用reload函数: +如果你运行过了%run test_script.py,然后修改了 some_lib.py,下一次再执行%run test_script.py,还会得到旧版本的 some_lib.py,这是因为 Python 模块系统的“一次加载”机制。这一点区分了 Python 和其它数据分析环境,比如 MATLAB,它会自动传播代码修改。解决这个问题,有多种方法。第一种是在标准库 importlib 模块中使用 reload 函数: ```python import some_lib import importlib @@ -597,7 +597,7 @@ import importlib importlib.reload(some_lib) ``` -这可以保证每次运行test_script.py时可以加载最新的some_lib.py。很明显,如果依赖更深,在各处都使用reload是非常麻烦的。对于这个问题,IPython有一个特殊的dreload函数(它不是魔术函数)重载深层的模块。如果我运行过some_lib.py,然后输入dreload(some_lib),就会尝试重载some_lib和它的依赖。不过,这个方法不适用于所有场景,但比重启IPython强多了。 +这可以保证每次运行 test_script.py 时可以加载最新的 some_lib.py。很明显,如果依赖更深,在各处都使用 reload 是非常麻烦的。对于这个问题,IPython 有一个特殊的 dreload 函数(它不是魔术函数)重载深层的模块。如果我运行过 some_lib.py,然后输入 dreload(some_lib),就会尝试重载 some_lib 和它的依赖。不过,这个方法不适用于所有场景,但比重启 IPython 强多了。 ## 代码设计技巧 @@ -621,25 +621,25 @@ if __name__ == '__main__': main() ``` -在IPython中运行这个程序会发生问题,你发现是什么了吗?运行之后,任何定义在main函数中的结果和对象都不能在IPython中被访问到。更好的方法是将main中的代码直接在模块的命名空间中执行(或者在``__name__ == '__main__':``中,如果你想让这个模块可以被引用)。这样,当你%rundiamante,就可以查看所有定义在main中的变量。这等价于在Jupyter notebook的代码格中定义一个顶级变量。 +在 IPython 中运行这个程序会发生问题,你发现是什么了吗?运行之后,任何定义在 main 函数中的结果和对象都不能在 IPython 中被访问到。更好的方法是将 main 中的代码直接在模块的命名空间中执行(或者在``__name__ == '__main__':``中,如果你想让这个模块可以被引用)。这样,当你%rundiamante,就可以查看所有定义在 main 中的变量。这等价于在 Jupyter notebook 的代码格中定义一个顶级变量。 ## 扁平优于嵌套 -深层嵌套的代码总让我联想到洋葱皮。当测试或调试一个函数时,你需要剥多少层洋葱皮才能到达目标代码呢?“扁平优于嵌套”是Python之禅的一部分,它也适用于交互式代码开发。尽量将函数和类去耦合和模块化,有利于测试(如果你是在写单元测试)、调试和交互式使用。 +深层嵌套的代码总让我联想到洋葱皮。当测试或调试一个函数时,你需要剥多少层洋葱皮才能到达目标代码呢?“扁平优于嵌套”是 Python 之禅的一部分,它也适用于交互式代码开发。尽量将函数和类去耦合和模块化,有利于测试(如果你是在写单元测试)、调试和交互式使用。 ## 克服对大文件的恐惧 -如果你之前是写JAVA(或者其它类似的语言),你可能被告知要让文件简短。在多数语言中,这都是合理的建议:太长会让人感觉是坏代码,意味着重构和重组是必要的。但是,在用IPython开发时,运行10个相关联的小文件(小于100行),比起两个或三个长文件,会让你更头疼。更少的文件意味着重载更少的模块和更少的编辑时在文件中跳转。我发现维护大模块,每个模块都是紧密组织的,会更实用和Pythonic。经过方案迭代,有时会将大文件分解成小文件。 +如果你之前是写 JAVA(或者其它类似的语言),你可能被告知要让文件简短。在多数语言中,这都是合理的建议:太长会让人感觉是坏代码,意味着重构和重组是必要的。但是,在用 IPython 开发时,运行 10 个相关联的小文件(小于 100 行),比起两个或三个长文件,会让你更头疼。更少的文件意味着重载更少的模块和更少的编辑时在文件中跳转。我发现维护大模块,每个模块都是紧密组织的,会更实用和 Pythonic。经过方案迭代,有时会将大文件分解成小文件。 我不建议极端化这条建议,那样会形成一个单独的超大文件。找到一个合理和直观的大型代码模块库和封装结构往往需要一点工作,但这在团队工作中非常重要。每个模块都应该结构紧密,并且应该能直观地找到负责每个功能领域功能和类。 -# B.5 IPython高级功能 +# B.5 IPython 高级功能 -要全面地使用IPython系统需要用另一种稍微不同的方式写代码,或深入IPython的配置。 +要全面地使用 IPython 系统需要用另一种稍微不同的方式写代码,或深入 IPython 的配置。 -## 让类是对IPython友好的 +## 让类是对 IPython 友好的 -IPython会尽可能地在控制台美化展示每个字符串。对于许多对象,比如字典、列表和元组,内置的pprint模块可以用来美化格式。但是,在用户定义的类中,你必自己生成字符串。假设有一个下面的简单的类: +IPython 会尽可能地在控制台美化展示每个字符串。对于许多对象,比如字典、列表和元组,内置的 pprint 模块可以用来美化格式。但是,在用户定义的类中,你必自己生成字符串。假设有一个下面的简单的类: ```python class Message: def __init__(self, msg): @@ -654,7 +654,7 @@ In [577]: x Out[577]: <__main__.Message instance at 0x60ebbd8> ``` -IPython会接收__repr__魔术方法返回的字符串(通过output = repr(obj)),并在控制台打印出来。因此,我们可以添加一个简单的__repr__方法到前面的类中,以得到一个更有用的输出: +IPython 会接收 __repr__ 魔术方法返回的字符串(通过 output = repr(obj)),并在控制台打印出来。因此,我们可以添加一个简单的 __repr__ 方法到前面的类中,以得到一个更有用的输出: ```python class Message: def __init__(self, msg): @@ -670,16 +670,16 @@ Out[580]: Message: I have a secret ## 文件和配置 -通过扩展配置系统,大多数IPython和Jupyter notebook的外观(颜色、提示符、行间距等等)和动作都是可以配置的。通过配置,你可以做到: +通过扩展配置系统,大多数 IPython 和 Jupyter notebook 的外观(颜色、提示符、行间距等等)和动作都是可以配置的。通过配置,你可以做到: - 改变颜色主题 - 改变输入和输出提示符,或删除输出之后、输入之前的空行 -- 执行任意Python语句(例如,引入总是要使用的代码或者每次加载IPython都要运行的内容) -- 启用IPython总是要运行的插件,比如line_profiler中的%lprun魔术函数 -- 启用Jupyter插件 +- 执行任意 Python 语句(例如,引入总是要使用的代码或者每次加载 IPython 都要运行的内容) +- 启用 IPython 总是要运行的插件,比如 line_profiler 中的%lprun 魔术函数 +- 启用 Jupyter 插件 - 定义自己的魔术函数或系统别名 -IPython的配置存储在特殊的ipython_config.py文件中,它通常是在用户home目录的.ipython/文件夹中。配置是通过一个特殊文件。当你启动IPython,就会默认加载这个存储在profile_default文件夹中的默认文件。因此,在我的Linux系统,完整的IPython配置文件路径是: +IPython 的配置存储在特殊的 ipython_config.py 文件中,它通常是在用户 home 目录的.ipython/文件夹中。配置是通过一个特殊文件。当你启动 IPython,就会默认加载这个存储在 profile_default 文件夹中的默认文件。因此,在我的 Linux 系统,完整的 IPython 配置文件路径是: ```python /home/wesm/.ipython/profile_default/ipython_config.py ``` @@ -689,12 +689,12 @@ IPython的配置存储在特殊的ipython_config.py文件中,它通常是在 ipython profile create ``` -这个文件中的内容留给读者自己探索。这个文件有注释,解释了每个配置选项的作用。另一点,可以有多个配置文件。假设你想要另一个IPython配置文件,专门是为另一个应用或项目的。创建一个新的配置文件很简单,如下所示: +这个文件中的内容留给读者自己探索。这个文件有注释,解释了每个配置选项的作用。另一点,可以有多个配置文件。假设你想要另一个 IPython 配置文件,专门是为另一个应用或项目的。创建一个新的配置文件很简单,如下所示: ```python ipython profile create secret_project ``` -做完之后,在新创建的profile_secret_project目录便捷配置文件,然后如下启动IPython: +做完之后,在新创建的 profile_secret_project 目录便捷配置文件,然后如下启动 IPython: ```python $ ipython --profile=secret_project Python 3.5.1 | packaged by conda-forge | (default, May 20 2016, 05:22:56) @@ -709,24 +709,24 @@ object? -> Details about 'object', use 'object??' for extra details. IPython profile: secret_project ``` -和之前一样,IPython的文档是一个极好的学习配置文件的资源。 +和之前一样,IPython 的文档是一个极好的学习配置文件的资源。 -配置Jupyter有些不同,因为你可以使用除了Python的其它语言。要创建一个类似的Jupyter配置文件,运行: +配置 Jupyter 有些不同,因为你可以使用除了 Python 的其它语言。要创建一个类似的 Jupyter 配置文件,运行: ```python jupyter notebook --generate-config ``` -这样会在home目录的.jupyter/jupyter_notebook_config.py创建配置文件。编辑完之后,可以将它重命名: +这样会在 home 目录的.jupyter/jupyter_notebook_config.py 创建配置文件。编辑完之后,可以将它重命名: ```python $ mv ~/.jupyter/jupyter_notebook_config.py ~/.jupyter/my_custom_config.py ``` -打开Jupyter之后,你可以添加--config参数: +打开 Jupyter 之后,你可以添加--config 参数: ```python jupyter notebook --config=~/.jupyter/my_custom_config.py ``` # B.6 总结 -学习过本书中的代码案例,你的Python技能得到了一定的提升,我建议你持续学习IPython和Jupyter。因为这两个项目的设计初衷就是提高生产率的,你可能还会发现一些工具,可以让你更便捷地使用Python和计算库。 +学习过本书中的代码案例,你的 Python 技能得到了一定的提升,我建议你持续学习 IPython 和 Jupyter。因为这两个项目的设计初衷就是提高生产率的,你可能还会发现一些工具,可以让你更便捷地使用 Python 和计算库。 -你可以在nbviewer(https://nbviewer.jupyter.org/)上找到更多有趣的Jupyter notebooks。 +你可以在 nbviewer(https://nbviewer.jupyter.org/)上找到更多有趣的 Jupyter notebooks。 -- GitLab