NumPy:初学者的绝对基础#

欢迎来到 NumPy 的初学者指南

NumPy(Numerical Python)是一个开源的 Python 库,广泛应用于科学和工程领域。NumPy 库包含多维数组数据结构,例如同质的 N 维 ndarray,以及大量高效操作这些数据结构的函数库。想了解更多关于 NumPy 的信息,请访问 什么是 NumPy

如何导入 NumPy#

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

import numpy as np

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

为什么要使用 NumPy?#

Python 的 list 是出色的通用容器。它们可以是“异构的”(heterogeneous),这意味着它们可以包含多种类型的元素,并且在对少量元素执行单独操作时速度非常快。

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

什么是“数组”?#

在计算机编程中,数组(array)是一种用于存储和检索数据的结构。通常将数组描述为空间中的网格,其中每个单元格存储数据的一个元素。例如,如果数据的每个元素都是一个数字,可以将“一维”数组可视化为一个列表。二维数组则类似于表格(table)。三维数组则类似于一组表格,可能像打印在不同页面上一样堆叠起来。在 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]
np.int64(1)

备注

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

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

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

与原始列表一样,Python 的切片符号也可以用于索引。

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

一个主要的区别是,列表的切片索引会将元素复制到新列表中,而数组的切片会返回视图(view):引用原始数组中数据的视图对象。可以通过该视图修改原始数组。

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 中,数组的维度有时被称为“轴”(axis)。这种术语可能有助于区分数组的维度和数组所表示数据的维度。例如,数组 a 可以表示三个点,每个点位于四维空间中,但 a 只有两个“轴”。

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

a[1, 3]
np.int64(8)

备注

在数学中,习惯上先通过行索引再通过列索引来引用矩阵的元素。对于二维数组来说,这恰好是正确的,但更好的思维模型是将列索引视为最后,行索引视为倒数第二。这种思维方式可以推广到具有任意数量维度的数组。

备注

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

数组属性#

本节介绍数组的 ndimshapesizedtype 属性。

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

a.ndim
2

数组的形状(shape)是非负整数的元组,用于指定沿每个维度上的元素数量。

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  # "int" for integer, "64" for 64-bit
dtype('int64')

阅读有关数组属性的更多信息,并了解数组对象

如何创建基本数组#

本节涵盖了 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()(或类似函数)的原因是速度——只需确保之后填充每个元素!

# 创建包含 2 个元素的空数组
np.empty(2) # 值可能变化
array([1., 1.])

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

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(返回数组的排序副本)之外,您还可以使用:

要了解更多关于数组排序的信息,请参阅: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 的信息,请参阅: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)

你可以 reshape 数组吗?#

当然可以!

使用 arr.reshape() 方法可以给数组赋予一个新的形状,而不会改变数据本身。只需记住,当你使用 reshape 方法时,你想要生成的新数组必须与原数组具有相同数量的元素。如果你从包含 12 个元素的数组开始,你需要确保你的新数组也总共包含 12 个元素。

假设你从这样的数组开始:

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

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

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

a 是要重塑的数组。

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

orderC 表示按照类似 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]
np.int64(2)
data[0:2]
array([1, 2])
data[1:]
array([2, 3])
data[-2:]
array([2, 3])

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

如果你想从数组中选择满足特定条件的值,使用 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

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

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

假设你有这样的数组:

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, 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”。

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

data = np.array([1, 2])
ones = np.ones(2, dtype=int)
data + ones
array([2, 3])

当然,你可以做的不仅仅是加法!

data - ones
array([0, 1])
data * data
array([1, 4])
data / data
array([1., 1.])

使用 NumPy 进行基本运算非常简单。如果你想找到数组中元素的总和,可以使用 sum() 函数。这适用于一维数组、二维数组以及更高维度的数组。

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

a.sum()
np.int64(10)

要在二维数组中对行或列进行相加,你需要指定轴(axis)。

假设你从以下数组开始:

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

你可以通过以下方式对行进行求和:

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

你可以通过以下方式对列进行求和:

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

了解更多关于基本运算的信息

广播(Broadcasting)#

有时你可能希望在数组和单个数字(也称为向量和标量之间的操作)之间或两个不同大小的数组之间执行操作。例如,你的数组(称之为“data”)可能包含以英里为单位的距离信息,但你希望将其转换为公里。你可以使用以下操作来完成:

data = np.array([1.0, 2.0])
data * 1.6
array([1.6, 3.2])

NumPy理解乘法应该应用于每个单元格。这个概念称为广播(Broadcasting)。广播是一种机制,允许 NumPy 对不同形状的数组执行操作。你的数组维度必须是兼容的,例如,当两个数组的维度相等或其中一个维度为 1 时。如果维度不兼容,你将遇到 ValueError

了解更多关于广播的信息

更多有用的数组运算#

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

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

data.max(), data.min(), data.sum()
(np.float64(2.0), np.float64(1.0), np.float64(3.0))

从以下数组开始,称为“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()
np.float64(4.8595784)

或者:

a.min()
np.float64(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]])

索引和切片操作在你操作矩阵时非常有用:

data[0, 1]
np.int64(2)
data[1:3]
array([[3, 4],
       [5, 6]])
data[0:2, 0]
array([1, 3])

你可以像聚合向量一样聚合矩阵:

data.max(), data.min(), data.sum()
(np.int64(6), np.int64(1), np.int64(21))

你可以聚合矩阵中的所有值,也可以使用axis参数按列或行进行聚合。为了说明这一点,来看稍微修改过的数据集:

data = np.array([[1, 2], [5, 3], [4, 6]])
data
array([[1, 2],
       [5, 3],
       [4, 6]])
data.max(axis=0), data.max(axis=1)
(array([5, 6]), array([2, 5, 6]))

一旦你创建了矩阵,你可以使用算术运算符对它们进行加法和乘法运算,前提是两个矩阵的大小相同。

data = np.array([[1, 2], [3, 4]])
ones = np.array([[1, 1], [1, 1]])
data + ones
array([[2, 3],
       [4, 5]])

你可以对不同大小的矩阵进行这些算术运算,但前提是其中一个矩阵只有一列或一行。在这种情况下,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]])

请注意,当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.79398102, 0.73015389, 0.19441187])

你还可以使用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.86191029, 0.23418522],
       [0.89301475, 0.1580615 ],
       [0.62562209, 0.49968765]])

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

生成随机数#

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

使用Generator.integers,你可以从低值(记住,NumPy中包含低值)到高值(不包含高值)生成随机整数。你可以设置endpoint=True以使高值包含在内。

你可以生成2x4的随机整数数组,范围在0到4之间,如下所示:

rng.integers(5, size=(2, 4)) 
array([[0, 2, 4, 4],
       [3, 0, 0, 0]])

了解更多关于随机数生成的信息

如何获取唯一项及其计数#

本节涵盖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]

你可以将return_counts参数与你的数组一起传递给np.unique(),以获取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)
print(indices)
print(occurrence_count)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
[0 1 2]
[2 1 1]

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

转置和重塑矩阵#

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

需要转置矩阵是很常见的。NumPy数组具有T属性,允许你转置矩阵。

你可能还需要交换矩阵的维度。例如,当你有一个模型期望的输入形状与你的数据集不同时,这可能会发生。这时,reshape方法就非常有用。你只需传递你希望矩阵具有的新维度即可。

data.reshape(2, 3)
array([[1, 2, 3],
       [4, 5, 6]])
data.reshape(3, 2)
array([[1, 2],
       [3, 4],
       [5, 6]])

你还可以使用.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]])

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

如何反转数组#

本节涵盖 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)  # 原始数组
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(a1)  # 新数组
[99  2  3  4  5  6  7  8  9 10 11 12]

但当你使用 ravel 时,对新数组的更改会影响父数组。

例如:

>>> a2 = x.ravel()
>>> a2[0] = 98
>>> print(x)  # 原始数组
[[98  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
>>> print(a2)  # 新数组
[98  2  3  4  5  6  7  8  9 10 11 12]

更多关于 flatten 的信息请参阅 flatten(),关于 ravel 的信息请参阅 ravel()

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

本节涵盖 help(), ?, ??

在数据科学生态系统中,Python 和 NumPy 是以用户为中心构建的。最好的例子之一是内置的文档访问功能。每个对象都包含一个称为 docstring 的字符串引用。在大多数情况下,这个 docstring 包含对象的快速和简明的摘要以及如何使用它的说明。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 本身的详细信息,然后是 a 所属的 ndarray 的 docstring):

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.
...

这也适用于你创建的函数和其他对象。只需记住在函数中包含一个 docstring,使用字符串字面量(""" """''' ''' 围绕你的文档)。

例如,如果你创建了这个函数:

>>> 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 社区中被广泛使用的原因之一。

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

在 NumPy 中实现这个公式非常简单和直接:

MSE 实现

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

你可以这样可视化它:

MSE 可视化1

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

MSE 可视化2

MSE 解释2

如何保存和加载 NumPy 对象#

本节涵盖 np.save, np.savez, np.savetxt, np.load, np.loadtxt

在某些时候,您会希望将数组保存到磁盘并在不重新运行代码的情况下加载它们。幸运的是,NumPy 提供了几种保存和加载对象的方法。ndarray 对象可以通过 loadtxtsavetxt 函数处理普通文本文件,通过 loadsave 函数处理带有 .npy 文件扩展名的 NumPy 二进制文件,以及通过 savez 函数处理带有 .npz 文件扩展名的 NumPy 文件。

.npy.npz 文件存储数据、形状、dtype 以及其他重建 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]

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

例如,如果您创建了这个数组:

>>> 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() 函数接受额外的可选参数,如 header、footer 和 delimiter。虽然文本文件更容易共享,但 .npy.npz 文件更小且读取速度更快。如果您需要更复杂的文本文件处理(例如,如果您需要处理包含缺失值的行),您可能需要使用 genfromtxt 函数。

使用 savetxt(),您可以指定标题、页脚、注释等。

了解更多关于 输入和输出例程