geekdoc-python-zh/docs/realpython/052.md

35 KiB
Raw Permalink Blame History

NumPy 的 max()和 maximum():在数组中查找极值

原文:# t0]https://realython . com/num py-max/最大值]

NumPy 库支持 Python 中富有表现力的、高效的数值编程。求极值是数据分析中非常常见的要求。NumPy max()maximum()函数是两个例子,说明 NumPy 如何让您将 Python 提供的编码舒适性与 c 语言的运行时效率结合起来。

在本教程中,您将学习如何:

  • 使用 NumPy max() 功能
  • 使用 NumPy maximum() 函数并理解为什么max()不同
  • 用这些函数解决实际问题
  • 处理数据中的缺失值
  • 将相同的概念应用于寻找最小值

本教程包括一个非常简短的 NumPy 介绍,所以即使你以前从未使用过 NumPy你也应该能够直接进入。有了这里提供的背景知识您就可以继续探索 NumPy 库中丰富的功能了。

免费奖励: 点击此处获取免费的 NumPy 资源指南,它会为您指出提高 NumPy 技能的最佳教程、视频和书籍。

NumPy:数字 Python

NumPy数值 Python 的简称。它是一个开源的 Python 库,通过支持对多维数字数组的快速并行计算,在科学、统计和数据分析领域实现了广泛的应用。许多最流行的数值软件包都使用 NumPy 作为它们的基本库。

Remove ads

介绍 NumPy

NumPy 库是围绕一个名为 np.ndarray 的类和一组方法和函数构建的,这些方法和函数利用 Python 语法来定义和操作任何形状或大小的数组。

NumPy 用于数组操作的核心代码是用 C 写的。你可以直接在一个ndarray上使用函数和方法,因为 NumPy 的基于 C 的代码在后台高效地循环所有数组元素。NumPy 的高级语法意味着你可以简单优雅地表达复杂的程序,并高速执行它们。

你可以用一个普通的 Python list 来表示一个数组。然而NumPy 数组比列表有效得多,并且它们由庞大的方法和函数库支持。这些包括数学和逻辑运算、排序、傅立叶变换、线性代数、数组整形等等。

今天NumPy 广泛应用于各种领域,如天文学量子计算生物信息学以及各种工程。

NumPy 被用作许多其他库的数字引擎,例如 pandasSciPy 。它还可以轻松地与可视化库集成,如 Matplotlibseaborn

NumPy 很容易用你的包管理器安装,比如 pip 或者 conda 。关于 NumPy 及其功能的详细说明和更广泛的介绍,请看一下 NumPy 教程:Python 数据科学入门NumPy 绝对初学者指南

在本教程中,您将学习如何迈出使用 NumPy 的第一步。然后您将探索 NumPy 的max()maximum()命令。

创建和使用 NumPy 数组

您将从快速概述 NumPy 数组开始研究,这种灵活的数据结构赋予了 NumPy 多功能性和强大的功能。

任何 NumPy 程序的基本构件都是ndarray。一个ndarray是一个包装数字数组的 Python 对象。原则上,它可以具有任何尺寸的任何数量的维度。有几种方法可以声明数组。最直接的方法是从常规的 Python 列表或元组开始:

>>> import numpy as np
>>> A = np.array([3, 7, 2, 4, 5])
>>> A
array([3, 7, 2, 4, 5])

>>> B = np.array(((1, 4), (1, 5), (9, 2)))
>>> B
array([[1, 4],
 [1, 5],
 [9, 2]])

您已经在别名np下导入了numpy。这是一个标准的、广泛的约定,所以你会在大多数教程和程序中看到它。在这个例子中,A是一个一维数组,而B是二维数组。

注意,np.array()工厂函数期望 Python 列表或元组作为它的第一个参数,因此列表或元组必须分别包装在它自己的一组括号或圆括号中。仅仅扔进一堆没有包装的数字是行不通的:

>>> np.array(3, 7, 2, 4, 5)
Traceback (most recent call last):
...
TypeError: array() takes from 1 to 2 positional arguments but 5 were given

使用这种语法,解释器会看到五个独立的位置参数,所以很混乱。

在数组B的构造函数中,嵌套元组参数需要一对额外的括号来标识它,作为np.array()的第一个参数。

寻址数组元素很简单。像所有 Python 序列一样NumPy 的索引从零开始。按照惯例,显示二维数组时,第一个索引指的是行,第二个索引指的是列。所以A[0]是一维数组A的第一个元素,B[2, 1]是二维数组B第三行的第二个元素:

>>> A[0]  # First element of A
3
>>> A[4]  # Fifth and last element of A
5
>>> A[-1]  # Last element of A, same as above
5
>>> A[5]  # This won't work because A doesn't have a sixth element
Traceback (most recent call last):
 ...
IndexError: index 5 is out of bounds for axis 0 with size 5
>>> B[2, 1]  # Second element in third row of B
2

到目前为止,您似乎只是做了一些额外的输入来创建看起来非常类似于 Python 列表的数组。但是外表是会骗人的!每个ndarray对象大约有 100 个内置属性和方法,您可以将它传递给 NumPy 库中的数百个函数。

几乎任何你能想到的对数组的操作都可以在几行代码中实现。在本教程中,您将只使用一些函数,但是您可以在 NumPy API 文档中探索数组的全部功能。

Remove ads

以其他方式创建数组

您已经从 Python 序列中创建了一些 NumPy 数组。但是数组可以用许多其他方式创建。最简单的一个是 np.arange() ,它的行为更像是 Python 内置的 range() 函数的增强版:

>>> np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

>>> np.arange(2, 3, 0.1)
array([ 2., 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9])

在上面的第一个例子中,你只指定了10的上限。NumPy 遵循范围的标准 Python 约定,并返回一个包含整数09ndarray。第二个例子指定了起始值2,上限3,增量0.1。与 Python 的标准range()函数不同,np.arange()可以处理非整数增量,在这种情况下它会自动生成一个包含 np.float 元素的数组。

NumPy 的数组也可能是从磁盘读取的、从 API返回的数据合成的,或者从缓冲区或其他数组构建的

NumPy 数组可以包含各种类型的整数、浮点数和复数,但是数组中的所有元素必须是同一类型。

首先,您将使用内置的ndarray属性来理解数组AB:

>>> A.size
5
>>> A.shape
(5,)

>>> B.size
6
>>> B.shape
(3, 2)

.size属性对数组中的元素进行计数,.shape属性包含一个有序的维度元组NumPy 称之为轴。A是一个一维数组,一行包含五个元素。因为A只有一个轴,A.shape返回一个单元素元组。

按照惯例,在二维矩阵中,0轴对应,而1轴对应,所以B.shape的输出告诉你B有三行两列。

Python 字符串和列表有一个非常方便的特性,叫做切片,它允许你通过指定索引或索引范围来选择字符串或列表的部分。这个想法很自然地推广到 NumPy 数组。例如,您可以从B中提取您需要的部分,而不影响原始数组:

>>> B[2, 0]
9
>>> B[1, :]
array([1, 5])

在上面的第一个例子中,您使用B[2, 0]选择了行2和列0中的单个元素。第二个例子使用一个来挑选一个子数组。这里,B[1, :]中的索引1选择B的第1行。第二个索引位置的:选择该行中的所有元素。因此,表达式B[1, :]返回一个一行两列的数组,包含来自B的行1的所有元素。

如果你需要处理三维或三维以上的矩阵NumPy 可以满足你。语法足够灵活,可以涵盖任何情况。但是,在本教程中,您将只处理一维和二维数组。

如果你在玩 NumPy 的时候有任何问题,官方 NumPy 文档是详尽且写得很好的。如果您使用 NumPy 进行严肃的开发,您会发现它们是不可或缺的。

NumPy 的max():数组中的最大元素

在本节中,您将熟悉np.max(),这是一个在各种情况下寻找最大值的通用工具。

注意: NumPy 既有一个包级函数,又有一个名为max()ndarray方法。它们以同样的方式工作,尽管包函数np.max()需要目标数组名作为它的第一个参数。在接下来的内容中,您将会交替使用函数和方法。

Python 还有一个内置的max()函数,可以计算 iterables 的最大值。您可以使用这个内置的max()来查找一维 NumPy 数组中的最大元素,但是它不支持更多维的数组。在处理 NumPy 数组时,应该坚持 NumPy 自己的最大值函数和方法。对于本教程的其余部分,max()将总是指 NumPy 版本。

np.max()是在一个单个数组中寻找最大值的工具。准备好试试了吗?

使用max()

为了说明max()函数,您将创建一个名为n_scores的数组,其中包含学生在牛顿教授的线性代数课上获得的测试分数。

每行代表一名学生,每列包含特定考试的分数。因此列0包含第一次测试的所有学生分数,列1包含第二次测试的分数,依此类推。这里是n_scores阵:

>>> import numpy as np
>>> n_scores = np.array([
...        [63, 72, 75, 51, 83],
...        [44, 53, 57, 56, 48],
...        [71, 77, 82, 91, 76],
...        [67, 56, 82, 33, 74],
...        [64, 76, 72, 63, 76],
...        [47, 56, 49, 53, 42],
...        [91, 93, 90, 88, 96],
...        [61, 56, 77, 74, 74],
... ])

如果您愿意,可以将这段代码复制并粘贴到 Python 控制台中。要在复制前简化格式,请单击代码块右上角的>>>。您可以对示例中的任何 Python 代码做同样的事情。一旦你这样做了,n_scores数组就在内存中了。您可以向解释器询问它的一些属性:

>>> n_scores.size
40
>>> n_scores.shape
(8, 5)

如上所述,.shape.size属性确认您有代表学生的8行和代表测试的5列,总共有40个测试分数。

假设现在你想找出任何学生在任何考试中取得的最高分。在牛顿教授的线性代数课上,你可以通过检查数据很快找到最高分。但是,当您处理更大的数据集时,有一种更快的方法会显示出它的价值,这种数据集可能包含数千行和数千列。

尝试使用数组的.max()方法:

>>> n_scores.max()
96

.max()方法已经扫描了整个数组并返回了最大的元素。使用这个方法完全等同于调用np.max(n_scores)

但是也许你想要一些更详细的信息。每次测试的最高分是多少?这里你可以使用axis参数:

>>> n_scores.max(axis=0)
array([91, 93, 90, 91, 96])

新参数axis=0告诉 NumPy 找出所有中的最大值。由于n_scores有五个NumPy 独立地为每一列做这件事。这将产生五个数字,每个数字都是该列中的最大值。axis参数使用索引尺寸的标准惯例。所以axis=0指的是一个数组的,而axis=1指的是

每个学生的最高分也很容易找到:

>>> n_scores.max(axis=1)
array([83, 57, 91, 82, 76, 56, 96, 77])

这一次NumPy 返回了一个包含八个元素的数组,每个学生一个元素。n_scores数组包含每个学生的一行。参数axis=1告诉 NumPy 找出每个学生跨列的最大值。因此,输出的每个元素都包含相应学生获得的最高分。

也许您想要每个学生的最高分,但是您已经决定排除第一次和最后一次测试。切片起了作用:

>>> filtered_scores = n_scores[:, 1:-1]
>>> filtered_scores.shape
(8, 3)

>>> filtered_scores
array([72, 75, 51],
 [53, 57, 56],
 [77, 82, 91],
 [56, 82, 33],
 [76, 72, 63],
 [56, 49, 53],
 [93, 90, 88],
 [56, 77, 74]])

>>> filtered_scores.max(axis=1)
array([75, 57, 91, 82, 76, 56, 93, 77])

可以这样理解切片标注n_scores[:, 1:-1]。第一个索引范围由单个的:表示,选择切片中的所有行。逗号后面的第二个索引范围1:-1告诉 NumPy 获取列,从第1列开始,到最后一列之前的第1列结束。切片的结果存储在一个名为filtered_scores的新数组中。

通过一点实践,您将学会动态地进行数组切片,因此您不需要显式地创建中间数组filtered_scores:

>>> n_scores[:, 1:-1].max(axis=1)
array([75, 57, 91, 82, 76, 56, 93, 77])

这里您已经在一行中执行了切片和方法调用但是结果是相同的。NumPy 返回受限测试集的每个学生的最大值集合n_scores

Remove ads

处理np.max()中的缺失值

现在你知道如何在任何完全填充的数组中找到最大值了。但是当一些数组值丢失时会发生什么呢?这在真实世界的数据中很常见。

举例来说,您将创建一个小数组,其中包含从星期一开始的一周的每日温度读数(以摄氏度为单位):

>>> temperatures_week_1 = np.array([7.1, 7.7, 8.1, 8.0, 9.2, np.nan, 8.4])
>>> temperatures_week_1.size
 7

看来温度计在星期六发生了故障,相应的温度值不见了,这种情况由 np.nan 值表示。这是一个特殊值而不是一个数字,它通常用于在现实世界的数据应用程序中标记缺失值。

到目前为止,一切顺利。但是,如果您无意中试图将.max()应用到这个数组,就会出现一个问题:

>>> temperatures_week_1.max()
nan

由于np.nan报告了一个丢失的值NumPy 的默认行为是通过报告最大值也是未知的来标记它。对于某些应用程序来说这非常有意义。但是对于您的应用程序也许您会发现忽略星期六的问题并从剩余的有效读数中获得最大值更有用。NumPy 提供了np.nanmax()函数来处理这种情况:

>>> np.nanmax(temperatures_week_1)
9.2

该函数忽略任何nan值,并返回最大数值,如预期的那样。注意,np.nanmax()是 NumPy 库中的一个函数,而不是ndarray对象的一个方法。

探索相关的最大值函数

现在,您已经看到了 NumPy 的单数组最大查找能力的最常见示例。但是还有一些与最大值相关的 NumPy 函数值得了解。

例如,代替数组中的最大,您可能想要最大值的索引。假设您想要使用您的n_scores数组来识别在每次测试中表现最好的学生。这里的.argmax()方法是你的朋友:

>>> n_scores.argmax(axis=0)
array([6, 6, 6, 2, 6])

似乎学生6除了一次考试外,每一次考试都得了最高分。学生2在第四次考试中表现最好。

您还记得,您也可以将np.max()应用为 NumPy 包的函数,而不是 NumPy 数组的方法。在这种情况下,数组必须作为函数的第一个参数提供。由于历史原因,包级函数np.max()有一个别名np.amax(),除了名字之外,其他方面都是一样的:

>>> n_scores.max(axis=1)
array([83, 57, 91, 82, 76, 56, 96, 77])

>>> np.max(n_scores, axis=1)
array([83, 57, 91, 82, 76, 56, 96, 77])

>>> np.amax(n_scores, axis=1)
array([83, 57, 91, 82, 76, 56, 96, 77])

在上面的代码中,你已经调用了.max()作为n_scores对象的一个方法,并且作为一个独立的库函数,将n_scores作为它的第一个参数。您也以同样的方式调用了别名np.amax()。所有三个调用产生完全相同的结果。

现在,您已经看到了如何使用np.max()np.amax().max()来查找数组沿不同轴的最大值。您还使用了np.nanmax()来查找最大值,而忽略了nan值,以及np.argmax().argmax()来查找最大值的索引。

当你得知 NumPy 有一组等价的最小函数:np.min()np.amin().min()np.nanmin()np.argmin().argmin()时,你不会感到惊讶。你不会和这里的人打交道,但是他们的行为和他们的近亲完全一样。

Remove ads

NumPy 的maximum():跨数组的最大元素数

数据科学中的另一个常见任务是比较两个相似的数组。NumPy 的maximum()函数是在数组中寻找最大值的首选工具。由于maximum()总是涉及到两个输入数组,所以没有相应的方法。np.maximum()函数期望输入数组作为它的前两个参数。

使用np.maximum()

继续前面涉及班级分数的例子,假设牛顿教授的同事——也是主要竞争对手——莱布尼茨教授也在管理一个有八名学生的线性代数班。用 Leibniz 类的值构造一个新数组:

>>> l_scores = np.array([
...         [87, 73, 71, 59, 67],
...         [60, 53, 82, 80, 58],
...         [92, 85, 60, 79, 77],
...         [67, 79, 71, 69, 87],
...         [86, 91, 92, 73, 61],
...         [70, 66, 60, 79, 57],
...         [83, 51, 64, 63, 58],
...         [89, 51, 72, 56, 49],
... ])

>>> l_scores.shape
(8, 5)

新数组l_scores的形状与n_scores相同。

你想比较两个班级一个学生一个学生一个测试一个测试找出每种情况下的高分。NumPy 有一个函数np.maximum(),专门用于以逐个元素的方式比较两个数组。查看实际情况:

>>> np.maximum(n_scores, l_scores)
array([[87, 73, 75, 59, 83],
 [60, 53, 82, 80, 58],
 [92, 85, 82, 91, 77],
 [67, 79, 82, 69, 87],
 [86, 91, 92, 73, 76],
 [70, 66, 60, 79, 57],
 [91, 93, 90, 88, 96],
 [89, 56, 77, 74, 74]])

如果你目测检查数组n_scoresl_scores,那么你会看到np.maximum()确实为每对[行,列]索引选择了两个分数中较高的一个。

如果你只想比较各个班级的最好考试成绩呢?您可以结合使用np.max()np.maximum()来获得这种效果:

>>> best_n = n_scores.max(axis=0)
>>> best_n
array([91, 93, 90, 91, 96])

>>> best_l = l_scores.max(axis=0)
>>> best_l
array([92, 91, 92, 80, 87])

>>> np.maximum(best_n, best_l)
array([92, 93, 92, 91, 96])

和以前一样,每次调用.max()都返回相关班级所有学生的最高分数数组,每个测试一个元素。但是这一次,您将这些返回的数组输入到maximum()函数中,该函数比较两个数组,并返回数组中每个测试的较高分数。

您可以通过去掉中间数组best_nbest_l将这些操作合并成一个操作:

>>> np.maximum(n_scores.max(axis=0), l_scores.max(axis=0))
array([91, 93, 90, 91, 96])

这给出了与以前相同的结果,但是输入更少。你可以选择你喜欢的任何一种方法。

处理np.maximum()中的缺失值

还记得早先例子中的temperatures_week_1数组吗?如果您使用第二周的温度记录和maximum()功能,您可能会发现一个熟悉的问题。

首先,您将创建一个新数组来保存新的温度:

>>> temperatures_week_2 = np.array(
...     [7.3, 7.9, np.nan, 8.1, np.nan, np.nan, 10.2]
... )

temperatures_week_2数据中也有缺失值。现在看看如果将np.maximum函数应用于这两个温度数组会发生什么:

>>> np.maximum(temperatures_week_1, temperatures_week_2)
array([ 7.3,  7.9,  nan,  8.1,  nan,  nan, 10.2])

两个数组中的所有nan值都在输出中作为缺失值出现。NumPy 宣传nan的方法有一个很好的理由。通常,对结果的完整性来说,重要的是跟踪缺失的值,而不是掩盖它们。但是在这里,您只想获得每周最大值的最佳视图。在这种情况下,解决方案是另一个 NumPy 包函数,np.fmax():

>>> np.fmax(temperatures_week_1, temperatures_week_2)
array([ 7.3,  7.9,  8.1,  8.1,  9.2,  nan, 10.2])

现在,两个丢失的值被忽略了,该索引处剩余的浮点值被作为最大值。但是星期六的温度不能用那种方式固定,因为两个源值都丢失了。因为这里没有合适的值可以插入,np.fmax()只是把它作为一个nan

正如np.max()np.nanmax()具有并行的最小值功能np.min()np.nanmin(),所以np.maximum()np.fmax()也具有相应的功能np.minimum()np.fmin(),它们反映了最小值的功能。

Remove ads

高级用法

现在您已经看到了 NumPy 的max()maximum()的所有基本用例的例子,以及一些相关的函数。现在,您将研究这些函数的一些更难理解的可选参数,并找出它们何时有用。

重用内存

在 Python 中调用函数时,会返回一个值或对象。您可以立即使用该结果,方法是将其打印或写入磁盘,或者作为输入参数直接输入到另一个函数中。您也可以将其保存到一个新变量中,以供将来参考。

如果你调用了 Python REPL 中的函数,但没有以其中一种方式使用它,那么 REPL 会在控制台上打印出返回值,这样你就知道有东西被返回了。所有这些都是标准的 Python 内容,并不特定于 NumPy。

NumPy 的数组函数是为处理巨大的输入而设计的,它们通常会产生巨大的输出。如果你调用这个函数成百上千次,那么你将会分配大量的内存。这可能会降低程序速度,在极端情况下,甚至可能导致内存或堆栈溢出。

这个问题可以通过使用out参数来避免,该参数对np.max()np.maximum()以及许多其他 NumPy 函数都可用。其思想是预先分配一个合适的数组来保存函数结果,并在后续调用中重用相同的内存块。

您可以重新考虑温度问题,创建一个将out参数用于np.max()函数的示例。您还将使用dtype参数来控制返回数组的类型:

>>> temperature_buffer = np.empty(7, dtype=np.float32)
>>> temperature_buffer.shape
(7,)

>>> np.maximum(temperatures_week_1, temperatures_week_2, out=temperature_buffer)
array([ 7.3,  7.9,  nan,  8.1,  nan,  nan, 10.2], dtype=float32)

temperature_buffer中的初始值无关紧要,因为它们会被覆盖。但是数组的形状很重要,因为它必须与输出形状相匹配。显示的结果看起来像您从最初的np.maximum()示例中收到的输出。那么有什么变化呢?不同的是,你现在有相同的数据存储在temperature_buffer:

>>> temperature_buffer
array([ 7.3,  7.9,  nan,  8.1,  nan,  nan, 10.2], dtype=float32)

np.maximum()返回值已经存储在temperature_buffer变量中,这个变量是您之前用正确的形状创建的,用来接受返回值。由于您在声明这个缓冲区时还指定了dtype=np.float32NumPy 会尽最大努力将输出数据转换成那个类型。

记得在下次调用这个函数时覆盖缓冲区内容之前使用它们。

过滤阵列

另一个偶尔有用的参数是where。这将对输入数组应用一个过滤器,这样只有那些where条件为True的值才会被包括在比较中。其他值将被忽略,输出数组的相应元素将保持不变。在大多数情况下,这将使他们持有任意值。

出于示例的目的,假设您出于某种原因决定忽略所有小于60的分数来计算牛顿教授班上每个学生的最大值。你的第一次尝试可能是这样的:

>>> n_scores
array([[63, 72, 75, 51, 83],
 [44, 53, 57, 56, 48],
 [71, 77, 82, 91, 76],
 [67, 56, 82, 33, 74],
 [64, 76, 72, 63, 76],
 [47, 56, 49, 53, 42],
 [91, 93, 90, 88, 96],
 [61, 56, 77, 74, 74]])

>>> n_scores.max(axis=1, where=(n_scores >= 60))
ValueError: reduction operation 'maximum' does not have an identity,
 so to use a where mask one has to specify 'initial'

这里的问题是NumPy 不知道如何对待第1排和第5排的学生,他们没有一次考试成绩达到60或更好。解决方案是提供一个initial参数:

>>> n_scores.max(axis=1, where=(n_scores >= 60), initial=60)
array([83, 60, 91, 82, 76, 60, 96, 77])

有了两个新参数whereinitial , n_scores.max()只考虑大于或等于60的元素。对于没有这种元素的行,它返回60initial值。所以指数为15的幸运学生通过这个操作将他们的最高分提高到了60!原来的n_scores阵原封不动。

Remove ads

用广播比较不同形状的阵列

您已经学习了如何使用np.maximum()来比较具有相同形状的数组。但是事实证明,这个函数,以及 NumPy 库中的许多其他函数比这个函数更加通用。NumPy 有一个名为广播的概念,它为涉及两个数组的大多数函数的行为提供了一个非常有用的扩展,包括np.maximum()

每当您调用对两个数组AB进行操作的 NumPy 函数时,它都会检查它们的.shape属性,以查看它们是否兼容。如果它们有完全相同的.shape,那么 NumPy 就逐个元素地匹配数组,将A[i, j]处的元素与B[i, j]处的元素配对。np.maximum()工作原理也是这样。

广播使得 NumPy 能够在两个具有不同 T2 形状的阵列上运行,前提是仍然有一种合理的方法来匹配元素对。最简单的例子就是在整个数组中传播一个元素。您将通过继续牛顿教授和他的线性代数课的例子来探索广播。假设他要求你确保他的学生没有一个分数低于75。你可以这样做:

>>> np.maximum(n_scores, 75)
array([[75, 75, 75, 75, 83],
 [75, 75, 75, 75, 75],
 [75, 77, 82, 91, 76],
 [75, 75, 82, 75, 75],
 [75, 76, 75, 75, 76],
 [75, 75, 75, 75, 75],
 [91, 93, 90, 88, 96],
 [75, 75, 77, 75, 75]])

您已经将np.maximum()函数应用于两个参数:n_scores,其.shape为(85),以及单个标量参数75。你可以把第二个参数想象成一个 1 × 1 的数组,它将在函数内部被拉伸以覆盖 8 行 5 列。然后,可以用n_scores逐个元素地比较拉伸后的数组,并且可以为结果的每个元素返回成对的最大值。

结果就好像您将n_scores与一个自身形状的数组(85)进行了比较,但是每个元素中的值都是75。这种拉伸只是概念性的——NumPy 足够聪明,可以在不实际创建拉伸数组的情况下完成所有这些工作。所以你可以在不影响效率的情况下得到这个例子的符号便利。

你可以通过广播做更多的事情。莱布尼茨教授已经注意到了牛顿在他的best_n_scores数组上的欺骗行为,并决定自己进行一点数据操作。

莱布尼茨的计划是人为地提高所有学生的分数,使其至少等于某次考试的平均分。这将增加所有低于平均水平的分数——从而产生一些非常误导的结果!你如何帮助教授达到她有些邪恶的目的?

第一步是使用数组的.mean()方法为每个测试创建一个一维平均值数组。然后你可以使用np.maximum()并在整个l_scores矩阵中传播这个数组:

>>> mean_l_scores = l_scores.mean(axis=0, dtype=np.integer)
>>> mean_l_scores
array([79, 68, 71, 69, 64])

>>> np.maximum(mean_l_scores, l_scores) array([[87, 73, 71, 69, 67],
 [79, 68, 82, 80, 64],
 [92, 85, 71, 79, 77],
 [79, 79, 71, 69, 87],
 [86, 91, 92, 73, 64],
 [79, 68, 71, 79, 64],
 [83, 68, 71, 69, 64],
 [89, 68, 72, 69, 64]])

广播发生在突出显示的函数调用中。一维mean_l_scores数组在概念上被拉伸以匹配二维l_scores数组。输出数组的.shape与两个输入数组中较大的那个l_scores相同。

遵循广播规则

那么,广播有什么规则呢?许多 NumPy 函数接受两个数组参数。np.maximum()只是其中之一。可以在这样的函数中一起使用的数组被称为兼容的,它们的兼容性取决于它们的维数和大小——也就是说,取决于它们的.shape

最简单的情况是两个数组,比如说AB,具有相同的形状。出于函数的目的,A中的每个元素都与B中相同索引地址的元素相匹配。

AB具有不同的形状时,广播规则变得更加有趣。兼容数组的元素必须以某种方式明确地配对在一起,以便较大数组的每个元素都可以与较小数组的元素交互。输出数组将具有两个输入数组中较大的那个的.shape。因此,兼容阵列必须遵循以下规则:

  1. 如果一个数组的维数比另一个少,则只有尾随维数匹配兼容性。尾部尺寸是出现在两个数组.shape中的尺寸,从右边开始计数。所以如果A.shape(99, 99, 2, 3),而B.shape(2, 3),那么AB是兼容的,因为(2, 3)是各自的尾部尺寸。可以完全忽略A最左边的两个维度。

  2. 即使尾部维度不相等,如果其中一个维度等于任一数组中的1,数组仍然是兼容的。所以如果A.shape像以前一样是(99, 99, 2, 3),而B.shape(1, 99, 1, 3)(1, 3)(1, 2, 1)(1, 1),那么在每种情况下B仍然与A兼容。

您可以在 Python REPL 中体验一下广播规则。您将创建一些玩具数组来说明广播是如何工作的以及输出数组是如何生成的:

>>> A = np.arange(24).reshape(2, 3, 4)
>>> A
array([[[ 0,  1,  2,  3], [ 4,  5,  6,  7], [ 8,  9, 10, 11]],
 [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]])

>>> A.shape
(2, 3, 4)

>>> B = np.array(
...     [
...         [[-7, 11, 10,  2], [-6,  7, -2, 14], [ 7,  4,  4, -1]],
...         [[18,  5, 22,  7], [25,  8, 15, 24], [31, 15, 19, 24]],
...     ]
... )

>>> B.shape
(2, 3, 4)

>>> np.maximum(A, B)
array([[[ 0, 11, 10,  3], [ 4,  7,  6, 14], [ 8,  9, 10, 11]],
 [[18, 13, 22, 15], [25, 17, 18, 24], [31, 21, 22, 24]]])

这里还没有什么新东西可看。您已经创建了两个相同的.shape数组,并对它们应用了np.maximum()操作。注意,方便的.reshape()方法可以让你构建任何形状的数组。您可以验证结果是两个输入的逐个元素的最大值。

当你实验比较两组不同形状的时,乐趣就开始了。尝试切片B来制作一个新的数组,C:

>>> C = B[:, :1, :]
>>> C
array([[[-7, 11, 10,  2]],
 [[18,  5, 22,  7]]])

>>> C.shape
(2, 1, 4)

>>> np.maximum(A, C)
array([[[ 0, 11, 10,  3], [ 4, 11, 10,  7], [ 8, 11, 10, 11]],
 [[18, 13, 22, 15], [18, 17, 22, 19], [20, 21, 22, 23]]]))

两个数组AC是兼容的,因为新数组的第二维度是1,其他维度是匹配的。注意maximum()操作结果的.shapeA.shape相同。这是因为较小的数组C正在通过A广播。阵列间广播操作的结果将总是具有比大的阵列的.shape

现在你可以尝试对B进行更激进的分割:

>>> D = B[:, :1, :1]
>>> D
array([[[-7]],[[18]]])

>>> D.shape
(2, 1, 1)

>>> np.maximum(A, D)
array([[[ 0,  1,  2,  3], [ 4,  5,  6,  7], [ 8,  9, 10, 11]],
 [[18, 18, 18, 18], [18, 18, 18, 19], [20, 21, 22, 23]]])

同样,AD的尾部尺寸要么都相等,要么都是1,因此数组是兼容的,广播可以工作。结果与A.shape相同。

也许最极端的广播类型发生在数组参数之一作为标量传递时:

>>> np.maximum(A, 10)
array([[[10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 11]],
 [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]])

NumPy 自动将第二个参数10转换为带有.shapeT3 的array([10]),确定这个转换后的参数与第一个参数兼容,并适时地在整个 2 × 3 × 4 数组A上广播它。

最后,这里有一个广播失败的例子:

>>> E = B[:, 1:, :]
>>> E
array([[[-6,  7, -2, 14], [ 7,  4,  4, -1]],
 [[25,  8, 15, 24], [31, 15, 19, 24]]])

>>> E.shape
(2, 2, 4)

>>> np.maximum(A, E)
Traceback (most recent call last):
...
ValueError: operands could not be broadcast together with shapes (2,3,4) (2,2,4)

如果你回头参考一下上面的广播规则,就会看到问题所在:AE的第二维不匹配,也不等于1,所以两个数组不兼容。

你可以在 Look MaNo For-Loops:Array Programming With NumPy中阅读更多关于广播的内容。在的数字文档中也有对规则的详细描述。

广播规则可能会令人困惑,所以最好先玩一些玩具阵列,直到你感觉到它是如何工作的!

Remove ads

结论

在本教程中,您已经研究了 NumPy 库的max()maximum()操作,以找到数组内或数组间的最大值。

下面是你学到的:

  • 为什么 NumPy 有自己的 max() 函数,如何使用
  • maximum() 功能与 max() 有何不同,何时需要
  • 每个功能有哪些实际应用
  • 你如何处理缺失数据以使你的结果有意义
  • 你如何将你的知识应用于补充任务寻找最小值

在这个过程中,您已经学习或更新了 NumPy 语法的基础知识。NumPy 是一个非常受欢迎的库,因为它对数组操作有强大的支持。

现在您已经掌握了 NumPy 的max()maximum()的细节,您已经准备好在您的应用程序中使用它们,或者继续了解 NumPy 支持的数百个数组函数中的更多函数。

免费奖励: 点击此处获取免费的 NumPy 资源指南,它会为您指出提高 NumPy 技能的最佳教程、视频和书籍。

如果您对使用 NumPy 进行数据科学感兴趣,那么您也会想研究一下 pandas ,这是一个基于 NumPy 构建的非常流行的数据科学库。你可以在熊猫数据框架:让数据工作变得愉快中了解到这一点。如果你想从数据中产生引人注目的图像,看看用 Matplotlib (Guide) 绘制的 Python。

NumPy 的应用是无限的。无论你的 NumPy 冒险带你下一步,前进和矩阵乘法!*******