课程
就像 SciPy、Scikit-Learn、pandas 和类似包一样,NumPy 也是如此。它们是在学习数据科学时不可错过的 Python 包,主要是因为该库为您提供了一种在多方面优于 Python 列表的数组数据结构,例如更紧凑、读写更快、更方便且更高效。
本 NumPy 教程将精准聚焦上述内容。它不仅会向您展示 NumPy 数组究竟是什么以及如何安装 Python,还将教您如何创建数组(即使数据来自文件)、广播如何工作、如何寻求帮助、如何操作数组以及如何可视化数组。
如果您想进一步了解 NumPy 数组和数据科学之旅中需要的其他数据结构,可考虑查看 DataCamp 的 数据科学 Python 入门,其中包含一章 NumPy 内容。
什么是 Python Numpy 数组?
您已经在介绍中读到,NumPy 数组有点像 Python 列表,但同时又非常不同。对于刚接触该主题的读者,让我们明确它到底是什么以及有何用途。
顾名思义,NumPy 数组是 numpy 库的核心数据结构。该库名称是 “Numeric Python” 或 “Numerical Python” 的缩写。
换言之,NumPy 是 Python 的一个库,是 Python 科学计算的核心库。它包含一系列工具与技术,可用于解决科学与工程问题的数学模型。
其中一种工具就是高性能的多维数组对象,它是一种强大的数据结构,能高效地进行数组与矩阵运算。为配合这些数组使用,还有大量高级数学函数可对这些矩阵与数组进行操作。
但数组到底是什么?
当您查看若干数组的打印结果时,可以把它看作一个包含同类型值的网格:
import numpy as np
# Define a 1D array
my_array = np.array([[1, 2, 3, 4],
[5, 6, 7, 8]],
dtype=np.int64)
# Define a 2D array
my_2d_array = np.array([[1, 2, 3, 4],
[5, 6, 7, 8]],
dtype=np.int64)
# Define a 3D array
my_3d_array = np.array([[[1, 2, 3, 4],
[5, 6, 7, 8]],
[[1, 2, 3, 4],
[9, 10, 11, 12]]],
dtype=np.int64)
# Print the 1D array
print("Printing my_array:")
print(my_array)
# Print the 2D array
print("Printing my_2d_array:")
print(my_2d_array)
# Print the 3D array
print("Printing my_3d_array:")
print(my_3d_array)
您会看到在上述示例中,数据为整数。数组以结构化方式保存并表示任意常规数据。
不过,您还应当知道,从结构层面讲,数组本质上不过是一些指针。它是内存地址、数据类型、形状与步幅的组合:
- 数据指针指示数组中第一个字节的内存地址。
- 数据类型或 dtype 指针描述数组中包含的元素类型。
- shape 指示数组的形状。
- strides 是为到达下一个元素在内存中应跳过的字节数。如果步幅为 (10,1),则需前进 1 个字节到达下一列,前进 10 个字节定位到下一行。
换句话说,数组包含关于原始数据、如何定位元素以及如何解释元素的信息。
理论够多了。让我们自己来验证一下:
您可以通过探索 numpy 数组的属性轻松测试这一点:
import numpy as np
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print out memory address
print(my_2d_array.data)
# Print out the shape of `my_array`
print(my_2d_array.shape)
# Print out the data type of `my_array`
print(my_2d_array.dtype)
# Print out the stride of `my_array`
print(my_2d_array.strides)
您会发现现在得到的信息更多了:例如,打印出的数据类型是 “int64” 或有符号 32 位整数类型;这要详细得多!这也意味着数组在内存中以 64 字节存储(因为每个整数占 8 字节,且数组有 8 个整数)。数组的步幅告诉我们,移动到下一列需跳过 8 字节(一个值),而到下一行同一位置需跳过 32 字节(4 个值)。因此,该数组的步幅为 (32,8)。
请注意,如果将数据类型设为 int32,那么返回的步幅元组将是 (16, 4),因为仍需要移动 1 个值到下一列、4 个值到同一位置。唯一改变的是每个整数将占用 4 字节而非 8 字节。

您在上图中看到的数组,顾名思义,是一个二维数组:包含行与列。行标记为 “axis 0”,列标记为 “axis 1”。轴的编号会随着维度数量增加而递增:在三维数组中(您也在前面的代码片段中看到过示例),会多一个 “axis 2”。请注意,这些轴仅对至少二维的数组有效,因为对于一维数组,这么做没有意义;
在您之后操作 NumPy 数组形状时,这些轴会派上用场。
如何安装 Numpy
在您开始亲自尝试这些 NumPy 数组之前,首先要确保已在本地安装它(假设您在自己的电脑上工作)。如果您已经可用该 Python 库,请跳过本节 :)
如果仍需搭建环境,请注意在您的电脑上安装 NumPy 主要有两种方式:使用 Python 轮子包(wheels)或 Anaconda Python 发行版。
使用 Python Wheels 安装
首先确保您已安装 Python。如果仍需安装,可以查看我们的 Python 安装指南 :)
如果您在 Windows 上工作,请确保已将 Python 添加到 PATH 环境变量。然后别忘了安装包管理器,如 pip,它可确保您能使用 Python 的开源库。
请注意,较新的 Python 3 版本自带 pip,因此请先检查是否已安装;如果已安装,请在安装 NumPy 前先升级它:
pip install pip --upgrade
pip --version
从 PyPI 网站下载适用于您系统的 NumPy wheel 文件。您可以在 Python 包索引上找到可用的 NumPy wheel 列表。pip install numpy-<version>-<architecture>.whl
pip install numpy-1.20.3-cp39-cp39-win_amd64.whl
import numpy
使用 Anaconda Python 发行版安装
您也可以通过下载 Anaconda Python 发行版来获取 NumPy。这种方式简单快捷!如果尚未下载,请前往官方网站获取。按照安装指引操作即可开始!
您或许会好奇为什么这更容易?
好处在于,使用该 Python 发行版,您无需为单独安装 NumPy 或数据分析中会用到的主要包(如 pandas、scikit-learn 等)而费心。
尤其是当您对 Python、编程或终端还很陌生时,Anaconda 已包含 最受欢迎的 100 个用于数据科学的 Python、R 与 Scala 包,这将让人如释重负。但即便对更有经验的数据科学家而言,如果您想快速开始解决数据科学问题,Anaconda 也是不错的选择。
此外,Anaconda 还包含多个开源开发环境,如 Jupyter 和 Spyder。如果您希望在本教程后开始使用 Jupyter Notebook,请参阅我们的 Jupyter Notebook 教程。
简言之,考虑下载 Anaconda,以便开始使用 numpy 及其他与数据科学相关的包!
如何创建 NumPy 数组
现在您已完成环境搭建,是时候开始正题了。诚然,您在上面的代码中已对数组做过一些尝试。不过,由于您首先需要在自己的电脑上安装 NumPy,因此并未真正进行动手练习。既然现在已经完成,让我们看看要在本地运行上述代码片段需要做什么。
要创建一个 numpy 数组,您可以直接使用 np.array() 函数。您只需向其传递一个列表,并可选地指定数据类型。如果想了解可选择的数据类型,请参阅此指南,或简要查看 DataCamp 的 NumPy 速查表。
如果您是新手,无需去死记硬背这些 NumPy 数据类型,但您确实需要了解并关心自己在处理什么数据。当您需要更细粒度地控制数据在内存与磁盘中的存储方式时,数据类型就派上用场。尤其在处理海量数据时,能控制存储类型很重要。
别忘了,要使用 np.array() 函数,需确保环境中已安装 numpy 库。
NumPy 库有一个导入约定:导入该库时,应将其命名为 np。这样做可确保其他 Pythonista 更容易理解您的代码。
在下面的示例中,您将创建前面已用到的 my_array 数组:
import numpy as np
# Make the array `my_array`
my_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print `my_array`
print(my_array)
如果您想进一步了解如何创建列表,请查看我们的 Python 列表问题教程。
不过,有时您并不知道要在数组中放入哪些数据,或者您希望从其他来源将数据导入到 numpy 数组中。在这些情况下,您将分别使用初始占位符或从文本加载数据到数组的函数。
下列部分将向您展示如何做到这一点。
如何创建“空”的 NumPy 数组
人们常说要创建“空”数组,通常是指想使用可在之后填充的初始占位符。您可以用全 1 或全 0 初始化数组,也可以创建带有等间距值、常数或随机值的数组。
当然,您也可以创建完全空的数组。
幸运的是,NumPy 提供了相当多的创建函数。
在下面试一试吧!
import numpy as np
# Create an array of ones
ones_array = np.ones((3, 4))
print("Ones Array:")
print(ones_array)
print()
# Create an array of zeros
zeros_array = np.zeros((2, 3, 4), dtype=np.int16)
print("Zeros Array:")
print(zeros_array)
print()
# Create an array with random values
random_array = np.random.random((2, 2))
print("Random Array:")
print(random_array)
print()
# Create an empty array
empty_array = np.empty((3, 2))
print("Empty Array:")
print(empty_array)
print()
# Create a full array
full_array = np.full((2, 2), 7)
print("Full Array:")
print(full_array)
print()
# Create an array of evenly-spaced values
arange_array = np.arange(10, 25, 5)
print("Arange Array:")
print(arange_array)
print()
# Create an array of evenly-spaced values
np.linspace(0,2,9)
提示:多尝试上述函数,以便理解其工作方式!
- 对于 np.ones()、np.random.random()、np.empty()、np.full() 或 np.zeros() 等函数,要创建全 1 或全 0 数组,只需传入您想要的数组形状。作为 np.ones() 与 np.zeros() 的可选参数,您还可以指定数据类型。使用 np.full() 时,您还需要指定要填入数组的常数值。
- 使用 np.linspace() 和 np.arange() 可以创建等间距值的数组。二者的区别在于,上述代码片段中传入的第三个值,对 np.linspace() 来说表示样本数量,对 np.arange() 来说表示步长。前者例如是要一个在 0 与 2 之间包含 9 个值的数组。后者则指定从 10 开始,以步长 5 生成数组值。
记住,NumPy 还允许您使用 np.eye() 和 np.identity() 创建单位数组或单位矩阵。单位矩阵是一个方阵,其主对角线上所有元素为 1,其他元素为 0。当您将一个矩阵与单位矩阵相乘时,该矩阵保持不变。
换言之,按照标准的矩阵乘法约定,如果将一个矩阵与单位矩阵相乘,所得结果仍是该矩阵本身。
尽管本教程不以演示单位矩阵工作原理为重点,但可以说,当您开始进行矩阵计算时,单位矩阵会很有用:它可以简化数学方程,使计算更高效、更稳健。
如何从文本加载 NumPy 数组
借助初始占位符或示例数据创建数组,是开始学习 numpy 的极佳方式。但当您想开始数据分析时,就需要从文本文件加载数据。
仅凭目前所学,您做不了太多。请使用一些特定函数从文件加载数据,例如 loadtxt() 或 genfromtxt()。
假设您有以下包含数据的文本文件,您需要将下面代码中的注释值复制粘贴到文本文件中,并将其保存为 “data.txt”。然后即可使用如下代码:
# This is your data in the text file
# Value1 Value2 Value3
# 0.2536 0.1008 0.3857
# 0.4839 0.4536 0.3561
# 0.1292 0.6875 0.5929
# 0.1781 0.3049 0.8928
# 0.6253 0.3486 0.8791
# Import your data
x, y, z = np.loadtxt('data.txt',
skiprows=1,
unpack=True)
在上述代码中,您使用 loadtxt() 将数据加载到环境中。可以看到,两者的第一个参数都是文本文件 data.txt。接下来是各自特定的参数:在第一条语句中,您跳过第一行,并使用 unpack=TRUE 将列作为单独的数组返回。这意味着 Value1 列的值会放入 x,依此类推。
请注意,如果您有逗号分隔的数据,或想指定数据类型,还可以在 loadtxt() 的参数中添加 delimiter 与 dtype。
很简单、很直接,对吧?
让我们看看第二个数据文件:
# Your data in the text file
# Value1 Value2 Value3
# 0.4839 0.4536 0.3561
# 0.1292 0.6875 MISSING
# 0.1781 0.3049 0.8928
# MISSING 0.5801 0.2038
# 0.5993 0.4357 0.7410
my_array2 = np.genfromtxt('data2.txt',
skip_header=1,
filling_values=-999)
您会看到,这里使用 genfromtxt() 来加载数据。在该情况下,您需要处理由字符串 'MISSING' 表示的缺失值。
由于 genfromtxt() 函数会将数值列中的字符串转换为 nan,您可以通过指定 filling_values 参数将这些值转换成其他值。在这里,您选择将这些缺失值设为 -999。
如果恰好有一些值不会被 genfromtxt() 转换为 nan,您还可以使用 missing_values 参数来指定数据中究竟哪些是缺失值。
但这还不止如此。
提示:查看 numpy.genfromtxt 页面,了解还能添加哪些参数以成功导入数据。
您现在可能想知道这两个函数之间的区别。
示例或许已隐含指出:总体而言,genfromtxt() 给予您更大的灵活性;它比 loadtxt() 更健壮。
让我们更具体些:后者 loadtxt() 仅在文本文件中每一行具有相同数量的值时才奏效,因此当您希望轻松处理缺失值时,通常会发现使用 genfromtxt() 更容易。
但这绝不是唯一原因。
简要查看 genfromtxt() 提供的参数数量,您会发现确实可以在导入时指定更多内容,例如读取的最大行数,或自动去除变量空白的选项。
如何保存 NumPy 数组
当您完成对数组的所有操作后,也可以将它们保存到文件。如果您想将数组保存为文本文件,可以使用 savetxt() 函数:
import numpy as np
x = np.arange(0.0,5.0,1.0)
np.savetxt('test.out', x, delimiter=',')
请记住,np.arange() 会创建一个等间距值的 NumPy 数组。传递给该函数的第三个值是步长。
当然,还有其他方式将 NumPy 数组保存为文本文件。如果您想将数据保存为二进制文件或归档,请查看下表中的函数:
| save() | 将数组保存为 NumPy .npy 格式的二进制文件 |
| savez() | 将多个数组保存到未压缩的 .npz 归档中 |
| savez_compressed() | 将多个数组保存到压缩的 .npz 归档中 |
有关如何使用上述函数保存数据的更多信息或示例,请参阅 NumPy 参考页面,或使用 NumPy 提供的帮助函数即时了解更多!
不确定这些 NumPy 帮助函数是什么?
别担心!您将在接下来的某一节中进一步了解它们!
如何检查您的 NumPy 数组
除了上文提到的数组属性(data、shape、dtype、strides)之外,还有一些属性可帮助您轻松了解数组。对于初学者,以下这些可能比较有用:
import numpy as np
# Create a numpy array with shape (2,4) and dtype int64
my_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print the number of dimensions of `my_array`
print("Number of dimensions of my_array:")
print(my_array.ndim)
print()
# Print the number of elements in `my_array`
print("Number of elements in my_array:")
print(my_array.size)
print()
# Print information about the memory layout of `my_array`
print("Information about the memory layout of my_array:")
print(my_array.flags)
print()
# Print the length of one array element in bytes
print("Length of one array element in bytes:")
print(my_array.itemsize)
print()
# Print the total consumed bytes by `my_array`'s elements
print("Total consumed bytes by my_array's elements:")
print(my_array.nbytes)
print()
这些几乎涵盖了数组可能具有的所有属性。
如果此时觉得并非都对您有用,也不要担心。这很正常,因为正如上一节所述,只有在处理大型数据集时,您才会开始关心内存。
另请注意,除了这些属性,您还可以通过其他方式获取更多信息,甚至稍微调整数组:
import numpy as np
my_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print the length of `my_array`
print(len(my_array))
# Change the data type of `my_array`
my_array.astype(float)
现在,您已经通过 np.array() 或初始占位符函数自行创建了数组,或者通过 loadtxt() 或 genfromtxt() 函数加载了数据,是时候更深入地了解真正定义 NumPy 库的第二个关键元素:科学计算。
NumPy 广播如何工作
在深入科学计算之前,先了解什么是广播可能是个好主意:当您执行算术运算时,广播机制允许 NumPy 处理形状不同的数组。
下方的信息图展示了 NumPy 广播的一个示例:

更实际地说,您经常会有一个较大的数组和一个稍小的数组。理想情况下,您希望多次使用较小的数组,在较大的数组上执行某个操作(如求和、乘法等)。
要做到这一点,您会使用广播机制。
不过,使用它需要遵循一些规则。在您叹气之前,这些“规则”其实非常简单、直观!
- 首先,为确保广播成功,数组的维度需要兼容。两维在相等时视为兼容。考虑以下示例:
# Import the NumPy library and give it an alias of `np`
import numpy as np
# Initialize a 3x4 array of ones and assign it to the variable `x`
x = np.ones((3,4))
# Print the shape of the array `x`
print("Shape of x:", x.shape)
# Initialize a 3x4 array of random numbers between 0 and 1 and assign it to the variable `y`
y = np.random.random((3,4))
# Print the shape of the array `y`
print("Shape of y:", y.shape)
# Add the arrays `x` and `y` element-wise and print the resulting array
z = x + y
print("Result of x + y:\n", z)
# Print the shape of the resulting array
print("Shape of x + y:", z.shape)
- 当其中一个维度为 1 时,两维也兼容:
# Import the NumPy library and give it an alias of `np`
import numpy as np
# Initialize a 3x4 array of ones and assign it to the variable `x`
x = np.ones((3,4))
# Print the shape of the array `x`
print("Shape of x:", x.shape)
# Initialize a 3x4 array of random numbers between 0 and 1 and assign it to the variable `y`
y = np.random.random((3,4))
# Print the shape of the array `y`
print("Shape of y:", y.shape)
# Add the arrays `x` and `y` element-wise and print the resulting array
z = x + y
print("Result of x + y:\n", z)
# Print the shape of the resulting array
print("Shape of x + y:", z.shape)
请注意,如果维度不兼容,您将得到 ValueError。
提示:还请测试在完成计算后结果数组的大小!您会看到其大小其实是输入数组沿每个维度的最大尺寸。
换言之,您会发现 x-y 的结果给出一个形状为 (3,4) 的数组:y 的形状为 (4,),x 的形状为 (3,4)。结果数组的形状取 x 和 y 沿各维度的最大尺寸。
- 最后,只有当数组在所有维度上都兼容时,才能进行广播。请看以下示例:
# Import `numpy` as `np`
import numpy as np
# Initialize `x` and `y`
x = np.ones((3,4))
y = np.random.random((5,1,4))
# Add `x` and `y`
z = x + y
您会看到,尽管 x 与 y 的维度看起来有所不同,但二者仍可相加。
这是因为它们在所有维度上都兼容:
- 数组 x 的维度是 3 × 4,
- 数组 y 的维度是 5 × 1 × 4
由于您已经看到当其中一个维度等于 1 时维度也兼容,因此这两个数组确实适合进行广播!
您会注意到,在 y 的某个维度大小为 1、而另一个数组该维度大小大于 1(即 3)的情况下,第一个数组会像是在该维度上被复制一样。
请注意,结果数组的形状同样将是 x 与 y 沿各维度的最大尺寸:结果的维度将为 (5,3,4)
简而言之,若要使用广播,您将大量依赖所使用数组的形状与维度。
但如果维度不兼容怎么办?
如果它们既不相等、也没有一个等于 1,该怎么办?
您需要通过操作数组来修复它!稍后章节将介绍如何做到这一点。
数组运算如何工作?
您已经看到广播在执行算术运算时很有用。本节将介绍一些可用于对数组进行数学运算的函数。
因此,您大概不会惊讶:可以直接使用 +、-、*、/ 或 % 对两个(或更多)数组进行加、减、乘、除或取余。不过,NumPy 之所以好用,原因很大一部分在于它还提供了相应的函数。上述运算的等效函数分别是 np.add()、np.subtract()、np.multiply()、np.divide() 和 np.remainder()。
您还可以使用 np.exp() 与 np.sqrt() 轻松进行指数与开方运算,或者使用 np.sin() 与 np.cos() 计算数组的正弦与余弦。最后,还值得一提的是,您可以使用 np.log() 计算自然对数,或对数组调用 dot() 计算点积。
在下面的代码中全部试试吧。
小提示:先查看为本练习加载的数组!
# Import `numpy` as `np`
import numpy as np
x = np.array([[1, 2, 3], [3, 4, 5]])
y = np.array([6,7,8])
# Add `x` and `y`
z = np.add(x,y)
print("Addition of x and y:\n", z)
# Subtract `x` and `y`
z = np.subtract(x,y)
print("Subtraction of y from x:\n", z)
# Multiply `x` and `y`
z = np.multiply(x,y)
print("Element-wise multiplication of x and y:\n", z)
还记得广播如何工作吗?在 IPython 终端中检查 x 和 y 的维度与形状。是否遵守了广播规则?
但还有更多内容。
请查看这份聚合函数小清单:
| a.sum() | 数组整体求和 |
| a.min() | 数组整体最小值 |
| b.max(axis=0) | 数组某行的最大值 |
| b.cumsum(axis=1) | 元素的累积和 |
| a.mean() | 均值 |
| b.median() | 中位数 |
| a.corrcoef() | 相关系数 |
| np.std(b) | 标准差 |
除了这些函数,您还可以比较数组元素。例如,如果您想检查两个数组的元素是否相同,可以使用 == 运算符。要检查元素是否更小或更大,使用 < 或 > 运算符。
这看起来都很直观,对吗?
不过,您也可以比较整个数组!此时,使用 np.array_equal() 函数。只需传入要相互比较的两个数组即可。
请注意,除了比较,还可以对数组执行逻辑运算。您可以从 np.logical_or()、np.logical_not() 与 np.logical_and() 开始。这基本和常见的 OR、NOT、AND 逻辑运算一样;
最简单的例子中,您可使用 OR 来查看元素是否相同(例如都是 1),或两个数组元素之一是否为 1。如果两者均为 0,则返回 FALSE。AND 用于查看第二个元素是否也为 1,NOT 用于查看第二个元素是否不同于 1。
在下面的代码片段中试试吧:
# Import `numpy` as `np`
import numpy as np
# Initialize arrays
a = np.array([1, 1, 0, 0], dtype=bool)
b = np.array([1, 0, 1, 0], dtype=bool)
# `a` AND `b`
np.logical_and(a, b)
# `a` OR `b`
np.logical_or(a, b)
# `a` NOT `b`
np.logical_not(a,b)
如何子集化、切片与索引数组
除了数学运算,您可能还想从原始数组(或结果数组)中取出一部分,或只取出某些数组元素,以用于进一步分析或其他操作。在这种情况下,您需要对子集化、切片和/或索引数组。
这些操作与对 Python 列表执行时非常相似。如果您想自行检验相似点,或想要更详细的说明,可以查看 DataCamp 的 Python 列表教程。
如果您完全不了解这些操作的工作方式,现阶段只需掌握两点基础知识:
- 使用方括号 [] 作为索引运算符,且
- 通常向这些方括号中传入整数,但也可以放置冒号 :,或将冒号与整数组合,以指示您要选择的元素/行/列。
子集化(Subsetting)
除了上述两点,了解这些如何组合在一起的最简单方式是查看一些子集化示例:
import numpy as np
# Initialize 1D array
my_array = np.array([1,2,3,4])
# Print subsets
print(my_array[1])
import numpy as np
# Initialize 2D array
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print subsets
print(my_2d_array[1][2])
print(my_2d_array[1,2])
import numpy as np
# Initialize 3D array
my_3d_array = np.array([[[1,2,3,4], [5,6,7,8]], [[1,2,3,4], [9,10,11,12]]], dtype=np.int64)
# Print subset
print(my_3d_array[1,1,2])
切片(Slicing)
相较子集化更进一步的是切片。此处,您不只考虑数组中的特定值,而是上升到行与列的层面。基本上,您是在处理数据的“区域”,而不仅是纯“位置”。
您可以在以下代码示例中理解这一类比:
import numpy as np
# Initialize 1D array
my_array = np.array([1,2,3,4])
# Print subsets
print(my_array[0:2])
import numpy as np
# Initialize 2D array
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print subsets
print(my_2d_array[0:2,1])
import numpy as np
# Initialize 3D array
my_3d_array = np.array([[[1,2,3,4], [5,6,7,8]], [[1,2,3,4], [9,10,11,12]]], dtype=np.int64)
# Print subset
print(my_3d_array[1,...])
您会看到,本质上遵循以下规则:
a[start:end] # items start through the end (but the end is not included!)
a[start:] # items start through the rest of the array
a[:end] # items from the beginning through the end (but the end is not included!)
在上述代码中,每个数组分别初始化,子集在单独的代码块中打印。my_array、my_2d_array 与 my_3d_array 分别是一维、二维与三维数组,子集采用索引表示法打印。
在第一个示例中,我们用切片选择 my_array 的索引 0 与 1 的项。在第二个示例中,我们用切片选择 my_2d_array 的第 0、1 行,第 1 列的项。在第三个示例中,我们使用 ... 表示法选择 my_3d_array 的第一与第二维上的所有元素,以及第三维上的第二个元素。
索引(Indexing)
最后还有索引。就 NumPy 而言,有布尔索引与高级(或“花式”)索引。
(如果您在想,这确实是 NumPy 术语,最后一个不是我编的!)
先说布尔索引。此处,您并非基于索引号选择元素、行或列,而是选择数组中满足某个条件的值。
实现为代码非常容易:
import numpy as np
my_array = np.array([1,2,3,4])
my_3d_array = np.array([[[1,2,3,4], [5,6,7,8]], [[1,2,3,4], [9,10,11,12]]], dtype=np.int64)
# Try out a simple example
print(my_array[my_array<2])
# Specify a condition
bigger_than_3 = (my_3d_array >= 3)
# Use the condition to index our 3d array
print(my_3d_array[bigger_than_3])
请注意,指定条件时也可以使用逻辑运算符 |(OR)和 &(AND)。如果要用这种方式改写上述条件(这会比较低效,但此处用于教学演示 :)),您会得到 bigger_than_3 = (my_3d_array > 3) | (my_3d_array == 3)。
对于已加载的数组,可尝试的可能性不多,但如果数组包含例如姓名或首都名称,可能性将无穷无尽!
对于花式索引,基本做法是:传入一个整数列表或数组,以指定要从原始数组中选择的行子集的顺序。
听起来有点抽象?
别担心,在下面的代码片段中试一试吧:
import numpy as np
my_array = np.array([1,2,3,4])
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
my_3d_array = np.array([[[1,2,3,4], [5,6,7,8]], [[1,2,3,4], [9,10,11,12]]], dtype=np.int64)
# Select elements at (1,0), (0,1), (1,2) and (0,0)
print(my_2d_array[[1, 0, 1, 0],[0, 1, 2, 0]])
# Select a subset of the rows and columns
print(my_2d_array[[1, 0, 1, 0]][:,[0,1,2,0]])
现在,第二条语句乍看之下可能有点难以理解。这很正常。如果您将其拆开来看,会更清晰:
- 如果只执行 my_2d_array[[1,0,1,0]],结果如下:
array([[5, 6, 7, 8],
[1, 2, 3, 4],
[5, 6, 7, 8],
[1, 2, 3, 4]])
- 第二部分 [:,[0,1,2,0]] 的意思是,保留该结果的全部行,但稍微调整列的顺序。您要按当前顺序显示第 0、1、2 列,但将第 0 列重复为最后一列,而不是显示第 3 列。这样即可得到如下结果:
array([[5, 6, 7, 5],
[1, 2, 3, 1],
[5, 6, 7, 5],
[1, 2, 3, 1]])
高级索引显然已经难不倒您了!
如何寻求帮助
作为一个小插曲,您应当知道,尤其是在刚开始使用 NumPy 时,您随时都可以就正在使用的模块、函数或类获取更多信息,因为 NumPy 一开始可能会让人感觉信息量很大。
寻求帮助相当容易。
您只需使用 numpy 提供的特定帮助函数即可上路:
- 使用 lookfor() 在文档字符串上进行关键字搜索。这对初学者尤其有用,因为背后的“理论”可能会从记忆中淡化。唯一的缺点是,如果您的查询不够具体(如下方代码示例),就得翻阅所有搜索结果,这可能让信息更杂乱难读。
- 对函数、类或模块使用 info() 获取简要说明与代码示例。如果您是通过实践学习的人,这是不错的方式!使用该函数唯一的缺点可能是,您需要了解某些属性或函数所在的模块。如果不太明白这是什么意思,请查看下面的代码示例。
如您所见,这两个函数各有优缺点,但您会体会到两者都很有用:在下面的代码片段中亲自试试吧!
import numpy as np
# Look up info on `mean` with `np.lookfor()`
print(np.lookfor("mean"))
# Get info on data types with `np.info()`
np.info(np.ndarray.dtype)
请注意,您确实需要知道 dtype 是 ndarray 的一个属性。另外,别忘了在所询问信息的模块、类或术语前加上 np,否则您会得到类似如下的错误信息:
Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'ndarray' is not defined
现在,您已经知道如何寻求帮助了,这很重要。接下来本 NumPy 教程将介绍数组操作。
并不是说您无法自行攻克该主题,恰恰相反!
但有些函数可能会让您有疑问,比如调整大小与重塑(reshape)有何区别?
横向堆叠与纵向堆叠数组有什么不同?
下一节将解答这些问题,但如果您有任何疑惑,欢迎使用刚刚介绍的帮助函数快速上手。
如何操作数组
对数组执行数学运算是您会做的事情之一,但让这些运算与广播正常工作的关键,可能更重要的是懂得如何操作数组。
下面是您最常会用到的一些操作。
如何转置数组
转置数组的本质是对其维度进行置换。换句话说,您在交换数组的形状。让我们用一个小例子来展示转置的效果:
import numpy as np
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print `my_2d_array`
print(my_2d_array)
# Transpose `my_2d_array`
print(np.transpose(my_2d_array))
# Or use `T` to transpose `my_2d_array`
print(my_2d_array.T)
提示:如果数组与其转置版本的直观对比不够清晰,请检查两个数组的形状,以确保理解为何维度发生置换。
请注意,这里有两种转置方式。两者做同样的事,区别不大。需要注意的是,T 更像是便利函数,而 np.transpose() 则灵活性更高。因此,若您需要更多参数,建议优先使用 np.transpose()。
对于一维以上的数组,转置一切顺利,但如果只是一个一维数组,会有任何效果吗?
请在下面的代码片段中自行尝试。您的一维数组已经加载好了:
import numpy as np
my_array = np.array([1,2,3,4])
# Print `my_2d_array
print(my_array)
# Transpose `my_2d_array
print(np.transpose(my_array))
# Or use `T` to transpose `my_2d_array
print(my_array.T)
您完全正确!转置一维数组并无效果!
重塑(Reshape)与调整大小(Resize)的区别
您可能已在广播部分读到,若要进行算术运算,数组的维度需要兼容。但当维度不兼容时该怎么办,这个问题尚未回答。
现在就来解答!
当数组的维度不一致时,您可以调整数组大小。此时返回一个新数组,其形状为您传递给 np.resize() 的形状。如果将原数组与新维度一起传入,且新数组大于原始数组,则新数组会用原数组的拷贝重复填充,直到满足大小为止。
但是,如果只是对数组调用 np.resize() 并传入新形状,新数组将用零填充。
让我们用一个示例来试试:
import numpy as np
x = np.ones((3,4))
# Print the shape of `x`
print(x.shape)
# Resize `x` to ((6,4))
np.resize(x, (6,4))
# Try out this as well
x.resize((6,4))
# Print out `x`
print(x)
除了调整大小,您还可以重塑数组。这意味着在不改变数据的情况下为数组赋予新形状。重塑的关键是确保新数组的总大小不变。以数组 x 为例,上文中它的大小为 3 × 4 或 12,您必须确保新数组的大小也为 12。
嘘……如果您想通过代码计算数组大小,请使用 size 属性:x.size 或 x.reshape((2,6)).size:
import numpy as np
# Initialize array with shape (3,4) containing all ones
x = np.ones((3,4))
# Print the shape of `x`
print("Shape of x:", x.shape)
# Resize `x` to ((6,4))
np.resize(x, (6,4))
# Try out this as well
x.resize((6,4))
# Print out `x` before and after resizing
print("Array before transposing:\n", x)
print("Shape of array before transposing:", x.shape)
# Transpose `x`
x = x.T
# Print out `x` after transposing
print("Array after transposing:\n", x)
print("Shape of array after transposing:", x.shape)
输出:
Shape of x: (3, 4)
Array before transposing:
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
Shape of array before transposing: (6, 4)
Array after transposing:
[[1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1.]]
Shape of array after transposing: (4, 6)
如果上述方法都不奏效,您还可以将一个数组追加到原数组,或插入、删除数组元素,以确保维度与您要用于计算的另一个数组相匹配。
在更改数组形状时,另一个可能派上用场的操作是 ravel()。该函数可将数组扁平化。也就是说,如果您有二维、三维或 n 维数组,可以直接用它将数组展平成一维数组。
相当方便,不是吗?
如何追加数组
当您将数组追加到原数组时,它们会“粘”在原数组的末尾。如果希望追加的内容不是放在末尾,可以考虑插入。若想了解更多,请前往下一节。
得益于 NumPy,追加操作非常简单;您可以直接使用 np.append()。
请在下面的代码片段中查看其用法。别忘了,您可以随时在 IPython 终端中输入例如 my_array 并按回车,检查已加载的数组。
import numpy as np
my_array = np.array([1,2,3,4])
# Print `my_array` before appending
print("my_array before appending:", my_array)
# Append a 1D array to `my_array`
new_array = np.append(my_array, [7, 8, 9, 10])
# Print `new_array`
print("new_array:", new_array)
# Print `my_array` after appending
print("my_array after appending:", my_array)
输出:
my_array before appending: [1 2 3 4]
new_array: [ 1 2 3 4 7 8 9 10]
my_array after appending: [1 2 3 4]
注意,my_array 在追加后保持不变,因为 np.append() 返回的是新数组,而不是就地修改原数组。
对 my_2d_array 进行追加:
import numpy as np
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Print `my_2d_array` before appending
print("my_2d_array before appending:\n", my_2d_array)
# Append an extra column to `my_2d_array`
new_2d_array = np.append(my_2d_array, [[7], [8]], axis=1)
# Print `new_2d_array`
print("new_2d_array:\n", new_2d_array)
# Print `my_2d_array` after appending
print("my_2d_array after appending:\n", my_2d_array)
输出:
my_2d_array before appending:
[[1 2 3 4]
[5 6 7 8]]
new_2d_array:
[[1 2 3 4 7]
[5 6 7 8 8]]
my_2d_array after appending:
[[1 2 3 4]
[5 6 7 8]]
请注意,在为 my_2d_array 追加额外一列时,指定了轴。记住,在二维数组中,axis 1 表示列,axis 0 表示行。
如何插入与删除数组元素
除了追加,您还可以插入与删除数组元素。您大概已经猜到,执行这些操作的函数分别是 np.insert() 与 np.delete():
import numpy as np
my_array = np.array([1,2,3,4])
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Insert `5` at index 1
np.insert(my_array, 1, 5)
# Delete the value at index 1
np.delete(my_array,[1])
如何合并与拆分数组
您还可以“合并”或连接数组。为此有一组函数可用,其中大多数列示如下。
试一试它们,同时也请在 IPython 终端中查看数组的形状。已加载的数组包括 x、my_array、my_resized_array 与 my_2d_array。
import numpy as np
x = np.ones((4,))
my_array = np.array([1,2,3,4])
my_resized_array=np.resize(my_array,(2,4))
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
# Concatentate `my_array` and `x`
print(np.concatenate((my_array,x)))
# Stack arrays row-wise
print(np.vstack((my_array, my_2d_array)))
# Stack arrays row-wise
print(np.r_[my_resized_array, my_2d_array])
# Stack arrays horizontally
print(np.hstack((my_resized_array, my_2d_array)))
# Stack arrays column-wise
print(np.column_stack((my_resized_array, my_2d_array)))
# Stack arrays column-wise
print(np.c_[my_resized_array, my_2d_array])
在使用这些函数时,您会注意到以下几点:
- 如果想用 np.concatenate() 连接两个数组,维度数量需要相同。因此,如果要与一维的 my_array 拼接,第二个数组也需要是一维。
- 使用 np.vstack(),您可以轻松将 my_array 与 my_2d_array 组合。只需确保在按行堆叠数组时,两数组的列数相同。因此,您也可以向 my_2d_array 添加形状为 (2,4) 或 (3,4) 的数组,只要列数匹配即可。换言之,除第一轴外,数组在其余各轴上的形状必须一致。使用 np.r[] 时同理。
- 对于 np.hstack(),需要确保维度数量相同,且两数组的行数一致。这意味着您可以将形状为 (2,3) 或 (2,4) 的数组与形状为 (2,4) 的 my_2d_array 横向堆叠。只要行数匹配,一切皆可。该函数仍受 NumPy 支持,但建议优先使用 np.concatenate() 或 np.stack()。
- 使用 np.column_stack() 时,需要确保输入数组的第一维相同。在此例中,两个形状相同;但即便 my_resized_array 为 (2,1) 或 (2,),数组也仍会被堆叠。
- np.c_[] 是另一种连接方式。此处同样要求两个数组的第一维匹配。
当您合并了数组后,可能还希望在某个时刻将它们拆分。就像可以横向堆叠一样,也可以纵向进行。分别使用 np.hsplit() 与 np.vsplit():
import numpy as np
my_array = np.array([1,2,3,4])
my_resized_array=np.resize(my_array,(2,4))
my_2d_array = np.array([[1,2,3,4], [5,6,7,8]], dtype=np.int64)
my_stacked_array = np.hstack((my_resized_array, my_2d_array))
# Split `my_stacked_array` horizontally at the 2nd index
print(np.hsplit(my_stacked_array, 2))
# Split `my_stacked_array` vertically at the 2nd index
print(np.vsplit(my_stacked_array, 2))
使用这两个拆分函数时,需要牢记的可能是数组的形状。以以上情况为例:my_stacked_array 的形状为 (2,8)。当您选择希望发生拆分的位置索引时,需要牢记其形状。
如何可视化 NumPy 数组
最后,了解如何绘制数组也一定会很有用。这在数据探索阶段尤为便利,也同样适用于数据科学工作流程的后续阶段,当您想要可视化数组时。
使用 np.histogram()
与函数名可能暗示的不同,np.histogram() 并不会绘制直方图,而是计算数组中落入各个箱(bin)的出现次数;这将决定直方图中每个条形的面积。
传入给 np.histogram() 的首先是输入数据或您正在处理的数组。在计算直方图时,数组会被扁平化。
# Import `numpy` as `np`
import numpy as np
# Initialize your array
my_3d_array = np.array([[[1,2,3,4], [5,6,7,8]], [[1,2,3,4], [9,10,11,12]]], dtype=np.int64)
# Pass the array to `np.histogram()`
print(np.histogram(my_3d_array))
# Specify the number of bins
print(np.histogram(my_3d_array, bins=range(0,13)))
您会看到,作为结果,直方图被计算出来:第一个数组列出数组全部元素的频率,第二个数组列出在未指定箱时将使用的箱。
如果指定了箱的数量,计算结果会不同:浮点数将消失,您将看到箱都是整数。
此外,还有一些其他参数可供指定,以影响计算出的直方图。您可以在这里找到它们。
但如果无法可视化,计算这样的直方图又有何意义?
借助 Matplotlib,可视化轻而易举,但您无需用 np.histogram() 来计算直方图。plt.hist() 在您传入(扁平化的)数据与箱时,会自行完成计算:
# Import numpy and matplotlib
import numpy as np
import matplotlib.pyplot as plt
# Construct the histogram with a flattened 3d array and a range of bins
plt.hist(my_3d_array.ravel(), bins=range(0,13))
# Add a title to the plot
plt.title('Frequency of My 3D Array Elements')
# Show the plot
plt.show()
上述代码将为您生成如下(基础)直方图:

使用 np.meshgrid()
另一种(间接)可视化数组的方式是使用 np.meshgrid()。使用数组面临的问题是,您需要 x 与 y 坐标值的二维数组。借助该函数,您可以从一维的 x 值数组与一维的 y 值数组创建一个矩形网格:np.meshgrid() 接受两个一维数组,生成两个二维矩阵,分别对应两个数组中所有 (x, y) 组合。随后,您可以使用这些矩阵绘制各种图形。
如果您想在网格上对函数进行求值,np.meshgrid() 尤其有用,如下代码所示:
# Import NumPy and Matplotlib
import numpy as np
import matplotlib.pyplot as plt
# Create an array
points = np.arange(-5, 5, 0.01)
# Make a meshgrid
xs, ys = np.meshgrid(points, points)
z = np.sqrt(xs ** 2 + ys ** 2)
# Display the image on the axes
plt.imshow(z, cmap=plt.cm.gray)
# Draw a color bar
plt.colorbar()
# Show the plot
plt.show()
上述代码给出如下结果:

超越数据分析的 NumPy
恭喜您,已到达本 NumPy 教程的结尾!
您已经覆盖了大量内容,现在需要确保巩固所学。别忘了获取 DataCamp 的 NumPy 速查表来助您一臂之力!
在这些理论之后,也该用本教程学到的概念与技术多做练习了。一个办法是返回 scikit-learn 教程,继续用其中的用于构建机器学习模型的数据数组展开实验。
如果这不是您的菜,请再检查一下是否已下载 Anaconda。然后,结合这份 Jupyter Notebook 权威指南,在 Jupyter 中开始使用 NumPy 数组。也别忘了查看 这个 Jupyter Notebook,它也会在 Jupyter 的交互式数据科学环境中,带您使用 NumPy 与其他库进行 Python 数据分析。
最后,考虑看看 DataCamp 的数据处理与可视化课程。尤其是我们与 Continuum Analytics 合作推出的最新课程一定会让您感兴趣!请浏览 用 Pandas 操作 DataFrame 或 Pandas 基础 课程。