Skip to content

Add v2 CN translations #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions translations_v2/cn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Python 数据科学手册第二版

> 原文:[`zh.annas-archive.org/md5/051facaf2908ae8198253e3a14b09ec1`](https://zh.annas-archive.org/md5/051facaf2908ae8198253e3a14b09ec1)
>
> 译者:[飞龙](https://github.com/wizardforcel)
>
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
57 changes: 57 additions & 0 deletions translations_v2/cn/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
+ [Python 数据科学手册第二版](README.md)
+ [前言](pydshb2e_00.md)
+ [第一部分:Jupyter:超越普通 Python](pydshb2e_01.md)
+ [第一章:在 IPython 和 Jupyter 中开始](pydshb2e_02.md)
+ [第二章:增强交互功能](pydshb2e_03.md)
+ [第三章:调试和性能分析](pydshb2e_04.md)
+ [第二部分:NumPy 入门](pydshb2e_05.md)
+ [第四章:理解 Python 中的数据类型](pydshb2e_06.md)
+ [第五章:NumPy 数组的基础知识](pydshb2e_07.md)
+ [第六章:NumPy 数组上的计算:通用函数](pydshb2e_08.md)
+ [第七章:聚合:最小值、最大值以及其他](pydshb2e_09.md)
+ [第八章:数组上的计算:广播](pydshb2e_10.md)
+ [第九章:比较、掩码和布尔逻辑](pydshb2e_11.md)
+ [第十章:Fancy 索引](pydshb2e_12.md)
+ [第十一章:数组排序](pydshb2e_13.md)
+ [第十二章:结构化数据:NumPy 的结构化数组](pydshb2e_14.md)
+ [第三部分:使用 Pandas 进行数据操作](pydshb2e_15.md)
+ [第十三章:介绍 Pandas 对象](pydshb2e_16.md)
+ [第十四章:数据索引和选择](pydshb2e_17.md)
+ [第十五章:在 Pandas 中操作数据](pydshb2e_18.md)
+ [第十六章:处理缺失数据](pydshb2e_19.md)
+ [第十七章:分层索引](pydshb2e_20.md)
+ [第十八章:组合数据集:concat 和 append](pydshb2e_21.md)
+ [第十九章:合并数据集:merge 和 join](pydshb2e_22.md)
+ [第二十章:聚合和分组](pydshb2e_23.md)
+ [第二十一章:透视表](pydshb2e_24.md)
+ [第二十二章:向量化字符串操作](pydshb2e_25.md)
+ [第二十三章: 与时间序列一起工作](pydshb2e_26.md)
+ [第二十四章:高性能 Pandas:eval 和 query](pydshb2e_27.md)
+ [第四部分:Matplotlib 可视化](pydshb2e_28.md)
+ [第二十五章:Matplotlib 一般提示](pydshb2e_29.md)
+ [第二十六章:简单线图](pydshb2e_30.md)
+ [第二十七章:简单散点图](pydshb2e_31.md)
+ [第二十八章:密度和等高线图](pydshb2e_32.md)
+ [第二十九章:自定义绘图图例](pydshb2e_33.md)
+ [第三十章:定制色条](pydshb2e_34.md)
+ [第三十一章:多个子图](pydshb2e_35.md)
+ [第三十二章:文本和注释](pydshb2e_36.md)
+ [第三十三章:自定义刻度](pydshb2e_37.md)
+ [第三十四章:自定义 Matplotlib:配置和样式表](pydshb2e_38.md)
+ [第三十五章:Matplotlib 中的三维绘图](pydshb2e_39.md)
+ [第三十六章:可视化与 Seaborn](pydshb2e_40.md)
+ [第五部分:机器学习](pydshb2e_41.md)
+ [第三十七章:什么是机器学习?](pydshb2e_42.md)
+ [第三十八章:介绍 Scikit-Learn](pydshb2e_43.md)
+ [第三十九章:超参数和模型验证](pydshb2e_44.md)
+ [第四十章:特征工程](pydshb2e_45.md)
+ [第四十一章:深入:朴素贝叶斯分类](pydshb2e_46.md)
+ [第四十二章:深入解析:线性回归](pydshb2e_47.md)
+ [第四十三章:深入探讨支持向量机](pydshb2e_48.md)
+ [第四十四章:深入探讨:决策树和随机森林](pydshb2e_49.md)
+ [第四十五章:深入解析:主成分分析](pydshb2e_50.md)
+ [第四十六章:深入探讨:流形学习](pydshb2e_51.md)
+ [第四十七章:深入理解 k-Means 聚类](pydshb2e_52.md)
+ [第四十八章:深入:高斯混合模型](pydshb2e_53.md)
+ [第四十九章:深入探讨:核密度估计](pydshb2e_54.md)
+ [第五十章:应用:一个人脸检测流水线](pydshb2e_55.md)
Binary file added translations_v2/cn/img/02.05-broadcasting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-classification-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-classification-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-clustering-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-clustering-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-dimesionality-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-dimesionality-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-regression-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-regression-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-regression-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.01-regression-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.02-samples-features.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.03-2-fold-CV.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.03-5-fold-CV.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.03-bias-variance-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.03-bias-variance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.03-learning-curve.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.03-validation-curve.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.05-gaussian-NB.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.06-gaussian-basis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.08-decision-tree.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added translations_v2/cn/img/05.09-PCA-rotation.png
Binary file added translations_v2/cn/img/05.10-LLE-vs-MDS.png
Binary file added translations_v2/cn/img/05.12-covariance-type.png
Binary file added translations_v2/cn/img/Data_Science_VD.png
Binary file added translations_v2/cn/img/array_vs_list.png
Binary file added translations_v2/cn/img/cint_vs_pyint.png
Binary file added translations_v2/cn/img/cover.png
Binary file added translations_v2/cn/img/output_100_0.png
Binary file added translations_v2/cn/img/output_102_0.png
Binary file added translations_v2/cn/img/output_106_0.png
Binary file added translations_v2/cn/img/output_10_0.png
Binary file added translations_v2/cn/img/output_11_0.png
Binary file added translations_v2/cn/img/output_12_0.png
Binary file added translations_v2/cn/img/output_13_0.png
Binary file added translations_v2/cn/img/output_13_1.png
Binary file added translations_v2/cn/img/output_14_0.png
Binary file added translations_v2/cn/img/output_15_0.png
Binary file added translations_v2/cn/img/output_16_0.png
Binary file added translations_v2/cn/img/output_17_0.png
Binary file added translations_v2/cn/img/output_18_0.png
Binary file added translations_v2/cn/img/output_19_0.png
Binary file added translations_v2/cn/img/output_20_0.png
Binary file added translations_v2/cn/img/output_21_0.png
Binary file added translations_v2/cn/img/output_22_0.png
Binary file added translations_v2/cn/img/output_23_0.png
Binary file added translations_v2/cn/img/output_24_0.png
Binary file added translations_v2/cn/img/output_25_0.png
Binary file added translations_v2/cn/img/output_26_0.png
Binary file added translations_v2/cn/img/output_27_0.png
Binary file added translations_v2/cn/img/output_28_0.png
Binary file added translations_v2/cn/img/output_29_0.png
Binary file added translations_v2/cn/img/output_30_0.png
Binary file added translations_v2/cn/img/output_31_0.png
Binary file added translations_v2/cn/img/output_32_0.png
Binary file added translations_v2/cn/img/output_33_0.png
Binary file added translations_v2/cn/img/output_34_0.png
Binary file added translations_v2/cn/img/output_35_0.png
Binary file added translations_v2/cn/img/output_36_0.png
Binary file added translations_v2/cn/img/output_37_0.png
Binary file added translations_v2/cn/img/output_37_1.png
Binary file added translations_v2/cn/img/output_38_0.png
Binary file added translations_v2/cn/img/output_39_0.png
Binary file added translations_v2/cn/img/output_3_0.png
Binary file added translations_v2/cn/img/output_40_0.png
Binary file added translations_v2/cn/img/output_41_0.png
Binary file added translations_v2/cn/img/output_42_0.png
Binary file added translations_v2/cn/img/output_43_0.png
Binary file added translations_v2/cn/img/output_44_0.png
Binary file added translations_v2/cn/img/output_45_0.png
Binary file added translations_v2/cn/img/output_46_0.png
Binary file added translations_v2/cn/img/output_47_0.png
Binary file added translations_v2/cn/img/output_48_0.png
Binary file added translations_v2/cn/img/output_4_0.png
Binary file added translations_v2/cn/img/output_50_0.png
Binary file added translations_v2/cn/img/output_50_1.png
Binary file added translations_v2/cn/img/output_51_0.png
Binary file added translations_v2/cn/img/output_52_0.png
Binary file added translations_v2/cn/img/output_53_0.png
Binary file added translations_v2/cn/img/output_55_0.png
Binary file added translations_v2/cn/img/output_56_0.png
Binary file added translations_v2/cn/img/output_57_0.png
Binary file added translations_v2/cn/img/output_58_0.png
Binary file added translations_v2/cn/img/output_59_0.png
Binary file added translations_v2/cn/img/output_5_0.png
Binary file added translations_v2/cn/img/output_60_0.png
Binary file added translations_v2/cn/img/output_65_0.png
Binary file added translations_v2/cn/img/output_68_0.png
Binary file added translations_v2/cn/img/output_6_0.png
Binary file added translations_v2/cn/img/output_70_0.png
Binary file added translations_v2/cn/img/output_73_0.png
Binary file added translations_v2/cn/img/output_76_0.png
Binary file added translations_v2/cn/img/output_7_0.png
Binary file added translations_v2/cn/img/output_80_0.png
Binary file added translations_v2/cn/img/output_81_0.png
Binary file added translations_v2/cn/img/output_83_0.png
Binary file added translations_v2/cn/img/output_8_0.png
Binary file added translations_v2/cn/img/output_92_0.png
Binary file added translations_v2/cn/img/output_94_0.png
Binary file added translations_v2/cn/img/output_96_0.png
Binary file added translations_v2/cn/img/output_98_0.png
Binary file added translations_v2/cn/img/output_9_0.png
Binary file added translations_v2/cn/img/titlepage_footer_ebook.png
138 changes: 138 additions & 0 deletions translations_v2/cn/pydshb2e_00.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# 前言

# 什么是数据科学?

这是一本关于使用 Python 进行数据科学的书籍,这立即引发了一个问题:什么是*数据科学*?这是一个令人惊讶地难以定义的问题,特别是考虑到这个术语已经如此普遍。激烈的批评者们曾将其视为一个多余的标签(毕竟,哪门科学不涉及数据?)或者一个简单的流行语,只存在于简历中或吸引过度热衷的技术招聘者的眼球。

在我看来,这些批评忽略了一些重要的东西。尽管数据科学带有被夸大的光环,但它或许是我们在工业和学术界越来越重要的许多应用中拥有的跨学科技能集合的最佳标签。跨学科的部分至关重要:在我看来,关于数据科学最好的现有定义可以通过德鲁·康威于 2010 年 9 月在他的博客上首次发布的数据科学维恩图来说明(图 P-1)。

虽然一些交集标签有些玩味,但这张图表捕捉到了人们说“数据科学”时我认为的本质:它本质上是一门跨学科的学科。数据科学包括三个明确且有重叠的领域:*统计学家*的技能,他们知道如何对数据集进行建模和总结(这些数据集越来越大);*计算机科学家*的技能,能够设计和使用算法来高效存储、处理和可视化这些数据;以及*领域专业知识*,我们可以称之为在一个学科中的“经典”训练,既需要制定正确的问题,又需要将其答案放入背景中。

在这种情况下,我鼓励您把数据科学看作不是要学习的新知识领域,而是可以应用于您当前专业领域的一组新技能。无论您是在报告选举结果、预测股票回报、优化在线广告点击、识别显微镜照片中的微生物、寻找新的天文物体类别,还是在任何其他领域处理数据,本书的目标是赋予您提出和回答有关所选主题领域的新问题的能力。

![数据科学 VD](img/Data_Science_VD.png)

###### 图 P-1\. 德鲁·康威的数据科学维恩图(来源:[德鲁·康威](https://oreil.ly/PkOOw),获得授权使用)

# 这本书是为谁写的?

在我在华盛顿大学以及各种技术会议和聚会上的教学中,我经常听到一个最常见的问题:“我应该如何学习 Python?” 提问的人通常是技术背景的学生、开发人员或研究人员,他们通常已经具有编写代码和使用计算和数值工具的坚实背景。大多数这些人不是想学习 Python 本身,而是想学习这门语言,以便将其作为数据密集型和计算科学的工具使用。尽管针对这一受众的大量视频、博客文章和教程在线上都可以找到,但长期以来我一直对这个问题缺乏一个单一的好答案感到沮丧;这就是这本书的灵感来源。

本书不旨在介绍 Python 或编程的基础知识;我假设读者已经熟悉 Python 语言,包括定义函数、赋值变量、调用对象的方法、控制程序流程以及其他基本任务。相反,它旨在帮助 Python 用户学习如何有效地使用 Python 的数据科学堆栈——如以下章节中提到的库和相关工具——来有效地存储、操作和从数据中获取见解。

# 为什么选择 Python?

过去几十年间,Python 已经成为科学计算任务的一流工具,包括大数据集的分析和可视化。这可能让 Python 语言的早期支持者感到惊讶:语言本身并不是专门设计用于数据分析或科学计算。Python 在数据科学中的实用性主要源于第三方包的大规模和活跃生态系统:*NumPy*用于处理同质数组数据,*Pandas*用于处理异构和标记数据,*SciPy*用于常见的科学计算任务,*Matplotlib*用于出版质量的可视化,*IPython*用于交互式执行和代码共享,*Scikit-Learn*用于机器学习,以及许多其他工具,这些工具将在接下来的页面中提到。

如果您正在寻找关于 Python 语言本身的指南,我建议参考本书的姊妹项目 [*Python 旋风之旅*](https://oreil.ly/jFtWj)。这份简短的报告为熟悉一种或多种其他编程语言的数据科学家提供了 Python 语言基本功能的概览。

# 书籍大纲

本书的每个编号部分都专注于贡献 Python 数据科学故事基础部分的特定包或工具,并分为短小而自包含的章节,每章讨论一个单一概念:

+ 第一部分,“Jupyter: 超越普通 Python” 介绍了 IPython 和 Jupyter。这些包提供了许多使用 Python 的数据科学家工作的计算环境。

+ 第二部分,“NumPy 简介”专注于 NumPy 库,它提供了`ndarray`,用于 Python 中密集数据数组的高效存储和操作。

+ 第三部分,“使用 Pandas 进行数据操作”介绍了 Pandas 库,它提供了`DataFrame`,用于 Python 中标记/列数据的高效存储和操作。

+ 第四部分,“使用 Matplotlib 进行可视化”专注于 Matplotlib,这是一个提供灵活数据可视化能力的 Python 库。

+ 第五部分,“机器学习”侧重于 Scikit-Learn 库,它提供了最重要和已建立的机器学习算法的高效且干净的 Python 实现。

PyData 世界显然比这六个软件包要大得多,而且它每天都在增长。考虑到这一点,我在本书中的每一个地方都尽力提供有关其他有趣工作、项目和软件包的参考信息,它们正在推动 Python 所能做的事情的边界。尽管如此,我专注的软件包目前对 Python 数据科学领域的许多工作至关重要,我预计它们在生态系统继续围绕它们增长的同时将继续保持重要性。

# 安装考虑事项

安装 Python 及其科学计算库套件是很简单的。本节将概述在设置计算机时需要考虑的一些事项。

尽管有各种安装 Python 的方式,但我建议用于数据科学的一个是 Anaconda 发行版,无论您使用 Windows、Linux 还是 macOS,它的工作方式都类似。Anaconda 发行版有两种版本:

+ [Miniconda](https://oreil.ly/dH7wJ)提供了 Python 解释器本身,以及一个名为*conda*的命令行工具,它作为一个跨平台的 Python 软件包管理器,类似于 Linux 用户熟悉的 apt 或 yum 工具。

+ [Anaconda](https://oreil.ly/ndxjm)包含 Python 和 conda,并且还捆绑了一套其他预装的面向科学计算的软件包。由于这一捆绑包的大小,安装过程将消耗几个千兆字节的磁盘空间。

Anaconda 中包含的任何软件包也可以手动安装在 Miniconda 之上;因此,我建议从 Miniconda 开始。

要开始,请下载并安装 Miniconda 包 —— 确保选择带有 Python 3 的版本 —— 然后安装本书中使用的核心包:

```py
[~]$ conda install numpy pandas scikit-learn matplotlib seaborn jupyter

```

在整个文本中,我们还将使用 Python 科学生态系统中的其他更专业的工具;安装通常只需输入**`conda install *packagename*`**。如果您遇到默认 conda 频道中不可用的软件包,请务必查看[*conda-forge*](https://oreil.ly/CCvwQ),这是一个广泛的、社区驱动的 conda 软件包存储库。

要了解 conda 的更多信息,包括关于创建和使用 conda 环境的信息(我强烈推荐),请参阅[conda 的在线文档](https://oreil.ly/MkqPw)

# 本书中使用的约定

本书使用以下排版约定:

*斜体*

表示新术语、URL、电子邮件地址、文件名和文件扩展名。

`常量宽度`

用于程序列表以及段落内引用程序元素,例如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。

**`常量宽度粗体`**

显示用户应该按照字面意义输入的命令或其他文本。

*`常量宽度斜体`*

显示应替换为用户提供值或由上下文确定值的文本。

###### 注意

这个元素表示一个一般的注意事项。

# 使用代码示例

补充材料(代码示例、图表等)可在[*http://github.com/jakevdp/PythonDataScienceHandbook*](http://github.com/jakevdp/PythonDataScienceHandbook)下载。

如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至*bookquestions@oreilly.com*

这本书旨在帮助您完成工作。一般情况下,如果本书提供了示例代码,您可以在自己的程序和文档中使用它。除非您复制了大部分代码,否则无需征得我们的许可。例如,编写一个使用本书多个代码片段的程序不需要许可。销售或分发 O'Reilly 书籍中的示例则需要许可。引用本书并引用示例代码来回答问题无需许可。将本书中大量示例代码整合到产品文档中则需要许可。

我们感谢您的致谢,但一般情况下不需要署名。致谢通常包括书名、作者、出版社和 ISBN。例如:"*Python 数据科学手册*,第 2 版,作者 Jake VanderPlas(O'Reilly)。版权所有 2023 年 Jake VanderPlas,978-1-098-12122-8。"

如果您觉得您对代码示例的使用超出了公平使用范围或上述许可,请随时通过*permissions@oreilly.com*与我们联系。

# O'Reilly 在线学习

###### 注意

超过 40 年来,[*O'Reilly Media*](https://oreilly.com)已经为企业提供技术和商业培训、知识和见解,帮助公司取得成功。

我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专长。O’Reilly 的在线学习平台让您随时访问现场培训课程、深度学习路径、交互式编码环境以及来自 O’Reilly 和其他 200 多个出版商的大量文本和视频。更多信息,请访问[*https://oreilly.com*](https://oreilly.com)

# 如何联系我们

请将有关本书的评论和问题发送至出版商:

+ O’Reilly Media, Inc.

+ 1005 Gravenstein Highway North

+ Sebastopol, CA 95472

+ 800-998-9938(美国或加拿大)

+ 707-829-0515(国际或本地)

+ 707-829-0104(传真)

我们为本书建立了一个网页,列出勘误、示例以及任何额外信息。您可以访问该网页:[*https://oreil.ly/python-data-science-handbook*](https://oreil.ly/python-data-science-handbook)

发送电子邮件至*bookquestions@oreilly.com*以评论或询问有关本书的技术问题。

关于我们的书籍和课程的新闻和信息,请访问[*https://oreilly.com*](https://oreilly.com)

在 LinkedIn 上找到我们:[*https://linkedin.com/company/oreilly-media*](https://linkedin.com/company/oreilly-media)

在 Twitter 上关注我们:[*https://twitter.com/oreillymedia*](https://twitter.com/oreillymedia)

在 YouTube 上关注我们:[*https://youtube.com/oreillymedia*](https://youtube.com/oreillymedia)
7 changes: 7 additions & 0 deletions translations_v2/cn/pydshb2e_01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 第一部分:Jupyter:超越普通 Python

Python 有很多开发环境的选择,人们经常问我在自己的工作中使用哪一个。我的答案有时会让人惊讶:我偏爱的环境是[IPython](http://ipython.org)加上一个文本编辑器(在我这里,取决于我的心情,可能是 Emacs 或 VSCode)。Jupyter 起源于 IPython shell,由 Fernando Perez 在 2001 年创建,作为增强版 Python 解释器,并已发展成为一个旨在提供“研究计算整个生命周期工具”的项目,用 Perez 的话来说。如果 Python 是我们数据科学任务的引擎,你可以把 Jupyter 看作是交互控制面板。

作为 Python 的一个有用的交互界面,[Jupyter](http://ipython.org)还为语言提供了一些有用的语法增强;我们将在这里介绍其中最有用的一些增强功能。也许 Jupyter 项目提供的最熟悉的界面是 Jupyter Notebook,这是一个基于浏览器的环境,非常适合开发、协作、共享,甚至是发布数据科学结果。作为笔记本格式有用性的一个例子,无需看得更远,就在你正在阅读的这页:这本书的整个手稿都是作为一组 Jupyter 笔记本来撰写的。

本书的这一部分将首先逐步介绍一些对数据科学实践有用的 Jupyter 和 IPython 特性,特别是它们提供的超出 Python 标准特性的语法。接下来,我们将更深入地讨论一些更有用的*魔术命令*,这些命令可以加快创建和使用数据科学代码中的常见任务。最后,我们将涉及一些使笔记本在理解数据和共享结果方面变得有用的特性。
352 changes: 352 additions & 0 deletions translations_v2/cn/pydshb2e_02.md

Large diffs are not rendered by default.

346 changes: 346 additions & 0 deletions translations_v2/cn/pydshb2e_03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
# 第二章:增强交互功能

IPython 和 Jupyter 的大部分功能来自它们提供的额外交互工具。本章将涵盖其中一些工具,包括所谓的魔术命令,用于探索输入和输出历史记录的工具,以及与 Shell 交互的工具。

# IPython 魔术命令

前一章展示了 IPython 如何让您高效、交互地使用和探索 Python。在这里,我们将开始讨论 IPython 在正常 Python 语法之上添加的一些增强功能。这些在 IPython 中被称为*魔术命令*,以`%`字符为前缀。这些魔术命令设计精炼地解决标准数据分析中的各种常见问题。魔术命令有两种类型:*行魔术*,以单个`%`前缀表示,作用于单行输入;*单元格魔术*,以双`%%`前缀表示,作用于多行输入。我将在这里展示和讨论一些简短的示例,并稍后回到更专注地讨论几个有用的魔术命令。

## 运行外部代码:`%run`

随着您开始开发更多的代码,您可能会发现自己在 IPython 中进行交互式探索,以及在文本编辑器中存储希望重用的代码。与其在新窗口中运行此代码,不如在您的 IPython 会话中运行更方便。可以通过`%run`魔术命令完成此操作。

例如,假设您创建了一个*myscript.py*文件,其中包含以下内容:

```py
# file: myscript.py

def square(x):
"""square a number"""
return x ** 2

for N in range(1, 4):
print(f"{N} squared is {square(N)}")
```

你可以从你的 IPython 会话中执行以下操作:

```py
In [1]: %run myscript.py
1 squared is 1
2 squared is 4
3 squared is 9
```

还要注意,在运行此脚本后,其中定义的任何函数都可以在您的 IPython 会话中使用:

```py
In [2]: square(5)
Out[2]: 25
```

有几种选项可以微调代码的运行方式;您可以通过在 IPython 解释器中键入**`%run?`**来查看正常的文档。

## 代码执行时间:`%timeit`

另一个有用的魔术函数示例是`%timeit`,它将自动确定其后的单行 Python 语句的执行时间。例如,我们可能希望检查列表理解的性能:

```py
In [3]: %timeit L = [n ** 2 for n in range(1000)]
430 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
```

`%timeit` 的好处在于对于短命令,它会自动执行多次运行以获得更稳健的结果。对于多行语句,添加第二个`%`符号将其转换为可以处理多行输入的单元格魔术。例如,这里是使用`for`循环的等效构造:

```py
In [4]: %%timeit
...: L = []
...: for n in range(1000):
...: L.append(n ** 2)
...:
484 µs ± 5.67 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
```

我们可以立即看到,在这种情况下,列表理解比等效的`for`循环结构快大约 10%。我们将在“分析和计时代码”中探索`%timeit`和其他计时和分析代码的方法。

## 魔术函数帮助:?,%magic 和%lsmagic

与普通的 Python 函数类似,IPython 的魔术函数有文档字符串,可以以标准方式访问这些有用的文档。例如,要查阅 `%timeit` 魔术函数的文档,只需输入以下内容:

```py
In [5]: %timeit?
```

类似地,还可以访问其他函数的文档。要访问可用魔术函数的一般描述,包括一些示例,请输入以下内容:

```py
In [6]: %magic
```

要快速简单地列出所有可用的魔术函数,请输入以下内容:

```py
In [7]: %lsmagic
```

最后,我将提到,如果你愿意的话,定义自己的魔术函数非常简单。我不会在这里讨论它,但如果你有兴趣,可以参考 “更多 IPython 资源” 中列出的参考资料。

# 输入和输出历史

正如之前所见,IPython shell 允许你使用上下箭头键(或等效的 Ctrl-p/Ctrl-n 快捷键)访问先前的命令。此外,在 shell 和笔记本中,IPython 还提供了几种获取先前命令输出以及命令字符串版本的方法。我们将在这里探讨这些方法。

## IPython 的 In 和 Out 对象

现在我想象你已经开始熟悉 IPython 使用的 `In [1]:`/`Out[1]:` 样式的提示了。但事实证明,这些不仅仅是漂亮的装饰:它们提供了一种方法,让你可以访问当前会话中的先前输入和输出。假设我们开始一个看起来像这样的会话:

```py
In [1]: import math

In [2]: math.sin(2)
Out[2]: 0.9092974268256817

In [3]: math.cos(2)
Out[3]: -0.4161468365471424
```

我们已经导入了内置的 `math` 包,然后计算了数字 2 的正弦和余弦。这些输入和输出显示在带有 `In`/`Out` 标签的 shell 中,但是 IPython 实际上创建了一些名为 `In``Out` 的 Python 变量,这些变量会自动更新以反映这些历史记录:

```py
In [4]: In
Out[4]: ['', 'import math', 'math.sin(2)', 'math.cos(2)', 'In']

In [5]: Out
Out[5]:
{2: 0.9092974268256817,
3: -0.4161468365471424,
4: ['', 'import math', 'math.sin(2)', 'math.cos(2)', 'In', 'Out']}
```

`In` 对象是一个列表,按顺序保存命令(列表的第一项是一个占位符,以便 `In [1]` 可以引用第一条命令):

```py
In [6]: print(In[1])
import math
```

`Out` 对象不是一个列表,而是一个将输入编号映射到它们的输出(如果有的话)的字典:

```py
In [7]: print(Out[2])
.9092974268256817
```

注意,并非所有操作都有输出:例如,`import` 语句和 `print` 语句不会影响输出。后者可能会令人惊讶,但如果考虑到 `print` 是一个返回 `None` 的函数,这是有道理的;为简洁起见,任何返回 `None` 的命令都不会添加到 `Out` 中。

当你想要与过去的结果交互时,这可能会很有用。例如,让我们使用先前计算的结果来检查 `sin(2) ** 2``cos(2) ** 2` 的总和:

```py
In [8]: Out[2] ** 2 + Out[3] ** 2
Out[8]: 1.0
```

结果是 `1.0`,这符合我们对这个著名的三角恒等式的预期。在这种情况下,使用这些先前的结果可能是不必要的,但如果你执行了一个非常昂贵的计算并且忘记将结果赋给一个变量,这可能会变得非常方便。

## 下划线快捷键和先前的输出

标准 Python shell 仅包含一种简单的快捷方式用于访问先前的输出:变量 `_`(即一个下划线)会随先前的输出更新。这在 IPython 中也适用:

```py
In [9]: print(_)
.0
```

但是 IPython 进一步扩展了这一点 — 您可以使用双下划线访问倒数第二个输出,并使用三个下划线访问倒数第三个输出(跳过任何没有输出的命令):

```py
In [10]: print(__)
-0.4161468365471424

In [11]: print(___)
.9092974268256817
```

IPython 到此为止:超过三个下划线开始变得有点难以计数,此时通过行号更容易引用输出。

我还应该提一下另一个快捷方式 — `Out[*X*]` 的简写是 `_*X*`(即一个下划线后跟行号):

```py
In [12]: Out[2]
Out[12]: 0.9092974268256817

In [13]: _2
Out[13]: 0.9092974268256817
```

## 禁止输出

有时候,您可能希望抑制语句的输出(这在我们将在第四部分探索的绘图命令中可能最常见)。或者,您执行的命令会产生一个您不希望存储在输出历史记录中的结果,也许是因为在其他引用被移除时它可以被释放。抑制命令的输出最简单的方法是在行尾添加分号:

```py
In [14]: math.sin(2) + math.cos(2);
```

结果将在不显示在屏幕上或存储在 `Out` 字典中的情况下计算:

```py
In [15]: 14 in Out
Out[15]: False
```

## 相关的魔术命令

要一次访问一批先前的输入,`%history` 魔术命令非常有帮助。以下是如何打印前四个输入:

```py
In [16]: %history -n 1-3
1: import math
2: math.sin(2)
3: math.cos(2)
```

如往常一样,您可以输入 `%history?` 以获取更多信息并查看可用选项的描述(有关 `?` 功能的详细信息,请参见第一章)。其他有用的魔术命令包括 `%rerun`,它将重新执行命令历史记录的某些部分,以及 `%save`,它将命令历史记录的某个集合保存到文件中。

# IPython 和 Shell 命令

在与标准 Python 解释器的交互工作时,一个令人沮丧的地方是需要在多个窗口之间切换以访问 Python 工具和系统命令行工具。IPython 弥合了这一差距,并为您提供了在 IPython 终端内直接执行 shell 命令的语法。这是通过感叹号实现的:在 `!` 后出现的任何内容将不会由 Python 内核执行,而是由系统命令行执行。

以下讨论假定您正在使用类 Unix 系统,如 Linux 或 macOS。接下来的一些示例在 Windows 上会失败,因为 Windows 默认使用不同类型的 shell,但如果您使用[*Windows 子系统 for Linux*](https://oreil.ly/H5MEE),这里的示例应该能正常运行。如果您对 shell 命令不熟悉,我建议您查看由始终优秀的软件教程基金会组织的[Unix shell 教程](https://oreil.ly/RrD2Y)

## Shell 快速入门

对于如何使用 shell/终端/命令行的全面介绍远远超出了本章的范围,但对于未接触过的人,我将在这里进行一个快速介绍。shell 是一种与计算机进行文本交互的方式。自从 20 世纪 80 年代中期以来,当微软和苹果推出了他们现在无处不在的图形操作系统的第一个版本时,大多数计算机用户通过熟悉的菜单选择和拖放操作与他们的操作系统交互。但是操作系统在这些图形用户界面之前就存在,主要通过文本输入的序列来控制:在提示符下,用户会输入一个命令,计算机会执行用户告诉它做的事情。那些早期的提示系统是今天大多数数据科学家仍在使用的 shell 和终端的前身。

对于不熟悉 shell 的人可能会问,为什么要费这个劲,当很多相同的结果可以通过简单点击图标和菜单来实现呢?一个 shell 用户可能会反问:为什么要去寻找图标和菜单项,而不是通过输入命令来更轻松地完成任务呢?虽然这听起来像是典型的技术偏好僵局,但当超越基本任务时,很快就会明显地感觉到 shell 在控制高级任务方面提供了更多的控制权——尽管学习曲线确实可能令人望而生畏。

例如,这里是一个 Linux/macOS shell 会话的示例,用户在其系统上探索、创建和修改目录和文件(`osx:~ $`是提示符,`$`之后的所有内容是键入的命令;以`#`开头的文本仅用作描述,而不是您实际要键入的内容):

```py
osx:~ $ echo "hello world" # echo is like Python's print function
hello world

osx:~ $ pwd # pwd = print working directory
/home/jake # This is the "path" that we're sitting in

osx:~ $ ls # ls = list working directory contents
notebooks projects

osx:~ $ cd projects/ # cd = change directory

osx:projects $ pwd
/home/jake/projects

osx:projects $ ls
datasci_book mpld3 myproject.txt

osx:projects $ mkdir myproject # mkdir = make new directory

osx:projects $ cd myproject/

osx:myproject $ mv ../myproject.txt ./ # mv = move file. Here we're moving the
# file myproject.txt from one directory
# up (../) to the current directory (./).
osx:myproject $ ls
myproject.txt

```

请注意,所有这些只是通过键入命令而不是点击图标和菜单来执行熟悉操作(导航目录结构、创建目录、移动文件等)的一种紧凑方式。仅仅几个命令(`pwd``ls``cd``mkdir``cp`)就可以完成许多最常见的文件操作,但当您超越这些基础操作时,shell 方法真正显示其强大之处。

## IPython 中的 Shell 命令

任何标准的 shell 命令都可以通过在其前面加上`!`字符直接在 IPython 中使用。例如,`ls``pwd``echo`命令可以如下运行:

```py
In [1]: !ls
myproject.txt

In [2]: !pwd
/home/jake/projects/myproject

In [3]: !echo "printing from the shell"
printing from the shell
```

## 向 Shell 传递值和从 Shell 获取值

Shell 命令不仅可以从 IPython 中调用,还可以与 IPython 命名空间交互。例如,您可以使用赋值操作符`=`将任何 shell 命令的输出保存到 Python 列表中:

```py
In [4]: contents = !ls

In [5]: print(contents)
['myproject.txt']

In [6]: directory = !pwd

In [7]: print(directory)
['/Users/jakevdp/notebooks/tmp/myproject']
```

这些结果不会以列表的形式返回,而是以 IPython 中定义的特殊的 shell 返回类型返回:

```py
In [8]: type(directory)
IPython.utils.text.SList
```

这看起来和行为很像 Python 列表,但具有额外的功能,比如`grep``fields`方法以及允许您以方便的方式搜索、过滤和显示结果的`s``n``p`属性。有关这些信息,您可以使用 IPython 内置的帮助功能。

可以使用 `{*varname*}` 语法将 Python 变量传递到 shell 中,实现双向通信:

```py
In [9]: message = "hello from Python"

In [10]: !echo {message}
hello from Python
```

大括号中包含变量名,这个变量名在 shell 命令中会被替换为变量的内容。

## 与 Shell 相关的魔术命令

如果你在 IPython 的 shell 命令中尝试了一段时间,你可能会注意到无法使用 `!cd` 来导航文件系统:

```py
In [11]: !pwd
/home/jake/projects/myproject

In [12]: !cd ..

In [13]: !pwd
/home/jake/projects/myproject
```

这是因为笔记本中的 shell 命令是在一个临时子 shell 中执行的,这个 shell 并不保留命令之间的状态。如果你想更持久地改变工作目录,可以使用 `%cd` 魔术命令:

```py
In [14]: %cd ..
/home/jake/projects
```

实际上,默认情况下你甚至可以不带 `%` 符号使用这些功能:

```py
In [15]: cd myproject
/home/jake/projects/myproject
```

这被称为*自动魔术*函数,可以通过 `%automagic` 魔术函数切换执行这些命令时是否需要显式 `%` 符号。

除了 `%cd`,还有其他可用的类似 shell 的魔术函数,如 `%cat``%cp``%env``%ls``%man``%mkdir``%more``%mv``%pwd``%rm``%rmdir`,如果 `automagic` 打开,这些命令都可以不带 `%` 符号使用。这使得你几乎可以像处理普通 shell 一样处理 IPython 提示符:

```py
In [16]: mkdir tmp

In [17]: ls
myproject.txt tmp/

In [18]: cp myproject.txt tmp/

In [19]: ls tmp
myproject.txt

In [20]: rm -r tmp
```

在同一个终端窗口中访问 shell,与你的 Python 会话结合得更自然,减少了上下文切换。
458 changes: 458 additions & 0 deletions translations_v2/cn/pydshb2e_04.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions translations_v2/cn/pydshb2e_05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 第二部分:NumPy 入门

本书的这一部分,连同第三部分,概述了在 Python 中有效加载、存储和操作内存中数据的技术。这个主题非常广泛:数据集可以来自各种来源,以及各种格式,包括文档集合、图像集合、声音片段集合、数字测量集合,或者几乎任何其他类型的数据。尽管看似多样化,但许多数据集本质上都可以表示为数字数组。

例如,图像——特别是数字图像——可以简单地被视为表示整个区域内像素亮度的二维数组。声音片段可以被视为与时间的强度一维数组。文本可以以各种方式转换为数字表示,比如表示特定单词或词组频率的二进制数字。无论数据是什么,使其可分析的第一步将是将其转换为数字数组。(我们将在第四十章中讨论此过程的一些具体示例。)

因此,对数字数组的有效存储和操作对于进行数据科学的过程是绝对基本的。我们现在将看看 Python 专门用于处理这种数字数组的专用工具:NumPy 软件包和 Pandas 软件包(在第三部分中讨论)。

本书的这一部分将详细介绍 NumPy。NumPy(缩写为 *Numerical Python*)提供了一个高效的接口来存储和操作密集数据缓冲区。在某些方面,NumPy 数组类似于 Python 的内置 `list` 类型,但是随着数组大小的增长,NumPy 数组提供了更高效的存储和数据操作。NumPy 数组几乎构成了 Python 数据科学工具生态系统的全部核心,因此花时间学习如何有效地使用 NumPy 对无论你感兴趣的数据科学的哪个方面都是有价值的。

如果你遵循了前言中的建议并安装了 Anaconda 栈,那么你已经安装并准备好使用 NumPy 了。如果你更喜欢自己动手,你可以访问[NumPy.org](http://www.numpy.org)并按照那里的安装说明进行操作。一旦你安装完成,你就可以导入 NumPy 并检查版本:

```py
In [1]: import numpy
numpy.__version__
Out[1]: '1.21.2'
```

对于本章讨论的软件包的组件,我建议使用 NumPy 版本 1.8 或更高版本。按照惯例,在 SciPy/PyData 世界中,大多数人都会使用 `np` 作为 NumPy 的别名导入:

```py
In [2]: import numpy as np
```

在本章以及书中的其余部分中,你会发现这是我们导入和使用 NumPy 的方式。
298 changes: 298 additions & 0 deletions translations_v2/cn/pydshb2e_06.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
# 第四章:理解 Python 中的数据类型

有效的数据驱动科学和计算需要理解数据如何存储和操作。本章概述了在 Python 语言本身中如何处理数据数组,并介绍了 NumPy 在此基础上的改进。理解这种差异对于理解本书其余部分的材料至关重要。

Python 的用户经常被其易用性所吸引,其中一个因素是动态类型。而静态类型语言如 C 或 Java 要求每个变量都必须显式声明,而像 Python 这样的动态类型语言则跳过了这个规范。例如,在 C 语言中,您可能会这样指定特定的操作:

```py
/* C code */
int result = 0;
for(int i=0; i<100; i++){
result += i;
}
```

而在 Python 中,等价的操作可以这样写:

```py
# Python code
result = 0
for i in range(100):
result += i
```

注意一个主要区别:在 C 语言中,每个变量的数据类型都是显式声明的,而在 Python 中类型是动态推断的。这意味着,例如,我们可以将任何类型的数据分配给任何变量:

```py
# Python code
x = 4
x = "four"
```

在这里,我们已经将`x`的内容从整数变为字符串。在 C 语言中,相同操作会导致(依赖于编译器设置)编译错误或其他意外后果:

```py
/* C code */
int x = 4;
x = "four"; // FAILS
```

这种灵活性是 Python 和其他动态类型语言方便和易于使用的一个要素。理解*这是如何工作*是学习如何有效和有效地分析数据的重要一环。但是,这种类型灵活性也指出了 Python 变量不仅仅是它们的值;它们还包含关于值的*类型*的额外信息。我们将在接下来的章节中更多地探讨这一点。

# Python 整数不仅仅是一个整数

标准的 Python 实现是用 C 编写的。这意味着每个 Python 对象实际上是一个巧妙伪装的 C 结构,不仅包含其值,还包含其他信息。例如,当我们在 Python 中定义一个整数,如`x = 10000``x`不只是一个“原始”整数。它实际上是一个指向复合 C 结构的指针,该结构包含几个值。浏览 Python 3.10 源代码时,我们发现整数(long)类型定义实际上看起来是这样的(一旦 C 宏被展开):

```py
struct _longobject {
long ob_refcnt;
PyTypeObject *ob_type;
size_t ob_size;
long ob_digit[1];
};
```

在 Python 3.10 中,一个单独的整数实际上包含四个部分:

+ `ob_refcnt`,一个引用计数,帮助 Python 静默处理内存分配和释放

+ `ob_type`,编码变量的类型

+ `ob_size`指定了接下来的数据成员的大小。

+ `ob_digit`包含了我们期望 Python 变量表示的实际整数值。

这意味着在 Python 中存储整数与在编译语言如 C 中的存储相比,会有一些额外开销,正如图 4-1 所示。

![cint vs pyint](img/cint_vs_pyint.png)

###### 图 4-1. C 和 Python 整数的差异

这里,`PyObject_HEAD` 是包含引用计数、类型代码和之前提到的其他部分的结构的一部分。

注意这里的区别:C 整数本质上是一个标签,指向内存中的一个位置,其字节编码包含一个整数值。Python 整数是指向内存中包含所有 Python 对象信息的位置的指针,包括包含整数值的字节。Python 整数结构中的这些额外信息是允许 Python 如此自由和动态编码的原因。然而,Python 类型中的所有这些额外信息都是有代价的,特别是在结合许多这些对象的结构中尤为明显。

# Python 列表不仅仅是一个列表

现在让我们考虑当我们使用一个包含许多 Python 对象的 Python 数据结构时会发生什么。Python 中标准的可变多元素容器是列表。我们可以按照以下方式创建一个整数列表:

```py
In [1]: L = list(range(10))
L
Out[1]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

```py
In [2]: type(L[0])
Out[2]: int
```

或者,类似地,一个字符串列表:

```py
In [3]: L2 = [str(c) for c in L]
L2
Out[3]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
```

```py
In [4]: type(L2[0])
Out[4]: str
```

由于 Python 的动态类型,我们甚至可以创建异构列表:

```py
In [5]: L3 = [True, "2", 3.0, 4]
[type(item) for item in L3]
Out[5]: [bool, str, float, int]
```

但这种灵活性是有代价的:为了允许这些灵活的类型,列表中的每个项目都必须包含自己的类型、引用计数和其他信息。也就是说,每个项目都是一个完整的 Python 对象。在所有变量都是相同类型的特殊情况下,大部分信息是冗余的,因此将数据存储在固定类型的数组中可能更有效。动态类型列表和固定类型(NumPy 风格)数组之间的区别在 图 4-2 中有所说明。

![array vs list](img/array_vs_list.png)

###### 图 4-2\. C 和 Python 列表之间的区别

在实现级别上,数组基本上包含一个指向一个连续数据块的单个指针。另一方面,Python 列表包含一个指向指针块的指针,每个指针又指向一个完整的 Python 对象,就像我们之前看到的 Python 整数一样。再次强调列表的优势在于灵活性:因为每个列表元素都是一个包含数据和类型信息的完整结构,所以列表可以填充任何所需类型的数据。固定类型的 NumPy 风格数组缺乏这种灵活性,但对于存储和操作数据来说更加高效。

# Python 中的固定类型数组

Python 提供了几种不同的选项来在高效的固定类型数据缓冲区中存储数据。内置的 `array` 模块(自 Python 3.3 起可用)可用于创建统一类型的密集数组:

```py
In [6]: import array
L = list(range(10))
A = array.array('i', L)
A
Out[6]: array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
```

这里,`'i'` 是指示内容为整数的类型代码。

然而,更有用的是 NumPy 包的 `ndarray` 对象。虽然 Python 的 `array` 对象提供了对基于数组的数据的有效存储,但 NumPy 在此基础上添加了对该数据的有效*操作*。我们将在后面的章节中探讨这些操作;接下来,我将向您展示创建 NumPy 数组的几种不同方法。

# 从 Python 列表创建数组

我们将从标准的 NumPy 导入开始,使用别名 `np`

```py
In [7]: import numpy as np
```

现在我们可以使用 `np.array` 来从 Python 列表创建数组:

```py
In [8]: # Integer array
np.array([1, 4, 2, 5, 3])
Out[8]: array([1, 4, 2, 5, 3])
```

请记住,与 Python 列表不同,NumPy 数组只能包含相同类型的数据。如果类型不匹配,NumPy 将根据其类型提升规则进行类型提升;在这里,整数被提升为浮点数:

```py
In [9]: np.array([3.14, 4, 2, 3])
Out[9]: array([3.14, 4. , 2. , 3. ])
```

如果我们想要显式地设置结果数组的数据类型,可以使用 `dtype` 关键字:

```py
In [10]: np.array([1, 2, 3, 4], dtype=np.float32)
Out[10]: array([1., 2., 3., 4.], dtype=float32)
```

最后,与 Python 列表不同,NumPy 数组可以是多维的。以下是使用列表的列表初始化多维数组的一种方法:

```py
In [11]: # Nested lists result in multidimensional arrays
np.array([range(i, i + 3) for i in [2, 4, 6]])
Out[11]: array([[2, 3, 4],
[4, 5, 6],
[6, 7, 8]])
```

内部列表被视为生成的二维数组的行。

# 从头创建数组

特别是对于较大的数组,使用 NumPy 内置的函数从头创建数组更有效率。以下是几个示例:

```py
In [12]: # Create a length-10 integer array filled with 0s
np.zeros(10, dtype=int)
Out[12]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
```

```py
In [13]: # Create a 3x5 floating-point array filled with 1s
np.ones((3, 5), dtype=float)
Out[13]: array([[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]])
```

```py
In [14]: # Create a 3x5 array filled with 3.14
np.full((3, 5), 3.14)
Out[14]: array([[3.14, 3.14, 3.14, 3.14, 3.14],
[3.14, 3.14, 3.14, 3.14, 3.14],
[3.14, 3.14, 3.14, 3.14, 3.14]])
```

```py
In [15]: # Create an array filled with a linear sequence
# starting at 0, ending at 20, stepping by 2
# (this is similar to the built-in range function)
np.arange(0, 20, 2)
Out[15]: array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
```

```py
In [16]: # Create an array of five values evenly spaced between 0 and 1
np.linspace(0, 1, 5)
Out[16]: array([0. , 0.25, 0.5 , 0.75, 1. ])
```

```py
In [17]: # Create a 3x3 array of uniformly distributed
# pseudorandom values between 0 and 1
np.random.random((3, 3))
Out[17]: array([[0.09610171, 0.88193001, 0.70548015],
[0.35885395, 0.91670468, 0.8721031 ],
[0.73237865, 0.09708562, 0.52506779]])
```

```py
In [18]: # Create a 3x3 array of normally distributed pseudorandom
# values with mean 0 and standard deviation 1
np.random.normal(0, 1, (3, 3))
Out[18]: array([[-0.46652655, -0.59158776, -1.05392451],
[-1.72634268, 0.03194069, -0.51048869],
[ 1.41240208, 1.77734462, -0.43820037]])
```

```py
In [19]: # Create a 3x3 array of pseudorandom integers in the interval [0, 10)
np.random.randint(0, 10, (3, 3))
Out[19]: array([[4, 3, 8],
[6, 5, 0],
[1, 1, 4]])
```

```py
In [20]: # Create a 3x3 identity matrix
np.eye(3)
Out[20]: array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
```

```py
In [21]: # Create an uninitialized array of three integers; the values will be
# whatever happens to already exist at that memory location
np.empty(3)
Out[21]: array([1., 1., 1.])
```

# NumPy 标准数据类型

NumPy 数组包含单一类型的值,因此对这些类型及其限制有详细了解很重要。因为 NumPy 是用 C 语言构建的,所以这些类型对于使用 C、Fortran 和其他相关语言的用户来说应该很熟悉。

标准的 NumPy 数据类型列在 表 4-1 中。注意,在构建数组时,它们可以使用字符串来指定:

```py
np.zeros(10, dtype='int16')
```

或者使用相关的 NumPy 对象:

```py
np.zeros(10, dtype=np.int16)
```

可以进行更高级的类型规定,例如指定大端或小端数字;有关更多信息,请参阅 [NumPy 文档](http://numpy.org)。NumPy 还支持复合数据类型,这将在 第 12 章 中介绍。

4-1\. 标准的 NumPy 数据类型

| 数据类型 | 描述 |
| --- | --- |
| `bool_` | 存储为字节的布尔值(TrueFalse|
| `int_` | 默认整数类型(与 C 语言的 `long` 相同;通常是 `int64``int32`|
| `intc` | 与 C 语言 `int` 相同(通常是 `int32``int64`|
| `intp` | 用于索引的整数(与 C 语言的 `ssize_t` 相同;通常是 `int32``int64`|
| `int8` | 字节(–128127|
| `int16` | 整数(–3276832767|
| `int32` | 整数(–21474836482147483647|
| `int64` | 整数(–92233720368547758089223372036854775807|
| `uint8` | 无符号整数(0255|
| `uint16` | 无符号整数(065535|
| `uint32` | 无符号整数(04294967295|
| `uint64` | 无符号整数(018446744073709551615|
| `float_` | `float64` 的简写 |
| `float16` | 半精度浮点数:符号位,5 位指数,10 位尾数 |
| `float32` | 单精度浮点数:符号位,8 位指数,23 位尾数 |
| `float64` | 双精度浮点数:符号位,11 位指数,52 位尾数 |
| `complex_` | `complex128` 的简写 |
| `complex64` | 复数,由两个 32 位浮点数表示 |
| `complex128` | 复数,由两个 64 位浮点数表示 |
457 changes: 457 additions & 0 deletions translations_v2/cn/pydshb2e_07.md

Large diffs are not rendered by default.

367 changes: 367 additions & 0 deletions translations_v2/cn/pydshb2e_08.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
# 第六章:NumPy 数组上的计算:通用函数

到目前为止,我们已经讨论了 NumPy 的一些基本要点。在接下来的几章中,我们将深入探讨 NumPy 在 Python 数据科学世界中如此重要的原因:因为它提供了一个简单灵活的接口来优化数据数组的计算。

NumPy 数组上的计算可能非常快,也可能非常慢。使其快速的关键在于使用向量化操作,通常通过 NumPy 的*通用函数*(ufuncs)实现。本章阐述了使用 NumPy ufuncs 的必要性,它可以使对数组元素的重复计算更加高效。然后介绍了 NumPy 包中许多常见和有用的算术 ufuncs。

# 循环的缓慢性

Python 的默认实现(称为 CPython)有时会非常慢地执行某些操作。这在一定程度上是由于语言的动态解释性质造成的;类型灵活,因此无法像 C 和 Fortran 语言那样将操作序列编译成高效的机器码。近年来,有各种尝试来解决这一弱点:著名的例子有 [PyPy 项目](http://pypy.org),一个即时编译的 Python 实现;[Cython 项目](http://cython.org),它将 Python 代码转换为可编译的 C 代码;以及 [Numba 项目](http://numba.pydata.org),它将 Python 代码片段转换为快速的 LLVM 字节码。每种方法都有其优势和劣势,但可以肯定的是,这三种方法都还没有超越标准 CPython 引擎的普及度和影响力。

Python 的相对缓慢通常在需要重复执行许多小操作的情况下显现出来;例如,循环遍历数组以对每个元素进行操作。例如,假设我们有一个值数组,并且想要计算每个值的倒数。一个直接的方法可能如下所示:

```py
In [1]: import numpy as np
rng = np.random.default_rng(seed=1701)

def compute_reciprocals(values):
output = np.empty(len(values))
for i in range(len(values)):
output[i] = 1.0 / values[i]
return output

values = rng.integers(1, 10, size=5)
compute_reciprocals(values)
Out[1]: array([0.11111111, 0.25 , 1. , 0.33333333, 0.125 ])
```

这种实现对于来自 C 或 Java 背景的人来说可能感觉相当自然。但是如果我们测量这段代码在大输入下的执行时间,我们会发现这个操作非常慢——也许出乎意料地慢!我们将使用 IPython 的 `%timeit` 魔术命令(在“分析和计时代码”中讨论)进行基准测试:

```py
In [2]: big_array = rng.integers(1, 100, size=1000000)
%timeit compute_reciprocals(big_array)
Out[2]: 2.61 s ± 192 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

计算这些百万次操作并存储结果需要几秒钟!即使是手机的处理速度也以亿次数的每秒运算来衡量,这看起来几乎是荒谬地慢。事实证明,这里的瓶颈不是操作本身,而是 CPython 在每个循环周期中必须进行的类型检查和函数调度。每次计算倒数时,Python 首先检查对象的类型,并动态查找正确的函数来使用该类型。如果我们在编译代码中工作,这种类型规范将在代码执行之前已知,并且结果可以更有效地计算。

# 引入 Ufuncs

对于许多类型的操作,NumPy 提供了一个便利的接口,用于这种静态类型化的、编译的例程。这被称为*向量化*操作。对于像这里的逐元素除法这样的简单操作,向量化操作只需直接在数组对象上使用 Python 算术运算符即可。这种向量化方法旨在将循环推入 NumPy 底层的编译层,从而实现更快的执行。

比较以下两个操作的结果:

```py
In [3]: print(compute_reciprocals(values))
print(1.0 / values)
Out[3]: [0.11111111 0.25 1. 0.33333333 0.125 ]
[0.11111111 0.25 1. 0.33333333 0.125 ]
```

查看我们大数组的执行时间,我们看到它完成的速度比 Python 循环快了几个数量级:

```py
In [4]: %timeit (1.0 / big_array)
Out[4]: 2.54 ms ± 383 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
```

NumPy 中的向量化操作是通过 ufuncs 实现的,其主要目的是在 NumPy 数组中快速执行重复操作。Ufuncs 非常灵活——我们之前看到了标量和数组之间的操作,但我们也可以在两个数组之间进行操作:

```py
In [5]: np.arange(5) / np.arange(1, 6)
Out[5]: array([0. , 0.5 , 0.66666667, 0.75 , 0.8 ])
```

并且 ufunc 操作不仅限于一维数组。它们也可以作用于多维数组:

```py
In [6]: x = np.arange(9).reshape((3, 3))
2 ** x
Out[6]: array([[ 1, 2, 4],
[ 8, 16, 32],
[ 64, 128, 256]])
```

通过 ufunc 的向量化计算几乎总是比使用 Python 循环实现的对应计算更有效率,特别是数组增长时。在 NumPy 脚本中看到这样的循环时,您应该考虑是否可以用向量化表达式替代它。

# 探索 NumPy 的 Ufuncs

Ufuncs 有两种类型:*一元 ufuncs*,作用于单个输入,和*二元 ufuncs*,作用于两个输入。我们将在这里看到这两种类型的函数示例。

## 数组算术

NumPy 的 ufunc 使用起来非常自然,因为它们利用了 Python 的本机算术运算符。可以使用标准的加法、减法、乘法和除法:

```py
In [7]: x = np.arange(4)
print("x =", x)
print("x + 5 =", x + 5)
print("x - 5 =", x - 5)
print("x * 2 =", x * 2)
print("x / 2 =", x / 2)
print("x // 2 =", x // 2) # floor division
Out[7]: x = [0 1 2 3]
x + 5 = [5 6 7 8]
x - 5 = [-5 -4 -3 -2]
x * 2 = [0 2 4 6]
x / 2 = [0. 0.5 1. 1.5]
x // 2 = [0 0 1 1]
```

还有一个一元 ufunc 用于求反,一个`**`运算符用于指数运算,一个`%`运算符用于求模:

```py
In [8]: print("-x = ", -x)
print("x ** 2 = ", x ** 2)
print("x % 2 = ", x % 2)
Out[8]: -x = [ 0 -1 -2 -3]
x ** 2 = [0 1 4 9]
x % 2 = [0 1 0 1]
```

此外,这些操作可以随意组合在一起,而且遵循标准的操作顺序:

```py
In [9]: -(0.5*x + 1) ** 2
Out[9]: array([-1. , -2.25, -4. , -6.25])
```

所有这些算术操作都只是围绕着 NumPy 中特定 ufunc 的方便包装。例如,`+`运算符是`add` ufunc 的一个包装器。

```py
In [10]: np.add(x, 2)
Out[10]: array([2, 3, 4, 5])
```

表 6-1 列出了 NumPy 中实现的算术运算符。

表 6-1\. NumPy 中实现的算术运算符

| 运算符 | 等效 ufunc | 描述 |
| --- | --- | --- |
| `+` | `np.add` | 加法(例如,`1 + 1 = 2`|
| `-` | `np.subtract` | 减法(例如,`3 - 2 = 1`|
| `-` | `np.negative` | 一元取反(例如,`-2`|
| `*` | `np.multiply` | 乘法(例如,`2 * 3 = 6`|
| `/` | `np.divide` | 除法(例如,`3 / 2 = 1.5`|
| `//` | `np.floor_divide` | 地板除法(例如,`3 // 2 = 1`|
| `**` | `np.power` | 指数运算(例如,`2 ** 3 = 8`|
| `%` | `np.mod` | 取模/取余数(例如,`9 % 4 = 1`|

此外,还有布尔/位运算符;我们将在第九章中探索这些。

## 绝对值

就像 NumPy 理解 Python 内置的算术运算符一样,它也理解 Python 内置的绝对值函数:

```py
In [11]: x = np.array([-2, -1, 0, 1, 2])
abs(x)
Out[11]: array([2, 1, 0, 1, 2])
```

对应的 NumPy ufunc 是`np.absolute`,也可以用别名`np.abs`调用:

```py
In [12]: np.absolute(x)
Out[12]: array([2, 1, 0, 1, 2])
```

```py
In [13]: np.abs(x)
Out[13]: array([2, 1, 0, 1, 2])
```

这个 ufunc 也可以处理复杂数据,此时它返回幅度:

```py
In [14]: x = np.array([3 - 4j, 4 - 3j, 2 + 0j, 0 + 1j])
np.abs(x)
Out[14]: array([5., 5., 2., 1.])
```

## 三角函数

NumPy 提供了大量有用的 ufunc,对于数据科学家来说,其中一些最有用的是三角函数。我们将从定义一组角度开始:

```py
In [15]: theta = np.linspace(0, np.pi, 3)
```

现在我们可以在这些值上计算一些三角函数了:

```py
In [16]: print("theta = ", theta)
print("sin(theta) = ", np.sin(theta))
print("cos(theta) = ", np.cos(theta))
print("tan(theta) = ", np.tan(theta))
Out[16]: theta = [0. 1.57079633 3.14159265]
sin(theta) = [0.0000000e+00 1.0000000e+00 1.2246468e-16]
cos(theta) = [ 1.000000e+00 6.123234e-17 -1.000000e+00]
tan(theta) = [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
```

这些值计算精度达到了机器精度,这就是为什么应该是零的值并不总是确切为零。反三角函数也是可用的:

```py
In [17]: x = [-1, 0, 1]
print("x = ", x)
print("arcsin(x) = ", np.arcsin(x))
print("arccos(x) = ", np.arccos(x))
print("arctan(x) = ", np.arctan(x))
Out[17]: x = [-1, 0, 1]
arcsin(x) = [-1.57079633 0. 1.57079633]
arccos(x) = [3.14159265 1.57079633 0. ]
arctan(x) = [-0.78539816 0. 0.78539816]
```

## 指数和对数

在 NumPy 的 ufunc 中还有其他常见的操作,如指数函数:

```py
In [18]: x = [1, 2, 3]
print("x =", x)
print("e^x =", np.exp(x))
print("2^x =", np.exp2(x))
print("3^x =", np.power(3., x))
Out[18]: x = [1, 2, 3]
e^x = [ 2.71828183 7.3890561 20.08553692]
2^x = [2. 4. 8.]
3^x = [ 3. 9. 27.]
```

逆指数函数,对数函数也是可用的。基本的`np.log`给出自然对数;如果你偏好计算以 2 为底或以 10 为底的对数,这些也是可用的:

```py
In [19]: x = [1, 2, 4, 10]
print("x =", x)
print("ln(x) =", np.log(x))
print("log2(x) =", np.log2(x))
print("log10(x) =", np.log10(x))
Out[19]: x = [1, 2, 4, 10]
ln(x) = [0. 0.69314718 1.38629436 2.30258509]
log2(x) = [0. 1. 2. 3.32192809]
log10(x) = [0. 0.30103 0.60205999 1. ]
```

还有一些专门用于保持极小输入精度的版本:

```py
In [20]: x = [0, 0.001, 0.01, 0.1]
print("exp(x) - 1 =", np.expm1(x))
print("log(1 + x) =", np.log1p(x))
Out[20]: exp(x) - 1 = [0. 0.0010005 0.01005017 0.10517092]
log(1 + x) = [0. 0.0009995 0.00995033 0.09531018]
```

`x`非常小时,这些函数比使用原始的`np.log``np.exp`给出更精确的值。

## 专用的 Ufuncs

NumPy 还有许多其他的 ufunc 可用,包括双曲三角函数,位运算,比较操作,弧度转角度的转换,取整和余数等等。查阅 NumPy 文档会发现许多有趣的功能。

另一个更专业的 ufunc 来源是子模块`scipy.special`。如果你想在数据上计算一些不常见的数学函数,很可能它已经在`scipy.special`中实现了。这里有太多函数无法一一列出,但以下代码片段展示了在统计上可能会遇到的一些函数:

```py
In [21]: from scipy import special
```

```py
In [22]: # Gamma functions (generalized factorials) and related functions
x = [1, 5, 10]
print("gamma(x) =", special.gamma(x))
print("ln|gamma(x)| =", special.gammaln(x))
print("beta(x, 2) =", special.beta(x, 2))
Out[22]: gamma(x) = [1.0000e+00 2.4000e+01 3.6288e+05]
ln|gamma(x)| = [ 0. 3.17805383 12.80182748]
beta(x, 2) = [0.5 0.03333333 0.00909091]
```

```py
In [23]: # Error function (integral of Gaussian),
# its complement, and its inverse
x = np.array([0, 0.3, 0.7, 1.0])
print("erf(x) =", special.erf(x))
print("erfc(x) =", special.erfc(x))
print("erfinv(x) =", special.erfinv(x))
Out[23]: erf(x) = [0. 0.32862676 0.67780119 0.84270079]
erfc(x) = [1. 0.67137324 0.32219881 0.15729921]
erfinv(x) = [0. 0.27246271 0.73286908 inf]
```

NumPy 和`scipy.special`中还有许多其他的 ufunc 可用。由于这些包的文档可以在线获取,因此通过类似“gamma function python”的网络搜索通常可以找到相关信息。

# 高级 Ufunc 功能

许多 NumPy 用户在不完全了解它们的全部特性的情况下就使用了 ufuncs。我在这里概述一些 ufunc 的专门特性。

## 指定输出

对于大型计算,有时指定计算结果存储的数组是很有用的。对于所有 ufunc,这可以通过函数的`out`参数来完成:

```py
In [24]: x = np.arange(5)
y = np.empty(5)
np.multiply(x, 10, out=y)
print(y)
Out[24]: [ 0. 10. 20. 30. 40.]
```

这甚至可以与数组视图一起使用。例如,我们可以将计算结果写入指定数组的每隔一个元素:

```py
In [25]: y = np.zeros(10)
np.power(2, x, out=y[::2])
print(y)
Out[25]: [ 1. 0. 2. 0. 4. 0. 8. 0. 16. 0.]
```

如果我们改为写成`y[::2] = 2 ** x`,这将导致创建一个临时数组来保存`2 ** x`的结果,然后进行第二个操作将这些值复制到`y`数组中。对于这样一个小的计算来说,这并不会有太大的区别,但是对于非常大的数组来说,通过谨慎使用`out`参数可以节省内存。

## 聚合

对于二元 ufunc,聚合可以直接从对象中计算。例如,如果我们想要使用特定操作*减少*一个数组,我们可以使用任何 ufunc 的`reduce`方法。reduce 会重复地将给定操作应用于数组的元素,直到只剩下一个单一的结果。

例如,对`add` ufunc 调用`reduce`将返回数组中所有元素的总和:

```py
In [26]: x = np.arange(1, 6)
np.add.reduce(x)
Out[26]: 15
```

类似地,对`multiply` ufunc 调用`reduce`将得到数组所有元素的乘积:

```py
In [27]: np.multiply.reduce(x)
Out[27]: 120
```

如果我们想要存储计算的所有中间结果,我们可以使用`accumulate`

```py
In [28]: np.add.accumulate(x)
Out[28]: array([ 1, 3, 6, 10, 15])
```

```py
In [29]: np.multiply.accumulate(x)
Out[29]: array([ 1, 2, 6, 24, 120])
```

请注意,对于这些特定情况,有专门的 NumPy 函数来计算结果(`np.sum``np.prod``np.cumsum``np.cumprod`),我们将在第七章中探讨。

## 外积

最后,任何 ufunc 都可以使用`outer`方法计算两个不同输入的所有对的输出。这使您可以在一行中执行诸如创建乘法表之类的操作:

```py
In [30]: x = np.arange(1, 6)
np.multiply.outer(x, x)
Out[30]: array([[ 1, 2, 3, 4, 5],
[ 2, 4, 6, 8, 10],
[ 3, 6, 9, 12, 15],
[ 4, 8, 12, 16, 20],
[ 5, 10, 15, 20, 25]])
```

`ufunc.at``ufunc.reduceat`方法同样也是非常有用的,我们将在第十章中探讨它们。

我们还将遇到 ufunc 能够在不同形状和大小的数组之间执行操作的能力,这一组操作被称为*广播*。这个主题非常重要,我们将专门为其设立一整章(参见第八章)。

# Ufuncs:了解更多

更多有关通用函数的信息(包括可用函数的完整列表)可以在[NumPy](http://www.numpy.org)[SciPy](http://www.scipy.org)文档网站上找到。

请回忆,您还可以通过在 IPython 中导入包并使用 IPython 的 tab 补全和帮助(`?`)功能来直接访问信息,如第一章中所述。
203 changes: 203 additions & 0 deletions translations_v2/cn/pydshb2e_09.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# 第七章:聚合:最小值、最大值以及其他

探索任何数据集的第一步通常是计算各种摘要统计信息。也许最常见的摘要统计信息是均值和标准差,它们帮助你总结数据集中的“典型”值,但其他聚合也很有用(总和、乘积、中位数、最小值和最大值、分位数等)。

NumPy 具有用于处理数组的快速内置聚合函数;我们将在这里讨论并尝试其中一些。

# 对数组中的值求和

举个快速的例子,考虑计算数组中所有值的总和。Python 本身可以使用内置的`sum`函数来完成这个操作:

```py
In [1]: import numpy as np
rng = np.random.default_rng()
```

```py
In [2]: L = rng.random(100)
sum(L)
Out[2]: 52.76825337322368
```

语法与 NumPy 的`sum`函数非常相似,在最简单的情况下结果是相同的:

```py
In [3]: np.sum(L)
Out[3]: 52.76825337322366
```

然而,由于它在编译代码中执行操作,NumPy 版本的操作速度要快得多:

```py
In [4]: big_array = rng.random(1000000)
%timeit sum(big_array)
%timeit np.sum(big_array)
Out[4]: 89.9 ms ± 233 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
521 µs ± 8.37 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
```

不过要小心:`sum`函数和`np.sum`函数并不相同,这有时可能会导致混淆!特别是,它们的可选参数具有不同的含义(`sum(x, 1)`将总和初始化为`1`,而`np.sum(x, 1)`沿着轴`1`求和),而`np.sum`能够识别多个数组维度,正如我们将在接下来的部分看到的。

# 最小值和最大值

同样,Python 内置了`min``max`函数,用于找到任意给定数组的最小值和最大值:

```py
In [5]: min(big_array), max(big_array)
Out[5]: (2.0114398036064074e-07, 0.9999997912802653)
```

NumPy 的相应函数具有类似的语法,并且在操作上也更快:

```py
In [6]: np.min(big_array), np.max(big_array)
Out[6]: (2.0114398036064074e-07, 0.9999997912802653)
```

```py
In [7]: %timeit min(big_array)
%timeit np.min(big_array)
Out[7]: 72 ms ± 177 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
564 µs ± 3.11 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
```

对于`min``max``sum`以及其他几个 NumPy 聚合函数,使用数组对象本身的方法可以简化语法:

```py
In [8]: print(big_array.min(), big_array.max(), big_array.sum())
Out[8]: 2.0114398036064074e-07 0.9999997912802653 499854.0273321711
```

在操作 NumPy 数组时,尽可能确保使用 NumPy 版本的这些聚合函数!

## 多维度聚合

一种常见的聚合操作类型是沿着行或列进行聚合。假设你有一些数据存储在二维数组中:

```py
In [9]: M = rng.integers(0, 10, (3, 4))
print(M)
Out[9]: [[0 3 1 2]
[1 9 7 0]
[4 8 3 7]]
```

NumPy 的聚合函数将应用于多维数组的所有元素:

```py
In [10]: M.sum()
Out[10]: 45
```

聚合函数接受一个额外的参数,指定沿着哪个**进行聚合计算。例如,我们可以通过指定`axis=0`找到每列中的最小值:

```py
In [11]: M.min(axis=0)
Out[11]: array([0, 3, 1, 0])
```

该函数返回四个值,对应于四列数字。

类似地,我们可以找到每行中的最大值:

```py
In [12]: M.max(axis=1)
Out[12]: array([3, 9, 8])
```

此处指定轴的方式可能会令从其他语言转过来的用户感到困惑。`axis`关键字指定将要*折叠*的数组维度,而不是将要返回的维度。因此,指定`axis=0`意味着将折叠轴 0:对于二维数组,将在每列内进行聚合。

## 其他聚合函数

NumPy 提供了几个其他具有类似 API 的聚合函数,此外大多数函数还有一个`NaN`安全的对应版本,用于在计算结果时忽略缺失值,这些值由特殊的 IEEE 浮点`NaN`值标记(参见第十六章)。

表 7-1 提供了 NumPy 中可用的一些有用的聚合函数列表。

表 7-1\. NumPy 中可用的聚合函数

| 函数名 | NaN 安全版本 | 描述 |
| --- | --- | --- |
| `np.sum` | `np.nansum` | 计算元素的总和 |
| `np.prod` | `np.nanprod` | 计算元素的乘积 |
| `np.mean` | `np.nanmean` | 计算元素的均值 |
| `np.std` | `np.nanstd` | 计算标准差 |
| `np.var` | `np.nanvar` | 计算方差 |
| `np.min` | `np.nanmin` | 查找最小值 |
| `np.max` | `np.nanmax` | 查找最大值 |
| `np.argmin` | `np.nanargmin` | 查找最小值的索引 |
| `np.argmax` | `np.nanargmax` | 查找最大值的索引 |
| `np.median` | `np.nanmedian` | 计算元素的中位数 |
| `np.percentile` | `np.nanpercentile` | 计算元素的基于排名的统计信息 |
| `np.any` | N/A | 评估是否有任何元素为真 |
| `np.all` | N/A | 评估所有元素是否为真 |

你将经常看到这些汇总统计信息在本书的其余部分中。

# 示例:美国总统的平均身高是多少?

NumPy 中可用的聚合函数可以作为一组值的汇总统计信息。作为一个小例子,让我们考虑所有美国总统的身高。这些数据包含在文件*president_heights.csv*中,这是一个标签和值的逗号分隔列表:

```py
In [13]: !head -4 data/president_heights.csv
Out[13]: order,name,height(cm)
1,George Washington,189
2,John Adams,170
3,Thomas Jefferson,189
```

我们将使用 Pandas 包,在第 III 部分中更全面地探讨,读取文件并提取这些信息(注意,身高以厘米为单位):

```py
In [14]: import pandas as pd
data = pd.read_csv('data/president_heights.csv')
heights = np.array(data['height(cm)'])
print(heights)
Out[14]: [189 170 189 163 183 171 185 168 173 183 173 173 175 178 183 193 178 173
174 183 183 168 170 178 182 180 183 178 182 188 175 179 183 193 182 183
177 185 188 188 182 185 191 182]
```

现在我们有了这个数据数组,我们可以计算各种汇总统计信息:

```py
In [15]: print("Mean height: ", heights.mean())
print("Standard deviation:", heights.std())
print("Minimum height: ", heights.min())
print("Maximum height: ", heights.max())
Out[15]: Mean height: 180.04545454545453
Standard deviation: 6.983599441335736
Minimum height: 163
Maximum height: 193
```

请注意,在每种情况下,聚合操作将整个数组减少为一个单一的汇总值,这为我们提供了关于值分布的信息。我们可能还希望计算分位数:

```py
In [16]: print("25th percentile: ", np.percentile(heights, 25))
print("Median: ", np.median(heights))
print("75th percentile: ", np.percentile(heights, 75))
Out[16]: 25th percentile: 174.75
Median: 182.0
75th percentile: 183.5
```

我们看到美国总统的中位身高为 182 厘米,几乎等于六英尺。

当然,有时更有用的是查看这些数据的视觉表示,我们可以使用 Matplotlib 工具来实现这一目标(我们将在第 IV 部分中更全面地讨论 Matplotlib)。例如,以下代码生成图 7-1:

```py
In [17]: %matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
```

```py
In [18]: plt.hist(heights)
plt.title('Height Distribution of US Presidents')
plt.xlabel('height (cm)')
plt.ylabel('number');
```

![output 39 0](img/output_39_0.png)

###### 图 7-1\. 美国总统身高的直方图
291 changes: 291 additions & 0 deletions translations_v2/cn/pydshb2e_10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
# 第八章:数组上的计算:广播

我们在第六章看到 NumPy 的通用函数如何用于*向量化*操作,从而消除缓慢的 Python 循环。本章讨论*广播*:这是 NumPy 允许你在不同大小和形状的数组之间应用二元操作(如加法、减法、乘法等)的一组规则。

# 引入广播

请记住,对于相同大小的数组,二元操作是逐元素执行的:

```py
In [1]: import numpy as np
```

```py
In [2]: a = np.array([0, 1, 2])
b = np.array([5, 5, 5])
a + b
Out[2]: array([5, 6, 7])
```

广播允许在不同大小的数组上执行这些类型的二元操作,例如,我们可以很容易地将标量(将其视为零维数组)加到数组中:

```py
In [3]: a + 5
Out[3]: array([5, 6, 7])
```

我们可以将其视为一种操作,将值`5`拉伸或复制到数组`[5, 5, 5]`中,并添加结果。

我们可以类似地将这个想法扩展到更高维度的数组。观察当我们将一个一维数组加到一个二维数组时的结果:

```py
In [4]: M = np.ones((3, 3))
M
Out[4]: array([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
```

```py
In [5]: M + a
Out[5]: array([[1., 2., 3.],
[1., 2., 3.],
[1., 2., 3.]])
```

在这里,一维数组`a`通过第二个维度被拉伸或广播,以匹配`M`的形状。

尽管这些示例相对容易理解,但更复杂的情况可能涉及广播两个数组。考虑以下例子:

```py
In [6]: a = np.arange(3)
b = np.arange(3)[:, np.newaxis]

print(a)
print(b)
Out[6]: [0 1 2]
[[0]
[1]
[2]]
```

```py
In [7]: a + b
Out[7]: array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
```

就像之前我们将一个值拉伸或广播到另一个形状相匹配的数组一样,这里我们拉伸了`a``b`,使它们匹配一个公共形状,结果是一个二维数组!这些示例的几何形状在图 8-1 中可视化。

浅色框表示广播的值。这种关于广播的思考方式可能会引发关于其内存使用效率的疑问,但不用担心:NumPy 广播实际上不会在内存中复制广播的值。尽管如此,这种思维模型在我们思考广播时仍然很有用。

![02.05 broadcasting](img/02.05-broadcasting.png)

###### 图 8-1\. NumPy 广播可视化(改编自 [astroML 文档](http://astroml.org),并获得许可使用)¹

# Broadcasting 规则

NumPy 中的广播遵循一组严格的规则来确定两个数组之间的交互:

规则 1

如果两个数组在它们的维数上不同,维数较少的数组的形状将在其前导(左)侧填充`1`

规则 2

如果两个数组在任何维度上的形状不匹配,则具有在该维度上形状等于 1 的数组将被拉伸以匹配另一个形状。

规则 3

如果在任何维度上大小不一致且都不等于 1,则会引发错误。

为了澄清这些规则,让我们详细考虑几个例子。

## 广播示例 1

假设我们想将一个二维数组加到一个一维数组中:

```py
In [8]: M = np.ones((2, 3))
a = np.arange(3)
```

让我们考虑对这两个具有以下形状的数组进行操作:

+ `M.shape``(2, 3)`

+ `a.shape``(3,)`

我们看到按照规则 1,数组 `a` 的维度较少,因此我们在左侧用 `1` 填充它:

+ `M.shape` 仍然是 `(2, 3)`

+ `a.shape` 变为 `(1, 3)`

根据规则 2,我们现在看到第一个维度不匹配,所以我们拉伸这个维度以匹配:

+ `M.shape` 仍然是 `(2, 3)`

+ `a.shape` 变为 `(2, 3)`

现在形状匹配了,我们可以看到最终的形状将是 `(2, 3)`

```py
In [9]: M + a
Out[9]: array([[1., 2., 3.],
[1., 2., 3.]])
```

## 广播示例 2

现在让我们看一个需要广播两个数组的例子:

```py
In [10]: a = np.arange(3).reshape((3, 1))
b = np.arange(3)
```

再次,我们将确定数组的形状:

+ `a.shape``(3, 1)`

+ `b.shape``(3,)`

规则 1 表示我们必须用 `1` 填充 `b` 的形状:

+ `a.shape` 仍然是 `(3, 1)`

+ `b.shape` 变为 `(1, 3)`

规则 2 告诉我们,我们必须将每个 `1` 扩展到与另一个数组的相应大小匹配:

+ `a.shape` 变为 `(3, 3)`

+ `b.shape` 变为 `(3, 3)`

因为结果匹配,这些形状是兼容的。我们可以在这里看到:

```py
In [11]: a + b
Out[11]: array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
```

## 广播示例 3

接下来,让我们看一个两个数组不兼容的例子:

```py
In [12]: M = np.ones((3, 2))
a = np.arange(3)
```

这只是比第一个例子略有不同的情况:矩阵 `M` 被转置了。这对计算有什么影响?数组的形状如下:

+ `M.shape``(3, 2)`

+ `a.shape``(3,)`

再次,规则 1 告诉我们,我们必须用 `1` 填充 `a` 的形状:

+ `M.shape` 仍然是 `(3, 2)`

+ `a.shape` 变为 `(1, 3)`

根据规则 2,`a` 的第一个维度被拉伸以匹配 `M` 的维度:

+ `M.shape` 仍然是 `(3, 2)`

+ `a.shape` 变为 `(3, 3)`

现在我们遇到了规则 3——最终的形状不匹配,所以这两个数组是不兼容的,我们可以通过尝试这个操作来观察:

```py
In [13]: M + a
ValueError: operands could not be broadcast together with shapes (3,2) (3,)
```

注意这里的潜在混淆:你可以想象通过在右侧而不是左侧用 `1` 填充 `a` 的形状来使 `a``M` 兼容。但这不是广播规则的工作方式!这种灵活性在某些情况下可能很有用,但它会导致潜在的歧义。如果你想要右侧填充,你可以通过显式地重新塑造数组来实现(我们将在第五章介绍 `np.newaxis` 关键字来实现这一点):

```py
In [14]: a[:, np.newaxis].shape
Out[14]: (3, 1)
```

```py
In [15]: M + a[:, np.newaxis]
Out[15]: array([[1., 1.],
[2., 2.],
[3., 3.]])
```

虽然我们在这里专注于 `+` 运算符,但这些广播规则适用于*任何*二元通用函数。例如,这是 `logaddexp(a, b)` 函数的示例,它计算 `log(exp(a) + exp(b))` 比朴素方法更精确:

```py
In [16]: np.logaddexp(M, a[:, np.newaxis])
Out[16]: array([[1.31326169, 1.31326169],
[1.69314718, 1.69314718],
[2.31326169, 2.31326169]])
```

欲知更多关于多个可用的通用函数的信息,请参阅第六章。

# 实际广播应用

广播操作是本书中许多示例的核心。现在让我们看看它们在哪些情况下可以派上用场。

## 数组居中

在第 6 章中,我们看到 ufunc 允许 NumPy 用户避免显式编写缓慢的 Python 循环。广播扩展了这种能力。数据科学中经常见到的一个例子是从数据数组中减去逐行均值。假设我们有一个由 10 个观测组成的数组,每个观测包含 3 个值。按照标准惯例(参见第 38 章),我们将其存储在一个<math alttext="10 times 3"><mrow><mn>10</mn> <mo>×</mo> <mn>3</mn></mrow></math>数组中:

```py
In [17]: rng = np.random.default_rng(seed=1701)
X = rng.random((10, 3))
```

我们可以使用沿第一维度的 `mean` 聚合计算每列的均值:

```py
In [18]: Xmean = X.mean(0)
Xmean
Out[18]: array([0.38503638, 0.36991443, 0.63896043])
```

现在我们可以通过减去均值来将 `X` 数组居中(这是一个广播操作):

```py
In [19]: X_centered = X - Xmean
```

为了确保我们做得正确,我们可以检查居中数组的平均值是否接近零:

```py
In [20]: X_centered.mean(0)
Out[20]: array([ 4.99600361e-17, -4.44089210e-17, 0.00000000e+00])
```

机器精度内,均值现在为零。

## 绘制二维函数

广播经常派上用场的一个地方是基于二维函数显示图像。如果我们想定义一个函数<math alttext="z equals f left-parenthesis x comma y right-parenthesis"><mrow><mi>z</mi> <mo>=</mo> <mi>f</mi> <mo>(</mo> <mi>x</mi> <mo>,</mo> <mi>y</mi> <mo>)</mo></mrow></math>,可以使用广播来计算整个网格上的函数:

```py
In [21]: # x and y have 50 steps from 0 to 5
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]

z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
```

我们将使用 Matplotlib 绘制这个二维数组,如图 8-2 所示(这些工具将在第 28 章中全面讨论):

```py
In [22]: %matplotlib inline
import matplotlib.pyplot as plt
```

```py
In [23]: plt.imshow(z, origin='lower', extent=[0, 5, 0, 5])
plt.colorbar();
```

![output 52 0](img/output_52_0.png)

###### 图 8-2\. 二维数组的可视化

结果是二维函数引人注目的可视化。

¹ 生成此图的代码可在在线[附录](https://oreil.ly/gtOaU)中找到。
378 changes: 378 additions & 0 deletions translations_v2/cn/pydshb2e_11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
# 第九章:比较、掩码和布尔逻辑

本章介绍了使用布尔掩码来检查和操作 NumPy 数组中的值。当你想要基于某些条件提取、修改、计数或以其他方式操作数组中的值时,掩码就会出现:例如,你可能希望计算大于某个特定值的所有值,或者删除所有超过某个阈值的异常值。在 NumPy 中,布尔掩码通常是实现这些任务的最高效方式。

# 示例:统计下雨天数

想象一下,你有一系列数据,代表了一个城市一年中每天的降水量。例如,在这里我们将加载西雅图市 2015 年的每日降雨统计数据,使用 Pandas(参见第三部分):

```py
In [1]: import numpy as np
from vega_datasets import data

# Use DataFrame operations to extract rainfall as a NumPy array
rainfall_mm = np.array(
data.seattle_weather().set_index('date')['precipitation']['2015'])
len(rainfall_mm)
Out[1]: 365
```

数组包含 365 个值,从 2015 年 1 月 1 日到 12 月 31 日的每日降雨量(以毫米为单位)。

首先快速可视化,让我们来看一下图 9-1 中的下雨天数直方图,这是使用 Matplotlib 生成的(我们将在第四部分中详细探讨这个工具):

```py
In [2]: %matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
```

```py
In [3]: plt.hist(rainfall_mm, 40);
```

![output 6 0](img/output_6_0.png)

###### 图 9-1\. 西雅图 2015 年降雨直方图

这个直方图给了我们一个关于数据外观的概括性的想法:尽管西雅图以多雨而闻名,2015 年西雅图大部分日子都几乎没有测得的降水量。但这并没有很好地传达一些我们想要看到的信息:例如,整年有多少天下雨?在那些下雨的日子里,平均降水量是多少?有多少天降水量超过 10 毫米?

其中一种方法是通过手工来回答这些问题:我们可以遍历数据,每次看到在某个期望范围内的值时增加一个计数器。但是出于本章讨论的原因,从编写代码的时间和计算结果的时间来看,这种方法非常低效。我们在第六章中看到,NumPy 的通用函数(ufuncs)可以用来替代循环,在数组上进行快速的逐元素算术操作;同样地,我们可以使用其他通用函数在数组上进行逐元素的*比较*,然后可以操作结果来回答我们的问题。暂且把数据放在一边,讨论一下 NumPy 中一些常用工具,使用*掩码*来快速回答这类问题。

# 比较运算符作为通用函数

第六章介绍了 ufuncs,特别关注算术运算符。我们看到,在数组上使用 `+`, `-`, `*`, `/` 和其他运算符会导致逐元素操作。NumPy 还实现了比较运算符,如 `<`(小于)和 `>`(大于)作为逐元素的 ufuncs。这些比较运算符的结果始终是一个布尔数据类型的数组。标准的六种比较操作都是可用的:

```py
In [4]: x = np.array([1, 2, 3, 4, 5])
```

```py
In [5]: x < 3 # less than
Out[5]: array([ True, True, False, False, False])
```

```py
In [6]: x > 3 # greater than
Out[6]: array([False, False, False, True, True])
```

```py
In [7]: x <= 3 # less than or equal
Out[7]: array([ True, True, True, False, False])
```

```py
In [8]: x >= 3 # greater than or equal
Out[8]: array([False, False, True, True, True])
```

```py
In [9]: x != 3 # not equal
Out[9]: array([ True, True, False, True, True])
```

```py
In [10]: x == 3 # equal
Out[10]: array([False, False, True, False, False])
```

还可以对两个数组进行逐元素比较,并包括复合表达式:

```py
In [11]: (2 * x) == (x ** 2)
Out[11]: array([False, True, False, False, False])
```

就像算术运算符的情况一样,NumPy 中的比较运算符也被实现为 ufuncs;例如,当你写 `x < 3` 时,NumPy 内部使用 `np.less(x, 3)`。这里显示了比较运算符及其等效的 ufuncs 的摘要:

| 操作符 | 等效的 ufunc | 操作符 | 等效的 ufunc |
| --- | --- | --- | --- |
| `==` | `np.equal` | `!=` | `np.not_equal` |
| `<` | `np.less` | `<=` | `np.less_equal` |
| `>` | `np.greater` | `>=` | `np.greater_equal` |

就像算术 ufuncs 的情况一样,这些函数适用于任何大小和形状的数组。这里是一个二维数组的例子:

```py
In [12]: rng = np.random.default_rng(seed=1701)
x = rng.integers(10, size=(3, 4))
x
Out[12]: array([[9, 4, 0, 3],
[8, 6, 3, 1],
[3, 7, 4, 0]])
```

```py
In [13]: x < 6
Out[13]: array([[False, True, True, True],
[False, False, True, True],
[ True, False, True, True]])
```

在每种情况下,结果都是一个布尔数组,NumPy 提供了一些简单的模式来处理这些布尔结果。

# 使用布尔数组

给定一个布尔数组,你可以进行许多有用的操作。我们将使用我们之前创建的二维数组 `x`

```py
In [14]: print(x)
Out[14]: [[9 4 0 3]
[8 6 3 1]
[3 7 4 0]]
```

## 计数条目

要计算布尔数组中 `True` 条目的数量,可以使用 `np.count_nonzero`

```py
In [15]: # how many values less than 6?
np.count_nonzero(x < 6)
Out[15]: 8
```

我们看到有八个数组条目小于 6。获取这些信息的另一种方法是使用 `np.sum`;在这种情况下,`False` 被解释为 `0``True` 被解释为 `1`

```py
In [16]: np.sum(x < 6)
Out[16]: 8
```

`np.sum` 的好处在于,与其他 NumPy 聚合函数一样,这种求和可以沿着行或列进行:

```py
In [17]: # how many values less than 6 in each row?
np.sum(x < 6, axis=1)
Out[17]: array([3, 2, 3])
```

这会统计矩阵每行中小于 6 的值的数量。

如果我们有兴趣快速检查任何或所有的值是否为 `True`,我们可以使用(你猜对了)`np.any``np.all`

```py
In [18]: # are there any values greater than 8?
np.any(x > 8)
Out[18]: True
```

```py
In [19]: # are there any values less than zero?
np.any(x < 0)
Out[19]: False
```

```py
In [20]: # are all values less than 10?
np.all(x < 10)
Out[20]: True
```

```py
In [21]: # are all values equal to 6?
np.all(x == 6)
Out[21]: False
```

`np.all``np.any` 也可以沿着特定的轴使用。例如:

```py
In [22]: # are all values in each row less than 8?
np.all(x < 8, axis=1)
Out[22]: array([False, False, True])
```

这里第三行的所有元素都小于 8,而其他行则不是这样。

最后,一个快速的警告:如第七章中提到的,Python 中有内置的 `sum``any``all` 函数。它们与 NumPy 版本的语法不同,特别是在多维数组上使用时可能会失败或产生意外的结果。确保在这些示例中使用 `np.sum``np.any``np.all`

## 布尔运算符

我们已经看到如何计算例如所有降雨量小于 20 毫米的天数,或所有降雨量大于 10 毫米的天数。但是如果我们想知道降雨量大于 10 毫米且小于 20 毫米的天数有多少呢?我们可以用 Python 的*位逻辑运算符* `&`, `|`, `^`, 和 `~` 来实现这一点。与标准算术运算符一样,NumPy 将它们重载为 ufuncs,这些 ufuncs 在(通常是布尔)数组上逐元素工作。

例如,我们可以这样处理这种复合问题:

```py
In [23]: np.sum((rainfall_mm > 10) & (rainfall_mm < 20))
Out[23]: 16
```

这告诉我们,有 16 天的降雨量在 10 到 20 毫米之间。

这里的括号很重要。由于操作符优先级规则,如果去掉括号,这个表达式将按照以下方式进行评估,从而导致错误:

```py
rainfall_mm > (10 & rainfall_mm) < 20
```

让我们演示一个更复杂的表达式。使用德摩根定律,我们可以以不同的方式计算相同的结果:

```py
In [24]: np.sum(~( (rainfall_mm <= 10) | (rainfall_mm >= 20) ))
Out[24]: 16
```

在数组上结合比较操作符和布尔操作符,可以进行广泛的高效逻辑操作。

以下表格总结了位运算布尔操作符及其等效的 ufuncs:

| 运算符 | 等效 ufunc | 运算符 | 等效 ufunc |
| --- | --- | --- | --- |
| `&` | `np.bitwise_and` | | `np.bitwise_or` |
| `^` | `np.bitwise_xor` | `~` | `np.bitwise_not` |

使用这些工具,我们可以开始回答关于我们天气数据的许多问题。以下是将布尔操作与聚合结合时可以计算的一些结果示例:

```py
In [25]: print("Number days without rain: ", np.sum(rainfall_mm == 0))
print("Number days with rain: ", np.sum(rainfall_mm != 0))
print("Days with more than 10 mm: ", np.sum(rainfall_mm > 10))
print("Rainy days with < 5 mm: ", np.sum((rainfall_mm > 0) &
(rainfall_mm < 5)))
Out[25]: Number days without rain: 221
Number days with rain: 144
Days with more than 10 mm: 34
Rainy days with < 5 mm: 83
```

# 布尔数组作为掩码

在前面的部分中,我们看过直接在布尔数组上计算的聚合。更强大的模式是使用布尔数组作为掩码,选择数据本身的特定子集。让我们回到之前的`x`数组:

```py
In [26]: x
Out[26]: array([[9, 4, 0, 3],
[8, 6, 3, 1],
[3, 7, 4, 0]])
```

假设我们想要一个数组,其中所有值都小于,比如说,5。我们可以轻松地为这个条件获取一个布尔数组,就像我们已经看到的那样:

```py
In [27]: x < 5
Out[27]: array([[False, True, True, True],
[False, False, True, True],
[ True, False, True, True]])
```

现在,要从数组中*选择*这些值,我们可以简单地在这个布尔数组上进行索引;这称为*掩码*操作:

```py
In [28]: x[x < 5]
Out[28]: array([4, 0, 3, 3, 1, 3, 4, 0])
```

返回的是一个填充有所有满足条件值的一维数组;换句话说,所有掩码数组为`True`位置上的值。

然后我们可以自由地按照我们的意愿操作这些值。例如,我们可以计算我们西雅图降雨数据的一些相关统计信息:

```py
In [29]: # construct a mask of all rainy days
rainy = (rainfall_mm > 0)

# construct a mask of all summer days (June 21st is the 172nd day)
days = np.arange(365)
summer = (days > 172) & (days < 262)

print("Median precip on rainy days in 2015 (mm): ",
np.median(rainfall_mm[rainy]))
print("Median precip on summer days in 2015 (mm): ",
np.median(rainfall_mm[summer]))
print("Maximum precip on summer days in 2015 (mm): ",
np.max(rainfall_mm[summer]))
print("Median precip on non-summer rainy days (mm):",
np.median(rainfall_mm[rainy & ~summer]))
Out[29]: Median precip on rainy days in 2015 (mm): 3.8
Median precip on summer days in 2015 (mm): 0.0
Maximum precip on summer days in 2015 (mm): 32.5
Median precip on non-summer rainy days (mm): 4.1
```

通过组合布尔操作、掩码操作和聚合,我们可以非常快速地回答关于我们数据集的这些问题。

# 使用关键词 and/or 与操作符 &/|

一个常见的混淆点是关键词 `and``or` 与操作符 `&``|` 之间的区别。什么情况下会使用其中一个而不是另一个?

区别在于:`and``or` 在整个对象上操作,而 `&``|` 在对象内的元素上操作。

当你使用`and``or`时,相当于要求 Python 将对象视为单个布尔实体。在 Python 中,所有非零整数都将被评估为`True`。因此:

```py
In [30]: bool(42), bool(0)
Out[30]: (True, False)
```

```py
In [31]: bool(42 and 0)
Out[31]: False
```

```py
In [32]: bool(42 or 0)
Out[32]: True
```

当你在整数上使用`&``|`时,表达式将作用于元素的位表示,对构成数字的各个位应用****

```py
In [33]: bin(42)
Out[33]: '0b101010'
```

```py
In [34]: bin(59)
Out[34]: '0b111011'
```

```py
In [35]: bin(42 & 59)
Out[35]: '0b101010'
```

```py
In [36]: bin(42 | 59)
Out[36]: '0b111011'
```

注意,要产生结果,需要按顺序比较二进制表示的相应位。

当你在 NumPy 中有一个布尔值数组时,可以将其视为一个比特串,其中`1 = True``0 = False``&``|`将类似于前面的示例中的操作:

```py
In [37]: A = np.array([1, 0, 1, 0, 1, 0], dtype=bool)
B = np.array([1, 1, 1, 0, 1, 1], dtype=bool)
A | B
Out[37]: array([ True, True, True, False, True, True])
```

但是,如果你在这些数组上使用`or`,它将尝试评估整个数组对象的真假,这不是一个明确定义的值:

```py
In [38]: A or B
ValueError: The truth value of an array with more than one element is
> ambiguous.
a.any() or a.all()
```

类似地,当对给定数组评估布尔表达式时,应该使用`|``&`而不是`or``and`

```py
In [39]: x = np.arange(10)
(x > 4) & (x < 8)
Out[39]: array([False, False, False, False, False, True, True, True, False,
False])
```

尝试评估整个数组的真假将会产生与我们之前看到的相同的`ValueError`

```py
In [40]: (x > 4) and (x < 8)
ValueError: The truth value of an array with more than one element is
> ambiguous.
a.any() or a.all()
```

因此,请记住:`and``or`对整个对象执行单个布尔评估,而`&``|`对对象的内容(各个位或字节)执行多个布尔评估。对于布尔 NumPy 数组,后者几乎总是期望的操作。
294 changes: 294 additions & 0 deletions translations_v2/cn/pydshb2e_12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
# 第十章:Fancy 索引

前面的章节讨论了如何使用简单索引(例如 `arr[0]`)、切片(例如 `arr[:5]`)和布尔掩码(例如 `arr[arr > 0]`)来访问和修改数组的部分内容。在本章中,我们将看看另一种数组索引方式,称为*fancy**向量化*索引,在这种方式中,我们传递数组索引替代单个标量。这使得我们能够非常快速地访问和修改数组值的复杂子集。

# 探索 Fancy 索引

Fancy 索引在概念上很简单:它意味着传递一个索引数组以一次访问多个数组元素。例如,考虑以下数组:

```py
In [1]: import numpy as np
rng = np.random.default_rng(seed=1701)

x = rng.integers(100, size=10)
print(x)
Out[1]: [90 40 9 30 80 67 39 15 33 79]
```

假设我们要访问三个不同的元素。我们可以这样做:

```py
In [2]: [x[3], x[7], x[2]]
Out[2]: [30, 15, 9]
```

或者,我们可以传递一个单一的索引列表或数组来获取相同的结果:

```py
In [3]: ind = [3, 7, 4]
x[ind]
Out[3]: array([30, 15, 80])
```

当使用索引数组时,结果的形状反映了*索引数组的形状*而不是*被索引数组的形状*

```py
In [4]: ind = np.array([[3, 7],
[4, 5]])
x[ind]
Out[4]: array([[30, 15],
[80, 67]])
```

fancy 索引也适用于多维度。考虑以下数组:

```py
In [5]: X = np.arange(12).reshape((3, 4))
X
Out[5]: array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
```

就像标准索引一样,第一个索引指的是行,第二个指的是列:

```py
In [6]: row = np.array([0, 1, 2])
col = np.array([2, 1, 3])
X[row, col]
Out[6]: array([ 2, 5, 11])
```

注意结果中的第一个值是 `X[0, 2]`,第二个是 `X[1, 1]`,第三个是 `X[2, 3]`。在 fancy 索引中索引的配对遵循所有广播规则,这些规则在第八章中已经提到。因此,例如,如果我们在索引中组合列向量和行向量,我们将得到一个二维结果:

```py
In [7]: X[row[:, np.newaxis], col]
Out[7]: array([[ 2, 1, 3],
[ 6, 5, 7],
[10, 9, 11]])
```

在这里,每行的值与每列向量匹配,正如我们在算术操作的广播中看到的一样。例如:

```py
In [8]: row[:, np.newaxis] * col
Out[8]: array([[0, 0, 0],
[2, 1, 3],
[4, 2, 6]])
```

使用 fancy 索引时,始终重要的是记住返回值反映了*广播后的索引形状*,而不是被索引数组的形状。

# 结合索引

对于更强大的操作,可以将 fancy 索引与我们之前看到的其他索引方案结合使用。例如,给定数组 `X`

```py
In [9]: print(X)
Out[9]: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
```

我们可以将 fancy 索引与简单索引结合使用:

```py
In [10]: X[2, [2, 0, 1]]
Out[10]: array([10, 8, 9])
```

我们还可以将 fancy 索引与切片结合使用:

```py
In [11]: X[1:, [2, 0, 1]]
Out[11]: array([[ 6, 4, 5],
[10, 8, 9]])
```

并且我们可以将 fancy 索引与掩码结合使用:

```py
In [12]: mask = np.array([True, False, True, False])
X[row[:, np.newaxis], mask]
Out[12]: array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
```

所有这些索引选项的结合为有效访问和修改数组值提供了非常灵活的操作集。

# 示例:选择随机点

fancy 索引的一个常见用途是从矩阵中选择行的子集。例如,我们可能有一个表示<math alttext="upper N"><mi>N</mi></math> × <math alttext="upper D"><mi>D</mi></math>维度的矩阵,如从二维正态分布中抽取的以下点:

```py
In [13]: mean = [0, 0]
cov = [[1, 2],
[2, 5]]
X = rng.multivariate_normal(mean, cov, 100)
X.shape
Out[13]: (100, 2)
```

使用我们将在第四部分讨论的绘图工具,我们可以将这些点可视化为散点图(Figure 10-1)。

```py
In [14]: %matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')

plt.scatter(X[:, 0], X[:, 1]);
```

![output 31 0](img/output_31_0.png)

###### 图 10-1。正态分布的点

让我们使用高级索引选择 20 个随机点。我们将首先选择 20 个随机索引,而不重复,然后使用这些索引来选择原始数组的一部分:

```py
In [15]: indices = np.random.choice(X.shape[0], 20, replace=False)
indices
Out[15]: array([82, 84, 10, 55, 14, 33, 4, 16, 34, 92, 99, 64, 8, 76, 68, 18, 59,
80, 87, 90])
```

```py
In [16]: selection = X[indices] # fancy indexing here
selection.shape
Out[16]: (20, 2)
```

现在,为了查看选定的点,请在选定点的位置上添加大圆圈(请参阅图 10-2)。

```py
In [17]: plt.scatter(X[:, 0], X[:, 1], alpha=0.3)
plt.scatter(selection[:, 0], selection[:, 1],
facecolor='none', edgecolor='black', s=200);
```

![output 36 0](img/output_36_0.png)

###### 图 10-2。点之间的随机选择

这种策略通常用于快速分割数据集,就像在统计模型验证中经常需要的训练/测试拆分那样(见第三十九章),以及回答统计问题的抽样方法。

# 使用高级索引修改值

正如高级索引可用于访问数组的部分一样,它也可以用于修改数组的部分。例如,想象一下我们有一个索引数组,我们想将数组中的相应项目设置为某个值:

```py
In [18]: x = np.arange(10)
i = np.array([2, 1, 8, 4])
x[i] = 99
print(x)
Out[18]: [ 0 99 99 3 99 5 6 7 99 9]
```

我们可以使用任何赋值类型的运算符。例如:

```py
In [19]: x[i] -= 10
print(x)
Out[19]: [ 0 89 89 3 89 5 6 7 89 9]
```

注意,使用这些操作的重复索引可能会导致一些可能意想不到的结果。请考虑以下情况:

```py
In [20]: x = np.zeros(10)
x[[0, 0]] = [4, 6]
print(x)
Out[20]: [6. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
```

4 去哪了?此操作首先将`x[0] = 4`赋值,然后是`x[0] = 6`。结果,当然是`x[0]`包含值 6。

够公平了,但请考虑以下操作:

```py
In [21]: i = [2, 3, 3, 4, 4, 4]
x[i] += 1
x
Out[21]: array([6., 0., 1., 1., 1., 0., 0., 0., 0., 0.])
```

您可能期望`x[3]`包含值 2,`x[4]`包含值 3,因为这是每个索引重复的次数。为什么不是这种情况?从概念上讲,这是因为`x[i] += 1`被理解为`x[i] = x[i] + 1`的简写形式。`x[i] + 1`被计算,然后结果被赋值给`x`中的索引。考虑到这一点,增加发生的次数不是多次,而是分配,这导致了相当非直观的结果。

那么,如果您想要重复操作的其他行为呢?对此,您可以使用 ufuncs 的`at`方法,并执行以下操作:

```py
In [22]: x = np.zeros(10)
np.add.at(x, i, 1)
print(x)
Out[22]: [0. 0. 1. 2. 3. 0. 0. 0. 0. 0.]
```

`at`方法在指定的索引(这里是`i`)处以指定的值(这里是 1)进行给定操作的就地应用。另一种在精神上类似的方法是 ufuncs 的`reduceat`方法,您可以在[NumPy 文档](https://oreil.ly/7ys9D)中阅读有关它的信息。

# 例:分箱数据

您可以使用这些思想来有效地对数据进行自定义分组计算。例如,假设我们有 100 个值,并且想要快速找到它们在一个箱子数组中的位置。我们可以像这样使用`ufunc.at`来计算:

```py
In [23]: rng = np.random.default_rng(seed=1701)
x = rng.normal(size=100)

# compute a histogram by hand
bins = np.linspace(-5, 5, 20)
counts = np.zeros_like(bins)

# find the appropriate bin for each x
i = np.searchsorted(bins, x)

# add 1 to each of these bins
np.add.at(counts, i, 1)
```

现在,计数反映了每个箱中的点数——换句话说,是一个直方图(请参阅图 10-3)。

```py
In [24]: # plot the results
plt.plot(bins, counts, drawstyle='steps');
```

![output 52 0](img/output_52_0.png)

###### 图 10-3。手动计算的直方图

当然,每次想要绘制直方图时都这样做是很不方便的。这就是为什么 Matplotlib 提供了`plt.hist`例程,它可以在一行代码中完成相同的操作:

```py
plt.hist(x, bins, histtype='step');
```

这个函数将创建一个几乎与刚刚显示的图表完全相同的图。为了计算分箱,Matplotlib 使用了`np.histogram`函数,这个函数与我们之前做过的计算非常相似。让我们在这里比较一下两者:

```py
In [25]: print(f"NumPy histogram ({len(x)} points):")
%timeit counts, edges = np.histogram(x, bins)

print(f"Custom histogram ({len(x)} points):")
%timeit np.add.at(counts, np.searchsorted(bins, x), 1)
Out[25]: NumPy histogram (100 points):
33.8 µs ± 311 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Custom histogram (100 points):
17.6 µs ± 113 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
```

我们自己的一行代码算法比 NumPy 中优化算法快两倍!这怎么可能呢?如果你深入查看`np.histogram`的源代码(你可以在 IPython 中键入`np.histogram??`来做到这一点),你会看到它比我们做的简单的搜索和计数要复杂得多;这是因为 NumPy 的算法更灵活,特别是在数据点数量变大时性能更好的设计:

```py
In [26]: x = rng.normal(size=1000000)
print(f"NumPy histogram ({len(x)} points):")
%timeit counts, edges = np.histogram(x, bins)

print(f"Custom histogram ({len(x)} points):")
%timeit np.add.at(counts, np.searchsorted(bins, x), 1)
Out[26]: NumPy histogram (1000000 points):
84.4 ms ± 2.82 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Custom histogram (1000000 points):
128 ms ± 2.04 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```

这个比较显示的是算法效率几乎从来都不是一个简单的问题。一个对大数据集有效的算法并不总是小数据集的最佳选择,反之亦然(见第十一章)。但是自己编写此算法的优势在于,掌握了这些基本方法之后,天空就是你的极限:你不再局限于内置程序,而是可以创造自己的方法来探索数据。在数据密集型应用中高效使用 Python 的关键不仅在于了解像`np.histogram`这样的通用方便函数及其适用时机,还在于知道如何在需要更具针对性行为时利用低级功能。
234 changes: 234 additions & 0 deletions translations_v2/cn/pydshb2e_13.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# 第十一章:数组排序

到目前为止,我们主要关注使用 NumPy 访问和操作数组数据的工具。本章涵盖了与 NumPy 数组中值排序相关的算法。这些算法是计算机科学导论课程的热门话题:如果你曾经参加过这样的课程,你可能曾经梦想过(或者,根据你的性格,噩梦)*插入排序**选择排序**归并排序**快速排序**冒泡排序*等等。所有这些方法都是完成相似任务的手段:对列表或数组中的值进行排序。

Python 有几个用于排序列表和其他可迭代对象的内置函数和方法。`sorted`函数接受一个列表并返回其排序版本:

```py
In [1]: L = [3, 1, 4, 1, 5, 9, 2, 6]
sorted(L) # returns a sorted copy
Out[1]: [1, 1, 2, 3, 4, 5, 6, 9]
```

相比之下,列表的`sort`方法会就地对列表进行排序:

```py
In [2]: L.sort() # acts in-place and returns None
print(L)
Out[2]: [1, 1, 2, 3, 4, 5, 6, 9]
```

Python 的排序方法非常灵活,可以处理任何可迭代对象。例如,这里我们对一个字符串进行排序:

```py
In [3]: sorted('python')
Out[3]: ['h', 'n', 'o', 'p', 't', 'y']
```

这些内置的排序方法很方便,但正如前面讨论的那样,Python 值的动态性意味着它们的性能比专门设计用于均匀数组的例程要差。这就是 NumPy 排序例程的用武之地。

# NumPy 中的快速排序: np.sort 和 np.argsort

`np.sort`函数类似于 Python 的内置`sorted`函数,并且能够高效地返回数组的排序副本:

```py
In [4]: import numpy as np

x = np.array([2, 1, 4, 3, 5])
np.sort(x)
Out[4]: array([1, 2, 3, 4, 5])
```

类似于 Python 列表的`sort`方法,你也可以使用数组的`sort`方法原地对数组进行排序:

```py
In [5]: x.sort()
print(x)
Out[5]: [1 2 3 4 5]
```

相关的函数是`argsort`,它返回排序元素的*索引*

```py
In [6]: x = np.array([2, 1, 4, 3, 5])
i = np.argsort(x)
print(i)
Out[6]: [1 0 3 2 4]
```

结果的第一个元素给出了最小元素的索引,第二个值给出了第二小的索引,依此类推。如果需要的话,这些索引可以用(通过花式索引)来构造排序后的数组:

```py
In [7]: x[i]
Out[7]: array([1, 2, 3, 4, 5])
```

你将在本章后面看到`argsort`的应用。

# 沿行或列排序

NumPy 排序算法的一个有用特性是可以使用`axis`参数沿着多维数组的特定行或列进行排序。例如:

```py
In [8]: rng = np.random.default_rng(seed=42)
X = rng.integers(0, 10, (4, 6))
print(X)
Out[8]: [[0 7 6 4 4 8]
[0 6 2 0 5 9]
[7 7 7 7 5 1]
[8 4 5 3 1 9]]
```

```py
In [9]: # sort each column of X
np.sort(X, axis=0)
Out[9]: array([[0, 4, 2, 0, 1, 1],
[0, 6, 5, 3, 4, 8],
[7, 7, 6, 4, 5, 9],
[8, 7, 7, 7, 5, 9]])
```

```py
In [10]: # sort each row of X
np.sort(X, axis=1)
Out[10]: array([[0, 4, 4, 6, 7, 8],
[0, 0, 2, 5, 6, 9],
[1, 5, 7, 7, 7, 7],
[1, 3, 4, 5, 8, 9]])
```

请注意,这将把每行或列视为独立的数组,行或列值之间的任何关系都将丢失!

# 部分排序: 分区

有时候我们并不想对整个数组进行排序,而只是想找出数组中最小的*k*个值。NumPy 通过`np.partition`函数实现了这一点。`np.partition`接受一个数组和一个数*k*;结果是一个新数组,最小的*k*个值位于分区的左侧,剩余的值位于右侧:

```py
In [11]: x = np.array([7, 2, 3, 1, 6, 5, 4])
np.partition(x, 3)
Out[11]: array([2, 1, 3, 4, 6, 5, 7])
```

注意结果数组中的前三个值是数组中最小的三个值,剩下的数组位置包含剩余的值。在这两个分区中,元素的顺序是任意的。

类似于排序,我们也可以沿着多维数组的任意轴进行分区:

```py
In [12]: np.partition(X, 2, axis=1)
Out[12]: array([[0, 4, 4, 7, 6, 8],
[0, 0, 2, 6, 5, 9],
[1, 5, 7, 7, 7, 7],
[1, 3, 4, 5, 8, 9]])
```

结果是一个数组,其中每行的前两个槽包含该行的最小值,其余值填充其余槽位。

最后,就像有一个计算排序索引的`np.argsort`函数一样,有一个计算分区索引的`np.argpartition`函数。我们将在接下来的部分中看到这两者的作用。

# 示例:k-最近邻算法

让我们快速看看如何沿着多个轴使用`argsort`函数来找到集合中每个点的最近邻居。我们将从在二维平面上创建的随机 10 个点集开始。按照标准约定,我们将这些点排列在一个<math alttext="10 times 2"><mrow><mn>10</mn> <mo>×</mo> <mn>2</mn></mrow></math>数组中:

```py
In [13]: X = rng.random((10, 2))
```

为了了解这些点的外观,让我们生成一个快速的散点图(见图 11-1)。

```py
In [14]: %matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
plt.scatter(X[:, 0], X[:, 1], s=100);
```

![output 30 0](img/output_30_0.png)

###### 图 11-1\. k-最近邻示例中的点的可视化

现在我们将计算每对点之间的距离。回想一下,两点之间的平方距离是每个维度上平方差的和;使用 NumPy 提供的高效广播(第八章)和聚合(第七章)例程,我们可以在一行代码中计算出平方距离矩阵:

```py
In [15]: dist_sq = np.sum((X[:, np.newaxis] - X[np.newaxis, :]) ** 2, axis=-1)
```

这个操作包含了很多内容,如果你不熟悉 NumPy 的广播规则,可能会感到有些困惑。当你遇到这样的代码时,将其分解为各个步骤可能会很有用:

```py
In [16]: # for each pair of points, compute differences in their coordinates
differences = X[:, np.newaxis] - X[np.newaxis, :]
differences.shape
Out[16]: (10, 10, 2)
```

```py
In [17]: # square the coordinate differences
sq_differences = differences ** 2
sq_differences.shape
Out[17]: (10, 10, 2)
```

```py
In [18]: # sum the coordinate differences to get the squared distance
dist_sq = sq_differences.sum(-1)
dist_sq.shape
Out[18]: (10, 10)
```

作为我们逻辑的快速检查,我们应该看到这个矩阵的对角线(即每个点与自身之间的距离集合)全为零:

```py
In [19]: dist_sq.diagonal()
Out[19]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
```

一旦转换为成对的平方距离,我们现在可以使用`np.argsort`沿着每一行排序。最左边的列将给出最近邻居的索引:

```py
In [20]: nearest = np.argsort(dist_sq, axis=1)
print(nearest)
Out[20]: [[0 9 3 5 4 8 1 6 2 7]
[1 7 2 6 4 8 3 0 9 5]
[2 7 1 6 4 3 8 0 9 5]
[3 0 4 5 9 6 1 2 8 7]
[4 6 3 1 2 7 0 5 9 8]
[5 9 3 0 4 6 8 1 2 7]
[6 4 2 1 7 3 0 5 9 8]
[7 2 1 6 4 3 8 0 9 5]
[8 0 1 9 3 4 7 2 6 5]
[9 0 5 3 4 8 6 1 2 7]]
```

注意,第一列按顺序给出了数字 0 到 9:这是因为每个点的最近邻居是它自己,这是我们预期的结果。

在这里使用完全排序,实际上做了比需要的更多的工作。如果我们只是对最近的<math alttext="k"><mi>k</mi></math>个邻居感兴趣,我们只需对每一行进行分区,使得最小的<math alttext="k plus 1"><mrow><mi>k</mi> <mo>+</mo> <mn>1</mn></mrow></math>个平方距离首先出现,剩余的距离填充数组的其余位置。我们可以使用`np.argpartition`函数实现这一点:

```py
In [21]: K = 2
nearest_partition = np.argpartition(dist_sq, K + 1, axis=1)
```

为了可视化这些邻居的网络,让我们快速绘制这些点以及代表从每个点到其两个最近邻居的连接的线条(见图 11-2)。

```py
In [22]: plt.scatter(X[:, 0], X[:, 1], s=100)

# draw lines from each point to its two nearest neighbors
K = 2

for i in range(X.shape[0]):
for j in nearest_partition[i, :K+1]:
# plot a line from X[i] to X[j]
# use some zip magic to make it happen:
plt.plot(*zip(X[j], X[i]), color='black')
```

![output 44 0](img/output_44_0.png)

###### 图 11-2\. 每个点的最近邻居的可视化

每个图中的点都有线连接到其两个最近的邻居。乍一看,一些点有超过两条线连接可能会显得奇怪:这是因为如果点 A 是点 B 的两个最近邻之一,这并不一定意味着点 B 是点 A 的两个最近邻之一。

尽管这种方法的广播和行排序可能比编写循环不那么直观,但事实证明这是一种非常高效的在 Python 中处理这些数据的方法。您可能会尝试通过手动循环遍历数据并逐个排序每组邻居来执行相同类型的操作,但这几乎肯定会导致比我们使用的向量化版本更慢的算法。这种方法的美妙之处在于它以一种对输入数据大小不可知的方式编写:我们可以轻松地在任意维度中计算 100 个或 1,000,000 个点之间的邻居,代码看起来都一样。

最后,我要注意的是,在进行非常大的最近邻搜索时,有基于树的和/或近似算法可以扩展为<math alttext="script upper O left-bracket upper N log upper N right-bracket"><mrow><mi>𝒪</mi> <mo>[</mo> <mi>N</mi> <mo form="prefix">log</mo> <mi>N</mi> <mo>]</mo></mrow></math>或更好,而不是粗暴算法的<math alttext="script upper O left-bracket upper N squared right-bracket"><mrow><mi>𝒪</mi> <mo>[</mo> <msup><mi>N</mi> <mn>2</mn></msup> <mo>]</mo></mrow></math>。这种算法的一个例子是 KD-Tree,[在 Scikit-Learn 中实现](https://oreil.ly/lUFb8)
176 changes: 176 additions & 0 deletions translations_v2/cn/pydshb2e_14.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# 第十二章:结构化数据:NumPy 的结构化数组

虽然通常我们的数据可以用值的同类数组很好地表示,但有时情况并非如此。本章演示了 NumPy 的*结构化数组**记录数组*的使用,它们为复合异构数据提供了高效的存储。虽然这里展示的模式对于简单操作很有用,但像这样的情景通常适合使用 Pandas 的`DataFrame`,我们将在第三部分中探讨。

```py
In [1]: import numpy as np
```

假设我们有几类数据关于一些人(比如,姓名、年龄和体重),我们想要存储这些值以供在 Python 程序中使用。可以将它们分别存储在三个单独的数组中:

```py
In [2]: name = ['Alice', 'Bob', 'Cathy', 'Doug']
age = [25, 45, 37, 19]
weight = [55.0, 85.5, 68.0, 61.5]
```

但这有点笨拙。这里没有告诉我们这三个数组是相关的;NumPy 的结构化数组允许我们通过使用单一结构更自然地存储所有这些数据。

回顾之前我们用这样的表达式创建了一个简单的数组:

```py
In [3]: x = np.zeros(4, dtype=int)
```

我们可以类似地使用复合数据类型规范来创建结构化数组:

```py
In [4]: # Use a compound data type for structured arrays
data = np.zeros(4, dtype={'names':('name', 'age', 'weight'),
'formats':('U10', 'i4', 'f8')})
print(data.dtype)
Out[4]: [('name', '<U10'), ('age', '<i4'), ('weight', '<f8')]
```

这里的`'U10'`被翻译为“最大长度为 10 的 Unicode 字符串”,`'i4'`被翻译为“4 字节(即 32 位)整数”,而`'f8'`被翻译为“8 字节(即 64 位)浮点数”。我们将在下一节讨论这些类型代码的其他选项。

现在我们创建了一个空的容器数组,我们可以用我们的值列表填充这个数组:

```py
In [5]: data['name'] = name
data['age'] = age
data['weight'] = weight
print(data)
Out[5]: [('Alice', 25, 55. ) ('Bob', 45, 85.5) ('Cathy', 37, 68. )
('Doug', 19, 61.5)]
```

正如我们所希望的那样,数据现在方便地排列在一个结构化数组中。

结构化数组的方便之处在于,我们现在既可以按索引也可以按名称引用值:

```py
In [6]: # Get all names
data['name']
Out[6]: array(['Alice', 'Bob', 'Cathy', 'Doug'], dtype='<U10')
```

```py
In [7]: # Get first row of data
data[0]
Out[7]: ('Alice', 25, 55.)
```

```py
In [8]: # Get the name from the last row
data[-1]['name']
Out[8]: 'Doug'
```

使用布尔遮罩,我们甚至可以进行一些更复杂的操作,例如按年龄进行过滤:

```py
In [9]: # Get names where age is under 30
data[data['age'] < 30]['name']
Out[9]: array(['Alice', 'Doug'], dtype='<U10')
```

如果您想要进行比这些更复杂的操作,您可能应该考虑 Pandas 包,详细介绍请参阅第四部分。正如您将看到的,Pandas 提供了一个`DataFrame`对象,它是建立在 NumPy 数组上的结构,提供了许多有用的数据操作功能,类似于您在这里看到的,以及更多更多。

# 探索结构化数组的创建

结构化数组数据类型可以用多种方式指定。早些时候,我们看到了字典方法:

```py
In [10]: np.dtype({'names':('name', 'age', 'weight'),
'formats':('U10', 'i4', 'f8')})
Out[10]: dtype([('name', '<U10'), ('age', '<i4'), ('weight', '<f8')])
```

为了清晰起见,数值类型可以使用 Python 类型或 NumPy `dtype`来指定:

```py
In [11]: np.dtype({'names':('name', 'age', 'weight'),
'formats':((np.str_, 10), int, np.float32)})
Out[11]: dtype([('name', '<U10'), ('age', '<i8'), ('weight', '<f4')])
```

复合类型也可以作为元组列表来指定:

```py
In [12]: np.dtype([('name', 'S10'), ('age', 'i4'), ('weight', 'f8')])
Out[12]: dtype([('name', 'S10'), ('age', '<i4'), ('weight', '<f8')])
```

如果类型名称对您不重要,您可以单独在逗号分隔的字符串中指定这些类型:

```py
In [13]: np.dtype('S10,i4,f8')
Out[13]: dtype([('f0', 'S10'), ('f1', '<i4'), ('f2', '<f8')])
```

缩短的字符串格式代码可能并不直观,但它们基于简单的原则构建。第一个(可选)字符 `<``>`,表示“小端”或“大端”,分别指定了显著位的排序约定。接下来的字符指定数据类型:字符、字节、整数、浮点数等(参见表 12-1)。最后一个字符或多个字符表示对象的字节大小。

表 12-1\. NumPy 数据类型

| 字符 | 描述 | 示例 |
| --- | --- | --- |
| `'b'` | 字节 | `np.dtype('b')` |
| `'i'` | 有符号整数 | `np.dtype('i4') == np.int32` |
| `'u'` | 无符号整数 | `np.dtype('u1') == np.uint8` |
| `'f'` | 浮点数 | `np.dtype('f8') == np.int64` |
| `'c'` | 复数浮点数 | `np.dtype('c16') == np.complex128` |
| `'S'`, `'a'` | 字符串 | `np.dtype('S5')` |
| `'U'` | Unicode 字符串 | `np.dtype('U') == np.str_` |
| `'V'` | 原始数据(空) | `np.dtype('V') == np.void` |

# 更高级的复合类型

可以定义更高级的复合类型。例如,您可以创建每个元素包含值数组或矩阵的类型。在这里,我们将创建一个数据类型,其 `mat` 组件由一个 <math alttext="3 times 3"><mrow><mn>3</mn> <mo>×</mo> <mn>3</mn></mrow></math> 浮点矩阵组成:

```py
In [14]: tp = np.dtype([('id', 'i8'), ('mat', 'f8', (3, 3))])
X = np.zeros(1, dtype=tp)
print(X[0])
print(X['mat'][0])
Out[14]: (0, [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]])
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
```

现在 `X` 数组中的每个元素包含一个 `id` 和一个 <math alttext="3 times 3"><mrow><mn>3</mn> <mo>×</mo> <mn>3</mn></mrow></math> 矩阵。为什么您会使用这个而不是简单的多维数组,或者可能是 Python 字典?一个原因是这个 NumPy `dtype` 直接映射到 C 结构定义,因此包含数组内容的缓冲区可以在适当编写的 C 程序中直接访问。如果您发现自己编写一个 Python 接口来操作结构化数据的传统 C 或 Fortran 库,结构化数组可以提供一个强大的接口。

# 记录数组:带有变化的结构化数组

NumPy 还提供了记录数组(`np.recarray` 类的实例),几乎与上述结构化数组相同,但有一个附加功能:可以将字段作为属性而不是字典键访问。回顾我们之前通过编写来访问样本数据集中的年龄:

```py
In [15]: data['age']
Out[15]: array([25, 45, 37, 19], dtype=int32)
```

如果我们将数据视为记录数组,我们可以用稍少的按键操作访问它:

```py
In [16]: data_rec = data.view(np.recarray)
data_rec.age
Out[16]: array([25, 45, 37, 19], dtype=int32)
```

缺点在于,对于记录数组,即使使用相同的语法,访问字段时也涉及一些额外的开销:

```py
In [17]: %timeit data['age']
%timeit data_rec['age']
%timeit data_rec.age
Out[17]: 121 ns ± 1.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
2.41 µs ± 15.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
3.98 µs ± 20.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
```

是否更便利的表示形式值得(轻微的)开销将取决于您自己的应用程序。

# 转向 Pandas

本章节关于结构化数组和记录数组被特意放置在本书的这一部分的末尾,因为它很好地过渡到我们将要介绍的下一个包:Pandas。结构化数组在某些情况下非常有用,比如当你使用 NumPy 数组来映射到 C、Fortran 或其他语言的二进制数据格式时。但对于日常使用结构化数据,Pandas 包是一个更好的选择;我们将在接下来的章节深入探讨它。
27 changes: 27 additions & 0 deletions translations_v2/cn/pydshb2e_15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 第三部分:使用 Pandas 进行数据操作

在 第二部分 中,我们详细介绍了 NumPy 及其 `ndarray` 对象,该对象使得在 Python 中高效存储和操作密集类型数组成为可能。在这里,我们将通过深入研究 Pandas 库提供的数据结构来构建这些知识。Pandas 是一个建立在 NumPy 之上的较新的包,提供了 `DataFrame` 的高效实现。`DataFrame` 本质上是带有附加行和列标签的多维数组,通常具有异构类型和/或缺失数据。除了为标记数据提供方便的存储接口外,Pandas 还实现了许多强大的数据操作,这些操作对数据库框架和电子表格程序的用户来说都很熟悉。

正如我们所见,NumPy 的 `ndarray` 数据结构为在数值计算任务中通常看到的整洁、良好组织的数据类型提供了基本功能。虽然它非常适合这个目的,但当我们需要更多的灵活性时(例如,将标签附加到数据、处理缺失数据等)以及当尝试的操作无法很好地映射到逐元素广播时(例如,分组、数据透视等),其中每个操作都是分析周围许多形式的不太结构化数据中的重要组成部分时,其局限性变得明显。Pandas,特别是其 `Series``DataFrame` 对象,基于 NumPy 数组结构,并提供了有效的访问这些“数据清洗”任务的方法,这些任务占据了数据科学家大部分时间。

在本书的这一部分中,我们将重点介绍如何有效地使用 `Series``DataFrame` 和相关结构的机制。我们将在适当的情况下使用从真实数据集中提取的示例,但这些示例并不一定是重点。

###### 注意

在您的系统上安装 Pandas 需要 NumPy,如果您正在从源代码构建库,则需要适当的工具来编译 Pandas 构建的 C 和 Cython 源代码。有关安装过程的详细信息可以在 [Pandas 文档](http://pandas.pydata.org) 中找到。如果您遵循了 前言 中概述的建议并使用了 Anaconda 栈,您已经安装了 Pandas。

一旦安装了 Pandas,你就可以导入它并检查版本;以下是本书使用的版本:

```py
In [1]: import pandas
pandas.__version__
Out[1]: '1.3.5'
```

就像我们通常将 NumPy 导入为别名 `np` 一样,我们将 Pandas 导入为别名 `pd`

```py
In [2]: import pandas as pd
```

在本书的剩余部分将使用此导入约定。
406 changes: 406 additions & 0 deletions translations_v2/cn/pydshb2e_16.md

Large diffs are not rendered by default.

350 changes: 350 additions & 0 deletions translations_v2/cn/pydshb2e_17.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
# 第十四章:数据索引和选择

在第二部分中,我们详细讨论了访问、设置和修改 NumPy 数组中的值的方法和工具。这些包括索引(例如`arr[2, 1]`)、切片(例如`arr[:, 1:5]`)、掩码(例如`arr[arr > 0]`)、花式索引(例如`arr[0, [1, 5]]`)以及它们的组合(例如`arr[:, [1, 5]]`)。在这里,我们将看一下类似的方法来访问和修改 Pandas `Series``DataFrame`对象中的值。如果你使用过 NumPy 模式,Pandas 中的相应模式会感觉非常熟悉,尽管有一些需要注意的怪癖。

我们将从一维`Series`对象的简单情况开始,然后转向更复杂的二维`DataFrame`对象。

# Series 中的数据选择

正如你在前一章中看到的,`Series`对象在许多方面都像一个一维 NumPy 数组,而在许多方面都像一个标准的 Python 字典。如果你记住这两个重叠的类比,将有助于你理解这些数组中的数据索引和选择模式。

## `Series`作为字典

像字典一样,`Series`对象提供了从一组键到一组值的映射:

```py
In [1]: import pandas as pd
data = pd.Series([0.25, 0.5, 0.75, 1.0],
index=['a', 'b', 'c', 'd'])
data
Out[1]: a 0.25
b 0.50
c 0.75
d 1.00
dtype: float64
```

```py
In [2]: data['b']
Out[2]: 0.5
```

我们还可以使用类似字典的 Python 表达式和方法来查看键/索引和值:

```py
In [3]: 'a' in data
Out[3]: True
```

```py
In [4]: data.keys()
Out[4]: Index(['a', 'b', 'c', 'd'], dtype='object')
```

```py
In [5]: list(data.items())
Out[5]: [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
```

`Series`对象也可以用类似字典的语法进行修改。就像你可以通过分配给新键来扩展字典一样,你可以通过分配给新索引值来扩展`Series`

```py
In [6]: data['e'] = 1.25
data
Out[6]: a 0.25
b 0.50
c 0.75
d 1.00
e 1.25
dtype: float64
```

这种对象的易变性是一个方便的特性:在幕后,Pandas 正在做出关于内存布局和数据复制的决策,这可能需要进行,而用户通常不需要担心这些问题。

## 一维数组中的 Series

`Series`建立在这种类似字典的接口上,并通过与 NumPy 数组相同的基本机制提供了数组样式的项目选择——即切片、掩码和花式索引。以下是这些的示例:

```py
In [7]: # slicing by explicit index
data['a':'c']
Out[7]: a 0.25
b 0.50
c 0.75
dtype: float64
```

```py
In [8]: # slicing by implicit integer index
data[0:2]
Out[8]: a 0.25
b 0.50
dtype: float64
```

```py
In [9]: # masking
data[(data > 0.3) & (data < 0.8)]
Out[9]: b 0.50
c 0.75
dtype: float64
```

```py
In [10]: # fancy indexing
data[['a', 'e']]
Out[10]: a 0.25
e 1.25
dtype: float64
```

其中,切片可能是最容易混淆的来源。请注意,当使用显式索引进行切片(例如`data['a':'c']`)时,最终索引被*包括*在切片中,而当使用隐式索引进行切片(例如`data[0:2]`)时,最终索引被*排除*在切片之外。

## 索引器:loc 和 iloc

如果你的`Series`有一个明确的整数索引,那么像`data[1]`这样的索引操作将使用明确的索引,而像`data[1:3]`这样的切片操作将使用隐式的 Python 风格索引:

```py
In [11]: data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
data
Out[11]: 1 a
3 b
5 c
dtype: object
```

```py
In [12]: # explicit index when indexing
data[1]
Out[12]: 'a'
```

```py
In [13]: # implicit index when slicing
data[1:3]
Out[13]: 3 b
5 c
dtype: object
```

由于整数索引可能会导致混淆,Pandas 提供了一些特殊的*索引器*属性,明确地暴露了某些索引方案。这些不是功能性方法,而是属性,它们向`Series`中的数据公开了特定的切片接口。

首先,`loc`属性允许始终引用显式索引的索引和切片:

```py
In [14]: data.loc[1]
Out[14]: 'a'
```

```py
In [15]: data.loc[1:3]
Out[15]: 1 a
3 b
dtype: object
```

`iloc`属性允许引用始终参考隐式 Python 样式索引的索引和切片:

```py
In [16]: data.iloc[1]
Out[16]: 'b'
```

```py
In [17]: data.iloc[1:3]
Out[17]: 3 b
5 c
dtype: object
```

Python 代码的一个指导原则是“明确优于隐式”。`loc``iloc`的显式特性使它们在保持代码清晰和可读性方面非常有帮助;特别是在整数索引的情况下,始终一致地使用它们可以防止由于混合索引/切片约定而导致的微妙错误。

# 数据框选择

回想一下,`DataFrame`在许多方面都像一个二维或结构化数组,而在其他方面则像一个共享相同索引的`Series`结构的字典。当我们探索在这种结构内进行数据选择时,这些类比可能会有所帮助。

## DataFrame 作为字典

我们首先考虑的类比是将`DataFrame`视为一组相关`Series`对象的字典。让我们回到我们州的面积和人口的例子:

```py
In [18]: area = pd.Series({'California': 423967, 'Texas': 695662,
'Florida': 170312, 'New York': 141297,
'Pennsylvania': 119280})
pop = pd.Series({'California': 39538223, 'Texas': 29145505,
'Florida': 21538187, 'New York': 20201249,
'Pennsylvania': 13002700})
data = pd.DataFrame({'area':area, 'pop':pop})
data
Out[18]: area pop
California 423967 39538223
Texas 695662 29145505
Florida 170312 21538187
New York 141297 20201249
Pennsylvania 119280 13002700
```

组成`DataFrame`列的单个`Series`可以通过列名的字典样式索引进行访问:

```py
In [19]: data['area']
Out[19]: California 423967
Texas 695662
Florida 170312
New York 141297
Pennsylvania 119280
Name: area, dtype: int64
```

类似地,我们可以使用列名为字符串的属性样式访问:

```py
In [20]: data.area
Out[20]: California 423967
Texas 695662
Florida 170312
New York 141297
Pennsylvania 119280
Name: area, dtype: int64
```

尽管这是一个有用的简写,但请记住,并非所有情况下都适用!例如,如果列名不是字符串,或者列名与`DataFrame`的方法冲突,这种属性样式访问就不可能。例如,`DataFrame`有一个`pop`方法,所以`data.pop`将指向这个方法而不是`pop`列:

```py
In [21]: data.pop is data["pop"]
Out[21]: False
```

特别地,你应该避免尝试通过属性进行列赋值(即,使用`data['pop'] = z`而不是`data.pop = z`)。

像之前讨论过的`Series`对象一样,这种字典样式的语法也可以用来修改对象,比如在这种情况下添加一个新列:

```py
In [22]: data['density'] = data['pop'] / data['area']
data
Out[22]: area pop density
California 423967 39538223 93.257784
Texas 695662 29145505 41.896072
Florida 170312 21538187 126.463121
New York 141297 20201249 142.970120
Pennsylvania 119280 13002700 109.009893
```

这展示了`Series`对象之间按元素进行算术运算的简单语法预览;我们将在第十五章进一步深入探讨这个问题。

## DataFrame 作为二维数组

正如前面提到的,我们也可以将`DataFrame`视为增强的二维数组。我们可以使用`values`属性查看原始的底层数据数组:

```py
In [23]: data.values
Out[23]: array([[4.23967000e+05, 3.95382230e+07, 9.32577842e+01],
[6.95662000e+05, 2.91455050e+07, 4.18960717e+01],
[1.70312000e+05, 2.15381870e+07, 1.26463121e+02],
[1.41297000e+05, 2.02012490e+07, 1.42970120e+02],
[1.19280000e+05, 1.30027000e+07, 1.09009893e+02]])
```

在这个画面中,许多熟悉的类似数组的操作可以在`DataFrame`本身上完成。例如,我们可以转置整个`DataFrame`来交换行和列:

```py
In [24]: data.T
Out[24]: California Texas Florida New York Pennsylvania
area 4.239670e+05 6.956620e+05 1.703120e+05 1.412970e+05 1.192800e+05
pop 3.953822e+07 2.914550e+07 2.153819e+07 2.020125e+07 1.300270e+07
density 9.325778e+01 4.189607e+01 1.264631e+02 1.429701e+02 1.090099e+02
```

然而,当涉及到`DataFrame`对象的索引时,很明显,列的字典样式索引排除了我们简单将其视为 NumPy 数组的能力。特别是,将单个索引传递给数组会访问一行:

```py
In [25]: data.values[0]
Out[25]: array([4.23967000e+05, 3.95382230e+07, 9.32577842e+01])
```

并且将一个单独的“索引”传递给`DataFrame`会访问一列:

```py
In [26]: data['area']
Out[26]: California 423967
Texas 695662
Florida 170312
New York 141297
Pennsylvania 119280
Name: area, dtype: int64
```

因此,对于数组样式的索引,我们需要另一种约定。在这里,Pandas 再次使用了前面提到的`loc``iloc`索引器。使用`iloc`索引器,我们可以像使用简单的 NumPy 数组一样索引底层数组(使用隐式的 Python 风格索引),但结果中保持了`DataFrame`的索引和列标签:

```py
In [27]: data.iloc[:3, :2]
Out[27]: area pop
California 423967 39538223
Texas 695662 29145505
Florida 170312 21538187
```

同样地,使用`loc`索引器,我们可以以类似于数组的样式索引底层数据,但使用显式的索引和列名:

```py
In [28]: data.loc[:'Florida', :'pop']
Out[28]: area pop
California 423967 39538223
Texas 695662 29145505
Florida 170312 21538187
```

在这些索引器中,可以使用任何熟悉的类似于 NumPy 的数据访问模式。例如,在`loc`索引器中,我们可以按以下方式组合遮罩和花式索引:

```py
In [29]: data.loc[data.density > 120, ['pop', 'density']]
Out[29]: pop density
Florida 21538187 126.463121
New York 20201249 142.970120
```

任何这些索引约定也可以用于设置或修改值;这是通过与您在使用 NumPy 工作时习惯的标准方式完成的:

```py
In [30]: data.iloc[0, 2] = 90
data
Out[30]: area pop density
California 423967 39538223 90.000000
Texas 695662 29145505 41.896072
Florida 170312 21538187 126.463121
New York 141297 20201249 142.970120
Pennsylvania 119280 13002700 109.009893
```

要提升您在 Pandas 数据操作中的熟练程度,我建议您花一些时间使用一个简单的`DataFrame`,并探索这些不同索引方法允许的索引、切片、遮罩和花式索引类型。

## 额外的索引约定

还有一些额外的索引约定,可能与前面的讨论看似不符,但在实践中仍然很有用。首先,*索引*指的是列,而*切片*指的是行:

```py
In [31]: data['Florida':'New York']
Out[31]: area pop density
Florida 170312 21538187 126.463121
New York 141297 20201249 142.970120
```

这种切片也可以通过数字而不是索引来引用行。

```py
In [32]: data[1:3]
Out[32]: area pop density
Texas 695662 29145505 41.896072
Florida 170312 21538187 126.463121
```

类似地,直接的遮罩操作是按行而不是按列进行解释。

```py
In [33]: data[data.density > 120]
Out[33]: area pop density
Florida 170312 21538187 126.463121
New York 141297 20201249 142.970120
```

这两种约定在语法上与 NumPy 数组上的约定类似,虽然它们可能不完全符合 Pandas 的约定模式,但由于它们的实际实用性,它们被包含了进来。
232 changes: 232 additions & 0 deletions translations_v2/cn/pydshb2e_18.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# 第十五章:在 Pandas 中操作数据

NumPy 的一个优点是它允许我们执行快速的逐元素操作,包括基本算术(加法、减法、乘法等)和更复杂的操作(三角函数、指数和对数函数等)。Pandas 从 NumPy 继承了许多这些功能,并且在第六章介绍的 ufuncs 对此至关重要。

然而,Pandas 还包括一些有用的技巧:对于像否定和三角函数这样的一元操作,这些 ufuncs 将在输出中 *保留索引和列标签*;对于像加法和乘法这样的二元操作,当将对象传递给 ufunc 时,Pandas 将自动 *对齐索引*。这意味着保持数据的上下文和组合来自不同来源的数据(这两个任务对于原始 NumPy 数组来说可能是错误的)在 Pandas 中基本上变得十分简单。我们还将看到在一维 `Series` 结构和二维 `DataFrame` 结构之间存在着明确定义的操作。

# Ufuncs:索引保留

因为 Pandas 是设计用于与 NumPy 协作的,任何 NumPy 的 ufunc 都可以在 Pandas 的 `Series``DataFrame` 对象上使用。让我们先定义一个简单的 `Series``DataFrame` 来演示这一点:

```py
In [1]: import pandas as pd
import numpy as np
```

```py
In [2]: rng = np.random.default_rng(42)
ser = pd.Series(rng.integers(0, 10, 4))
ser
Out[2]: 0 0
1 7
2 6
3 4
dtype: int64
```

```py
In [3]: df = pd.DataFrame(rng.integers(0, 10, (3, 4)),
columns=['A', 'B', 'C', 'D'])
df
Out[3]: A B C D
0 4 8 0 6
1 2 0 5 9
2 7 7 7 7
```

如果我们在这些对象中的任一对象上应用 NumPy 的 ufunc,结果将是另一个 Pandas 对象 *并保留索引*

```py
In [4]: np.exp(ser)
Out[4]: 0 1.000000
1 1096.633158
2 403.428793
3 54.598150
dtype: float64
```

对于更复杂的操作序列,情况也是如此:

```py
In [5]: np.sin(df * np.pi / 4)
Out[5]: A B C D
0 1.224647e-16 -2.449294e-16 0.000000 -1.000000
1 1.000000e+00 0.000000e+00 -0.707107 0.707107
2 -7.071068e-01 -7.071068e-01 -0.707107 -0.707107
```

任何在第六章中讨论过的 ufunc 都可以以类似的方式使用。

# Ufuncs:索引对齐

对于两个 `Series``DataFrame` 对象的二元操作,Pandas 将在执行操作的过程中对齐索引。这在处理不完整数据时非常方便,我们将在接下来的一些示例中看到。

## Series 中的索引对齐

例如,假设我们正在结合两个不同的数据源,并希望仅找到按 *面积* 排名前三的美国州和按 *人口* 排名前三的美国州:

```py
In [6]: area = pd.Series({'Alaska': 1723337, 'Texas': 695662,
'California': 423967}, name='area')
population = pd.Series({'California': 39538223, 'Texas': 29145505,
'Florida': 21538187}, name='population')
```

现在让我们来看看在进行人口密度计算时会发生什么:

```py
In [7]: population / area
Out[7]: Alaska NaN
California 93.257784
Florida NaN
Texas 41.896072
dtype: float64
```

结果数组包含两个输入数组的索引的 *并集*,这可以直接从这些索引中确定:

```py
In [8]: area.index.union(population.index)
Out[8]: Index(['Alaska', 'California', 'Florida', 'Texas'], dtype='object')
```

任何其中一个没有条目的项目都标记有 `NaN`,即“不是数字”,这是 Pandas 标记缺失数据的方式(详见第十六章对缺失数据的进一步讨论)。对于 Python 内置的任何算术表达式,都会实现这种索引匹配;任何缺失值都将被 `NaN` 标记:

```py
In [9]: A = pd.Series([2, 4, 6], index=[0, 1, 2])
B = pd.Series([1, 3, 5], index=[1, 2, 3])
A + B
Out[9]: 0 NaN
1 5.0
2 9.0
3 NaN
dtype: float64
```

如果不希望使用`NaN`值,可以使用适当的对象方法修改填充值,而不是使用操作符。例如,调用`A.add(B)`等效于调用`A + B`,但允许可选地显式指定`A``B`中可能缺失元素的填充值:

```py
In [10]: A.add(B, fill_value=0)
Out[10]: 0 2.0
1 5.0
2 9.0
3 5.0
dtype: float64
```

## 数据帧中的索引对齐

当对`DataFrame`对象进行操作时,*同时*在列和索引上进行类似的对齐:

```py
In [11]: A = pd.DataFrame(rng.integers(0, 20, (2, 2)),
columns=['a', 'b'])
A
Out[11]: a b
0 10 2
1 16 9
```

```py
In [12]: B = pd.DataFrame(rng.integers(0, 10, (3, 3)),
columns=['b', 'a', 'c'])
B
Out[12]: b a c
0 5 3 1
1 9 7 6
2 4 8 5
```

```py
In [13]: A + B
Out[12]: a b c
0 13.0 7.0 NaN
1 23.0 18.0 NaN
2 NaN NaN NaN
```

请注意,无论这两个对象中的顺序如何,索引都正确对齐,并且结果中的索引是排序的。与`Series`一样,我们可以使用关联对象的算术方法,并传递任何希望用于替代缺失条目的`fill_value`。这里我们将用`A`中所有值的平均值填充:

```py
In [14]: A.add(B, fill_value=A.values.mean())
Out[14]: a b c
0 13.00 7.00 10.25
1 23.00 18.00 15.25
2 17.25 13.25 14.25
```

表 15-1 列出了 Python 运算符及其相应的 Pandas 对象方法。

表 15-1。Python 运算符与 Pandas 方法的映射

| Python 运算符 | Pandas 方法 |
| --- | --- |
| `+` | `add` |
| `-` | `sub`, `subtract` |
| `*` | `mul`, `multiply` |
| `/` | `truediv`, `div`, `divide` |
| `//` | `floordiv` |
| `%` | `mod` |
| `**` | `pow` |

# Ufuncs:DataFrame 与 Series 之间的操作

当对`DataFrame``Series`进行操作时,索引和列的对齐方式类似地保持,并且结果类似于二维数组和一维 NumPy 数组之间的操作。考虑一种常见的操作,即查找二维数组与其一行之间的差异:

```py
In [15]: A = rng.integers(10, size=(3, 4))
A
Out[15]: array([[4, 4, 2, 0],
[5, 8, 0, 8],
[8, 2, 6, 1]])
```

```py
In [16]: A - A[0]
Out[16]: array([[ 0, 0, 0, 0],
[ 1, 4, -2, 8],
[ 4, -2, 4, 1]])
```

根据 NumPy 的广播规则(参见第八章),二维数组与其一行之间的减法操作是逐行应用的。

在 Pandas 中,默认情况下也是逐行操作的约定:

```py
In [17]: df = pd.DataFrame(A, columns=['Q', 'R', 'S', 'T'])
df - df.iloc[0]
Out[17]: Q R S T
0 0 0 0 0
1 1 4 -2 8
2 4 -2 4 1
```

如果您希望以列为单位进行操作,可以使用前面提到的对象方法,并指定`axis`关键字:

```py
In [18]: df.subtract(df['R'], axis=0)
Out[18]: Q R S T
0 0 0 -2 -4
1 -3 0 -8 0
2 6 0 4 -1
```

注意,像前面讨论过的操作一样,这些`DataFrame`/`Series`操作会自动对齐两个元素之间的索引:

```py
In [19]: halfrow = df.iloc[0, ::2]
halfrow
Out[19]: Q 4
S 2
Name: 0, dtype: int64
```

```py
In [20]: df - halfrow
Out[20]: Q R S T
0 0.0 NaN 0.0 NaN
1 1.0 NaN -2.0 NaN
2 4.0 NaN 4.0 NaN
```

这种索引和列的保留与对齐意味着在 Pandas 中对数据进行的操作将始终保持数据上下文,这可以防止在原始 NumPy 数组中处理异构和/或不对齐数据时可能出现的常见错误。
378 changes: 378 additions & 0 deletions translations_v2/cn/pydshb2e_19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
# 第十六章:处理缺失数据

许多教程中找到的数据与现实世界中的数据之间的区别在于,现实世界的数据很少是干净且同质的。特别是,许多有趣的数据集会有一些数据缺失。更复杂的是,不同的数据源可能以不同的方式指示缺失数据。

在本章中,我们将讨论一些有关缺失数据的一般考虑事项,看看 Pandas 如何选择表示它,并探索一些处理 Python 中缺失数据的内置 Pandas 工具。在本书中,我将通常将缺失数据总称为*null*`*NaN*`*NA*值。

# 缺失数据约定中的权衡

已开发出许多方法来跟踪表格或`DataFrame`中缺失数据的存在。通常,它们围绕两种策略之一展开:使用*掩码*全局指示缺失值,或选择指示缺失条目的*哨兵值*

在掩码方法中,掩码可以是一个完全独立的布尔数组,也可以涉及在数据表示中占用一个比特来局部指示值的空状态。

在哨兵方法中,哨兵值可以是一些特定于数据的约定,比如用 -9999 表示缺失的整数值或一些罕见的比特模式,或者可以是一个更全局的约定,比如用`NaN`(不是数字)表示缺失的浮点值,这是 IEEE 浮点规范的一部分。

这两种方法都不是没有权衡的。使用单独的掩码数组需要分配额外的布尔数组,这在存储和计算上都增加了开销。哨兵值会减少可以表示的有效值范围,并且可能需要额外的(通常是非优化的)CPU 和 GPU 算术逻辑,因为常见的特殊值如`NaN`并不适用于所有数据类型。

就像大多数情况下没有普遍适用的最佳选择一样,不同的语言和系统使用不同的约定。例如,R 语言使用每种数据类型内的保留比特模式作为指示缺失数据的哨兵值,而 SciDB 系统使用附加到每个单元的额外字节来指示 NA 状态。

# Pandas 中的缺失数据

Pandas 处理缺失值的方式受其对 NumPy 包的依赖限制,后者对非浮点数据类型没有内置的 NA 值概念。

也许 Pandas 本可以效仿 R 在指定每个单独数据类型的位模式以指示空值方面的领先地位,但是这种方法事实证明相当笨拙。虽然 R 只有 4 种主要数据类型,但是 NumPy 支持的数据类型远远超过这个数字:例如,虽然 R 只有一个整数类型,但是考虑到可用的位宽、符号性和编码的字节顺序,NumPy 支持 14 种基本整数类型。在所有可用的 NumPy 类型中保留特定的位模式将导致在各种类型的操作中特殊处理大量操作,很可能甚至需要新的 NumPy 软件包的分支。此外,对于较小的数据类型(如 8 位整数),牺牲一位用作掩码将显著减少其可以表示的值的范围。

由于这些限制和权衡,Pandas 有两种存储和操作空值的“模式”:

+ 默认模式是使用基于哨兵值的缺失数据方案,哨兵值为`NaN``None`,具体取决于数据类型。

+ 或者,您可以选择使用 Pandas 提供的可空数据类型(dtypes)(稍后在本章讨论),这将导致创建一个伴随的掩码数组来跟踪缺失的条目。然后,这些缺失的条目将被呈现给用户作为特殊的`pd.NA`值。

无论哪种情况,Pandas API 提供的数据操作和操作将以可预测的方式处理和传播这些缺失的条目。但是为了对为什么会做出这些选择有一些直觉,让我们快速探讨一下`None``NaN``NA`中固有的权衡。像往常一样,我们将从导入 NumPy 和 Pandas 开始:

```py
In [1]: import numpy as np
import pandas as pd
```

## `None`作为哨兵值

对于某些数据类型,Pandas 使用`None`作为哨兵值。`None`是一个 Python 对象,这意味着包含`None`的任何数组必须具有`dtype=object`,即它必须是 Python 对象的序列。

例如,观察将`None`传递给 NumPy 数组会发生什么:

```py
In [2]: vals1 = np.array([1, None, 2, 3])
vals1
Out[2]: array([1, None, 2, 3], dtype=object)
```

这个`dtype=object`意味着 NumPy 可以推断出数组内容的最佳公共类型表示是 Python 对象。以这种方式使用`None`的缺点是数据的操作将在 Python 级别完成,其开销比通常对具有本地类型的数组所见的快速操作要大得多:

```py
In [3]: %timeit np.arange(1E6, dtype=int).sum()
Out[3]: 2.73 ms ± 288 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
```

```py
In [4]: %timeit np.arange(1E6, dtype=object).sum()
Out[4]: 92.1 ms ± 3.42 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```

此外,因为 Python 不支持与`None`的算术运算,像`sum``min`这样的聚合通常会导致错误:

```py
In [5]: vals1.sum()
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
```

因此,Pandas 在其数值数组中不使用`None`作为哨兵值。

## `NaN`:缺失的数值数据

另一个缺失数据哨兵,`NaN`,是不同的;它是一种特殊的浮点值,在所有使用标准 IEEE 浮点表示的系统中都被识别:

```py
In [6]: vals2 = np.array([1, np.nan, 3, 4])
vals2
Out[6]: array([ 1., nan, 3., 4.])
```

请注意,NumPy 为此数组选择了本地的浮点类型:这意味着与之前的对象数组不同,此数组支持快速操作并推入编译代码中。请记住,`NaN`有点像数据病毒——它会感染到任何它接触到的其他对象。

不管进行何种操作,带有`NaN`的算术运算的结果都将是另一个`NaN`

```py
In [7]: 1 + np.nan
Out[7]: nan
```

```py
In [8]: 0 * np.nan
Out[8]: nan
```

这意味着对值的聚合是定义良好的(即,它们不会导致错误),但并不总是有用的:

```py
In [9]: vals2.sum(), vals2.min(), vals2.max()
Out[9]: (nan, nan, nan)
```

也就是说,NumPy 确实提供了对`NaN`敏感的聚合函数版本,将忽略这些缺失值:

```py
In [10]: np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
Out[10]: (8.0, 1.0, 4.0)
```

`NaN`的主要缺点是它是特定的浮点值;对于整数、字符串或其他类型,都没有等价的`NaN`值。

## Pandas 中的 NaN 和 None

`NaN``None`都有它们的用途,而且 Pandas 几乎可以在它们之间自由转换,视情况而定:

```py
In [11]: pd.Series([1, np.nan, 2, None])
Out[11]: 0 1.0
1 NaN
2 2.0
3 NaN
dtype: float64
```

对于没有可用哨兵值的类型,当存在 NA 值时,Pandas 会自动进行类型转换。例如,如果我们将整数数组中的一个值设置为`np.nan`,它将自动提升为浮点类型以容纳 NA

```py
In [12]: x = pd.Series(range(2), dtype=int)
x
Out[12]: 0 0
1 1
dtype: int64
```

```py
In [13]: x[0] = None
x
Out[13]: 0 NaN
1 1.0
dtype: float64
```

请注意,除了将整数数组转换为浮点数外,Pandas 还会自动将`None`转换为`NaN`值。

虽然这种类型的魔法操作可能与像 R 这样的特定领域语言中的 NA 值的更统一方法相比显得有些投机,但是 Pandas 的哨兵/转换方法在实践中运作得非常好,并且据我经验,很少引起问题。

表 16-1 列出了引入 NA 值时 Pandas 中的提升转换规则。

表 16-1\. Pandas 按类型处理 NA 值

| 类型 | 存储 NA 时的转换 | NA 哨兵值 |
| --- | --- | --- |
| `floating` | 无变化 | `np.nan` |
| `object` | 无变化 | `None``np.nan` |
| `integer` | 转换为`float64` | `np.nan` |
| `boolean` | 转换为`object` | `None``np.nan` |

请记住,在 Pandas 中,字符串数据始终以`object`类型存储。

# Pandas 可空数据类型

在早期版本的 Pandas 中,`NaN``None`作为哨兵值是唯一可用的缺失数据表示。这引入的主要困难是隐式类型转换:例如,无法表示真正的整数数组带有缺失数据。

为了解决这个问题,Pandas 后来添加了*可空数据类型*,它们通过名称的大写区分于常规数据类型(例如,`pd.Int32``np.int32`)。为了向后兼容,只有在明确请求时才会使用这些可空数据类型。

例如,这是一个带有缺失数据的整数`Series`,由包含所有三种可用缺失数据标记的列表创建:

```py
In [14]: pd.Series([1, np.nan, 2, None, pd.NA], dtype='Int32')
Out[14]: 0 1
1 <NA>
2 2
3 <NA>
4 <NA>
dtype: Int32
```

这种表示可以在本章剩余的所有操作中与其他表示方法交替使用。

# 操作空值

正如我们所见,Pandas 将 `None``NaN``NA` 视为基本可以互换,用于指示缺失或空值。为了促进这一约定,Pandas 提供了几种方法来检测、删除和替换 Pandas 数据结构中的空值。它们包括:

`isnull`

生成一个指示缺失值的布尔掩码

`notnull`

`isnull` 的反操作

`dropna`

返回数据的过滤版本

`fillna`

返回填充或插补了缺失值的数据副本

我们将以对这些程序的简要探索和演示来结束本章。

## 检测空值

Pandas 数据结构有两个有用的方法来检测空数据:`isnull``notnull`。任何一个都将返回数据的布尔掩码。例如:

```py
In [15]: data = pd.Series([1, np.nan, 'hello', None])
```

```py
In [16]: data.isnull()
Out[16]: 0 False
1 True
2 False
3 True
dtype: bool
```

正如在 第十四章 中提到的那样,布尔掩码可以直接用作 `Series``DataFrame` 的索引:

```py
In [17]: data[data.notnull()]
Out[17]: 0 1
2 hello
dtype: object
```

对于 `DataFrame` 对象,`isnull()``notnull()` 方法生成类似的布尔结果。

## 删除空值

除了这些掩码方法之外,还有方便的方法 `dropna`(用于删除 NA 值)和 `fillna`(用于填充 NA 值)。对于 `Series`,结果是直接的:

```py
In [18]: data.dropna()
Out[18]: 0 1
2 hello
dtype: object
```

对于 `DataFrame`,有更多的选择。考虑以下 `DataFrame`

```py
In [19]: df = pd.DataFrame([[1, np.nan, 2],
[2, 3, 5],
[np.nan, 4, 6]])
df
Out[19]: 0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
```

我们不能从 `DataFrame` 中删除单个值;我们只能删除整行或整列。根据应用程序的不同,您可能需要其中一个,因此 `dropna` 包含了一些 `DataFrame` 的选项。

默认情况下,`dropna` 将删除*任何*存在空值的行:

```py
In [20]: df.dropna()
Out[20]: 0 1 2
1 2.0 3.0 5
```

或者,您可以沿不同的轴删除 NA 值。使用 `axis=1``axis='columns'` 将删除包含空值的所有列:

```py
In [21]: df.dropna(axis='columns')
Out[21]: 2
0 2
1 5
2 6
```

但是这样会丢掉一些好数据;您可能更感兴趣的是删除具有*所有* NA 值或大多数 NA 值的行或列。这可以通过 `how``thresh` 参数进行指定,这些参数允许对允许通过的空值数量进行精细控制。

默认值为 `how='any'`,这样任何包含空值的行或列都将被删除。您还可以指定 `how='all'`,这样只会删除包含*所有*空值的行/列:

```py
In [22]: df[3] = np.nan
df
Out[22]: 0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
```

```py
In [23]: df.dropna(axis='columns', how='all')
Out[23]: 0 1 2
0 1.0 NaN 2
1 2.0 3.0 5
2 NaN 4.0 6
```

对于更精细的控制,`thresh` 参数允许您指定保留行/列的最小非空值数:

```py
In [24]: df.dropna(axis='rows', thresh=3)
Out[24]: 0 1 2 3
1 2.0 3.0 5 NaN
```

在这里,第一行和最后一行已被删除,因为它们各自只包含两个非空值。

## 填充空值

有时候,您不想丢弃 NA 值,而是希望用有效值替换它们。这个值可以是一个单独的数字,比如零,或者可能是一些从好的值中插补或插值出来的值。您可以使用 `isnull` 方法作为掩码来原地进行这个操作,但是因为这是一个常见的操作,Pandas 提供了 `fillna` 方法,它返回一个替换了空值的数组的副本。

考虑以下 `Series`

```py
In [25]: data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'),
dtype='Int32')
data
Out[25]: a 1
b <NA>
c 2
d <NA>
e 3
dtype: Int32
```

我们可以用一个单一的值(如零)填充 NA 条目:

```py
In [26]: data.fillna(0)
Out[26]: a 1
b 0
c 2
d 0
e 3
dtype: Int32
```

我们可以指定向前填充以向前传播上一个值:

```py
In [27]: # forward fill
data.fillna(method='ffill')
Out[27]: a 1
b 1
c 2
d 2
e 3
dtype: Int32
```

或者我们可以指定向后填充以向后传播下一个值:

```py
In [28]: # back fill
data.fillna(method='bfill')
Out[28]: a 1
b 2
c 2
d 3
e 3
dtype: Int32
```

对于`DataFrame`,选项类似,但我们还可以指定填充应该沿着的`axis`

```py
In [29]: df
Out[29]: 0 1 2 3
0 1.0 NaN 2 NaN
1 2.0 3.0 5 NaN
2 NaN 4.0 6 NaN
```

```py
In [30]: df.fillna(method='ffill', axis=1)
Out[30]: 0 1 2 3
0 1.0 1.0 2.0 2.0
1 2.0 3.0 5.0 5.0
2 NaN 4.0 6.0 6.0
```

如果在向前填充时前一个值不可用,NA 值将保留。
591 changes: 591 additions & 0 deletions translations_v2/cn/pydshb2e_20.md

Large diffs are not rendered by default.

251 changes: 251 additions & 0 deletions translations_v2/cn/pydshb2e_21.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# 第十八章:组合数据集:concat 和 append

一些最有趣的数据研究来自于结合不同的数据源。这些操作可以涉及从两个不同数据集的非常简单的连接到更复杂的数据库风格的联接和合并,正确处理数据集之间的任何重叠。`Series``DataFrame`是专为这类操作而构建的,Pandas 包含使这种数据处理快速和简单的函数和方法。

在这里,我们将使用`pd.concat`函数查看`Series``DataFrame`的简单连接;稍后我们将深入探讨 Pandas 中实现的更复杂的内存合并和连接。

我们从标准导入开始:

```py
In [1]: import pandas as pd
import numpy as np
```

为方便起见,我们将定义这个函数,它创建一个特定形式的`DataFrame`,在接下来的示例中将非常有用:

```py
In [2]: def make_df(cols, ind):
"""Quickly make a DataFrame"""
data = {c: [str(c) + str(i) for i in ind]
for c in cols}
return pd.DataFrame(data, ind)

# example DataFrame
make_df('ABC', range(3))
Out[2]: A B C
0 A0 B0 C0
1 A1 B1 C1
2 A2 B2 C2
```

另外,我们将创建一个快速的类,允许我们将多个`DataFrame`并排显示。该代码利用了特殊的`_repr_html_`方法,IPython/Jupyter 用它来实现其丰富的对象显示:

```py
In [3]: class display(object):
"""Display HTML representation of multiple objects"""
template = """<div style="float: left; padding: 10px;">
<p style='font-family:"Courier New", Courier, monospace'>{0}{1}
"""
def __init__(self, *args):
self.args = args

def _repr_html_(self):
return '\n'.join(self.template.format(a, eval(a)._repr_html_())
for a in self.args)

def __repr__(self):
return '\n\n'.join(a + '\n' + repr(eval(a))
for a in self.args)
```

随着我们在以下部分继续讨论,使用这个将会更加清晰。

# 回顾:NumPy 数组的连接

`Series``DataFrame`对象的连接行为与 NumPy 数组的连接类似,可以通过`np.concatenate`函数完成,如第五章中所讨论的那样。记住,您可以使用它将两个或多个数组的内容合并为单个数组:

```py
In [4]: x = [1, 2, 3]
y = [4, 5, 6]
z = [7, 8, 9]
np.concatenate([x, y, z])
Out[4]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
```

第一个参数是要连接的数组的列表或元组。此外,在多维数组的情况下,它接受一个`axis`关键字,允许您指定沿其进行连接的轴:

```py
In [5]: x = [[1, 2],
[3, 4]]
np.concatenate([x, x], axis=1)
Out[5]: array([[1, 2, 1, 2],
[3, 4, 3, 4]])
```

# 使用`pd.concat`进行简单连接

`pd.concat`函数提供了与`np.concatenate`类似的语法,但包含我们稍后将讨论的多个选项:

```py
# Signature in Pandas v1.3.5
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None,
levels=None, names=None, verify_integrity=False,
sort=False, copy=True)
```

`pd.concat`可用于简单连接`Series``DataFrame`对象,就像`np.concatenate`可用于数组的简单连接一样:

```py
In [6]: ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
pd.concat([ser1, ser2])
Out[6]: 1 A
2 B
3 C
4 D
5 E
6 F
dtype: object
```

它还可以用于连接更高维度的对象,如`DataFrame`

```py
In [7]: df1 = make_df('AB', [1, 2])
df2 = make_df('AB', [3, 4])
display('df1', 'df2', 'pd.concat([df1, df2])')
Out[7]: df1 df2 pd.concat([df1, df2])
A B A B A B
1 A1 B1 3 A3 B3 1 A1 B1
2 A2 B2 4 A4 B4 2 A2 B2
3 A3 B3
4 A4 B4
```

其默认行为是在`DataFrame`内按行连接(即`axis=0`)。与`np.concatenate`类似,`pd.concat`允许指定沿其进行连接的轴。考虑以下示例:

```py
In [8]: df3 = make_df('AB', [0, 1])
df4 = make_df('CD', [0, 1])
display('df3', 'df4', "pd.concat([df3, df4], axis='columns')")
Out[8]: df3 df4 pd.concat([df3, df4], axis='columns')
A B C D A B C D
0 A0 B0 0 C0 D0 0 A0 B0 C0 D0
1 A1 B1 1 C1 D1 1 A1 B1 C1 D1
```

我们也可以等效地指定`axis=1`;这里我们使用了更直观的`axis='columns'`

## 重复的索引

`np.concatenate``pd.concat`之间的一个重要区别是,Pandas 的连接*保留索引*,即使结果会有重复的索引!考虑以下简单示例:

```py
In [9]: x = make_df('AB', [0, 1])
y = make_df('AB', [2, 3])
y.index = x.index # make indices match
display('x', 'y', 'pd.concat([x, y])')
Out[9]: x y pd.concat([x, y])
A B A B A B
0 A0 B0 0 A2 B2 0 A0 B0
1 A1 B1 1 A3 B3 1 A1 B1
0 A2 B2
1 A3 B3
```

注意结果中的重复索引。虽然这在`DataFrame`中是有效的,但结果通常不理想。`pd.concat`提供了几种处理方法。

### 将重复的索引视为错误处理

如果你想简单地验证`pd.concat`的结果中的索引是否重叠,可以包含`verify_integrity`标志。将其设置为`True`,如果存在重复索引,连接将引发异常。以下是一个示例,为了清晰起见,我们将捕获并打印错误消息:

```py
In [10]: try:
pd.concat([x, y], verify_integrity=True)
except ValueError as e:
print("ValueError:", e)
ValueError: Indexes have overlapping values: Int64Index([0, 1], dtype='int64')
```

### 忽略索引

有时索引本身并不重要,你更希望它被简单地忽略。可以使用`ignore_index`标志指定此选项。将其设置为`True`,连接将为结果的`DataFrame`创建一个新的整数索引:

```py
In [11]: display('x', 'y', 'pd.concat([x, y], ignore_index=True)')
Out[11]: x y pd.concat([x, y], ignore_index=True)
A B A B A B
0 A0 B0 0 A2 B2 0 A0 B0
1 A1 B1 1 A3 B3 1 A1 B1
2 A2 B2
3 A3 B3
```

### 添加 MultiIndex 键

另一个选项是使用`keys`选项指定数据源的标签;结果将是一个具有层次索引的系列,其中包含数据:

```py
In [12]: display('x', 'y', "pd.concat([x, y], keys=['x', 'y'])")
Out[12]: x y pd.concat([x, y], keys=['x', 'y'])
A B A B A B
0 A0 B0 0 A2 B2 x 0 A0 B0
1 A1 B1 1 A3 B3 1 A1 B1
y 0 A2 B2
1 A3 B3
```

我们可以使用第十七章中讨论的工具将这个多重索引的 `DataFrame` 转换为我们感兴趣的表示形式。

## 使用连接进行连接

在我们刚刚查看的短示例中,我们主要是连接具有共享列名的 `DataFrame`。在实践中,来自不同来源的数据可能具有不同的列名集,`pd.concat` 在这种情况下提供了几个选项。考虑以下两个 `DataFrame` 的连接,它们具有一些(但不是全部!)共同的列:

```py
In [13]: df5 = make_df('ABC', [1, 2])
df6 = make_df('BCD', [3, 4])
display('df5', 'df6', 'pd.concat([df5, df6])')
Out[13]: df5 df6 pd.concat([df5, df6])
A B C B C D A B C D
1 A1 B1 C1 3 B3 C3 D3 1 A1 B1 C1 NaN
2 A2 B2 C2 4 B4 C4 D4 2 A2 B2 C2 NaN
3 NaN B3 C3 D3
4 NaN B4 C4 D4
```

默认行为是用 NA 值填充无可用数据的条目。要更改这一点,可以调整`concat`函数的`join`参数。默认情况下,连接是输入列的并集(`join='outer'`),但我们可以使用`join='inner'`将其更改为列的交集:

```py
In [14]: display('df5', 'df6',
"pd.concat([df5, df6], join='inner')")
Out[14]: df5 df6
A B C B C D
1 A1 B1 C1 3 B3 C3 D3
2 A2 B2 C2 4 B4 C4 D4

pd.concat([df5, df6], join='inner')
B C
1 B1 C1
2 B2 C2
3 B3 C3
4 B4 C4
```

另一个有用的模式是在连接之前使用`reindex`方法对要丢弃的列进行更精细的控制:

```py
In [15]: pd.concat([df5, df6.reindex(df5.columns, axis=1)])
Out[15]: A B C
1 A1 B1 C1
2 A2 B2 C2
3 NaN B3 C3
4 NaN B4 C4
```

## `append` 方法

因为直接数组连接是如此常见,`Series``DataFrame` 对象具有一个`append`方法,可以用更少的按键完成相同的操作。例如,可以使用 `df1.append(df2)` 替代 `pd.concat([df1, df2])`

```py
In [16]: display('df1', 'df2', 'df1.append(df2)')
Out[16]: df1 df2 df1.append(df2)
A B A B A B
1 A1 B1 3 A3 B3 1 A1 B1
2 A2 B2 4 A4 B4 2 A2 B2
3 A3 B3
4 A4 B4
```

请注意,与 Python 列表的 `append``extend` 方法不同,Pandas 中的 `append` 方法不会修改原始对象;相反,它会创建一个包含组合数据的新对象。它也不是一种非常有效的方法,因为它涉及到新索引的创建 *以及* 数据缓冲区。因此,如果你计划进行多个 `append` 操作,通常最好建立一个 `DataFrame` 对象的列表,并一次性将它们全部传递给 `concat` 函数。

在下一章中,我们将介绍一种更强大的方法来组合来自多个来源的数据:`pd.merge` 中实现的数据库风格的合并/连接。有关 `concat``append` 和相关功能的更多信息,请参阅[Pandas 文档中的“Merge, Join, Concatenate and Compare”](https://oreil.ly/cY16c)
583 changes: 583 additions & 0 deletions translations_v2/cn/pydshb2e_22.md

Large diffs are not rendered by default.

509 changes: 509 additions & 0 deletions translations_v2/cn/pydshb2e_23.md

Large diffs are not rendered by default.

321 changes: 321 additions & 0 deletions translations_v2/cn/pydshb2e_24.md

Large diffs are not rendered by default.

405 changes: 405 additions & 0 deletions translations_v2/cn/pydshb2e_25.md

Large diffs are not rendered by default.

625 changes: 625 additions & 0 deletions translations_v2/cn/pydshb2e_26.md

Large diffs are not rendered by default.

316 changes: 316 additions & 0 deletions translations_v2/cn/pydshb2e_27.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
# 第二十四章:高性能 Pandas:eval 和 query

正如我们在之前的章节中已经看到的,PyData 栈的强大建立在 NumPy 和 Pandas 将基本操作推送到低级编译代码中的能力上,通过直观的高级语法:例如 NumPy 中的向量化/广播操作,以及 Pandas 中的分组类型操作。虽然这些抽象对许多常见用例是高效和有效的,但它们经常依赖于临时中间对象的创建,这可能会导致计算时间和内存使用的不必要开销。

为了解决这个问题,Pandas 包括一些方法,允许您直接访问 C 速度操作,而无需昂贵地分配中间数组:`eval``query`,这些方法依赖于 [NumExpr 包](https://oreil.ly/acvj5)

# 激励查询和 eval:复合表达式

我们之前已经看到,NumPy 和 Pandas 支持快速的向量化操作;例如,当添加两个数组的元素时:

```py
In [1]: import numpy as np
rng = np.random.default_rng(42)
x = rng.random(1000000)
y = rng.random(1000000)
%timeit x + y
Out[1]: 2.21 ms ± 142 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
```

如在 第六章 中讨论的,这比通过 Python 循环或理解式添加要快得多:

```py
In [2]: %timeit np.fromiter((xi + yi for xi, yi in zip(x, y)),
dtype=x.dtype, count=len(x))
Out[2]: 263 ms ± 43.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

但是当计算复合表达式时,这种抽象可能变得不那么高效。例如,考虑以下表达式:

```py
In [3]: mask = (x > 0.5) & (y < 0.5)
```

因为 NumPy 评估每个子表达式,这大致等同于以下内容:

```py
In [4]: tmp1 = (x > 0.5)
tmp2 = (y < 0.5)
mask = tmp1 & tmp2
```

换句话说,*每个中间步骤都显式地分配在内存中*。如果 `x``y` 数组非常大,这可能导致显著的内存和计算开销。NumExpr 库使您能够逐个元素地计算这种复合表达式,而无需分配完整的中间数组。有关更多详细信息,请参阅 [NumExpr 文档](https://oreil.ly/acvj5),但目前足以说,该库接受一个 *字符串*,该字符串给出您想计算的 NumPy 风格表达式:

```py
In [5]: import numexpr
mask_numexpr = numexpr.evaluate('(x > 0.5) & (y < 0.5)')
np.all(mask == mask_numexpr)
Out[5]: True
```

这里的好处在于,NumExpr 以避免可能的临时数组方式评估表达式,因此对于长数组上的长序列计算比 NumPy 要高效得多。我们将在这里讨论的 Pandas `eval``query` 工具在概念上类似,并且本质上是 NumExpr 功能的 Pandas 特定包装。

# pandas.eval 用于高效操作

Pandas 中的 `eval` 函数使用字符串表达式来高效地计算 `DataFrame` 对象上的操作。例如,考虑以下数据:

```py
In [6]: import pandas as pd
nrows, ncols = 100000, 100
df1, df2, df3, df4 = (pd.DataFrame(rng.random((nrows, ncols)))
for i in range(4))
```

要使用典型的 Pandas 方法计算所有四个 `DataFrame` 的总和,我们只需写出总和:

```py
In [7]: %timeit df1 + df2 + df3 + df4
Out[7]: 73.2 ms ± 6.72 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```

可以通过构造字符串表达式来使用 `pd.eval` 计算相同的结果:

```py
In [8]: %timeit pd.eval('df1 + df2 + df3 + df4')
Out[8]: 34 ms ± 4.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```

`eval` 版本的这个表达式大约快 50%(并且使用的内存要少得多),同时给出相同的结果:

```py
In [9]: np.allclose(df1 + df2 + df3 + df4,
pd.eval('df1 + df2 + df3 + df4'))
Out[9]: True
```

`pd.eval`支持广泛的操作。为了展示这些操作,我们将使用以下整数数据:

```py
In [10]: df1, df2, df3, df4, df5 = (pd.DataFrame(rng.integers(0, 1000, (100, 3)))
for i in range(5))
```

下面是`pd.eval`支持的操作的总结:

算术运算符

`pd.eval`支持所有算术运算符。例如:

```py
In [11]: result1 = -df1 * df2 / (df3 + df4) - df5
result2 = pd.eval('-df1 * df2 / (df3 + df4) - df5')
np.allclose(result1, result2)
Out[11]: True
```

比较运算符

`pd.eval`支持所有比较运算符,包括链式表达式:

```py
In [12]: result1 = (df1 < df2) & (df2 <= df3) & (df3 != df4)
result2 = pd.eval('df1 < df2 <= df3 != df4')
np.allclose(result1, result2)
Out[12]: True
```

位运算符

`pd.eval`支持`&``|`位运算符:

```py
In [13]: result1 = (df1 < 0.5) & (df2 < 0.5) | (df3 < df4)
result2 = pd.eval('(df1 < 0.5) & (df2 < 0.5) | (df3 < df4)')
np.allclose(result1, result2)
Out[13]: True
```

此外,它还支持在布尔表达式中使用字面量`and``or`

```py
In [14]: result3 = pd.eval('(df1 < 0.5) and (df2 < 0.5) or (df3 < df4)')
np.allclose(result1, result3)
Out[14]: True
```

对象属性和索引

`pd.eval`支持通过`obj.attr`语法和`obj[index]`语法访问对象属性:

```py
In [15]: result1 = df2.T[0] + df3.iloc[1]
result2 = pd.eval('df2.T[0] + df3.iloc[1]')
np.allclose(result1, result2)
Out[15]: True
```

其他操作

其他操作,例如函数调用、条件语句、循环和其他更复杂的构造,目前**`pd.eval`中实现。如果你想执行这些更复杂的表达式类型,可以使用 NumExpr 库本身。

# DataFrame.eval 进行按列操作

就像 Pandas 有一个顶级的`pd.eval`函数一样,`DataFrame`对象也有一个`eval`方法,功能类似。`eval`方法的好处是可以按名称引用列。我们将用这个带标签的数组作为示例:

```py
In [16]: df = pd.DataFrame(rng.random((1000, 3)), columns=['A', 'B', 'C'])
df.head()
Out[16]: A B C
0 0.850888 0.966709 0.958690
1 0.820126 0.385686 0.061402
2 0.059729 0.831768 0.652259
3 0.244774 0.140322 0.041711
4 0.818205 0.753384 0.578851
```

使用前面一节中的`pd.eval`,我们可以像这样计算三个列的表达式:

```py
In [17]: result1 = (df['A'] + df['B']) / (df['C'] - 1)
result2 = pd.eval("(df.A + df.B) / (df.C - 1)")
np.allclose(result1, result2)
Out[17]: True
```

`DataFrame.eval`方法允许更简洁地评估列的表达式:

```py
In [18]: result3 = df.eval('(A + B) / (C - 1)')
np.allclose(result1, result3)
Out[18]: True
```

请注意,在这里我们将*列名视为评估表达式中的变量*,结果正是我们希望的。

## 在 DataFrame.eval 中的赋值

除了刚才讨论的选项之外,`DataFrame.eval`还允许对任何列进行赋值。让我们使用之前的`DataFrame`,它有列`'A'``'B'``'C'`

```py
In [19]: df.head()
Out[19]: A B C
0 0.850888 0.966709 0.958690
1 0.820126 0.385686 0.061402
2 0.059729 0.831768 0.652259
3 0.244774 0.140322 0.041711
4 0.818205 0.753384 0.578851
```

我们可以使用`df.eval`创建一个新的列`'D'`,并将其赋值为从其他列计算得到的值:

```py
In [20]: df.eval('D = (A + B) / C', inplace=True)
df.head()
Out[20]: A B C D
0 0.850888 0.966709 0.958690 1.895916
1 0.820126 0.385686 0.061402 19.638139
2 0.059729 0.831768 0.652259 1.366782
3 0.244774 0.140322 0.041711 9.232370
4 0.818205 0.753384 0.578851 2.715013
```

以同样的方式,任何现有的列都可以被修改:

```py
In [21]: df.eval('D = (A - B) / C', inplace=True)
df.head()
Out[21]: A B C D
0 0.850888 0.966709 0.958690 -0.120812
1 0.820126 0.385686 0.061402 7.075399
2 0.059729 0.831768 0.652259 -1.183638
3 0.244774 0.140322 0.041711 2.504142
4 0.818205 0.753384 0.578851 0.111982
```

## DataFrame.eval 中的本地变量

`DataFrame.eval`方法支持一种额外的语法,使其能够与本地 Python 变量一起使用。考虑以下内容:

```py
In [22]: column_mean = df.mean(1)
result1 = df['A'] + column_mean
result2 = df.eval('A + @column_mean')
np.allclose(result1, result2)
Out[22]: True
```

这里的`@`字符标记的是*变量名*而不是*列名*,并且让你能够高效地评估涉及两个“命名空间”的表达式:列的命名空间和 Python 对象的命名空间。请注意,这个`@`字符只支持`DataFrame.eval`*方法*,而不支持`pandas.eval`*函数*,因为`pandas.eval`函数只能访问一个(Python)命名空间。

# `DataFrame.query`方法

`DataFrame`还有一个基于评估字符串的方法,叫做`query`。考虑以下内容:

```py
In [23]: result1 = df[(df.A < 0.5) & (df.B < 0.5)]
result2 = pd.eval('df[(df.A < 0.5) & (df.B < 0.5)]')
np.allclose(result1, result2)
Out[23]: True
```

正如我们在讨论`DataFrame.eval`时使用的示例一样,这是一个涉及`DataFrame`列的表达式。然而,它不能使用`DataFrame.eval`语法表示!相反,对于这种类型的筛选操作,你可以使用`query`方法:

```py
In [24]: result2 = df.query('A < 0.5 and B < 0.5')
np.allclose(result1, result2)
Out[24]: True
```

与掩码表达式相比,这不仅是更有效的计算,而且更易于阅读和理解。请注意,`query`方法还接受`@`标志来标记本地变量:

```py
In [25]: Cmean = df['C'].mean()
result1 = df[(df.A < Cmean) & (df.B < Cmean)]
result2 = df.query('A < @Cmean and B < @Cmean')
np.allclose(result1, result2)
Out[25]: True
```

# 性能:何时使用这些函数

在考虑是否使用`eval``query`时,有两个考虑因素:*计算时间**内存使用*。内存使用是最可预测的方面。正如前面提到的,涉及 NumPy 数组或 Pandas `DataFrame`的每个复合表达式都会导致临时数组的隐式创建。例如,这个:

```py
In [26]: x = df[(df.A < 0.5) & (df.B < 0.5)]
```

大致相当于这个:

```py
In [27]: tmp1 = df.A < 0.5
tmp2 = df.B < 0.5
tmp3 = tmp1 & tmp2
x = df[tmp3]
```

如果临时`DataFrame`的大小与您可用的系统内存(通常为几个千兆字节)相比显著,则使用`eval``query`表达式是个好主意。您可以使用以下命令检查数组的大约大小(以字节为单位):

```py
In [28]: df.values.nbytes
Out[28]: 32000
```

就性能而言,即使您没有使用完系统内存,`eval`可能会更快。问题在于您的临时对象与系统的 L1 或 L2 CPU 缓存大小(通常为几兆字节)相比如何;如果它们要大得多,那么`eval`可以避免在不同内存缓存之间移动值时可能出现的某些潜在缓慢。实际上,我发现传统方法与`eval`/`query`方法之间的计算时间差异通常不显著——如果有什么的话,对于较小的数组来说,传统方法更快!`eval`/`query`的好处主要在于节省内存,以及它们有时提供的更清晰的语法。

我们在这里已经涵盖了关于`eval``query`的大部分细节;有关更多信息,请参阅 Pandas 文档。特别是,可以为运行这些查询指定不同的解析器和引擎;有关详细信息,请参阅文档中的[“提升性能”部分](https://oreil.ly/DHNy8)

# 更多资源

在本书的这一部分中,我们已经涵盖了有效使用 Pandas 进行数据分析的许多基础知识。但我们的讨论还有很多内容未涉及。要了解更多关于 Pandas 的信息,我推荐以下资源:

[Pandas 在线文档](http://pandas.pydata.org)

这是完整文档的首选来源。虽然文档中的示例通常基于小型生成的数据集,但选项的描述是全面的,并且通常非常有助于理解各种函数的使用。

[*Python for Data Analysis*](https://oreil.ly/0hdsf)

由 Pandas 的原始创建者 Wes McKinney 撰写,这本书包含了比我们在本章中有空间讨论的 Pandas 包更多的细节。特别是,McKinney 深入探讨了用于时间序列的工具,这些工具是他作为金融顾问的核心内容。这本书还包含许多将 Pandas 应用于从实际数据集中获得洞察的有趣例子。

[*Effective Pandas*](https://oreil.ly/cn1ls)

Pandas 开发者 Tom Augspurger 的这本简短电子书,简洁地概述了如何有效和惯用地使用 Pandas 库的全部功能。

[PyVideo 上的 Pandas](https://oreil.ly/mh4wI)

从 PyCon 到 SciPy 再到 PyData,许多会议都有 Pandas 开发者和高级用户提供的教程。特别是 PyCon 的教程通常由经过严格筛选的优秀演讲者提供。

结合这些资源,再加上这些章节中的详细介绍,我希望你能够准备好使用 Pandas 解决任何遇到的数据分析问题!
7 changes: 7 additions & 0 deletions translations_v2/cn/pydshb2e_28.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 第四部分:Matplotlib 可视化

现在我们将深入研究 Python 中用于可视化的 Matplotlib 包。Matplotlib 是一个建立在 NumPy 数组上的跨平台数据可视化库,旨在与更广泛的 SciPy 栈配合使用。它由 John Hunter 在 2002 年构思,最初作为 IPython 的补丁,用于通过 IPython 命令行从`gnuplot`实现交互式 MATLAB 风格的绘图。当时,IPython 的创始人 Fernando Perez 正在忙于完成他的博士论文,没有时间几个月内审查该补丁。John 将此视为自己行动的信号,于是 Matplotlib 包诞生了,版本 0.1 于 2003 年发布。当它被采纳为太空望远镜科学研究所(背后是哈勃望远镜的人们)首选的绘图包,并得到财政支持以及大幅扩展其功能时,Matplotlib 得到了早期的推广。

Matplotlib 最重要的特点之一是其与多种操作系统和图形后端的良好兼容性。Matplotlib 支持数十种后端和输出类型,这意味着无论您使用哪种操作系统或希望使用哪种输出格式,它都能正常工作。这种跨平台、面面俱到的方法一直是 Matplotlib 的一大优势。它导致了大量用户的使用,进而促使了活跃的开发者基础以及 Matplotlib 在科学 Python 社区中强大的工具和普及率。

近年来,然而,Matplotlib 的界面和风格开始显得有些过时。像 R 语言中的`ggplot`和`ggvis`以及基于 D3js 和 HTML5 canvas 的 Web 可视化工具包,常使 Matplotlib 感觉笨重和老旧。尽管如此,我认为我们不能忽视 Matplotlib 作为一个经过良好测试的跨平台图形引擎的优势。最近的 Matplotlib 版本使得设置新的全局绘图样式相对容易(参见 第三十四章),人们一直在开发新的包,利用其强大的内部机制通过更清晰、更现代的 API 驱动 Matplotlib,例如 Seaborn(在 第三十六章 讨论),[`ggpy`](http://yhat.github.io/ggpy),[HoloViews](http://holoviews.org),甚至 Pandas 本身可以作为 Matplotlib API 的封装器使用。即使有了这些封装器,深入了解 Matplotlib 的语法来调整最终的绘图输出仍然经常很有用。因此,我认为即使新工具意味着社区逐渐不再直接使用 Matplotlib API,Matplotlib 本身仍将保持数据可视化堆栈中不可或缺的一部分。
195 changes: 195 additions & 0 deletions translations_v2/cn/pydshb2e_29.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# 第二十五章:Matplotlib 一般提示

在我们深入研究使用 Matplotlib 创建可视化的详细信息之前,有几个有用的事情您应该了解如何使用这个包。

# 导入 Matplotlib

正如我们使用`np`简写代表 NumPy 和`pd`简写代表 Pandas 一样,我们将使用一些标准缩写来导入 Matplotlib:

```py
In [1]: import matplotlib as mpl
import matplotlib.pyplot as plt
```

我们将最常用的是`plt`接口,您将在本书的这一部分中看到。

# 设置样式

我们将使用`plt.style`指令为我们的图形选择合适的美学样式。在这里,我们将设置`classic`样式,确保我们创建的图使用经典的 Matplotlib 样式:

```py
In [2]: plt.style.use('classic')
```

在本章中,我们将根据需要调整这种样式。有关样式表的更多信息,请参阅第三十四章。

# 显示还是不显示?如何显示您的图形

您看不到的可视化对您没有多大用处,但是您查看 Matplotlib 图形的方式取决于上下文。Matplotlib 的最佳用法因您如何使用它而异;大致上,适用的三种上下文是在脚本中使用 Matplotlib,在 IPython 终端中使用 Matplotlib 或在 Jupyter 笔记本中使用 Matplotlib。

## 从脚本绘图

如果您正在脚本中使用 Matplotlib,则函数`plt.show`是您的好帮手。`plt.show`启动一个事件循环,查找所有当前活动的`Figure`对象,并打开一个或多个交互窗口来显示您的图形或图形。

因此,例如,您可能有一个名为*myplot.py*的文件,其中包含以下内容:

```py
# file: myplot.py
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)

plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))

plt.show()
```

然后,您可以从命令行提示符运行此脚本,将导致一个窗口打开,并显示您的图形:

```py
$ python myplot.py

```

`plt.show`命令在幕后做了很多工作,因为它必须与系统的交互式图形后端进行交互。此操作的详细信息在不同的系统甚至不同的安装中可能会有很大的差异,但是 Matplotlib 会尽力隐藏所有这些细节。

有一件事需要注意:`plt.show`命令应该在每个 Python 会话中*仅使用一次*,并且最常见的情况是在脚本的最后。多个`show`命令可能会导致不可预测的基于后端的行为,并且应该尽量避免。

## 从 IPython Shell 绘图

Matplotlib 在 IPython shell 中也可以无缝运行(请参阅第 I 部分)。IPython 是构建用于与 Matplotlib 很好配合的,如果您指定 Matplotlib 模式。要启用此模式,可以在启动`ipython`后使用`%matplotlib`魔术命令:

```py
In [1]: %matplotlib
Using matplotlib backend: TkAgg

In [2]: import matplotlib.pyplot as plt
```

此时,任何`plt`绘图命令都将导致一个图形窗口打开,并且可以运行进一步的命令来更新绘图。某些更改(例如修改已经绘制的线的属性)不会自动绘制:要强制更新,请使用`plt.draw`。在 IPython 的 Matplotlib 模式中不需要使用`plt.show`

## 从 Jupyter 笔记本绘图

Jupyter Notebook 是一个基于浏览器的交互式数据分析工具,可以将叙述、代码、图形、HTML 元素等多种内容组合到一个可执行文档中(参见第 I 部分)。

在 Jupyter Notebook 中进行交互式绘图可以通过 `%matplotlib` 命令完成,其工作方式类似于 IPython Shell。您还可以选择直接在笔记本中嵌入图形,有两种可能的选项:

+ `%matplotlib inline` 将导致您的图形以*静态*图像嵌入到笔记本中。

+ `%matplotlib notebook` 将导致交互式图形嵌入到笔记本中。

对于本书,通常会使用默认设置,图形渲染为静态图像(见图 25-1 以查看此基本绘图示例的结果):

```py
In [3]: %matplotlib inline
```

```py
In [4]: import numpy as np
x = np.linspace(0, 10, 100)

fig = plt.figure()
plt.plot(x, np.sin(x), '-')
plt.plot(x, np.cos(x), '--');
```

![output 15 0](img/output_15_0.png)

###### 图 25-1\. 基本绘图示例

## 将图形保存到文件中

Matplotlib 的一个很好的特性是能够以多种格式保存图形。使用 `savefig` 命令可以保存图形。例如,要将前面的图形保存为 PNG 文件,可以运行以下命令:

```py
In [5]: fig.savefig('my_figure.png')
```

现在我们在当前工作目录中有一个名为*my_figure.png*的文件:

```py
In [6]: !ls -lh my_figure.png
Out[6]: -rw-r--r-- 1 jakevdp staff 26K Feb 1 06:15 my_figure.png
```

为了确认它包含我们认为包含的内容,让我们使用 IPython 的 `Image` 对象来显示此文件的内容(见图 25-2)。

```py
In [7]: from IPython.display import Image
Image('my_figure.png')
```

![output 21 0](img/output_21_0.png)

###### 图 25-2\. 基本绘图的 PNG 渲染

`savefig` 中,文件格式根据给定文件名的扩展名推断。根据安装的后端程序,可以使用多种不同的文件格式。可以通过图形画布对象的以下方法找到系统支持的文件类型列表:

```py
In [8]: fig.canvas.get_supported_filetypes()
Out[8]: {'eps': 'Encapsulated Postscript',
'jpg': 'Joint Photographic Experts Group',
'jpeg': 'Joint Photographic Experts Group',
'pdf': 'Portable Document Format',
'pgf': 'PGF code for LaTeX',
'png': 'Portable Network Graphics',
'ps': 'Postscript',
'raw': 'Raw RGBA bitmap',
'rgba': 'Raw RGBA bitmap',
'svg': 'Scalable Vector Graphics',
'svgz': 'Scalable Vector Graphics',
'tif': 'Tagged Image File Format',
'tiff': 'Tagged Image File Format'}
```

请注意,在保存图形时,不需要使用 `plt.show` 或前面讨论过的相关命令。

## 两个界面的价格

Matplotlib 的一个可能令人困惑的特性是其双界面:一个方便的基于状态的 MATLAB 风格界面和一个更强大的面向对象界面。在这里,我将快速介绍这两者之间的区别。

### MATLAB 风格界面

Matplotlib 最初被构想为 MATLAB 用户的 Python 替代方案,其语法大部分反映了这一事实。MATLAB 风格的工具包含在 `pyplot` (`plt`) 接口中。例如,以下代码对 MATLAB 用户可能看起来非常熟悉(见图 25-3 显示的结果)。

```py
In [9]: plt.figure() # create a plot figure

# create the first of two panels and set current axis
plt.subplot(2, 1, 1) # (rows, columns, panel number)
plt.plot(x, np.sin(x))

# create the second panel and set current axis
plt.subplot(2, 1, 2)
plt.plot(x, np.cos(x));
```

![output 27 0](img/output_27_0.png)

###### 图 25-3\. 使用 MATLAB 风格界面的子图

重要的是要认识到这种接口是*有状态的*:它跟踪“当前”图形和坐标轴,所有`plt`命令都应用于这些对象。您可以使用`plt.gcf`(获取当前图形)和`plt.gca`(获取当前坐标轴)来获取对这些对象的引用。

虽然这种状态接口在简单绘图时快捷方便,但也容易遇到问题。例如,一旦创建了第二个面板,如何返回并向第一个面板添加内容?这在 MATLAB 风格接口中是可能的,但有点笨拙。幸运的是,有更好的方法。

### 面向对象接口

对于这些更复杂的情况以及当您希望对图形有更多控制时,可以使用面向对象的接口。与依赖“活跃”图形或坐标轴的概念不同,在面向对象的接口中,绘图函数是显式`Figure``Axes`对象的*方法*。要使用这种绘图风格重新创建之前的图形,如在图 25-4 中所示,可以执行以下操作:

```py
In [10]: # First create a grid of plots
# ax will be an array of two Axes objects
fig, ax = plt.subplots(2)

# Call plot() method on the appropriate object
ax[0].plot(x, np.sin(x))
ax[1].plot(x, np.cos(x));
```

![output 30 0](img/output_30_0.png)

###### 图 25-4\. 使用面向对象接口的子图

对于更简单的绘图,使用哪种风格主要是偏好问题,但随着绘图变得更加复杂,面向对象的方法可能变得必不可少。在接下来的章节中,我们将根据方便性在 MATLAB 风格和面向对象接口之间切换。在大多数情况下,区别仅在于将`plt.plot`切换为`ax.plot`,但在接下来的章节中遇到的一些陷阱我将会特别提出。
230 changes: 230 additions & 0 deletions translations_v2/cn/pydshb2e_30.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# 第二十六章:简单线图

可能所有绘图中最简单的是单个函数 <math alttext="y equals f left-parenthesis x right-parenthesis"><mrow><mi>y</mi> <mo>=</mo> <mi>f</mi> <mo>(</mo> <mi>x</mi> <mo>)</mo></mrow></math> 的可视化。在这里,我们将首次创建这种类型的简单绘图。如同接下来的所有章节一样,我们将从设置用于绘图的笔记本开始,并导入我们将使用的包:

```py
In [1]: %matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
```

对于所有的 Matplotlib 图,我们首先创建一个图形和坐标轴。在它们最简单的形式下,可以像下面这样做(见图 26-1)。

```py
In [2]: fig = plt.figure()
ax = plt.axes()
```

在 Matplotlib 中,*figure*(一个 `plt.Figure` 类的实例)可以被视为一个包含所有代表坐标轴、图形、文本和标签的对象的单个容器。*axes*(一个 `plt.Axes` 类的实例)就是我们看到的上述内容:一个带有刻度、网格和标签的边界框,最终将包含构成我们可视化的绘图元素。在本书的这一部分,我通常使用变量名 `fig` 表示一个图形实例,使用 `ax` 表示一个坐标轴实例或一组坐标轴实例。

![output 4 0](img/output_4_0.png)

###### 图 26-1\. 一个空的网格坐标轴

一旦我们创建了一个坐标轴,就可以使用 `ax.plot` 方法绘制一些数据。让我们从一个简单的正弦波开始,如 图 26-2 所示。

```py
In [3]: fig = plt.figure()
ax = plt.axes()

x = np.linspace(0, 10, 1000)
ax.plot(x, np.sin(x));
```

![output 6 0](img/output_6_0.png)

###### 图 26-2\. 一个简单的正弦波

注意最后一行末尾的分号是有意为之:它抑制了从输出中显示绘图的文本表示。

或者,我们可以使用 PyLab 接口,让图形和坐标轴在后台自动创建(参见 第 IV 部分 讨论这两种接口);如 图 26-3 所示,结果是相同的。

```py
In [4]: plt.plot(x, np.sin(x));
```

![output 8 0](img/output_8_0.png)

###### 图 26-3\. 通过面向对象接口的简单正弦波

如果我们想要创建一个包含多条线的单个图形(参见 图 26-4),我们可以简单地多次调用 `plot` 函数:

```py
In [5]: plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x));
```

这就是在 Matplotlib 中绘制简单函数的全部内容!现在我们将深入了解如何控制坐标轴和线条的外观的更多细节。

![output 10 0](img/output_10_0.png)

###### 图 26-4\. 多条线的重叠绘图

# 调整绘图:线条颜色和样式

你可能希望对图表进行的第一个调整是控制线条的颜色和样式。`plt.plot` 函数接受额外的参数来指定这些内容。要调整颜色,可以使用 `color` 关键字,接受一个表示几乎任何想象的颜色的字符串参数。颜色可以以多种方式指定;参见 图 26-5 来查看以下示例的输出:

```py
In [6]: plt.plot(x, np.sin(x - 0), color='blue') # specify color by name
plt.plot(x, np.sin(x - 1), color='g') # short color code (rgbcmyk)
plt.plot(x, np.sin(x - 2), color='0.75') # grayscale between 0 and 1
plt.plot(x, np.sin(x - 3), color='#FFDD44') # hex code (RRGGBB, 00 to FF)
plt.plot(x, np.sin(x - 4), color=(1.0,0.2,0.3)) # RGB tuple, values 0 to 1
plt.plot(x, np.sin(x - 5), color='chartreuse'); # HTML color names supported
```

![output 14 0](img/output_14_0.png)

###### 图 26-5\. 控制绘图元素的颜色

如果未指定颜色,则 Matplotlib 将自动循环使用一组默认颜色来绘制多条线。

同样地,可以使用 `linestyle` 关键字来调整线条样式(参见 图 26-6)。

```py
In [7]: plt.plot(x, x + 0, linestyle='solid')
plt.plot(x, x + 1, linestyle='dashed')
plt.plot(x, x + 2, linestyle='dashdot')
plt.plot(x, x + 3, linestyle='dotted');

# For short, you can use the following codes:
plt.plot(x, x + 4, linestyle='-') # solid
plt.plot(x, x + 5, linestyle='--') # dashed
plt.plot(x, x + 6, linestyle='-.') # dashdot
plt.plot(x, x + 7, linestyle=':'); # dotted
```

![output 16 0](img/output_16_0.png)

###### 图 26-6\. 各种线条样式的示例

虽然对于阅读你的代码的人来说可能不太清晰,但你可以通过将 `linestyle``color` 代码合并为单个非关键字参数传递给 `plt.plot` 函数来节省一些按键。 图 26-7 显示了结果。

```py
In [8]: plt.plot(x, x + 0, '-g') # solid green
plt.plot(x, x + 1, '--c') # dashed cyan
plt.plot(x, x + 2, '-.k') # dashdot black
plt.plot(x, x + 3, ':r'); # dotted red
```

![output 18 0](img/output_18_0.png)

###### 图 26-7\. 使用简写语法控制颜色和样式

这些单字符颜色代码反映了 RGB(红/绿/蓝)和 CMYK(青/洋红/黄/黑)颜色系统中的标准缩写,通常用于数字彩色图形。

还有许多其他关键字参数可用于微调图表的外观;有关详细信息,请通过 IPython 的帮助工具阅读 `plt.plot` 函数的文档字符串(参见 第 1 章)。

# 调整图表:坐标轴限制

Matplotlib 在为你的图表选择默认的轴限制方面做得相当不错,但有时更精细的控制会更好。调整限制的最基本方法是使用 `plt.xlim``plt.ylim` 函数(参见 图 26-8)。

```py
In [9]: plt.plot(x, np.sin(x))

plt.xlim(-1, 11)
plt.ylim(-1.5, 1.5);
```

![output 21 0](img/output_21_0.png)

###### 图 26-8\. 设置坐标轴限制的示例

如果因某种原因你希望任一轴显示反向,只需反转参数的顺序(参见 图 26-9)。

```py
In [10]: plt.plot(x, np.sin(x))

plt.xlim(10, 0)
plt.ylim(1.2, -1.2);
```

![output 23 0](img/output_23_0.png)

###### 图 26-9\. 反转 y 轴的示例

一个有用的相关方法是 `plt.axis`(请注意这里可能会导致 *axes*(带有 *e*)和 *axis*(带有 *i*)之间的潜在混淆),它允许更质量化地指定轴限制。例如,你可以自动收紧当前内容周围的边界,如 图 26-10 所示。

```py
In [11]: plt.plot(x, np.sin(x))
plt.axis('tight');
```

![output 25 0](img/output_25_0.png)

###### 图 26-10\. “紧凑”布局的示例

或者,您可以指定希望有一个相等的轴比率,这样 `x` 中的一个单位在视觉上等同于 `y` 中的一个单位,如 Figure 26-11 所示。

```py
In [12]: plt.plot(x, np.sin(x))
plt.axis('equal');
```

![output 27 0](img/output_27_0.png)

###### Figure 26-11\. “equal” 布局示例,单位与输出分辨率匹配

其他轴选项包括 `'on'``'off'``'square'``'image'` 等。有关更多信息,请参阅 `plt.axis` 文档字符串。

# 绘图标签

作为本章的最后一部分,我们将简要讨论绘图的标签:标题、坐标轴标签和简单图例。标题和坐标轴标签是最简单的标签——有方法可以快速设置它们(见 Figure 26-12)。

```py
In [13]: plt.plot(x, np.sin(x))
plt.title("A Sine Curve")
plt.xlabel("x")
plt.ylabel("sin(x)");
```

![output 30 0](img/output_30_0.png)

###### Figure 26-12\. 坐标轴标签和标题示例

可以使用函数的可选参数调整这些标签的位置、大小和样式,这些参数在文档字符串中有描述。

当在单个坐标轴中显示多行时,创建一个标签每种线型的图例是非常有用的。再次强调,Matplotlib 提供了一种内置的快速创建这种图例的方法;通过(你猜对了)`plt.legend` 方法来实现。虽然有几种有效的使用方法,但我发现最简单的方法是使用 `plot` 函数的 `label` 关键字来指定每条线的标签(见 Figure 26-13)。

```py
In [14]: plt.plot(x, np.sin(x), '-g', label='sin(x)')
plt.plot(x, np.cos(x), ':b', label='cos(x)')
plt.axis('equal')

plt.legend();
```

![output 33 0](img/output_33_0.png)

###### Figure 26-13\. 绘图图例示例

如您所见,`plt.legend` 函数跟踪线型和颜色,并将其与正确的标签匹配。有关指定和格式化绘图图例的更多信息,请参阅 `plt.legend` 文档字符串;此外,我们将在 第二十九章 中涵盖一些更高级的图例选项。

# Matplotlib 的一些注意事项

虽然大多数 `plt` 函数可以直接转换为 `ax` 方法(`plt.plot``ax.plot``plt.legend``ax.legend` 等),但并非所有命令都是如此。特别是用于设置限制、标签和标题的功能略有修改。为了在 MATLAB 风格函数和面向对象方法之间进行过渡,请进行以下更改:

+ `plt.xlabel``ax.set_xlabel`

+ `plt.ylabel``ax.set_ylabel`

+ `plt.xlim``ax.set_xlim`

+ `plt.ylim``ax.set_ylim`

+ `plt.title``ax.set_title`

在面向对象的绘图接口中,与单独调用这些函数不同,通常更方便使用 `ax.set` 方法一次性设置所有这些属性(见 Figure 26-14)。

```py
In [15]: ax = plt.axes()
ax.plot(x, np.sin(x))
ax.set(xlim=(0, 10), ylim=(-2, 2),
xlabel='x', ylabel='sin(x)',
title='A Simple Plot');
```

![output 36 0](img/output_36_0.png)

###### Figure 26-14\. 使用 `ax.set` 一次性设置多个属性的示例
216 changes: 216 additions & 0 deletions translations_v2/cn/pydshb2e_31.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# 第二十七章:简单散点图

另一种常用的图表类型是简单的散点图,它与线图非常相似。点不是通过线段连接,而是分别用点、圆或其他形状表示。我们将从设置绘图笔记本和导入我们将使用的包开始:

```py
In [1]: %matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
```

# 使用 plt.plot 创建散点图

在前一章中,我们使用 `plt.plot`/`ax.plot` 来生成线图。事实证明,这个函数也可以生成散点图(参见 图 27-1)。

```py
In [2]: x = np.linspace(0, 10, 30)
y = np.sin(x)

plt.plot(x, y, 'o', color='black');
```

![output 4 0](img/output_4_0.png)

###### 图 27-1\. 散点图示例

函数调用中的第三个参数是一个字符,代表用于绘图的符号类型。正如你可以指定 `'-'``'--'` 控制线条样式一样,标记样式也有自己一套简短的字符串代码。可用符号的完整列表可以在 `plt.plot` 的文档中或 Matplotlib 的[在线文档](https://oreil.ly/tmYIL)中找到。大多数可能性都相当直观,并且其中一些更常见的示例在此处演示(参见 图 27-2)。

```py
In [3]: rng = np.random.default_rng(0)
for marker in ['o', '.', ',', 'x', '+', 'v', '^', '<', '>', 's', 'd']:
plt.plot(rng.random(2), rng.random(2), marker, color='black',
label="marker='{0}'".format(marker))
plt.legend(numpoints=1, fontsize=13)
plt.xlim(0, 1.8);
```

![output 6 0](img/output_6_0.png)

###### 图 27-2\. 点数示例

进一步地,这些字符代码可以与线条和颜色代码一起使用,以绘制带有连接线的点(参见 图 27-3)。

```py
In [4]: plt.plot(x, y, '-ok');
```

![output 8 0](img/output_8_0.png)

###### 图 27-3\. 结合线条和点标记的示例

`plt.plot` 的额外关键字参数可以指定线条和标记的多种属性,正如你可以在 图 27-4 中看到的。

```py
In [5]: plt.plot(x, y, '-p', color='gray',
markersize=15, linewidth=4,
markerfacecolor='white',
markeredgecolor='gray',
markeredgewidth=2)
plt.ylim(-1.2, 1.2);
```

![output 10 0](img/output_10_0.png)

###### 图 27-4\. 自定义线条和点标记

这些选项使得 `plt.plot` 成为 Matplotlib 中二维图的主要工具。要了解所有可用选项的详细描述,请参考 [`plt.plot` 文档](https://oreil.ly/ON1xj)

# 使用 plt.scatter 创建散点图

创建散点图的第二种更强大的方法是 `plt.scatter` 函数,其用法与 `plt.plot` 函数非常相似(参见 图 27-5)。

```py
In [6]: plt.scatter(x, y, marker='o');
```

![output 13 0](img/output_13_0.png)

###### 图 27-5\. 一个简单的散点图

`plt.scatter``plt.plot` 的主要区别在于,它可以用于创建散点图,其中可以单独控制或映射到数据的每个点的属性(大小、填充颜色、边缘颜色等)。

为了更好地观察重叠的结果,我们创建一个随机散点图,点具有多种颜色和大小。为了调整透明度,我们还会使用 `alpha` 关键字(参见 图 27-6)。

```py
In [7]: rng = np.random.default_rng(0)
x = rng.normal(size=100)
y = rng.normal(size=100)
colors = rng.random(100)
sizes = 1000 * rng.random(100)

plt.scatter(x, y, c=colors, s=sizes, alpha=0.3)
plt.colorbar(); # show color scale
```

![output 15 0](img/output_15_0.png)

###### 图 27-6\. 在散点图中更改点的大小和颜色

注意,颜色参数自动映射到颜色比例(这里通过`colorbar`命令显示),点的大小以像素表示。通过这种方式,可以利用点的颜色和大小来传达可视化信息,以便可视化多维数据。

例如,我们可以使用来自 Scikit-Learn 的鸢尾花数据集,其中每个样本是三种类型的花之一,其花瓣和萼片的大小已经被仔细测量(见图 27-7)。

```py
In [8]: from sklearn.datasets import load_iris
iris = load_iris()
features = iris.data.T

plt.scatter(features[0], features[1], alpha=0.4,
s=100*features[3], c=iris.target, cmap='viridis')
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1]);
```

![output 17 0](img/output_17_0.png)

###### 图 27-7\. 使用点属性来编码鸢尾花数据的特征¹

我们可以看到,这个散点图使我们能够同时探索数据的四个不同维度:每个点的(*x*, *y*)位置对应于萼片的长度和宽度,点的大小与花瓣的宽度相关,颜色与特定种类的花相关。像这样的多颜色和多特征散点图既可以用于数据探索,也可以用于数据展示。

# 绘图与散点图:关于效率的一点说明

除了`plt.plot``plt.scatter`中提供的不同特性外,为什么你可能选择使用一个而不是另一个?虽然对于少量数据来说这并不重要,但是随着数据集超过几千个点,`plt.plot``plt.scatter`效率显著更高。原因在于,`plt.scatter`可以为每个点渲染不同的大小和/或颜色,因此渲染器必须额外工作来构建每个点。而对于`plt.plot`,每个点的标记是相同的,因此确定点的外观的工作仅需一次处理整个数据集。对于大数据集,这种差异可能导致性能大不相同,因此在处理大数据集时,应优先选择`plt.plot`而不是`plt.scatter`

# 可视化不确定性

对于任何科学测量,准确地考虑不确定性几乎与准确报告数字本身同样重要,甚至更重要。例如,想象我正在使用一些天体物理观测来估计哈勃常数,即宇宙膨胀速率的本地测量。我知道当前文献建议的值约为 70 (km/s)/Mpc,而我的方法测量的值为 74 (km/s)/Mpc。这些值是否一致?基于这些信息,唯一正确的答案是:没有办法知道。

假设我将这些信息与报告的不确定性一起增加:当前文献建议的值为 70 ± 2.5 (km/s)/Mpc,而我的方法测量的值为 74 ± 5 (km/s)/Mpc。现在这些值是否一致?这是一个可以定量回答的问题。

在数据和结果的可视化中,有效地显示这些误差可以使绘图传达更完整的信息。

## 基本误差条

一种标准的可视化不确定性的方法是使用误差条。可以通过单个 Matplotlib 函数调用创建基本的误差条,如图 27-8 所示。

```py
In [1]: %matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
```

```py
In [2]: x = np.linspace(0, 10, 50)
dy = 0.8
y = np.sin(x) + dy * np.random.randn(50)

plt.errorbar(x, y, yerr=dy, fmt='.k');
```

这里的 `fmt` 是一个控制线条和点的外观的格式代码,其语法与前一章和本章早些时候概述的 `plt.plot` 的简写相同。

![output 4 0](img/output_4_0.png)

###### 图 27-8\. 一个误差条示例

除了这些基本选项外,`errorbar` 函数还有许多选项可以微调输出结果。使用这些附加选项,您可以轻松定制误差条绘图的美学效果。特别是在拥挤的图中,我经常发现将误差条的颜色设为比点本身更浅是有帮助的(见图 27-9)。

```py
In [3]: plt.errorbar(x, y, yerr=dy, fmt='o', color='black',
ecolor='lightgray', elinewidth=3, capsize=0);
```

![output 6 0](img/output_6_0.png)

###### 图 27-9\. 自定义误差条

除了这些选项外,您还可以指定水平误差条、单侧误差条和许多其他变体。有关可用选项的更多信息,请参阅 `plt.errorbar` 的文档字符串。

## 连续误差

在某些情况下,希望在连续量上显示误差条。虽然 Matplotlib 没有针对这种类型应用的内置便捷例程,但可以相对轻松地结合 `plt.plot``plt.fill_between` 这样的基本图形元素来得到有用的结果。

在这里,我们将执行简单的*高斯过程回归*,使用 Scikit-Learn API(详见第三十八章)。这是一种将非常灵活的非参数函数拟合到具有连续不确定度测量的数据的方法。我们目前不会深入讨论高斯过程回归的细节,而是专注于如何可视化这种连续误差测量:

```py
In [4]: from sklearn.gaussian_process import GaussianProcessRegressor

# define the model and draw some data
model = lambda x: x * np.sin(x)
xdata = np.array([1, 3, 5, 6, 8])
ydata = model(xdata)

# Compute the Gaussian process fit
gp = GaussianProcessRegressor()
gp.fit(xdata[:, np.newaxis], ydata)

xfit = np.linspace(0, 10, 1000)
yfit, dyfit = gp.predict(xfit[:, np.newaxis], return_std=True)
```

现在我们有 `xfit``yfit``dyfit`,它们对我们数据的连续拟合进行了采样。我们可以像前面的部分一样将它们传递给 `plt.errorbar` 函数,但我们实际上不想绘制 1,000 个点和 1,000 个误差条。相反,我们可以使用 `plt.fill_between` 函数并使用浅色来可视化这个连续误差(见图 27-10)。

```py
In [5]: # Visualize the result
plt.plot(xdata, ydata, 'or')
plt.plot(xfit, yfit, '-', color='gray')
plt.fill_between(xfit, yfit - dyfit, yfit + dyfit,
color='gray', alpha=0.2)
plt.xlim(0, 10);
```

![output 11 0](img/output_11_0.png)

###### 图 27-10\. 用填充区域表示连续不确定性

查看 `fill_between` 的调用签名:我们传递一个 x 值,然后是下限 *y* 值和上限 *y* 值,结果是这些区域之间的区域被填充。

得到的图形直观地展示了高斯过程回归算法的运行情况:在接近测量数据点的区域,模型受到强约束,这反映在较小的模型不确定性中。在远离测量数据点的区域,模型约束不强,模型不确定性增加。

欲了解更多关于`plt.fill_between`(及其紧密相关的`plt.fill`函数)可用选项的信息,请参阅函数文档字符串或 Matplotlib 文档。

最后,如果这对你来说有点太低级了,请参考第三十六章,在那里我们讨论了 Seaborn 包,它具有更简化的 API 来可视化这种连续误差条类型。

¹ 这幅图的全彩版可在[GitHub](https://oreil.ly/PDSH_GitHub)上找到。
Loading