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)
Loading