如何学习 NumPy?这篇教程带你入门 – wiki基地


如何学习 NumPy?这篇超详细教程带你轻松入门数据科学基石!

欢迎来到数据科学的世界!如果你正踏上这条令人兴奋的道路,那么你很快就会遇到一个名字:NumPy。NumPy 是 Python 中用于科学计算和数据分析的核心库,尤其擅长处理大型多维数组和矩阵。无论是进行复杂的数学运算、统计分析,还是为机器学习模型准备数据,NumPy 都是不可或缺的强大工具。

对于初学者来说,NumPy 的概念和功能可能看起来有些 daunting(令人望而生畏),但请放心,它远没有你想的那么复杂。一旦你掌握了 NumPy 的核心思想——多维数组(ndarray)以及它提供的各种便捷操作,你的数据处理效率将会大大提升。

本文将作为你的 NumPy 入门指南。我们将从最基础的概念开始,一步步深入,直到你能够熟练使用 NumPy 进行常见的数据操作。准备好了吗?让我们开始这段 NumPy 学习之旅吧!

1. 为什么需要学习 NumPy?它有什么优势?

在深入学习之前,我们先来了解一下 NumPy 为什么如此重要,以及它相对于 Python 原生列表(list)的优势。

想象一下,你需要对一百万个数字执行同一个数学运算(比如,每个数字都加 5)。如果使用 Python 列表,你可能需要写一个循环:

“`python

Python 列表示例

my_list = list(range(1000000))
new_list = []
for number in my_list:
new_list.append(number + 5)

这段代码可以工作,但对于大量数据,它相对较慢

“`

这段代码是可行的,但在处理大量数据时,Python 循环的效率通常不高。这是因为 Python 的列表是动态的,可以存储不同类型的对象,并且循环是在解释器层面进行的。

现在,让我们看看 NumPy 如何处理相同的任务:

“`python

NumPy 数组示例

import numpy as np

my_array = np.arange(1000000) # 创建一个包含100万个元素的NumPy数组
new_array = my_array + 5

这段代码非常快!

“`

看到了吗?代码更简洁,而且 NumPy 数组的操作是向量化的(vectorized),这意味着运算是在底层编译好的代码(通常是 C、C++ 或 Fortran)中执行的,速度非常快。

NumPy 的主要优势归结为以下几点:

  • 性能卓越: NumPy 操作是在编译过的代码中执行的,比 Python 列表上的循环快得多。尤其在处理大规模数组时,性能优势更为显著。
  • 内存效率高: NumPy 数组是同构的(homogeneous),即所有元素的数据类型相同。这使得 NumPy 能够更紧凑地存储数据,从而节省内存。而 Python 列表可以存储不同类型的对象,每个对象都需要额外的开销来存储类型信息和引用计数。
  • 功能丰富: NumPy 提供了大量的数学函数、统计函数、线性代数函数、随机数生成功能等,这些都是科学计算中常用的工具。
  • 生态系统基石: 许多流行的数据科学库,如 Pandas、SciPy、Matplotlib、Scikit-learn、TensorFlow 和 PyTorch,都是建立在 NumPy 数组的基础之上的。学习 NumPy 是使用这些库的前提。

因此,无论你是想进行数据分析、机器学习、图像处理、信号处理还是任何需要处理大量数值数据的任务,掌握 NumPy 都是至关重要的。

2. 安装 NumPy

学习 NumPy 的第一步是安装它。如果你的 Python 环境还没有安装 NumPy,可以使用 pip 包管理器进行安装。

打开你的终端或命令提示符,运行以下命令:

bash
pip install numpy

如果你使用的是 Anaconda 发行版,NumPy 通常已经预装好了。如果没有,可以使用 conda 命令安装:

bash
conda install numpy

安装完成后,你就可以在 Python 脚本或交互式环境中导入 NumPy 并开始使用了。通常,我们会使用一个标准别名 np 来导入 NumPy,这样写代码更简洁:

python
import numpy as np

从现在开始,本文中的所有 NumPy 功能都将通过 np. 前缀来访问。

3. NumPy 的核心:ndarray

NumPy 的核心是 ndarray 对象,它代表了一个多维同构数组(n-dimensional array)。

  • 多维: 它可以是一维的(类似于 Python 列表)、二维的(类似于表格或矩阵)、三维的(类似于立方体),甚至更高维的。
  • 同构: 数组中的所有元素必须是相同的数据类型(如整数、浮点数、布尔值等)。这与 Python 列表不同,后者可以包含各种类型的对象。

下面我们来学习如何创建和检查 ndarray

3.1 创建 ndarray

有多种方法可以创建 NumPy 数组:

从 Python 列表或元组创建:

这是最常见的方式之一,将已有的 Python 数据结构转换为 NumPy 数组。

“`python

从列表创建一维数组

list1 = [1, 2, 3, 4, 5]
arr1 = np.array(list1)
print(“一维数组:”, arr1)
print(“类型:”, type(arr1))

从列表的列表创建二维数组

list2 = [[1, 2, 3], [4, 5, 6]]
arr2 = np.array(list2)
print(“\n二维数组:\n”, arr2)

从元组创建

tuple1 = (10, 20, 30)
arr_from_tuple = np.array(tuple1)
print(“\n从元组创建数组:”, arr_from_tuple)
“`

创建全零、全一或指定值的数组:

在很多场景下,我们需要创建特定大小并用特定值填充的数组,NumPy 提供了方便的函数:

“`python

创建一个形状为 (2, 3) 的全零数组

zeros_array = np.zeros((2, 3))
print(“\n全零数组:\n”, zeros_array)

创建一个形状为 (3, 4) 的全一数组

ones_array = np.ones((3, 4))
print(“\n全一数组:\n”, ones_array)

创建一个形状为 (2, 2) 并用指定值填充的数组

full_array = np.full((2, 2), 7)
print(“\n指定值数组:\n”, full_array)

创建一个与现有数组形状和数据类型相同的全零数组

arr_like_zeros = np.zeros_like(arr2)
print(“\n像arr2一样的全零数组:\n”, arr_like_zeros)
“`

创建空数组:

np.empty() 创建一个数组,其内容是任意的(取决于内存中的现有内容)。这比创建全零或全一数组稍快,因为它不需要初始化数组元素,但要小心使用,因为里面的值是不可预测的。

“`python

创建一个形状为 (2, 3) 的空数组(内容随机)

empty_array = np.empty((2, 3))
print(“\n空数组:\n”, empty_array) # 注意:输出值是不确定的
“`

创建有序序列的数组:

类似于 Python 的 range() 函数,np.arange() 可以创建一个包含等间隔值的数组。

“`python

创建一个从 0 到 9 的数组

arr_range = np.arange(10)
print(“\narange 数组:”, arr_range)

创建一个从 5 到 14 的数组

arr_range_start_stop = np.arange(5, 15)
print(“arange (start, stop) 数组:”, arr_range_start_stop)

创建一个从 0 到 10,步长为 2 的数组

arr_range_step = np.arange(0, 11, 2)
print(“arange (start, stop, step) 数组:”, arr_range_step)

注意:arange 也可以处理浮点数步长,但可能存在精度问题

arr_range_float = np.arange(0, 1, 0.1)
print(“arange (float step) 数组:”, arr_range_float)
“`

np.linspace() 创建一个包含指定数量、均匀分布在指定间隔内的值的数组。这在科学计算中非常有用。

“`python

创建一个在 0 到 10 之间均匀分布的 5 个点

arr_linspace = np.linspace(0, 10, 5)
print(“\nlinspace 数组:”, arr_linspace)

创建一个在 0 到 1 之间均匀分布的 10 个点(包含起点和终点)

arr_linspace_inclusive = np.linspace(0, 1, 10)
print(“linspace 数组 (10 points):”, arr_linspace_inclusive)
“`

创建随机数的数组:

np.random 模块提供了多种创建随机数数组的功能,这在模拟和统计中非常常见。

“`python

创建一个形状为 (3, 3) 的,元素在 [0.0, 1.0) 之间均匀分布的数组

random_uniform = np.random.rand(3, 3)
print(“\n均匀分布随机数组:\n”, random_uniform)

创建一个形状为 (2, 4) 的,服从标准正态分布(均值0,方差1)的数组

random_normal = np.random.randn(2, 4)
print(“\n标准正态分布随机数组:\n”, random_normal)

创建一个形状为 (2, 3) 的,元素在 [0, 10) 之间随机整数的数组

random_integers = np.random.randint(0, 10, size=(2, 3))
print(“\n随机整数数组:\n”, random_integers)

设置随机种子,保证结果的可复现性

np.random.seed(42)
random_seeded = np.random.rand(2, 2)
print(“\n设置种子后的随机数组:\n”, random_seeded)
np.random.seed(42) # 再次设置相同种子
random_seeded_again = np.random.rand(2, 2)
print(“再次设置相同种子后的随机数组:\n”, random_seeded_again) # 输出与上面相同
“`

3.2 数组属性

创建数组后,我们可以访问其属性来了解它的结构和内容:

  • .shape: 一个元组,表示数组在每个维度上的大小。
  • .ndim: 数组的维数(轴数)。
  • .size: 数组中元素的总数。
  • .dtype: 数组元素的数据类型。
  • .itemsize: 数组中每个元素占用的字节数。
  • .nbytes: 数组占用的总字节数(等于 size * itemsize)。

“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])

print(“数组:\n”, arr)
print(“形状 (shape):”, arr.shape)
print(“维数 (ndim):”, arr.ndim)
print(“元素总数 (size):”, arr.size)
print(“数据类型 (dtype):”, arr.dtype)
print(“单个元素字节数 (itemsize):”, arr.itemsize)
print(“总字节数 (nbytes):”, arr.nbytes)

arr_float = np.array([[1.1, 2.2], [3.3, 4.4]], dtype=np.float64)
print(“\n浮点数数组:\n”, arr_float)
print(“数据类型 (dtype):”, arr_float.dtype)
print(“单个元素字节数 (itemsize):”, arr_float.itemsize)
“`

数据类型(dtype)是 NumPy 数组的一个重要特性。默认情况下,NumPy 会尝试从输入数据推断最合适的数据类型,但你也可以在创建数组时通过 dtype 参数明确指定。常见的数据类型包括:

  • np.int64 (或 np.int32, np.int16):64位(或32位,16位)整数
  • np.float64 (或 np.float32):64位(或32位)浮点数
  • np.bool:布尔值
  • np.str_:字符串
  • np.complex128:复数

指定合适的数据类型可以节省内存并确保计算精度。

4. 索引和切片

访问 NumPy 数组中的元素与 Python 列表类似,但功能更强大,尤其是在处理多维数组时。

4.1 基本索引

  • 一维数组: 与 Python 列表相同,使用方括号和索引号。

    python
    arr = np.arange(10)
    print("原数组:", arr)
    print("第一个元素:", arr[0])
    print("最后一个元素:", arr[-1])
    print("索引为 3 的元素:", arr[3])

  • 多维数组: 使用逗号分隔每个维度的索引。例如,对于二维数组 arr[row_index, column_index]

    python
    arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    print("\n二维数组:\n", arr2d)
    print("第 0 行,第 0 列的元素:", arr2d[0, 0])
    print("第 1 行,第 2 列的元素:", arr2d[1, 2])
    print("第 2 行,第 1 列的元素:", arr2d[2, 1])

4.2 切片 (Slicing)

切片用于获取数组的子部分。语法与 Python 列表切片类似:[start:stop:step]

  • 一维数组:

    python
    arr = np.arange(10)
    print("原数组:", arr)
    print("前 5 个元素:", arr[:5]) # 从开头到索引 4
    print("索引 5 及之后的元素:", arr[5:]) # 从索引 5 到结尾
    print("索引 2 到 7 之间的元素:", arr[2:8]) # 从索引 2 到索引 7
    print("每隔一个元素:", arr[::2]) # 从头到尾,步长为 2
    print("反转数组:", arr[::-1]) # 反转数组

  • 多维数组: 可以对每个维度进行切片,用逗号分隔。

    “`python
    arr2d = np.array([[1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]])
    print(“\n二维数组:\n”, arr2d)

    切片获取第一行

    print(“第一行:”, arr2d[0, :]) # 或直接 arr2d[0]

    切片获取所有行的第三列

    print(“第三列:”, arr2d[:, 2])

    切片获取前两行,所有列

    print(“前两行:\n”, arr2d[:2, :]) # 或 arr2d[:2]

    切片获取所有行,前两列

    print(“前两列:\n”, arr2d[:, :2])

    切片获取子矩阵:第一行到第二行(不含),第一列到第二列(不含)

    print(“子矩阵:\n”, arr2d[:2, :2])

    切片获取子矩阵:从索引 1 的行到索引 2 的行,从索引 1 的列到索引 2 的列

    print(“另一个子矩阵:\n”, arr2d[1:3, 1:3])

    切片时使用步长

    print(“隔行取:\n”, arr2d[::2, :])
    print(“隔列取:\n”, arr2d[:, ::2])
    “`

重要提示: NumPy 数组的切片是原始数组的 视图(view),而不是副本(copy)。这意味着对切片进行的修改也会反映在原始数组中。如果需要一个独立的副本,请使用 .copy() 方法。

“`python
arr_original = np.arange(10)
arr_slice = arr_original[5:8]
print(“\n原始数组:”, arr_original)
print(“切片:”, arr_slice)

修改切片

arr_slice[0] = 99
print(“修改切片后 – 原始数组:”, arr_original) # 原始数组也变了!
print(“修改切片后 – 切片:”, arr_slice)

创建一个副本

arr_copy = arr_original.copy()
print(“\n创建副本:”, arr_copy)

修改副本

arr_copy[0] = 100
print(“修改副本后 – 原始数组:”, arr_original) # 原始数组不受影响
print(“修改副本后 – 副本:”, arr_copy)
“`

理解视图和副本对于避免程序中不期望的副作用至关重要。

4.3 花式索引 (Fancy Indexing) 和布尔索引 (Boolean Indexing)

除了基本索引和切片,NumPy 还提供了更灵活的高级索引方法。

花式索引: 使用一个整数数组来指定要访问的元素或行/列的索引。这将返回一个包含指定元素的 副本

“`python
arr = np.arange(100).reshape(10, 10)
print(“\n原数组 (10×10):\n”, arr)

获取指定行 (索引 0, 2, 5)

rows = arr[[0, 2, 5]]
print(“\n获取指定行:\n”, rows)

获取指定列 (索引 1, 4, 8)

cols = arr[:, [1, 4, 8]]
print(“获取指定列:\n”, cols)

获取指定位置的元素 (索引对: (0, 1), (2, 4), (5, 8))

注意:这会创建一个一维数组,包含这些特定位置的元素

elements = arr[[0, 2, 5], [1, 4, 8]]
print(“获取指定位置元素:”, elements)

如果你想获取由这些行和列交叉形成的子数组,你需要使用切片和索引的组合,或者更复杂的花式索引技巧,但上面的例子是花式索引的基本用法。

例如,获取第 0, 2, 5 行,和第 1, 4 列交叉的子数组

sub_array = arr[[0, 2, 5]][:, [1, 4]]
print(“获取指定行和指定列交叉的子数组:\n”, sub_array)
“`

布尔索引 (掩码索引): 使用一个与数组形状相同的布尔数组来选择元素。布尔数组中为 True 的位置对应的元素会被选中。这通常用于根据条件过滤数组元素,返回的是一个一维数组(即便原数组是多维的),包含所有满足条件的元素的 副本

“`python
arr = np.arange(10)
print(“\n原数组:”, arr)

创建一个布尔数组(掩码)

mask = arr > 5
print(“布尔掩码:”, mask)

使用布尔掩码选择元素

selected_elements = arr[mask]
print(“大于 5 的元素:”, selected_elements)

可以直接在索引位置使用条件表达式

selected_elements_direct = arr[arr % 2 == 0]
print(“偶数元素:”, selected_elements_direct)

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(“\n二维数组:\n”, arr2d)

找到所有大于 5 的元素

mask2d = arr2d > 5
print(“二维布尔掩码:\n”, mask2d)
selected_elements2d = arr2d[mask2d]
print(“二维数组中大于 5 的元素:”, selected_elements2d) # 结果是一维数组
“`

布尔索引是 NumPy 中非常强大的数据过滤方式。

5. 数组运算

NumPy 数组支持各种各样的运算,包括元素级运算、广播机制以及矩阵运算。

5.1 元素级运算

NumPy 的核心优势之一是其对数组的向量化操作。基本的数学运算符(+, -, *, /, ** 等)以及比较运算符(>, <, ==, !=, >=, <=)都可以在数组上执行元素级运算。这意味着运算会独立地应用于数组中的每个元素。

“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

加法

print(“加法:”, arr1 + arr2) # [1+4, 2+5, 3+6] -> [5 7 9]

减法

print(“减法:”, arr1 – arr2) # [1-4, 2-5, 3-6] -> [-3 -3 -3]

乘法 (注意:这是元素级乘法,不是矩阵乘法)

print(“乘法:”, arr1 * arr2) # [14, 25, 3*6] -> [4 10 18]

除法

print(“除法:”, arr1 / arr2) # [1/4, 2/5, 3/6] -> [0.25 0.4 0.5 ]

指数

print(“指数:”, arr1 ** 2) # [1^2, 2^2, 3^2] -> [1 4 9]

余数

print(“余数:”, arr2 % arr1) # [4%1, 5%2, 6%3] -> [0 1 0]

比较运算

print(“arr1 > 2:”, arr1 > 2) # [False False True]
print(“arr1 == arr2:”, arr1 == arr2) # [False False False]
“`

NumPy 还提供了对应的函数版本,这些函数被称为 通用函数 (Universal Functions 或 ufuncs)。Ufuncs 是对 NumPy 数组进行快速元素级操作的函数。

python
print("np.add(arr1, arr2):", np.add(arr1, arr2))
print("np.sqrt(arr1):", np.sqrt(arr1))
print("np.exp(arr1):", np.exp(arr1))
print("np.sin(arr1):", np.sin(arr1))
print("np.maximum(arr1, arr2):", np.maximum(arr1, arr2)) # 元素级取最大值

5.2 广播 (Broadcasting)

广播是 NumPy 的一个强大机制,它允许 NumPy 在执行算术运算时,对不同形状的数组进行处理。当两个数组的形状不同时,NumPy 会尝试根据一组广播规则来“扩展”较小数组的形状,使其与较大数组的形状兼容,然后再执行元素级运算。

广播规则:

  1. 如果两个数组的维数不同,维数较小的数组会在前面填充形状为 1 的维度,直到它们的维数相同。
  2. 从后往前比较数组的形状(维度):
    • 如果两个维度大小相同,或者其中一个维度的大小为 1,则它们是兼容的。
    • 如果一个维度大小为 1,另一个大于 1,则大小为 1 的维度会被“广播”到与另一个维度相同的大小。
    • 如果维度大小不同且都不为 1,则广播会失败,抛出错误。

理解广播可以通过一些例子:

数组与标量相加:

python
arr = np.array([1, 2, 3])
scalar = 5
print("数组 + 标量:", arr + scalar) # 标量 5 被广播到数组 [5, 5, 5]

一维数组与二维数组相加:

这取决于它们的形状是否兼容。

“`python
arr2d = np.array([[1, 2, 3], [4, 5, 6]]) # 形状 (2, 3)
arr1d = np.array([10, 20, 30]) # 形状 (3,)

print(“二维数组 + 一维数组:\n”, arr2d + arr1d)

arr1d 的形状 (3,) 被广播为 (1, 3),然后这个 (1, 3) 再被广播到 (2, 3)

相当于 [[10, 20, 30], [10, 20, 30]]

然后执行元素级加法:

[[1, 2, 3] + [[10, 20, 30] = [[1+10, 2+20, 3+30] = [[11 22 33]

[4, 5, 6]] [10, 20, 30]] [4+10, 5+20, 6+30]] [14 25 36]]

再看一个例子

arr2d_b = np.array([[1, 2], [3, 4], [5, 6]]) # 形状 (3, 2)
arr1d_b = np.array([10, 20]) # 形状 (2,)
print(“\n另一个二维数组 + 一维数组:\n”, arr2d_b + arr1d_b)

arr1d_b 的形状 (2,) 被广播为 (1, 2),然后这个 (1, 2) 再被广播到 (3, 2)

相当于 [[10, 20], [10, 20], [10, 20]]

然后执行元素级加法:

[[1, 2] + [[10, 20] = [[1+10, 2+20] = [[11 22]

[3, 4] [10, 20] [3+10, 4+20] [13 24]

[5, 6]] [10, 20]] [5+10, 6+20]] [15 26]]

形状不兼容的例子

arr2d_c = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
arr1d_c = np.array([10, 20, 30]) # 形状 (3,)

print(arr2d_c + arr1d_c) # 这会引发 ValueError: operands could not be broadcast together with shapes (2,2) (3,)

“`

理解广播规则对于高效使用 NumPy 进行数组运算非常重要。

5.3 矩阵/线性代数运算

NumPy 的 np.dot() 函数或 @ 运算符用于执行矩阵乘法(点积)。这与前面提到的元素级乘法 * 不同。

“`python
matrix_a = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
matrix_b = np.array([[5, 6], [7, 8]]) # 形状 (2, 2)

矩阵乘法(使用 @ 运算符,Python 3.5+)

matrix_product = matrix_a @ matrix_b
print(“矩阵乘法 (@):\n”, matrix_product)

等价于 np.dot()

matrix_product_dot = np.dot(matrix_a, matrix_b)
print(“矩阵乘法 (np.dot):\n”, matrix_product_dot)

元素级乘法回顾

element_wise_product = matrix_a * matrix_b
print(“元素级乘法 (*):\n”, element_wise_product)

向量点积 (两个一维数组的点积就是对应元素乘积之和)

vec1 = np.array([1, 2])
vec2 = np.array([3, 4])
dot_product = np.dot(vec1, vec2) # 13 + 24 = 3 + 8 = 11
print(“\n向量点积:”, dot_product)
“`

NumPy 的 np.linalg 模块提供了更多线性代数功能,如计算逆矩阵、行列式、特征值等。

“`python
matrix = np.array([[1, 2], [3, 4]])

计算逆矩阵

try:
inverse_matrix = np.linalg.inv(matrix)
print(“\n逆矩阵:\n”, inverse_matrix)
# 验证:矩阵乘以其逆矩阵应得到单位矩阵
print(“矩阵 * 逆矩阵:\n”, matrix @ inverse_matrix)
except np.linalg.LinAlgError:
print(“\n矩阵是奇异的,无法计算逆矩阵。”)

计算行列式

determinant = np.linalg.det(matrix)
print(“行列式:”, determinant)
“`

6. 数组操作和重塑

NumPy 提供了多种函数来改变数组的形状、合并数组、分割数组等。

6.1 重塑 (Reshaping)

reshape() 方法允许你在不改变数组数据的情况下改变其形状。新的形状必须与原始数组的元素总数兼容。

“`python
arr = np.arange(12)
print(“原数组:”, arr) # 形状 (12,)

将一维数组重塑为 3×4 的二维数组

reshaped_arr = arr.reshape((3, 4))
print(“\n重塑为 3×4:\n”, reshaped_arr)

将数组重塑为 4×3

reshaped_arr2 = arr.reshape((4, 3))
print(“重塑为 4×3:\n”, reshaped_arr2)

可以使用 -1 让 NumPy 自动计算一个维度的大小

reshaped_arr3 = arr.reshape((2, -1)) # NumPy 会计算出第二个维度是 12 / 2 = 6
print(“重塑为 2x?:\n”, reshaped_arr3)

reshaped_arr4 = arr.reshape((-1, 3)) # NumPy 会计算出第一个维度是 12 / 3 = 4
print(“重塑为 ?x3:\n”, reshaped_arr4)

重塑为三维数组

reshaped_arr5 = arr.reshape((2, 2, 3))
print(“重塑为 2x2x3:\n”, reshaped_arr5)
“`

ravel()flatten() 可以将多维数组展平(转换为一维数组)。ravel() 返回一个视图(如果可能),而 flatten() 总是返回一个副本。

“`python
arr2d = np.arange(12).reshape(3, 4)
print(“\n原二维数组:\n”, arr2d)

raveled_arr = arr2d.ravel() # 返回视图(如果可能)
print(“展平 (ravel):\n”, raveled_arr)

flattened_arr = arr2d.flatten() # 返回副本
print(“展平 (flatten):\n”, flattened_arr)

修改 ravel() 返回的视图会影响原数组

raveled_arr[0] = 999
print(“修改 ravel() 后的原数组:\n”, arr2d)

修改 flatten() 返回的副本不会影响原数组

flattened_arr[0] = 888
print(“修改 flatten() 后的原数组:\n”, arr2d) # 原数组不变
“`

6.2 转置 (Transposing)

transpose() 方法或 .T 属性用于转置数组(交换行列)。

“`python
arr = np.arange(6).reshape(2, 3)
print(“\n原数组:\n”, arr)

转置

transposed_arr = arr.transpose()
print(“转置:\n”, transposed_arr)

使用 .T 属性

transposed_arr_T = arr.T
print(“转置 (.T):\n”, transposed_arr_T)
“`

对于更高维度的数组,transpose() 可以接受一个轴的顺序元组来重新排列轴。

6.3 合并 (Concatenating/Stacking)

可以使用 np.concatenate() 或更方便的 np.vstack() (row_stack) 和 np.hstack() (column_stack) 来合并多个数组。

np.concatenate() 允许你指定沿哪个轴进行合并。

“`python
arr1 = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
arr2 = np.array([[5, 6], [7, 8]]) # 形状 (2, 2)

沿轴 0 合并(垂直堆叠,像 stacking rows)

concatenated_axis0 = np.concatenate([arr1, arr2], axis=0)
print(“\n沿轴 0 合并:\n”, concatenated_axis0)

结果形状 (4, 2)

沿轴 1 合并(水平堆叠,像 stacking columns)

concatenated_axis1 = np.concatenate([arr1, arr2], axis=1)
print(“沿轴 1 合并:\n”, concatenated_axis1)

结果形状 (2, 4)

“`

np.vstack()np.hstack()np.concatenate() 的特殊情况,更直观:

  • np.vstack(): 垂直堆叠(沿轴 0 合并)。
  • np.hstack(): 水平堆叠(沿轴 1 合并)。

“`python
vstacked_arr = np.vstack([arr1, arr2])
print(“\n垂直堆叠 (vstack):\n”, vstacked_arr)

hstacked_arr = np.hstack([arr1, arr2])
print(“水平堆叠 (hstack):\n”, hstacked_arr)

注意:vstack 和 hstack 对一维数组的处理

arr_1d_a = np.array([1, 2])
arr_1d_b = np.array([3, 4])
vstacked_1d = np.vstack([arr_1d_a, arr_1d_b])
print(“\n垂直堆叠一维数组:\n”, vstacked_1d) # 将一维数组视为一行,堆叠后变成二维 (2, 2)

hstacked_1d = np.hstack([arr_1d_a, arr_1d_b])
print(“水平堆叠一维数组:\n”, hstacked_1d) # 将一维数组连接起来,仍然是一维 (4,)
“`

np.stack() 可以在新创建的轴上堆叠数组。

“`python
arr1 = np.array([1, 2])
arr2 = np.array([3, 4])
stacked_arr = np.stack([arr1, arr2], axis=0) # 沿新轴 0 堆叠
print(“\nnp.stack (axis=0):\n”, stacked_arr) # 结果形状 (2, 2)

stacked_arr_axis1 = np.stack([arr1, arr2], axis=1) # 沿新轴 1 堆叠
print(“np.stack (axis=1):\n”, stacked_arr_axis1) # 结果形状 (2, 2)
“`

6.4 分割 (Splitting)

np.split(), np.vsplit(), np.hsplit() 用于将数组分割成多个较小的数组。

  • np.split(arr, indices_or_sections, axis): 按指定轴和索引或段数分割。
  • np.vsplit(arr, indices_or_sections): 沿垂直方向(轴 0)分割。
  • np.hsplit(arr, indices_or_sections): 沿水平方向(轴 1)分割。

“`python
arr = np.arange(16).reshape(4, 4)
print(“\n原数组:\n”, arr)

垂直分割成 2 等份

v_split = np.vsplit(arr, 2)
print(“\n垂直分割 (2 份):\n”, v_split) # 结果是包含两个数组的列表

垂直分割,在索引 1 和 3 之后分割

v_split_indices = np.vsplit(arr, [1, 3])
print(“垂直分割 (索引 [1, 3]):\n”, v_split_indices)

水平分割成 4 等份

h_split = np.hsplit(arr, 4)
print(“水平分割 (4 份):\n”, h_split) # 结果是包含四个数组的列表

水平分割,在索引 1 和 3 之后分割

h_split_indices = np.hsplit(arr, [1, 3])
print(“水平分割 (索引 [1, 3]):\n”, h_split_indices)
“`

7. 聚合函数 (Aggregation Functions)

NumPy 提供了许多用于计算数组统计量的聚合函数,例如求和、求平均值、找到最大/最小值等。这些函数也可以通过数组的方法来调用。

“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(“原数组:\n”, arr)

所有元素的总和

total_sum = np.sum(arr) # 或 arr.sum()
print(“\n所有元素总和:”, total_sum)

平均值

mean_value = np.mean(arr) # 或 arr.mean()
print(“所有元素平均值:”, mean_value)

最大值和最小值

max_value = np.max(arr) # 或 arr.max()
min_value = np.min(arr) # 或 arr.min()
print(“最大值:”, max_value)
print(“最小值:”, min_value)

找到最大值和最小值的索引

argmax_index = np.argmax(arr) # 或 arr.argmax()
argmin_index = np.argmin(arr) # 或 arr.argmin()
print(“最大值展平后的索引:”, argmax_index) # 展平后索引 5
print(“最小值展平后的索引:”, argmin_index) # 展平后索引 0

标准差和方差

std_dev = np.std(arr) # 或 arr.std()
variance = np.var(arr) # 或 arr.var()
print(“标准差:”, std_dev)
print(“方差:”, variance)
“`

沿着轴进行计算:

聚合函数通常有一个 axis 参数,允许你在特定维度上进行计算。

  • axis=0:沿垂直方向(行)操作,对每一列进行计算。结果的维数会减少一维。
  • axis=1:沿水平方向(列)操作,对每一行进行计算。结果的维数会减少一维。

“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(“\n原数组:\n”, arr)

沿轴 0 求和 (每列的和)

sum_axis0 = np.sum(arr, axis=0) # 或 arr.sum(axis=0)
print(“\n沿轴 0 求和 (每列):”, sum_axis0) # [1+4, 2+5, 3+6] -> [5 7 9]

沿轴 1 求和 (每行的和)

sum_axis1 = np.sum(arr, axis=1) # 或 arr.sum(axis=1)
print(“沿轴 1 求和 (每行):”, sum_axis1) # [1+2+3, 4+5+6] -> [6 15]

沿轴 0 求平均值 (每列的平均值)

mean_axis0 = np.mean(arr, axis=0) # 或 arr.mean(axis=0)
print(“沿轴 0 平均值:”, mean_axis0) # [(1+4)/2, (2+5)/2, (3+6)/2] -> [2.5 3.5 4.5]

沿轴 1 最大值 (每行的最大值)

max_axis1 = np.max(arr, axis=1) # 或 arr.max(axis=1)
print(“沿轴 1 最大值:”, max_axis1) # [max(1,2,3), max(4,5,6)] -> [3 6]
“`

理解 axis 参数对于处理多维数据集至关重要,例如在数据表中计算每列的平均值或每行的总和。

8. 文件输入/输出 (File I/O)

NumPy 可以方便地将数组保存到文件或从文件加载。

  • np.save(filename, array): 将单个数组保存为 .npy 格式(NumPy 专用的二进制格式)。
  • np.load(filename): 从 .npy 文件加载数组。
  • np.savez(filename, **kwds): 将多个数组保存为 .npz 格式(一个包含多个 .npy 文件的压缩包)。
  • np.load(filename): 从 .npz 文件加载多个数组(返回一个字典状对象)。
  • np.savetxt(filename, array, delimiter=' '): 将数组保存为文本文件。
  • np.loadtxt(filename, delimiter=None): 从文本文件加载数据。

“`python
arr = np.arange(10).reshape(2, 5)
print(“\n要保存的数组:\n”, arr)

保存为 .npy 文件

np.save(‘my_array.npy’, arr)
print(“数组已保存到 my_array.npy”)

从 .npy 文件加载

loaded_arr = np.load(‘my_array.npy’)
print(“从 my_array.npy 加载的数组:\n”, loaded_arr)

保存多个数组为 .npz 文件

arr1 = np.array([1, 2, 3])
arr2 = np.random.rand(2, 2)
np.savez(‘multiple_arrays.npz’, array1=arr1, array2=arr2)
print(“\n多个数组已保存到 multiple_arrays.npz”)

从 .npz 文件加载

loaded_npz = np.load(‘multiple_arrays.npz’)
print(“从 multiple_arrays.npz 加载的数组:”)
print(“array1:”, loaded_npz[‘array1’])
print(“array2:\n”, loaded_npz[‘array2’])

loaded_npz 是一个字典状对象,可以使用 .keys() 查看包含的数组名称

print(“包含的数组名称:”, loaded_npz.keys())

保存为文本文件 (例如 CSV)

np.savetxt(‘my_array.csv’, arr, delimiter=’,’)
print(“\n数组已保存到 my_array.csv (文本格式)”)

从文本文件加载

loaded_txt = np.loadtxt(‘my_array.csv’, delimiter=’,’)
print(“从 my_array.csv 加载的数组:\n”, loaded_txt)

清理生成的文件 (可选)

import os

os.remove(‘my_array.npy’)

os.remove(‘multiple_arrays.npz’)

os.remove(‘my_array.csv’)

“`

np.savetxtnp.loadtxt 对于导入/导出标准的表格数据非常方便。

9. NumPy 与其他库的结合

如前所述,NumPy 是 Python 数据科学栈的基础。许多其他库都接受 NumPy 数组作为输入或返回 NumPy 数组作为输出。

  • Pandas: Pandas 的 DataFrame 对象内部使用了 NumPy 数组来存储数据。Pandas 提供了更高级的数据结构(如 Series 和 DataFrame)和数据分析工具,但其性能和功能依赖于底层的 NumPy 实现。你可以轻松地在 Pandas DataFrame 和 NumPy 数组之间转换。
  • Matplotlib: 这个流行的绘图库可以接收 NumPy 数组作为绘图数据。
  • SciPy: SciPy 提供了更高级的科学计算模块,如优化、积分、插值、信号处理等。SciPy 的许多功能都基于 NumPy 数组。
  • Scikit-learn: 机器学习库 Scikit-learn 主要使用 NumPy 数组作为其算法的输入数据结构。
  • 深度学习框架 (TensorFlow, PyTorch): 这些框架使用自己的张量(Tensor)对象,但这些张量对象与 NumPy 数组概念非常相似,并且通常可以方便地与 NumPy 数组进行转换。

这意味着你学习的 NumPy 技能可以直接应用于这些更高级的库,为你打开更广阔的数据科学应用领域。

10. 学习建议和最佳实践

  1. 理解 ndarray 的核心概念: 它是同构的、多维的。这是 NumPy 所有功能的基础。
  2. 掌握索引和切片: 这是访问和操作数组数据的基本技能。理解视图和副本的区别非常重要。
  3. 拥抱向量化运算和广播: 尽量使用 NumPy 提供的向量化函数和运算符进行数组运算,而不是使用 Python 循环。理解广播规则能帮助你更灵活地组合不同形状的数组进行计算。
  4. 多练习使用 axis 参数: 在聚合函数、合并、分割等操作中,axis 参数控制操作的方向,理解它对于处理多维数据至关重要。
  5. 查阅官方文档: NumPy 的官方文档(numpy.org)非常详细和全面,是学习和解决问题的宝库。遇到不清楚的地方,查阅文档是最好的方式。
  6. 动手实践: 理论知识很重要,但最好的学习方式是编写代码、运行代码、修改代码。尝试用 NumPy 解决一些简单的数据处理或数学问题。
  7. 阅读他人代码: 查看使用 NumPy 的开源项目或教程代码,学习其他人是如何高效使用 NumPy 的。

11. 进阶之路

当你熟练掌握了 NumPy 的基本功能后,可以进一步探索更高级的话题:

  • 通用函数 (Ufuncs) 的更多细节: 了解 Ufuncs 的各种方法(如 reduce, accumulate, outer)。
  • 结构化数组 (Structured Arrays): 创建包含不同数据类型字段的数组,类似于 C 语言中的结构体或数据库中的行。
  • 掩码数组 (Masked Arrays): 处理包含无效或缺失数据的数组。
  • 更复杂的线性代数 (np.linalg): 特征值分解、奇异值分解等。
  • 傅里叶变换 (np.fft): 信号处理常用。
  • 随机数 (np.random) 模块的高级用法: 各种概率分布的采样。
  • 性能优化: 了解 NumPy 内部工作原理,如何进一步优化代码。

12. 总结

NumPy 是 Python 数据科学的基石,它以其高性能、内存效率和丰富的功能,为处理数值数据提供了强大的支持。通过本文的学习,你已经了解了 NumPy 的核心概念 ndarray,学会了如何创建数组、访问元素、进行基本运算、处理数组形状以及执行聚合操作。

学习 NumPy 是掌握更高级数据科学工具(如 Pandas、Scikit-learn)的必经之路。不断练习,将 NumPy 的思想融入你的编程习惯中,你会发现它将极大地提高你的数据处理能力和效率。

现在,是时候打开你的 Python 环境,亲自敲下那些代码,感受 NumPy 的力量了!祝你在 NumPy 的学习旅程中一切顺利!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部