NumPy 数组详解:从基础到高级的全方位指南
NumPy(Numerical Python)是 Python 科学计算的基础库,它提供了高性能的多维数组对象以及用于处理这些数组的工具。在数据分析、机器学习、科学计算等领域,NumPy 数组几乎是不可或缺的核心数据结构。本文将全面介绍 NumPy 数组的各种特性、操作方法和高级技巧,帮助你掌握这一强大工具。
一、NumPy 数组简介
1.1 什么是 NumPy 数组
NumPy 数组(ndarray,即 N-dimensional array)是一种多维的同构数据容器,它可以存储相同类型的元素。与 Python 的列表相比,NumPy 数组具有以下优势:
- 更高的性能:NumPy 数组的运算速度通常比 Python 列表快 10-100 倍
- 更简洁的语法:可以用简洁的方式表达复杂的数学运算
- 支持广播机制:使不同形状的数组之间可以进行算术运算
- 与其他科学计算库兼容:是 Pandas、Matplotlib、Scikit-learn 等库的基础
1.2 安装 NumPy
如果你还没有安装 NumPy,可以使用 pip 或 conda 进行安装:
python
运行
# 使用pip安装
!pip install numpy
# 使用conda安装
!conda install numpy
安装完成后,通常将其导入并简写为np
:
python
运行
import numpy as np
这个约定在 NumPy 社区中被广泛遵循,我们将在整篇文章中使用这个简写。
二、创建 NumPy 数组
NumPy 提供了多种创建数组的方法,适用于不同的场景。
2.1 从 Python 列表创建
最基本的创建数组的方法是使用np.array()
函数,它可以将 Python 列表转换为 NumPy 数组:
python
运行
# 创建一维数组
arr1 = np.array([1, 2, 3, 4, 5])
print("一维数组:")
print(arr1)
print("数组类型:", type(arr1))
# 创建二维数组
arr2 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("\n二维数组:")
print(arr2)
# 创建三维数组
arr3 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print("\n三维数组:")
print(arr3)
输出结果:
plaintext
一维数组:
[1 2 3 4 5]
数组类型: <class 'numpy.ndarray'>
二维数组:
[[1 2 3]
[4 5 6]
[7 8 9]]
三维数组:
[[[1 2]
[3 4]]
[[5 6]
[7 8]]]
可以通过ndmin
参数指定数组的最小维度:
python
运行
# 创建至少3维的数组
arr = np.array([1, 2, 3], ndmin=3)
print("数组:")
print(arr)
print("数组维度:", arr.ndim)
输出结果:
plaintext
数组:
[[[1 2 3]]]
数组维度: 3
2.2 使用内置函数创建数组
NumPy 提供了许多实用函数来创建具有特定模式的数组。
2.2.1 等差数列
np.arange()
函数类似于 Python 的range()
,但返回的是一个 NumPy 数组:
python
运行
# 创建从0到9的数组
arr1 = np.arange(10)
print("arr1:", arr1)
# 创建从5到14的数组
arr2 = np.arange(5, 15)
print("arr2:", arr2)
# 创建从10到30,步长为5的数组
arr3 = np.arange(10, 31, 5)
print("arr3:", arr3)
输出结果:
plaintext
arr1: [0 1 2 3 4 5 6 7 8 9]
arr2: [ 5 6 7 8 9 10 11 12 13 14]
arr3: [10 15 20 25 30]
np.linspace()
函数创建指定范围内的等间隔数字:
python
运行
# 创建从0到10的5个等间隔数字(包含终点)
arr1 = np.linspace(0, 10, 5)
print("arr1:", arr1)
# 创建从0到10的5个等间隔数字(不包含终点)
arr2 = np.linspace(0, 10, 5, endpoint=False)
print("arr2:", arr2)
# 查看步长
arr3, step = np.linspace(0, 10, 5, retstep=True)
print("arr3:", arr3)
print("步长:", step)
输出结果:
plaintext
arr1: [ 0. 2.5 5. 7.5 10. ]
arr2: [0. 2. 4. 6. 8.]
arr3: [ 0. 2.5 5. 7.5 10. ]
步长: 2.5
2.2.2 特殊数组
创建全零数组:
python
运行
# 创建3x4的全零数组
zeros = np.zeros((3, 4))
print("3x4的全零数组:")
print(zeros)
# 指定数据类型
zeros_int = np.zeros((2, 2), dtype=int)
print("\n2x2的整数全零数组:")
print(zeros_int)
输出结果:
plaintext
3x4的全零数组:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
2x2的整数全零数组:
[[0 0]
[0 0]]
创建全一数组:
python
运行
# 创建2x3的全一数组
ones = np.ones((2, 3))
print("2x3的全一数组:")
print(ones)
# 调整数据类型和形状
ones_custom = np.ones(5, dtype=np.int32).reshape(5, 1)
print("\n5x1的整数全一数组:")
print(ones_custom)
输出结果:
plaintext
2x3的全一数组:
[[1. 1. 1.]
[1. 1. 1.]]
5x1的整数全一数组:
[[1]
[1]
[1]
[1]
[1]]
创建单位矩阵:
python
运行
# 创建3x3的单位矩阵
eye = np.eye(3)
print("3x3的单位矩阵:")
print(eye)
# 创建非方阵的单位矩阵
eye2 = np.eye(3, 5, k=1)
print("\n3x5的单位矩阵(偏移1):")
print(eye2)
输出结果:
plaintext
3x3的单位矩阵:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
3x5的单位矩阵(偏移1):
[[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]]
创建对角矩阵:
python
运行
# 从对角线元素创建对角矩阵
diag = np.diag([1, 2, 3, 4])
print("对角矩阵:")
print(diag)
# 提取矩阵的对角线元素
mat = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("\n原始矩阵:")
print(mat)
print("对角线元素:", np.diag(mat))
输出结果:
plaintext
对角矩阵:
[[1 0 0 0]
[0 2 0 0]
[0 0 3 0]
[0 0 0 4]]
原始矩阵:
[[1 2 3]
[4 5 6]
[7 8 9]]
对角线元素: [1 5 9]
2.2.3 随机数组
NumPy 的random
模块提供了多种生成随机数组的函数。
生成均匀分布的随机数:
python
运行
# 生成0到1之间的随机浮点数
rand = np.random.rand(3, 3)
print("3x3的随机数组(0-1):")
print(rand)
# 生成指定范围内的随机整数
randint = np.random.randint(10, 50, size=(2, 4))
print("\n2x4的随机整数数组(10-50):")
print(randint)
输出结果(每次运行结果不同):
plaintext
3x3的随机数组(0-1):
[[0.46180183 0.26654741 0.60485547]
[0.73354578 0.74647039 0.33420086]
[0.93935347 0.96709572 0.36496975]]
2x4的随机整数数组(10-50):
[[23 45 12 37]
[19 33 28 41]]
生成正态分布的随机数:
python
运行
# 生成标准正态分布(均值0,标准差1)的随机数
randn = np.random.randn(3, 3)
print("3x3的标准正态分布数组:")
print(randn)
# 生成指定均值和标准差的正态分布随机数
mu, sigma = 10, 2
normal = mu + sigma * np.random.randn(2, 4)
print(f"\n2x4的正态分布数组(均值{mu},标准差{sigma}):")
print(normal)
输出结果(每次运行结果不同):
plaintext
3x3的标准正态分布数组:
[[ 0.48375672 -0.53129892 0.26764573]
[ 0.39326751 0.18841541 -0.08299631]
[ 0.36533361 0.02635666 -0.04385225]]
2x4的正态分布数组(均值10,标准差2):
[[10.87636535 9.48547626 11.56360455 8.73663672]
[ 9.76633121 9.35226147 10.29934463 10.98236156]]
2.3 从文件读取数组
NumPy 提供了读取文本文件和二进制文件的功能。
读取文本文件:
python
运行
# 首先创建一个示例文本文件
data = np.arange(12).reshape(3, 4)
np.savetxt('data.txt', data, fmt='%d', delimiter=',')
print("保存的文本文件内容:")
with open('data.txt', 'r') as f:
print(f.read())
# 读取文本文件
loaded_data = np.loadtxt('data.txt', delimiter=',')
print("\n读取的数组:")
print(loaded_data)
输出结果:
plaintext
保存的文本文件内容:
0,1,2,3
4,5,6,7
8,9,10,11
读取的数组:
[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]]
读取二进制文件(NumPy 专用格式):
python
运行
# 保存为NumPy二进制格式
np.save('data.npy', data)
# 读取NumPy二进制文件
loaded_npy = np.load('data.npy')
print("从npy文件读取的数组:")
print(loaded_npy)
# 保存多个数组到一个文件
data2 = np.arange(10, 22).reshape(3, 4)
np.savez('data.npz', arr1=data, arr2=data2)
# 读取多个数组
loaded_npz = np.load('data.npz')
print("\n从npz文件读取的数组1:")
print(loaded_npz['arr1'])
print("从npz文件读取的数组2:")
print(loaded_npz['arr2'])
输出结果:
plaintext
从npy文件读取的数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
从npz文件读取的数组1:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
从npz文件读取的数组2:
[[10 11 12 13]
[14 15 16 17]
[18 19 20 21]]
三、NumPy 数组的属性
NumPy 数组有许多重要的属性,可以帮助我们了解数组的特征。
python
运行
# 创建一个示例数组
arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print("示例数组:")
print(arr)
# 数组的维度(ndim)
print("\n数组的维度:", arr.ndim)
# 数组的形状(shape)
print("数组的形状:", arr.shape)
# 数组的元素总数(size)
print("数组的元素总数:", arr.size)
# 数组的数据类型(dtype)
print("数组的数据类型:", arr.dtype)
# 每个元素的字节大小(itemsize)
print("每个元素的字节大小:", arr.itemsize)
# 数组的总字节大小(nbytes)
print("数组的总字节大小:", arr.nbytes)
# 数组的转置(T)
print("数组的转置:")
print(arr.T)
输出结果:
plaintext
示例数组:
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
数组的维度: 2
数组的形状: (3, 4)
数组的元素总数: 12
数组的数据类型: int64
每个元素的字节大小: 8
数组的总字节大小: 96
数组的转置:
[[ 1 5 9]
[ 2 6 10]
[ 3 7 11]
[ 4 8 12]]
数据类型
NumPy 支持多种数据类型,比 Python 内置的类型更加丰富:
python
运行
# 创建不同数据类型的数组
arr_int = np.array([1, 2, 3], dtype=np.int32)
arr_float = np.array([1.1, 2.2, 3.3], dtype=np.float64)
arr_bool = np.array([True, False, True], dtype=np.bool_)
arr_complex = np.array([1+2j, 3+4j], dtype=np.complex_)
arr_str = np.array(['a', 'b', 'c'], dtype=np.str_)
print("整数数组类型:", arr_int.dtype)
print("浮点数数组类型:", arr_float.dtype)
print("布尔数组类型:", arr_bool.dtype)
print("复数数组类型:", arr_complex.dtype)
print("字符串数组类型:", arr_str.dtype)
# 数据类型转换
arr = np.array([1.5, 2.7, 3.9])
print("\n原始数组:", arr, "类型:", arr.dtype)
arr_int = arr.astype(np.int32)
print("转换为整数:", arr_int, "类型:", arr_int.dtype)
arr_str = arr.astype(np.str_)
print("转换为字符串:", arr_str, "类型:", arr_str.dtype)
输出结果:
plaintext
整数数组类型: int32
浮点数数组类型: float64
布尔数组类型: bool
复数数组类型: complex128
字符串数组类型: <U1
原始数组: [1.5 2.7 3.9] 类型: float64
转换为整数: [1 2 3] 类型: int32
转换为字符串: ['1.5' '2.7' '3.9'] 类型: <U32
四、数组的索引与切片
访问和修改数组元素是最基本的操作,NumPy 提供了灵活的索引和切片方式。
4.1 一维数组的索引与切片
一维数组的索引和切片与 Python 列表类似:
python
运行
arr = np.arange(10)
print("原始数组:", arr)
# 索引访问(从0开始)
print("\n索引为3的元素:", arr[3])
print("索引为-1的元素(最后一个):", arr[-1])
# 切片操作:start:end:step
print("\n从索引2到5的元素:", arr[2:5]) # 不包含end
print("从开始到索引5的元素:", arr[:5])
print("从索引5到结束的元素:", arr[5:])
print("每隔2个元素取一个:", arr[::2])
print("逆序数组:", arr[::-1])
# 修改元素
arr[2] = 99
print("\n修改索引2的元素后:", arr)
# 切片赋值
arr[5:8] = 100
print("修改索引5-7的元素后:", arr)
输出结果:
plaintext
原始数组: [0 1 2 3 4 5 6 7 8 9]
索引为3的元素: 3
索引为-1的元素(最后一个): 9
从索引2到5的元素: [2 3 4]
从开始到索引5的元素: [0 1 2 3 4]
从索引5到结束的元素: [5 6 7 8 9]
每隔2个元素取一个: [0 2 4 6 8]
逆序数组: [9 8 7 6 5 4 3 2 1 0]
修改索引2的元素后: [ 0 1 99 3 4 5 6 7 8 9]
修改索引5-7的元素后: [ 0 1 99 3 4 100 100 100 8 9]
4.2 多维数组的索引与切片
多维数组的索引和切片稍微复杂一些,需要为每个维度指定索引或切片:
python
运行
# 创建一个3x4的二维数组
arr = np.arange(12).reshape(3, 4)
print("原始数组:")
print(arr)
# 访问单个元素
print("\n第二行第三列的元素(索引从0开始):", arr[1, 2])
# 等价于
print("另一种方式:", arr[1][2])
# 切片操作
# 取前两行,前两列
print("\n前两行,前两列:")
print(arr[:2, :2])
# 取所有行,每隔一列取一列
print("\n所有行,每隔一列:")
print(arr[:, ::2])
# 取最后一行
print("\n最后一行:", arr[-1])
# 取最后一列
print("\n最后一列:", arr[:, -1])
# 取子数组
sub_arr = arr[1:3, 1:3]
print("\n子数组(行1-2,列1-2):")
print(sub_arr)
# 修改子数组会影响原始数组(因为是视图,不是副本)
sub_arr[:] = 0
print("\n修改子数组后的原始数组:")
print(arr)
输出结果:
plaintext
原始数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
第二行第三列的元素(索引从0开始): 6
另一种方式: 6
前两行,前两列:
[[0 1]
[4 5]]
所有行,每隔一列:
[[ 0 2]
[ 4 6]
[ 8 10]]
最后一行: [ 8 9 10 11]
最后一列: [ 3 7 11]
子数组(行1-2,列1-2):
[[5 6]
[9 10]]
修改子数组后的原始数组:
[[ 0 1 2 3]
[ 4 0 0 7]
[ 8 0 0 11]]
4.3 高级索引
除了基本的索引和切片,NumPy 还支持更高级的索引方式。
4.3.1 整数数组索引
可以使用整数数组来索引:
python
运行
arr = np.arange(10, 30)
print("原始数组:", arr)
# 使用整数数组索引
indices = [1, 3, 5, 7]
print("\n索引为1,3,5,7的元素:", arr[indices])
# 负数索引
print("索引为-2,-4的元素:", arr[[-2, -4]])
# 二维数组的整数索引
arr2d = np.arange(12).reshape(3, 4)
print("\n二维数组:")
print(arr2d)
# 取(0,0), (1,1), (2,2)位置的元素
print("\n特定位置的元素:", arr2d[[0, 1, 2], [0, 1, 2]])
# 取第0行和第2行的第1列和第3列
print("第0行和第2行的第1列和第3列:")
print(arr2d[[0, 2]][:, [1, 3]])
输出结果:
plaintext
原始数组: [10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29]
索引为1,3,5,7的元素: [11 13 15 17]
索引为-2,-4的元素: [27 25]
二维数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
特定位置的元素: [ 0 5 10]
第0行和第2行的第1列和第3列:
[[ 1 3]
[ 9 11]]
4.3.2 布尔索引
可以使用布尔数组来筛选元素:
python
运行
arr = np.arange(10, 30)
print("原始数组:", arr)
# 创建布尔数组
mask = (arr > 15) & (arr < 25)
print("\n布尔掩码:", mask)
# 使用布尔索引筛选元素
print("筛选后的元素:", arr[mask])
# 直接在索引中使用条件
print("大于20的元素:", arr[arr > 20])
# 二维数组的布尔索引
arr2d = np.arange(12).reshape(3, 4)
print("\n二维数组:")
print(arr2d)
# 筛选出所有大于5的元素
print("所有大于5的元素:", arr2d[arr2d > 5])
# 按行筛选
row_mask = [True, False, True]
print("第一行和第三行:")
print(arr2d[row_mask])
输出结果:
plaintext
原始数组: [10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29]
布尔掩码: [False False False False False False True True True True True True
True True True False False False False False]
筛选后的元素: [16 17 18 19 20 21 22 23 24]
大于20的元素: [21 22 23 24 25 26 27 28 29]
二维数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
所有大于5的元素: [ 6 7 8 9 10 11]
第一行和第三行:
[[ 0 1 2 3]
[ 8 9 10 11]]
五、数组的形状操作
NumPy 提供了多种方法来改变数组的形状,而不改变其数据。
5.1 重塑数组
reshape()
方法可以改变数组的形状:
python
运行
arr = np.arange(12)
print("原始数组:", arr)
# 重塑为3x4的二维数组
arr_3x4 = arr.reshape(3, 4)
print("\n3x4的数组:")
print(arr_3x4)
# 重塑为2x2x3的三维数组
arr_2x2x3 = arr.reshape(2, 2, 3)
print("\n2x2x3的数组:")
print(arr_2x2x3)
# 可以指定一个维度为-1,让NumPy自动计算该维度的大小
arr_auto = arr.reshape(2, -1) # 2行,自动计算列数
print("\n2行的数组:")
print(arr_auto)
arr_auto2 = arr.reshape(-1, 3) # 3列,自动计算行数
print("\n3列的数组:")
print(arr_auto2)
输出结果:
plaintext
原始数组: [ 0 1 2 3 4 5 6 7 8 9 10 11]
3x4的数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
2x2x3的数组:
[[[ 0 1 2]
[ 3 4 5]]
[[ 6 7 8]
[ 9 10 11]]]
2行的数组:
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
3列的数组:
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
5.2 展平数组
可以将多维数组展平为一维数组:
python
运行
arr = np.arange(12).reshape(3, 4)
print("原始数组:")
print(arr)
# 使用ravel()展平
flattened_ravel = arr.ravel()
print("\nravel()展平:", flattened_ravel)
# 使用flatten()展平
flattened_flatten = arr.flatten()
print("flatten()展平:", flattened_flatten)
# 修改展平后的数组,查看对原始数组的影响
flattened_ravel[0] = 99
print("\n修改ravel()结果后原始数组:")
print(arr)
flattened_flatten[1] = 99
print("修改flatten()结果后原始数组:")
print(arr)
输出结果:
plaintext
原始数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
ravel()展平: [ 0 1 2 3 4 5 6 7 8 9 10 11]
flatten()展平: [ 0 1 2 3 4 5 6 7 8 9 10 11]
修改ravel()结果后原始数组:
[[99 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
修改flatten()结果后原始数组:
[[99 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
注意:ravel()
返回的是数组的视图(如果可能),修改它会影响原始数组;而flatten()
总是返回一个新的数组(副本),修改它不会影响原始数组。
5.3 数组转置
数组转置是交换数组的维度,对于二维数组来说就是行列互换:
python
运行
arr = np.arange(12).reshape(3, 4)
print("原始数组:")
print(arr)
# 使用T属性转置
transposed_T = arr.T
print("\n使用T属性转置:")
print(transposed_T)
# 使用transpose()方法转置
transposed_transpose = arr.transpose()
print("使用transpose()方法转置:")
print(transposed_transpose)
# 三维数组的转置
arr3d = np.arange(24).reshape(2, 3, 4)
print("\n三维数组:")
print(arr3d)
print("形状:", arr3d.shape)
# 交换轴0和轴1
transposed3d = arr3d.transpose(1, 0, 2)
print("\n交换轴0和轴1后的三维数组:")
print(transposed3d)
print("形状:", transposed3d.shape)
# 使用swapaxes()交换轴
swapped = arr3d.swapaxes(0, 2)
print("\n交换轴0和轴2后的三维数组:")
print(swapped)
print("形状:", swapped.shape)
输出结果:
plaintext
原始数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
使用T属性转置:
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
使用transpose()方法转置:
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
三维数组:
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
形状: (2, 3, 4)
交换轴0和轴1后的三维数组:
[[[ 0 1 2 3]
[12 13 14 15]]
[[ 4 5 6 7]
[16 17 18 19]]
[[ 8 9 10 11]
[20 21 22 23]]]
形状: (3, 2, 4)
交换轴0和轴2后的三维数组:
[[[ 0 12]
[ 4 16]
[ 8 20]]
[[ 1 13]
[ 5 17]
[ 9 21]]
[[ 2 14]
[ 6 18]
[10 22]]
[[ 3 15]
[ 7 19]
[11 23]]]
形状: (4, 3, 2)
5.4 数组的合并与分割
5.4.1 合并数组
可以使用concatenate()
、vstack()
、hstack()
等函数合并数组:
python
运行
# 创建两个数组
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])
print("arr1:")
print(arr1)
print("\narr2:")
print(arr2)
# 使用concatenate()在轴0上合并(垂直方向)
concat_axis0 = np.concatenate([arr1, arr2], axis=0)
print("\n在轴0上合并:")
print(concat_axis0)
# 使用concatenate()在轴1上合并(水平方向)
concat_axis1 = np.concatenate([arr1, arr2], axis=1)
print("在轴1上合并:")
print(concat_axis1)
# 使用vstack()垂直合并(等价于axis=0)
vstacked = np.vstack([arr1, arr2])
print("\nvstack垂直合并:")
print(vstacked)
# 使用hstack()水平合并(等价于axis=1)
hstacked = np.hstack([arr1, arr2])
print("hstack水平合并:")
print(hstacked)
# 合并一维数组
arr3 = np.array([1, 2, 3])
arr4 = np.array([4, 5, 6])
print("\n一维数组合并:", np.concatenate([arr3, arr4]))
# 使用dstack()按深度合并
dstacked = np.dstack([arr1, arr2])
print("\ndstack按深度合并:")
print(dstacked)
print("形状:", dstacked.shape)
输出结果:
plaintext
arr1:
[[1 2]
[3 4]]
arr2:
[[5 6]
[7 8]]
在轴0上合并:
[[1 2]
[3 4]
[5 6]
[7 8]]
在轴1上合并:
[[1 2 5 6]
[3 4 7 8]]
vstack垂直合并:
[[1 2]
[3 4]
[5 6]
[7 8]]
hstack水平合并:
[[1 2 5 6]
[3 4 7 8]]
一维数组合并: [1 2 3 4 5 6]
dstack按深度合并:
[[[1 5]
[2 6]]
[[3 7]
[4 8]]]
形状: (2, 2, 2)
5.4.2 分割数组
可以使用split()
、vsplit()
、hsplit()
等函数分割数组:
python
运行
# 创建一个6x4的数组
arr = np.arange(24).reshape(6, 4)
print("原始数组:")
print(arr)
# 使用split()在轴0上分割为3个部分
split_axis0 = np.split(arr, 3, axis=0)
print("\n在轴0上分割为3个部分:")
for i, part in enumerate(split_axis0):
print(f"部分{i+1}:")
print(part)
# 使用split()在指定位置分割
split_pos = np.split(arr, [2, 4], axis=0)
print("\n在轴0上的2和4位置分割:")
for i, part in enumerate(split_pos):
print(f"部分{i+1}:")
print(part)
# 使用vsplit()垂直分割(等价于axis=0)
vsplit_result = np.vsplit(arr, 3)
print("\nvsplit垂直分割为3个部分:")
print("部分1的形状:", vsplit_result[0].shape)
# 使用hsplit()水平分割(等价于axis=1)
hsplit_result = np.hsplit(arr, 2)
print("\nhsplit水平分割为2个部分:")
for i, part in enumerate(hsplit_result):
print(f"部分{i+1}:")
print(part)
# 分割一维数组
arr1d = np.arange(10)
split_1d = np.split(arr1d, [3, 7])
print("\n一维数组分割:")
for i, part in enumerate(split_1d):
print(f"部分{i+1}:", part)
输出结果:
plaintext
原始数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]
在轴0上分割为3个部分:
部分1:
[[0 1 2 3]
[4 5 6 7]]
部分2:
[[ 8 9 10 11]
[12 13 14 15]]
部分3:
[[16 17 18 19]
[20 21 22 23]]
在轴0上的2和4位置分割:
部分1:
[[0 1 2 3]
[4 5 6 7]]
部分2:
[[ 8 9 10 11]
[12 13 14 15]]
部分3:
[[16 17 18 19]
[20 21 22 23]]
vsplit垂直分割为3个部分:
部分1的形状: (2, 4)
hsplit水平分割为2个部分:
部分1:
[[ 0 1]
[ 4 5]
[ 8 9]
[12 13]
[16 17]
[20 21]]
部分2:
[[ 2 3]
[ 6 7]
[10 11]
[14 15]
[18 19]
[22 23]]
一维数组分割:
部分1: [0 1 2]
部分2: [3 4 5 6]
部分3: [7 8 9]
六、数组的运算
NumPy 数组支持丰富的算术和数学运算,并且这些运算都是向量化的,即不需要编写循环就能对数组中的所有元素进行操作。
6.1 基本算术运算
数组的算术运算会对每个元素执行相应的操作:
python
运行
arr1 = np.array([10, 20, 30, 40])
arr2 = np.array([1, 2, 3, 4])
print("arr1:", arr1)
print("arr2:", arr2)
# 加法
print("\narr1 + arr2:", arr1 + arr2)
# 等价于
print("np.add(arr1, arr2):", np.add(arr1, arr2))
# 减法
print("\narr1 - arr2:", arr1 - arr2)
# 等价于
print("np.subtract(arr1, arr2):", np.subtract(arr1, arr2))
# 乘法
print("\narr1 * arr2:", arr1 * arr2)
# 等价于
print("np.multiply(arr1, arr2):", np.multiply(arr1, arr2))
# 除法
print("\narr1 / arr2:", arr1 / arr2)
# 等价于
print("np.divide(arr1, arr2):", np.divide(arr1, arr2))
# 整数除法
print("\narr1 // arr2:", arr1 // arr2)
# 取余
print("\narr1 % arr2:", arr1 % arr2)
# 幂运算
print("\narr1 ** arr2:", arr1 ** arr2)
# 等价于
print("np.power(arr1, arr2):", np.power(arr1, arr2))
输出结果:
plaintext
arr1: [10 20 30 40]
arr2: [1 2 3 4]
arr1 + arr2: [11 22 33 44]
np.add(arr1, arr2): [11 22 33 44]
arr1 - arr2: [ 9 18 27 36]
np.subtract(arr1, arr2): [ 9 18 27 36]
arr1 * arr2: [ 10 40 90 160]
np.multiply(arr1, arr2): [ 10 40 90 160]
arr1 / arr2: [10. 10. 10. 10.]
np.divide(arr1, arr2): [10. 10. 10. 10.]
arr1 // arr2: [10 10 10 10]
arr1 % arr2: [0 0 0 0]
arr1 ** arr2: [ 10 400 27000 2560000]
np.power(arr1, arr2): [ 10 400 27000 2560000]
6.2 广播机制
广播(Broadcasting)是 NumPy 中一种强大的机制,它允许不同形状的数组之间进行算术运算。基本规则是:如果两个数组的维度不匹配,NumPy 会自动扩展其中一个或两个数组,使它们的形状兼容。
python
运行
# 标量与数组的运算(标量被广播到数组的每个元素)
arr = np.array([1, 2, 3, 4])
print("数组:", arr)
print("数组 + 5:", arr + 5)
print("数组 * 2:", arr * 2)
# 不同形状的数组运算
arr1 = np.array([[0, 0, 0], [10, 10, 10], [20, 20, 20]])
arr2 = np.array([1, 2, 3])
print("\narr1:")
print(arr1)
print("arr2:", arr2)
print("arr1 + arr2:")
print(arr1 + arr2)
# 另一个广播的例子
arr3 = np.array([[0], [10], [20]])
print("\narr3:")
print(arr3)
print("arr3 + arr2:")
print(arr3 + arr2)
# 不兼容的形状(无法广播)
try:
arr4 = np.array([1, 2])
print("\n尝试将arr1与arr4相加:")
print(arr1 + arr4)
except ValueError as e:
print("错误:", e)
输出结果:
plaintext
数组: [1 2 3 4]
数组 + 5: [6 7 8 9]
数组 * 2: [2 4 6 8]
arr1:
[[ 0 0 0]
[10 10 10]
[20 20 20]]
arr2: [1 2 3]
arr1 + arr2:
[[ 1 2 3]
[11 12 13]
[21 22 23]]
arr3:
[[ 0]
[10]
[20]]
arr3 + arr2:
[[ 1 2 3]
[11 12 13]
[21 22 23]]
尝试将arr1与arr4相加:
错误: operands could not be broadcast together with shapes (3,3) (2,)
广播的规则可以总结为:
- 如果两个数组的维度数不同,维度较少的数组会在前面添加新维度(形状为 1)
- 对于每个维度,如果两个数组的形状在该维度上不相等且都不为 1,则无法广播,会抛出错误
- 对于每个维度,如果两个数组的形状在该维度上相等或其中一个为 1,则可以广播,结果的形状取该维度上的较大值
6.3 通用函数(ufunc)
通用函数(Universal Functions)是对数组进行元素级操作的函数,它们可以处理标量和数组,并且支持广播。
6.3.1 数学函数
python
运行
arr = np.array([1, 4, 9, 16])
print("原始数组:", arr)
# 平方根
print("\n平方根:", np.sqrt(arr))
# 指数
print("指数:", np.exp(arr))
# 对数
print("自然对数:", np.log(arr))
print("以10为底的对数:", np.log10(arr))
# 三角函数
angles = np.array([0, np.pi/2, np.pi])
print("\n角度(弧度):", angles)
print("正弦:", np.sin(angles))
print("余弦:", np.cos(angles))
print("正切:", np.tan(angles))
# 取整函数
arr = np.array([1.2, 1.5, 1.7, -1.2, -1.5, -1.7])
print("\n原始数组:", arr)
print("向上取整:", np.ceil(arr))
print("向下取整:", np.floor(arr))
print("四舍五入:", np.round(arr))
print("截断小数部分:", np.trunc(arr))
输出结果:
plaintext
原始数组: [ 1 4 9 16]
平方根: [1. 2. 3. 4.]
指数: [2.71828183e+00 5.45981500e+01 8.10308393e+03 8.88611052e+06]
自然对数: [0. 1.38629436 2.19722458 2.77258872]
以10为底的对数: [0. 0.60205999 0.95424251 1.20411998]
角度(弧度): [0. 1.57079633 3.14159265]
正弦: [0.0000000e+00 1.0000000e+00 1.2246468e-16]
余弦: [ 1.0000000e+00 6.1232340e-17 -1.0000000e+00]
正切: [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
原始数组: [ 1.2 1.5 1.7 -1.2 -1.5 -1.7]
向上取整: [ 2. 2. 2. -1. -1. -1.]
向下取整: [ 1. 1. 1. -2. -2. -2.]
四舍五入: [ 1. 2. 2. -1. -2. -2.]
截断小数部分: [ 1. 1. 1. -1. -1. -1.]
6.3.2 统计函数
python
运行
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("原始数组:")
print(arr)
# 求和
print("\n总和:", arr.sum())
print("按行求和:", arr.sum(axis=1))
print("按列求和:", arr.sum(axis=0))
# 平均值
print("\n平均值:", arr.mean())
print("按行平均值:", arr.mean(axis=1))
print("按列平均值:", arr.mean(axis=0))
# 标准差和方差
print("\n标准差:", arr.std())
print("方差:", arr.var())
# 最大值和最小值
print("\n最大值:", arr.max())
print("最小值:", arr.min())
print("每行最大值:", arr.max(axis=1))
print("每列最小值:", arr.min(axis=0))
# 最大值和最小值的索引
print("\n最大值索引:", arr.argmax())
print("最小值索引:", arr.argmin())
print("每行最大值索引:", arr.argmax(axis=1))
print("每列最小值索引:", arr.argmin(axis=0))
# 累积和和累积积
print("\n累积和:", arr.cumsum())
print("按行累积和:")
print(arr.cumsum(axis=1))
输出结果:print (arr.cumsum (axis=1))
plaintext
输出结果:
原始数组:
[[1 2 3]
[4 5 6]
[7 8 9]]
总和: 45
按行求和: [6 15 24]
按列求和: [12 15 18]
平均值: 5.0
按行平均值: [2. 5. 8.]
按列平均值: [4. 5. 6.]
标准差: 2.581988897471611
方差: 6.666666666666667
最大值: 9
最小值: 1
每行最大值: [3 6 9]
每列最小值: [1 2 3]
最大值索引: 8
最小值索引: 0
每行最大值索引: [2 2 2]
每列最小值索引: [0 0 0]
累积和: [1 3 6 10 15 21 28 36 45]
按行累积和:
[[ 1 3 6]
[ 4 9 15]
[ 7 15 24]]
plaintext
## 七、数组的迭代
虽然NumPy鼓励使用向量化操作来避免显式循环,但在某些情况下,我们仍然需要迭代数组的元素。
### 7.1 基本迭代
对于一维数组,迭代与Python列表类似:
```python
# 一维数组迭代
arr1d = np.array([1, 2, 3, 4, 5])
print("一维数组迭代:")
for element in arr1d:
print(element, end=' ')
print()
输出结果:
plaintext
一维数组迭代:
1 2 3 4 5
对于多维数组,默认迭代的是第一轴(行):
python
运行
# 二维数组迭代
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("\n二维数组默认迭代(行):")
for row in arr2d:
print(row)
# 迭代每个元素
print("\n二维数组迭代每个元素:")
for row in arr2d:
for element in row:
print(element, end=' ')
print()
输出结果:
plaintext
二维数组默认迭代(行):
[1 2 3]
[4 5 6]
[7 8 9]
二维数组迭代每个元素:
1 2 3 4 5 6 7 8 9
7.2 使用 flat 属性迭代
flat
属性可以将数组展平为一维迭代器:
python
运行
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("使用flat属性迭代每个元素:")
for element in arr2d.flat:
print(element, end=' ')
print()
输出结果:
plaintext
使用flat属性迭代每个元素:
1 2 3 4 5 6 7 8 9
7.3 使用 nditer 迭代器
np.nditer
是一个灵活的迭代器,可以控制迭代的方式:
python
运行
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("原始数组:")
print(arr2d)
# 基本迭代(C风格,行优先)
print("\nC风格迭代(行优先):")
for element in np.nditer(arr2d, order='C'):
print(element, end=' ')
print()
# Fortran风格迭代(列优先)
print("Fortran风格迭代(列优先):")
for element in np.nditer(arr2d, order='F'):
print(element, end=' ')
print()
# 修改数组元素(需要指定op_flags)
print("\n修改数组元素:")
arr_copy = arr2d.copy()
for element in np.nditer(arr_copy, op_flags=['readwrite']):
element[...] = element * 2
print(arr_copy)
# 同时迭代多个数组
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])
print("\n同时迭代两个数组:")
for x, y in np.nditer([arr1, arr2]):
print(f"{x} * {y} = {x * y}", end='; ')
print()
输出结果:
plaintext
原始数组:
[[1 2 3]
[4 5 6]
[7 8 9]]
C风格迭代(行优先):
1 2 3 4 5 6 7 8 9
Fortran风格迭代(列优先):
1 4 7 2 5 8 3 6 9
修改数组元素:
[[ 2 4 6]
[ 8 10 12]
[14 16 18]]
同时迭代两个数组:
1 * 5 = 5; 2 * 6 = 12; 3 * 7 = 21; 4 * 8 = 32;
八、数组的高级操作
8.1 数组的复制
在 NumPy 中,数组的复制需要特别注意,因为简单的赋值通常只是创建一个引用,而不是真正的副本:
python
运行
# 简单赋值(引用,不是副本)
arr = np.array([1, 2, 3, 4])
arr_ref = arr
arr_ref[0] = 99
print("简单赋值后原数组:", arr) # 原数组被修改
# 使用view()创建浅拷贝(视图)
arr = np.array([1, 2, 3, 4])
arr_view = arr.view()
arr_view[0] = 99
print("\n修改视图后原数组:", arr) # 原数组被修改
print("视图的形状修改不影响原数组:")
arr_view.shape = (2, 2)
print("视图形状:", arr_view.shape)
print("原数组形状:", arr.shape)
# 使用copy()创建深拷贝
arr = np.array([1, 2, 3, 4])
arr_copy = arr.copy()
arr_copy[0] = 99
print("\n修改副本后原数组:", arr) # 原数组不受影响
print("副本:", arr_copy)
输出结果:
plaintext
简单赋值后原数组: [99 2 3 4]
修改视图后原数组: [99 2 3 4]
视图的形状修改不影响原数组:
视图形状: (2, 2)
原数组形状: (4,)
修改副本后原数组: [1 2 3 4]
副本: [99 2 3 4]
总结:
- 简单赋值(
arr_ref = arr
):创建引用,两者指向同一数据 view()
方法:创建浅拷贝(视图),共享数据但可以有不同的形状copy()
方法:创建深拷贝,完全独立的数组
8.2 数组的排序
NumPy 提供了多种排序方法:
python
运行
# 一维数组排序
arr1d = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print("一维数组:", arr1d)
print("排序后:", np.sort(arr1d))
print("原数组(未改变):", arr1d) # sort()返回新数组,不修改原数组
# 原地排序(修改原数组)
arr1d.sort()
print("原地排序后:", arr1d)
# 二维数组排序
arr2d = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5]])
print("\n二维数组:")
print(arr2d)
# 按行排序
print("按行排序:")
print(np.sort(arr2d, axis=1))
# 按列排序
print("按列排序:")
print(np.sort(arr2d, axis=0))
# 按特定方式排序
dt = np.dtype([('name', 'S10'), ('age', int)])
people = np.array([
('Alice', 25),
('Bob', 20),
('Charlie', 30)
], dtype=dt)
print("\n结构化数组:")
print(people)
print("按年龄排序:")
print(np.sort(people, order='age'))
输出结果:
plaintext
一维数组: [3 1 4 1 5 9 2 6]
排序后: [1 1 2 3 4 5 6 9]
原数组(未改变): [3 1 4 1 5 9 2 6]
原地排序后: [1 1 2 3 4 5 6 9]
二维数组:
[[3 1 4]
[1 5 9]
[2 6 5]]
按行排序:
[[1 3 4]
[1 5 9]
[2 5 6]]
按列排序:
[[1 1 4]
[2 5 5]
[3 6 9]]
结构化数组:
[(b'Alice', 25) (b'Bob', 20) (b'Charlie', 30)]
按年龄排序:
[(b'Bob', 20) (b'Alice', 25) (b'Charlie', 30)]
8.3 条件操作
np.where()
函数是一个强大的条件操作工具,类似于三元表达式:
python
运行
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
print("原始数组:", arr)
# 基本用法:where(condition, x, y) 满足条件返回x,否则返回y
result = np.where(arr % 2 == 0, "偶数", "奇数")
print("\n奇偶判断:", result)
# 对满足条件的元素进行修改
arr2 = arr.copy()
arr2[np.where(arr2 % 2 == 0)] = 0
print("偶数替换为0:", arr2)
# 二维数组的条件操作
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("\n二维数组:")
print(arr2d)
# 将大于5的元素替换为10,否则替换为0
result2d = np.where(arr2d > 5, 10, 0)
print("条件替换后:")
print(result2d)
# 找到满足条件的元素的索引
indices = np.where(arr2d > 5)
print("大于5的元素的索引:", indices)
print("大于5的元素:", arr2d[indices])
输出结果:
plaintext
原始数组: [1 2 3 4 5 6 7 8 9]
奇偶判断: ['奇数' '偶数' '奇数' '偶数' '奇数' '偶数' '奇数' '偶数' '奇数']
偶数替换为0: [1 0 3 0 5 0 7 0 9]
二维数组:
[[1 2 3]
[4 5 6]
[7 8 9]]
条件替换后:
[[ 0 0 0]
[ 0 0 10]
[10 10 10]]
大于5的元素的索引: (array([1, 2, 2, 2]), array([2, 0, 1, 2]))
大于5的元素: [6 7 8 9]
8.4 集合操作
NumPy 提供了一些集合操作函数:
python
运行
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([4, 5, 6, 7, 8])
print("arr1:", arr1)
print("arr2:", arr2)
# 交集
intersection = np.intersect1d(arr1, arr2)
print("\n交集:", intersection)
# 并集
union = np.union1d(arr1, arr2)
print("并集:", union)
# 差集(arr1 - arr2)
difference = np.setdiff1d(arr1, arr2)
print("差集(arr1 - arr2):", difference)
# 对称差集(并集 - 交集)
symmetric_diff = np.setxor1d(arr1, arr2)
print("对称差集:", symmetric_diff)
# 判断元素是否在另一个数组中
isin = np.isin(arr1, arr2)
print("\narr1中的元素是否在arr2中:", isin)
print("arr1中在arr2中的元素:", arr1[isin])
输出结果:
plaintext
arr1: [1 2 3 4 5]
arr2: [4 5 6 7 8]
交集: [4 5]
并集: [1 2 3 4 5 6 7 8]
差集(arr1 - arr2): [1 2 3]
对称差集: [1 2 3 6 7 8]
arr1中的元素是否在arr2中: [False False False True True]
arr1中在arr2中的元素: [4 5]
九、总结与展望
NumPy 数组是 Python 科学计算的基石,它提供了高效的多维数组操作能力,为数据分析、机器学习、科学计算等领域提供了强大的支持。本文详细介绍了 NumPy 数组的创建、属性、索引与切片、形状操作、运算、迭代以及高级操作等内容,涵盖了从基础到进阶的各个方面。
掌握 NumPy 数组的操作不仅能够提高数据处理的效率,还能为学习更高级的库(如 Pandas、Scikit-learn 等)打下坚实的基础。NumPy 的功能远不止本文所介绍的这些,还有许多高级特性和优化技巧值得深入学习和探索。
随着数据科学领域的不断发展,NumPy 作为基础库也在持续更新和完善。建议读者在实际应用中多实践、多探索,充分利用 NumPy 的强大功能来解决实际问题。
希望本文能帮助你更好地理解和使用 NumPy 数组,在数据科学的道路上走得更远!