NumPy:面向初学者的绝对基础#

欢迎阅读 NumPy 绝对初学者指南!

NumPy (Numerical Python) 是一个开源 Python 库,广泛应用于科学和工程领域。NumPy 库包含多维数组数据结构,例如同构的 N 维 ndarray,以及一个大型函数库,可以高效地操作这些数据结构。在 什么是 NumPy 中了解更多关于 NumPy 的信息,如果您有评论或建议,请联系我们

如何导入 NumPy#

安装 NumPy 后,可以像这样将其导入 Python 代码中

import numpy as np

这种广泛的约定允许使用简短易识别的前缀 (np.) 访问 NumPy 功能,同时将 NumPy 功能与同名其他功能区分开来。

阅读示例代码#

在整个 NumPy 文档中,您会发现如下代码块

>>> a = np.array([[1, 2, 3],
...               [4, 5, 6]])
>>> a.shape
(2, 3)

>>>... 开头的文本是输入,是您将在脚本中或 Python 提示符下输入的代码。其他所有内容都是输出,是运行代码的结果。请注意,>>>... 不属于代码的一部分,如果在 Python 提示符下输入可能会导致错误。

要运行示例中的代码,您可以将其复制并粘贴到 Python 脚本或 REPL 中,或者使用文档中各处提供的实验性浏览器交互式示例。

为什么要使用 NumPy?#

Python 列表是优秀的通用容器。它们可以是“异构”的,这意味着它们可以包含各种类型的元素,并且在对少量元素执行单个操作时速度相当快。

根据数据的特性和需要执行的操作类型,其他容器可能更合适;通过利用这些特性,我们可以提高速度,减少内存消耗,并提供用于执行各种常见处理任务的高级语法。当需要处理大量“同构”(相同类型)数据时,NumPy 在 CPU 上表现出色。

什么是“数组”?#

在计算机编程中,数组是用于存储和检索数据的一种结构。我们经常将数组视为一个空间网格,每个单元格存储一个数据元素。例如,如果数据的每个元素都是一个数字,我们可能会将“一维”数组可视化为一个列表

\[\begin{split}\begin{array}{|c||c|c|c|} \hline 1 & 5 & 2 & 0 \\ \hline \end{array}\end{split}\]

二维数组就像一个表格

\[\begin{split}\begin{array}{|c||c|c|c|} \hline 1 & 5 & 2 & 0 \\ \hline 8 & 3 & 6 & 1 \\ \hline 1 & 7 & 2 & 9 \\ \hline \end{array}\end{split}\]

三维数组就像一组表格,可能像印在单独的页面上一样堆叠起来。在 NumPy 中,这一思想被推广到任意维度,因此基本数组类被称为 ndarray:它表示一个“N 维数组”。

大多数 NumPy 数组都有一些限制。例如

  • 数组的所有元素必须是相同类型的数据。

  • 一旦创建,数组的总大小不能改变。

  • 形状必须是“矩形”的,而不是“锯齿状”的;例如,二维数组的每一行必须具有相同的列数。

当满足这些条件时,NumPy 利用这些特性,使数组比限制较少的数据结构更快、更节省内存、更方便使用。

在本文档的其余部分,我们将使用“数组”一词来指代 ndarray 的实例。

数组基础#

初始化数组的一种方法是使用 Python 序列,例如列表。例如

>>> a = np.array([1, 2, 3, 4, 5, 6])
>>> a
array([1, 2, 3, 4, 5, 6])

数组元素可以通过多种方式访问。例如,我们可以像访问原始列表中的元素一样访问此数组中的单个元素:使用方括号内的整数索引。

>>> a[0]
1

注意

与内置的 Python 序列一样,NumPy 数组是“0 索引”的:数组的第一个元素使用索引 0 访问,而不是 1

与原始列表一样,数组是可变的。

>>> a[0] = 10
>>> a
array([10,  2,  3,  4,  5,  6])

与原始列表一样,Python 切片表示法可用于索引。

>>> a[:3]
array([10, 2, 3])

一个主要区别是,列表的切片索引将元素复制到一个新列表中,但对数组进行切片会返回一个视图:一个引用原始数组中数据的对象。原始数组可以使用视图进行修改。

>>> b = a[3:]
>>> b
array([4, 5, 6])
>>> b[0] = 40
>>> a
array([ 10,  2,  3, 40,  5,  6])

有关数组操作何时返回视图而非副本的更全面解释,请参阅副本和视图

二维和更高维数组可以从嵌套的 Python 序列初始化

>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
>>> a
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])

在 NumPy 中,数组的维度有时被称为“轴”。此术语可能有助于区分数组的维度和数组所表示数据的维度。例如,数组 a 可以表示三个点,每个点位于一个四维空间中,但 a 只有两个“轴”。

数组与列表的列表之间的另一个区别是,可以通过在一组方括号内指定每个轴的索引来访问数组元素,索引之间用逗号分隔。例如,元素 8 位于第 1 行和第 3

>>> a[1, 3]
8

注意

在数学中,先用行索引再用列索引来指代矩阵元素是很常见的做法。对于二维数组来说确实如此,但一个更好的心智模型是认为列索引在最后,行索引在倒数第二。这适用于任意维度的数组。

注意

您可能会听到 0 维(零维)数组被称为“标量”,1 维(一维)数组被称为“向量”,2 维(二维)数组被称为“矩阵”,或者 N 维(N 维,其中“N”通常是大于 2 的整数)数组被称为“张量”。为了清晰起见,在引用数组时最好避免使用数学术语,因为具有这些名称的数学对象与数组的行为不同(例如,“矩阵”乘法与“数组”乘法本质上不同),并且科学 Python 生态系统中还有其他具有这些名称的对象(例如,PyTorch 的基本数据结构是“张量”)。

数组属性#

本节涵盖数组的 ndimshapesize dtype 属性


数组的维度数量包含在 ndim 属性中。

>>> a.ndim
2

数组的形状是一个非负整数元组,指定每个维度上的元素数量。

>>> a.shape
(3, 4)
>>> len(a.shape) == a.ndim
True

数组中固定总元素数包含在 size 属性中。

>>> a.size
12
>>> import math
>>> a.size == math.prod(a.shape)
True

数组通常是“同构”的,这意味着它们只包含一种“数据类型”的元素。数据类型记录在 dtype 属性中。

>>> a.dtype
dtype('int64')  # "int" for integer, "64" for 64-bit

在此处阅读更多关于数组属性的信息,并在此处了解数组对象

如何创建基本数组#

本节涵盖 np.zeros()np.ones()np.empty()np.arange()np.linspace()


除了从元素序列创建数组外,您还可以轻松地创建一个填充 0 的数组

>>> np.zeros(2)
array([0., 0.])

或一个填充 1 的数组

>>> np.ones(2)
array([1., 1.])

甚至一个空数组!empty 函数创建一个数组,其初始内容是随机的,取决于内存的状态。使用 empty 而不是 zeros(或类似函数)的原因是速度——只需确保之后填充每个元素即可!

>>> # Create an empty array with 2 elements
>>> np.empty(2) 
array([3.14, 42.  ])  # may vary

您可以创建一个包含一系列元素的数组

>>> np.arange(4)
array([0, 1, 2, 3])

甚至是一个包含一系列均匀间隔的数组。为此,您需要指定起始数字结束数字步长

>>> np.arange(2, 9, 2)
array([2, 4, 6, 8])

您还可以使用 np.linspace() 创建一个在指定区间内线性间隔的值的数组

>>> np.linspace(0, 10, num=5)
array([ 0. ,  2.5,  5. ,  7.5, 10. ])

指定数据类型

虽然默认数据类型是浮点型 (np.float64),但您可以使用 dtype 关键字明确指定所需的数据类型。

>>> x = np.ones(2, dtype=np.int64)
>>> x
array([1, 1])

在此处了解更多关于创建数组的信息

添加、删除和排序元素#

本节涵盖 np.sort()np.concatenate()


使用 np.sort() 排序数组很简单。您可以在调用函数时指定轴、种类和顺序。

如果您从这个数组开始

>>> arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])

您可以使用以下方法快速按升序排序数字

>>> np.sort(arr)
array([1, 2, 3, 4, 5, 6, 7, 8])

除了返回数组已排序副本的 sort 之外,您还可以使用

  • argsort,它是沿指定轴的间接排序,

  • lexsort,它是对多个键的间接稳定排序,

  • searchsorted,它将在已排序数组中查找元素,以及

  • partition,它是一个部分排序。

要了解更多关于数组排序的信息,请参阅:sort

如果您从这些数组开始

>>> a = np.array([1, 2, 3, 4])
>>> b = np.array([5, 6, 7, 8])

您可以使用 np.concatenate() 连接它们。

>>> np.concatenate((a, b))
array([1, 2, 3, 4, 5, 6, 7, 8])

或者,如果您从这些数组开始

>>> x = np.array([[1, 2], [3, 4]])
>>> y = np.array([[5, 6]])

您可以使用以下方法连接它们

>>> np.concatenate((x, y), axis=0)
array([[1, 2],
       [3, 4],
       [5, 6]])

要从数组中删除元素,使用索引选择要保留的元素很简单。

要了解更多关于连接的信息,请参阅:concatenate

如何知道数组的形状和大小?#

本节涵盖 ndarray.ndimndarray.sizendarray.shape


ndarray.ndim 将告诉您数组的轴数或维度数。

ndarray.size 将告诉您数组中元素的总数。这是数组形状元素的乘积

ndarray.shape 将显示一个整数元组,指示数组沿每个维度存储的元素数量。例如,如果您有一个 2 行 3 列的二维数组,则数组的形状是 (2, 3)

例如,如果您创建此数组

>>> array_example = np.array([[[0, 1, 2, 3],
...                            [4, 5, 6, 7]],
...
...                           [[0, 1, 2, 3],
...                            [4, 5, 6, 7]],
...
...                           [[0 ,1 ,2, 3],
...                            [4, 5, 6, 7]]])

要查找数组的维度数量,请运行

>>> array_example.ndim
3

要查找数组中元素的总数,请运行

>>> array_example.size
24

要查找数组的形状,请运行

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

你能重塑数组吗?#

本节涵盖 arr.reshape()


可以!

使用 arr.reshape() 将在不改变数据的情况下为数组赋予新形状。请记住,当您使用 reshape 方法时,您想要生成的数组需要与原始数组具有相同数量的元素。如果您从一个包含 12 个元素的数组开始,您需要确保您的新数组也总共有 12 个元素。

如果您从这个数组开始

>>> a = np.arange(6)
>>> print(a)
[0 1 2 3 4 5]

您可以使用 reshape() 来重塑您的数组。例如,您可以将此数组重塑为具有三行两列的数组

>>> b = a.reshape(3, 2)
>>> print(b)
[[0 1]
 [2 3]
 [4 5]]

使用 np.reshape,您可以指定一些可选参数

>>> np.reshape(a, shape=(1, 6), order='C')
array([[0, 1, 2, 3, 4, 5]])

a 是要重塑的数组。

shape 是您想要的新形状。您可以指定一个整数或一个整数元组。如果您指定一个整数,结果将是该长度的数组。形状应与原始形状兼容。

order: C 表示使用 C 风格索引顺序读/写元素,F 表示使用 Fortran 风格索引顺序读/写元素,A 表示如果 a 在内存中是 Fortran 连续的,则使用 Fortran 风格索引顺序读/写元素,否则使用 C 风格顺序。(这是一个可选参数,不需要指定。)

如果您想了解更多关于 C 和 Fortran 顺序的信息,您可以在此处阅读更多关于 NumPy 数组内部组织的信息。本质上,C 和 Fortran 顺序与索引如何对应数组在内存中的存储顺序有关。在 Fortran 中,当遍历存储在内存中的二维数组的元素时,第一个索引是变化最快的索引。随着第一个索引的变化,矩阵按列存储。这就是为什么 Fortran 被认为是列主序语言。另一方面,在 C 中,最后一个索引变化最快。矩阵按行存储,使其成为行主序语言。您对 C 或 Fortran 所做的操作取决于保留索引约定还是不重新排序数据更重要。

在此处了解更多关于形状操作的信息.

如何将一维数组转换为二维数组(如何向数组添加新轴)#

本节涵盖 np.newaxisnp.expand_dims


您可以使用 np.newaxisnp.expand_dims 来增加现有数组的维度。

使用 np.newaxis 一次使用时将数组维度增加一维。这意味着一维数组将变为二维数组,二维数组将变为三维数组,依此类推。

例如,如果您从这个数组开始

>>> a = np.array([1, 2, 3, 4, 5, 6])
>>> a.shape
(6,)

您可以使用 np.newaxis 添加新轴

>>> a2 = a[np.newaxis, :]
>>> a2.shape
(1, 6)

您可以使用 np.newaxis 明确地将一维数组转换为行向量或列向量。例如,您可以通过沿第一个维度插入轴来将一维数组转换为行向量

>>> row_vector = a[np.newaxis, :]
>>> row_vector.shape
(1, 6)

或者,对于列向量,您可以沿第二个维度插入轴

>>> col_vector = a[:, np.newaxis]
>>> col_vector.shape
(6, 1)

您还可以通过使用 np.expand_dims 在指定位置插入新轴来扩展数组。

例如,如果您从这个数组开始

>>> a = np.array([1, 2, 3, 4, 5, 6])
>>> a.shape
(6,)

您可以使用 np.expand_dims 在索引位置 1 处添加轴

>>> b = np.expand_dims(a, axis=1)
>>> b.shape
(6, 1)

您可以在索引位置 0 处添加轴

>>> c = np.expand_dims(a, axis=0)
>>> c.shape
(1, 6)

此处找到更多关于 newaxis 的信息,在expand_dims 处找到 expand_dims 的信息。

索引和切片#

您可以使用与切片 Python 列表相同的方式对 NumPy 数组进行索引和切片。

>>> data = np.array([1, 2, 3])

>>> data[1]
2
>>> data[0:2]
array([1, 2])
>>> data[1:]
array([2, 3])
>>> data[-2:]
array([2, 3])

您可以这样可视化它

../_images/np_indexing.png

您可能希望获取数组的某个部分或特定的数组元素以用于进一步分析或额外操作。为此,您需要对数组进行子集、切片和/或索引。

如果您想从数组中选择满足特定条件的值,使用 NumPy 简单明了。

例如,如果您从这个数组开始

>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

您可以轻松打印数组中所有小于 5 的值。

>>> print(a[a < 5])
[1 2 3 4]

您还可以选择,例如,大于或等于 5 的数字,并使用该条件对数组进行索引。

>>> five_up = (a >= 5)
>>> print(a[five_up])
[ 5  6  7  8  9 10 11 12]

您可以选择可被 2 整除的元素

>>> divisible_by_2 = a[a%2==0]
>>> print(divisible_by_2)
[ 2  4  6  8 10 12]

或者您可以使用 &| 运算符选择满足两个条件的元素

>>> c = a[(a > 2) & (a < 11)]
>>> print(c)
[ 3  4  5  6  7  8  9 10]

您还可以利用逻辑运算符 &| 来返回布尔值,指定数组中的值是否满足特定条件。这对于包含名称或其他分类值的数组很有用。

>>> five_up = (a > 5) | (a == 5)
>>> print(five_up)
[[False False False False]
 [ True  True  True  True]
 [ True  True  True True]]

您还可以使用 np.nonzero() 从数组中选择元素或索引。

从这个数组开始

>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

您可以使用 np.nonzero() 打印例如小于 5 的元素的索引

>>> b = np.nonzero(a < 5)
>>> print(b)
(array([0, 0, 0, 0]), array([0, 1, 2, 3]))

在此示例中,返回了一个数组元组:每个维度一个。第一个数组表示找到这些值的行索引,第二个数组表示找到这些值的列索引。

如果您想生成一个元素存在的坐标列表,您可以压缩数组,遍历坐标列表,并打印它们。例如

>>> list_of_coordinates= list(zip(b[0], b[1]))

>>> for coord in list_of_coordinates:
...     print(coord)
(np.int64(0), np.int64(0))
(np.int64(0), np.int64(1))
(np.int64(0), np.int64(2))
(np.int64(0), np.int64(3))

您还可以使用 np.nonzero() 打印数组中小于 5 的元素

>>> print(a[b])
[1 2 3 4]

如果您要查找的元素不存在于数组中,则返回的索引数组将为空。例如

>>> not_there = np.nonzero(a == 42)
>>> print(not_there)
(array([], dtype=int64), array([], dtype=int64))

此处此处了解更多关于索引和切片的信息。

要了解更多关于使用 nonzero 函数的信息,请参阅:nonzero

如何从现有数据创建数组#

本节涵盖 slicing and indexingnp.vstack()np.hstack()np.hsplit().view()copy()


您可以轻松地从现有数组的一部分创建一个新数组。

假设您有这个数组

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

您可以通过指定要切片数组的位置,随时从数组的一部分创建一个新数组。

>>> arr1 = a[3:8]
>>> arr1
array([4, 5, 6, 7, 8])

在这里,您从索引位置 3 到索引位置 8(不包括位置 8 本身)获取了数组的一部分。

提醒:数组索引从 0 开始。这意味着数组的第一个元素在索引 0 处,第二个元素在索引 1 处,依此类推。

您还可以垂直和水平堆叠两个现有数组。假设您有两个数组,a1a2

>>> a1 = np.array([[1, 1],
...                [2, 2]])

>>> a2 = np.array([[3, 3],
...                [4, 4]])

您可以使用 vstack 垂直堆叠它们

>>> np.vstack((a1, a2))
array([[1, 1],
       [2, 2],
       [3, 3],
       [4, 4]])

或者使用 hstack 水平堆叠它们

>>> np.hstack((a1, a2))
array([[1, 1, 3, 3],
       [2, 2, 4, 4]])

您可以使用 hsplit 将数组分成几个较小的数组。您可以指定要返回的形状相同的数组数量,或指定在其后发生分割的列。

假设您有这个数组

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

如果您想将此数组分成三个形状相同的数组,您可以运行

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

如果您想在第三列和第四列之后分割数组,您可以运行

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

在此处了解更多关于数组堆叠和分割的信息.

您可以使用 view 方法创建一个新数组对象,该对象查看与原始数组相同的数据(一个浅拷贝)。

视图是 NumPy 的重要概念!NumPy 函数以及索引和切片等操作将尽可能返回视图。这节省了内存并且速度更快(无需复制数据)。然而,重要的是要意识到这一点——修改视图中的数据也会修改原始数组!

假设您创建了这个数组

>>> a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

现在我们通过切片 a 创建一个数组 b1 并修改 b1 的第一个元素。这也将修改 a 中对应的元素!

>>> b1 = a[0, :]
>>> b1
array([1, 2, 3, 4])
>>> b1[0] = 99
>>> b1
array([99,  2,  3,  4])
>>> a
array([[99,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])

使用 copy 方法将制作数组及其数据的完整副本(一个深拷贝)。要在数组上使用此方法,您可以运行

>>> b2 = a.copy()

在此处了解更多关于副本和视图的信息.

基本数组操作#

本节涵盖加法、减法、乘法、除法等


创建数组后,就可以开始使用它们了。例如,假设您创建了两个数组,一个名为“data”,一个名为“ones”

../_images/np_array_dataones.png

您可以用加号将数组相加。

>>> data = np.array([1, 2])
>>> ones = np.ones(2, dtype=int)
>>> data + ones
array([2, 3])
../_images/np_data_plus_ones.png

当然,您不仅可以进行加法运算!

>>> data - ones
array([0, 1])
>>> data * data
array([1, 4])
>>> data / data
array([1., 1.])
../_images/np_sub_mult_divide.png

NumPy 中的基本操作很简单。如果您想查找数组中元素的总和,可以使用 sum()。这适用于一维数组、二维数组和更高维数组。

>>> a = np.array([1, 2, 3, 4])

>>> a.sum()
10

要在二维数组中添加行或列,您需要指定轴。

如果您从这个数组开始

>>> b = np.array([[1, 1], [2, 2]])

您可以沿行轴求和

>>> b.sum(axis=0)
array([3, 3])

您可以沿列轴求和

>>> b.sum(axis=1)
array([2, 4])

在此处了解更多关于基本操作的信息.

广播#

有时您可能希望在数组和单个数字(也称为向量与标量之间的操作)之间或在两个不同大小的数组之间执行操作。例如,您的数组(我们称之为“data”)可能包含以英里计的距离信息,但您想将信息转换为公里。您可以使用以下方法执行此操作

>>> data = np.array([1.0, 2.0])
>>> data * 1.6
array([1.6, 3.2])
../_images/np_multiply_broadcasting.png

NumPy 知道乘法应该发生在每个单元格上。这个概念称为广播。广播是一种允许 NumPy 对不同形状的数组执行操作的机制。数组的维度必须兼容,例如,当两个数组的维度相等或其中一个为 1 时。如果维度不兼容,您将得到一个 ValueError

在此处了解更多关于广播的信息.

更多有用的数组操作#

本节涵盖最大值、最小值、总和、平均值、乘积、标准差等


NumPy 还执行聚合函数。除了 minmaxsum 之外,您还可以轻松运行 mean 来获取平均值,prod 来获取元素相乘的结果,std 来获取标准差等。

>>> data.max()
2.0
>>> data.min()
1.0
>>> data.sum()
3.0
../_images/np_aggregation.png

让我们从这个名为“a”的数组开始

>>> a = np.array([[0.45053314, 0.17296777, 0.34376245, 0.5510652],
...               [0.54627315, 0.05093587, 0.40067661, 0.55645993],
...               [0.12697628, 0.82485143, 0.26590556, 0.56917101]])

沿行或列进行聚合是很常见的。默认情况下,每个 NumPy 聚合函数都将返回整个数组的聚合。要查找数组中元素的总和或最小值,请运行

>>> a.sum()
4.8595784

或者

>>> a.min()
0.05093587

您可以指定希望在哪个轴上计算聚合函数。例如,您可以通过指定 axis=0 来查找每列中的最小值。

>>> a.min(axis=0)
array([0.12697628, 0.05093587, 0.26590556, 0.5510652 ])

上面列出的四个值对应于数组中的列数。对于一个四列数组,您将得到四个值作为结果。

在此处阅读更多关于数组方法的信息。

创建矩阵#

您可以将 Python 列表的列表传递给 NumPy,以创建二维数组(或“矩阵”)来表示它们。

>>> data = np.array([[1, 2], [3, 4], [5, 6]])
>>> data
array([[1, 2],
       [3, 4],
       [5, 6]])
../_images/np_create_matrix.png

索引和切片操作在操作矩阵时很有用

>>> data[0, 1]
2
>>> data[1:3]
array([[3, 4],
       [5, 6]])
>>> data[0:2, 0]
array([1, 3])
../_images/np_matrix_indexing.png

您可以像聚合向量一样聚合矩阵

>>> data.max()
6
>>> data.min()
1
>>> data.sum()
21
../_images/np_matrix_aggregation.png

您可以聚合矩阵中的所有值,并且可以使用 axis 参数跨列或行聚合它们。为了说明这一点,我们来看一个稍微修改过的数据集

>>> data = np.array([[1, 2], [5, 3], [4, 6]])
>>> data
array([[1, 2],
       [5, 3],
       [4, 6]])
>>> data.max(axis=0)
array([5, 6])
>>> data.max(axis=1)
array([2, 5, 6])
../_images/np_matrix_aggregation_row.png

创建矩阵后,如果两个矩阵大小相同,您可以使用算术运算符对它们进行加法和乘法运算。

>>> data = np.array([[1, 2], [3, 4]])
>>> ones = np.array([[1, 1], [1, 1]])
>>> data + ones
array([[2, 3],
       [4, 5]])
../_images/np_matrix_arithmetic.png

您可以对不同大小的矩阵执行这些算术操作,但前提是其中一个矩阵只有一列或一行。在这种情况下,NumPy 将对其操作使用其广播规则。

>>> data = np.array([[1, 2], [3, 4], [5, 6]])
>>> ones_row = np.array([[1, 1]])
>>> data + ones_row
array([[2, 3],
       [4, 5],
       [6, 7]])
../_images/np_matrix_broadcasting.png

请注意,当 NumPy 打印 N 维数组时,最后一个轴循环最快,而第一个轴循环最慢。例如

>>> np.ones((4, 3, 2))
array([[[1., 1.],
        [1., 1.],
        [1., 1.]],

       [[1., 1.],
        [1., 1.],
        [1., 1.]],

       [[1., 1.],
        [1., 1.],
        [1., 1.]],

       [[1., 1.],
        [1., 1.],
        [1., 1.]]])

通常情况下,我们希望 NumPy 初始化数组的值。NumPy 提供了 ones()zeros() 等函数,以及用于生成随机数的 random.Generator 类。您只需传入您希望它生成的元素数量即可

>>> np.ones(3)
array([1., 1., 1.])
>>> np.zeros(3)
array([0., 0., 0.])
>>> rng = np.random.default_rng()  # the simplest way to generate random numbers
>>> rng.random(3) 
array([0.63696169, 0.26978671, 0.04097352])
../_images/np_ones_zeros_random.png

如果您给 ones()zeros()random() 一个描述矩阵维度的元组,也可以创建一个二维数组

>>> np.ones((3, 2))
array([[1., 1.],
       [1., 1.],
       [1., 1.]])
>>> np.zeros((3, 2))
array([[0., 0.],
       [0., 0.],
       [0., 0.]])
>>> rng.random((3, 2)) 
array([[0.01652764, 0.81327024],
       [0.91275558, 0.60663578],
       [0.72949656, 0.54362499]])  # may vary
../_images/np_ones_zeros_matrix.png

数组创建例程中,了解更多关于创建填充 0、1、其他值或未初始化的数组的信息。

生成随机数#

随机数生成是许多数值和机器学习算法配置和评估的重要组成部分。无论您是需要在人工神经网络中随机初始化权重、将数据分成随机集,还是随机打乱数据集,能够生成随机数(实际上是可重复的伪随机数)都是至关重要的。

使用 Generator.integers,您可以生成从低(请记住,这在 NumPy 中是包含的)到高(不包含)的随机整数。您可以设置 endpoint=True 使高位数字包含在内。

您可以使用以下方法生成一个 0 到 4 之间的 2x4 随机整数数组

>>> rng.integers(5, size=(2, 4)) 
array([[2, 1, 1, 0],
       [0, 0, 0, 4]])  # may vary

在此处了解更多关于随机数生成的信息.

如何获取唯一项和计数#

本节涵盖 np.unique()


您可以使用 np.unique 轻松查找数组中的唯一元素。

例如,如果您从这个数组开始

>>> a = np.array([11, 11, 12, 13, 14, 15, 16, 17, 12, 13, 11, 14, 18, 19, 20])

您可以使用 np.unique 打印数组中的唯一值

>>> unique_values = np.unique(a)
>>> print(unique_values)
[11 12 13 14 15 16 17 18 19 20]

要获取 NumPy 数组中唯一值的索引(数组中唯一值的第一个索引位置的数组),只需在 np.unique() 中以及您的数组中传递 return_index 参数。

>>> unique_values, indices_list = np.unique(a, return_index=True)
>>> print(indices_list)
[ 0  2  3  4  5  6  7 12 13 14]

您可以在 np.unique() 中以及您的数组中传递 return_counts 参数,以获取 NumPy 数组中唯一值的频率计数。

>>> unique_values, occurrence_count = np.unique(a, return_counts=True)
>>> print(occurrence_count)
[3 2 2 2 1 1 1 1 1 1]

这也适用于二维数组!如果您从这个数组开始

>>> a_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [1, 2, 3, 4]])

您可以使用以下方法查找唯一值

>>> unique_values = np.unique(a_2d)
>>> print(unique_values)
[ 1  2  3  4  5  6  7  8  9 10 11 12]

如果未传递 axis 参数,您的二维数组将被展平。

如果您想获取唯一的行或列,请确保传递 axis 参数。要查找唯一的行,请指定 axis=0;对于列,请指定 axis=1

>>> unique_rows = np.unique(a_2d, axis=0)
>>> print(unique_rows)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

要获取唯一的行、索引位置和出现次数,您可以使用

>>> unique_rows, indices, occurrence_count = np.unique(
...      a_2d, axis=0, return_counts=True, return_index=True)
>>> print(unique_rows)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(indices)
[0 1 2]
>>> print(occurrence_count)
[2 1 1]

要了解更多关于查找数组中唯一元素的信息,请参阅 unique

转置和重塑矩阵#

本节涵盖 arr.reshape()arr.transpose()arr.T


通常需要转置矩阵。NumPy 数组具有 T 属性,允许您转置矩阵。

../_images/np_transposing_reshaping.png

您可能还需要切换矩阵的维度。当例如您的模型需要某种与数据集不同的输入形状时,就会发生这种情况。此时 reshape 方法会很有用。您只需传入您想要的矩阵的新维度。

>>> data.reshape(2, 3)
array([[1, 2, 3],
       [4, 5, 6]])
>>> data.reshape(3, 2)
array([[1, 2],
       [3, 4],
       [5, 6]])
../_images/np_reshape.png

您还可以使用 .transpose() 根据您指定的值反转或更改数组的轴。

如果您从这个数组开始

>>> arr = np.arange(6).reshape((2, 3))
>>> arr
array([[0, 1, 2],
       [3, 4, 5]])

您可以使用 arr.transpose() 转置您的数组。

>>> arr.transpose()
array([[0, 3],
       [1, 4],
       [2, 5]])

您还可以使用 arr.T

>>> arr.T
array([[0, 3],
       [1, 4],
       [2, 5]])

要了解更多关于转置和重塑数组的信息,请参阅 transposereshape

如何反转数组#

本节涵盖 np.flip()


NumPy 的 np.flip() 函数允许您沿轴翻转或反转数组的内容。使用 np.flip() 时,请指定要反转的数组和轴。如果不指定轴,NumPy 将沿输入数组的所有轴反转内容。

反转一维数组

如果您从这样的一维数组开始

>>> arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])

您可以使用以下方法反转它

>>> reversed_arr = np.flip(arr)

如果您想打印反转后的数组,可以运行

>>> print('Reversed Array: ', reversed_arr)
Reversed Array:  [8 7 6 5 4 3 2 1]

反转二维数组

二维数组的工作方式大致相同。

如果您从这个数组开始

>>> arr_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

您可以反转所有行和所有列中的内容

>>> reversed_arr = np.flip(arr_2d)
>>> print(reversed_arr)
[[12 11 10  9]
 [ 8  7  6  5]
 [ 4  3  2  1]]

您可以轻松地仅反转

>>> reversed_arr_rows = np.flip(arr_2d, axis=0)
>>> print(reversed_arr_rows)
[[ 9 10 11 12]
 [ 5  6  7  8]
 [ 1  2  3  4]]

或者仅反转

>>> reversed_arr_columns = np.flip(arr_2d, axis=1)
>>> print(reversed_arr_columns)
[[ 4  3  2  1]
 [ 8  7  6  5]
 [12 11 10  9]]

您还可以反转仅一列或一行内容。例如,您可以反转索引位置 1 处(第二行)的行内容

>>> arr_2d[1] = np.flip(arr_2d[1])
>>> print(arr_2d)
[[ 1  2  3  4]
 [ 8  7  6  5]
 [ 9 10 11 12]]

您还可以反转索引位置 1 处(第二列)的列

>>> arr_2d[:,1] = np.flip(arr_2d[:,1])
>>> print(arr_2d)
[[ 1 10  3  4]
 [ 8  7  6  5]
 [ 9  2 11 12]]

flip 处了解更多关于反转数组的信息。

重塑和展平多维数组#

本节涵盖 .flatten()ravel()


展平数组有两种常用方法:.flatten().ravel()。两者之间的主要区别在于,使用 ravel() 创建的新数组实际上是对父数组的引用(即“视图”)。这意味着对新数组的任何更改也会影响父数组。由于 ravel 不创建副本,因此内存效率高。

如果您从这个数组开始

>>> x = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

您可以使用 flatten 将数组展平为一维数组。

>>> x.flatten()
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])

当您使用 flatten 时,对新数组的更改不会更改父数组。

例如

>>> a1 = x.flatten()
>>> a1[0] = 99
>>> print(x)  # Original array
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(a1)  # New array
[99  2  3  4  5  6  7  8  9 10 11 12]

但是当您使用 ravel 时,您对新数组所做的更改将影响父数组。

例如

>>> a2 = x.ravel()
>>> a2[0] = 98
>>> print(x)  # Original array
[[98  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(a2)  # New array
[98  2  3  4  5  6  7  8  9 10 11 12]

ndarray.flatten 处了解更多关于 flatten 的信息,在 ravel 处了解更多关于 ravel 的信息。

如何访问文档字符串以获取更多信息#

本节涵盖 help()???


在数据科学生态系统中,Python 和 NumPy 的设计都以用户为中心。其中一个最好的例子就是内置的文档访问功能。每个对象都包含对字符串的引用,该字符串被称为文档字符串。在大多数情况下,此文档字符串包含对对象及其使用方法的快速简洁摘要。Python 有一个内置的 help() 函数可以帮助您访问此信息。这意味着几乎任何时候您需要更多信息,都可以使用 help() 快速找到所需信息。

例如

>>> help(max)
Help on built-in function max in module builtins:

max(...)
    max(iterable, *[, default=obj, key=func]) -> value
    max(arg1, arg2, *args, *[, key=func]) -> value

    With a single iterable argument, return its biggest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more arguments, return the largest argument.

由于访问额外信息非常有用,IPython 使用 ? 字符作为访问此文档以及其他相关信息的简写。IPython 是一个用于多语言交互式计算的命令行 shell。在此处了解更多关于 IPython 的信息

例如

In [0]: max?
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value

With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
Type:      builtin_function_or_method

您甚至可以将此表示法用于对象方法和对象本身。

假设您创建了这个数组

>>> a = np.array([1, 2, 3, 4, 5, 6])

然后您可以获得大量有用信息(首先是关于 a 本身的信息,然后是 ndarray 的文档字符串,andarray 的一个实例)

In [1]: a?
Type:            ndarray
String form:     [1 2 3 4 5 6]
Length:          6
File:            ~/anaconda3/lib/python3.9/site-packages/numpy/__init__.py
Docstring:       <no docstring>
Class docstring:
ndarray(shape, dtype=float, buffer=None, offset=0,
        strides=None, order=None)

An array object represents a multidimensional, homogeneous array
of fixed-size items.  An associated data-type object describes the
format of each element in the array (its byte-order, how many bytes it
occupies in memory, whether it is an integer, a floating point number,
or something else, etc.)

Arrays should be constructed using `array`, `zeros` or `empty` (refer
to the See Also section below).  The parameters given here refer to
a low-level method (`ndarray(...)`) for instantiating an array.

For more information, refer to the `numpy` module and examine the
methods and attributes of an array.

Parameters
----------
(for the __new__ method; see Notes below)

shape : tuple of ints
        Shape of created array.
...

这也适用于您创建的函数和其他对象。请记住使用字符串字面量(""" """''' ''' 围绕您的文档)为您的函数添加文档字符串。

例如,如果您创建此函数

>>> def double(a):
...   '''Return a * 2'''
...   return a * 2

您可以获取有关函数的信息

In [2]: double?
Signature: double(a)
Docstring: Return a * 2
File:      ~/Desktop/<ipython-input-23-b5adf20be596>
Type:      function

您可以通过阅读您感兴趣的对象的源代码来获取另一个级别的信息。使用双问号 (??) 允许您访问源代码。

例如

In [3]: double??
Signature: double(a)
Source:
def double(a):
    '''Return a * 2'''
    return a * 2
File:      ~/Desktop/<ipython-input-23-b5adf20be596>
Type:      function

如果问题对象是用 Python 以外的语言编译的,则使用 ?? 将返回与 ? 相同的信息。您会在许多内置对象和类型中发现这种情况,例如

In [4]: len?
Signature: len(obj, /)
Docstring: Return the number of items in a container.
Type:      builtin_function_or_method

In [5]: len??
Signature: len(obj, /)
Docstring: Return the number of items in a container.
Type:      builtin_function_or_method

具有相同的输出,因为它们是用 Python 以外的编程语言编译的。

使用数学公式#

实现对数组进行操作的数学公式的简易性是 NumPy 在科学 Python 社区中如此广泛使用的原因之一。

例如,这是均方误差公式(在处理回归的监督机器学习模型中使用的核心公式)

../_images/np_MSE_formula.png

在 NumPy 中实现此公式简单明了

../_images/np_MSE_implementation.png

之所以如此有效,是因为 predictionslabels 可以包含一个或一千个值。它们只需要大小相同。

您可以这样可视化它

../_images/np_mse_viz1.png

在此示例中,预测向量和标签向量都包含三个值,这意味着 n 的值为三。执行减法后,向量中的值被平方。然后 NumPy 对这些值求和,结果是该预测的误差值和模型质量评分。

../_images/np_mse_viz2.png ../_images/np_MSE_explanation2.png

如何保存和加载 NumPy 对象#

本节涵盖 np.savenp.saveznp.savetxtnp.loadnp.loadtxt


您总会希望将数组保存到磁盘并重新加载它们,而无需重新运行代码。幸运的是,NumPy 提供了几种保存和加载对象的方法。ndarray 对象可以使用 loadtxtsavetxt 函数保存和加载到磁盘文件(处理普通文本文件),使用 loadsave 函数保存和加载 NumPy 二进制文件(带有 .npy 文件扩展名),以及使用 savez 函数保存和加载 NumPy 文件(带有 .npz 文件扩展名)。

.npy.npz 文件存储数据、形状、数据类型以及重建 ndarray 所需的其他信息,以便即使文件位于具有不同架构的另一台机器上也能正确检索数组。

如果您想存储单个 ndarray 对象,请使用 np.save 将其保存为 .npy 文件。如果您想在单个文件中存储多个 ndarray 对象,请使用 np.savez 将其保存为 .npz 文件。您还可以使用 savez_compressed 将多个数组保存到单个压缩的 npz 格式文件中。

使用 np.save() 保存和加载数组很简单。只需确保指定要保存的数组和文件名即可。例如,如果您创建此数组

>>> a = np.array([1, 2, 3, 4, 5, 6])

您可以将其保存为“filename.npy”

>>> np.save('filename', a)

您可以使用 np.load() 重建您的数组。

>>> b = np.load('filename.npy')

如果您想检查数组,可以运行

>>> print(b)
[1 2 3 4 5 6]

您可以使用 np.savetxt 将 NumPy 数组保存为纯文本文件,例如 .csv.txt 文件。

例如,如果您创建此数组

>>> csv_arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])

您可以轻松地将其保存为名为“new_file.csv”的 .csv 文件,如下所示

>>> np.savetxt('new_file.csv', csv_arr)

您可以使用 loadtxt() 快速轻松地加载保存的文本文件

>>> np.loadtxt('new_file.csv')
array([1., 2., 3., 4., 5., 6., 7., 8.])

savetxt()loadtxt() 函数接受额外的可选参数,例如页眉、页脚和分隔符。虽然文本文件可能更容易共享,但 .npy 和 .npz 文件更小且读取速度更快。如果您需要对文本文件进行更复杂的处理(例如,如果需要处理包含缺失值的行),您将需要使用 genfromtxt 函数。

使用 savetxt,您可以指定页眉、页脚、注释等。

在此处了解更多关于输入和输出例程的信息。

导入和导出 CSV#

读取包含现有信息的 CSV 简单易行。最好和最简单的方法是使用 Pandas

>>> import pandas as pd

>>> # If all of your columns are the same type:
>>> x = pd.read_csv('music.csv', header=0).values
>>> print(x)
[['Billie Holiday' 'Jazz' 1300000 27000000]
 ['Jimmie Hendrix' 'Rock' 2700000 70000000]
 ['Miles Davis' 'Jazz' 1500000 48000000]
 ['SIA' 'Pop' 2000000 74000000]]

>>> # You can also simply select the columns you need:
>>> x = pd.read_csv('music.csv', usecols=['Artist', 'Plays']).values
>>> print(x)
[['Billie Holiday' 27000000]
 ['Jimmie Hendrix' 70000000]
 ['Miles Davis' 48000000]
 ['SIA' 74000000]]
../_images/np_pandas.png

使用 Pandas 导出数组也很简单。如果您刚开始使用 NumPy,您可能希望从数组中的值创建一个 Pandas 数据框,然后使用 Pandas 将该数据框写入 CSV 文件。

如果您创建了这个数组“a”

>>> a = np.array([[-2.58289208,  0.43014843, -1.24082018, 1.59572603],
...               [ 0.99027828, 1.17150989,  0.94125714, -0.14692469],
...               [ 0.76989341,  0.81299683, -0.95068423, 0.11769564],
...               [ 0.20484034,  0.34784527,  1.96979195, 0.51992837]])

您可以创建一个 Pandas 数据框

>>> df = pd.DataFrame(a)
>>> print(df)
          0         1         2         3
0 -2.582892  0.430148 -1.240820  1.595726
1  0.990278  1.171510  0.941257 -0.146925
2  0.769893  0.812997 -0.950684  0.117696
3  0.204840  0.347845  1.969792  0.519928

您可以轻松保存您的数据框

>>> df.to_csv('pd.csv')

并读取您的 CSV

>>> data = pd.read_csv('pd.csv')
../_images/np_readcsv.png

您还可以使用 NumPy savetxt 方法保存数组。

>>> np.savetxt('np.csv', a, fmt='%.2f', delimiter=',', header='1,  2,  3,  4')

如果您正在使用命令行,您可以随时使用以下命令读取保存的 CSV

$ cat np.csv
#  1,  2,  3,  4
-2.58,0.43,-1.24,1.60
0.99,1.17,0.94,-0.15
0.77,0.81,-0.95,0.12
0.20,0.35,1.97,0.52

或者您可以随时使用文本编辑器打开文件!

如果您对学习 Pandas 感兴趣,请查阅官方 Pandas 文档。有关安装 Pandas 的说明,请参阅官方 Pandas 安装信息

使用 Matplotlib 绘制数组#

如果您需要为您的值生成图表,使用 Matplotlib 非常简单。

例如,您可能有一个这样的数组

>>> a = np.array([2, 1, 5, 7, 4, 6, 8, 14, 10, 9, 18, 20, 22])

如果您已经安装了 Matplotlib,您可以使用以下方法导入它

>>> import matplotlib.pyplot as plt

# If you're using Jupyter Notebook, you may also want to run the following
# line of code to display your code in the notebook:

%matplotlib inline

您需要做的就是运行以下命令来绘制您的值

>>> plt.plot(a)

# If you are running from a command line, you may need to do this:
# >>> plt.show()
../_images/matplotlib1.png

例如,您可以这样绘制一个一维数组

>>> x = np.linspace(0, 5, 20)
>>> y = np.linspace(0, 10, 20)
>>> plt.plot(x, y, 'purple') # line
>>> plt.plot(x, y, 'o')      # dots
../_images/matplotlib2.png

使用 Matplotlib,您可以访问大量的可视化选项。

>>> fig = plt.figure()
>>> ax = fig.add_subplot(projection='3d')
>>> X = np.arange(-5, 5, 0.15)
>>> Y = np.arange(-5, 5, 0.15)
>>> X, Y = np.meshgrid(X, Y)
>>> R = np.sqrt(X**2 + Y**2)
>>> Z = np.sin(R)

>>> ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis')
../_images/matplotlib3.png

要了解更多关于 Matplotlib 及其功能的信息,请查阅官方文档。有关安装 Matplotlib 的说明,请参阅官方安装部分


图像来源:Jay Alammar https://jalammar.github.io/