NumPy 学习笔记系列(三):深入理解数组操作
在前两篇文章中,我们讨论了NumPy的基础知识以及广播机制的工作原理。在这一篇中,我们将进一步探讨NumPy中其他重要的数组操作。包括数组切片与索引、数组形状操作(如reshape、flatten、transpose)、统计函数、条件筛选与布尔索引。这些操作是日常数据处理不可或缺的工具,深入理解它们的原理和应用将显著提升你的数据分析能力。
1. 数组切片与索引(Slicing and Indexing)
切片(Slicing)和索引(Indexing) 是从数组中提取子数组或特定元素的基本操作。它们在处理多维数组时尤为重要,因为你经常需要对部分数据进行操作。
1.1 基本切片语法
切片的基本语法是start:stop:step
,用于从数组中提取元素。start
表示切片的起始位置(包含),stop
表示结束位置(不包含),step
表示步长。
1.1.1 基本切片
让我们从一维数组的基本切片操作开始:
import numpy as np
# 创建一个一维数组
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 提取索引从2到6的元素(不包括6)
sub_arr1 = arr[2:6]
print("从索引2到6的切片结果:", sub_arr1)
# 提取每隔两个元素
sub_arr2 = arr[::2]
print("每隔两个元素的切片结果:", sub_arr2)
输出:
从索引2到6的切片结果: [2 3 4 5]
每隔两个元素的切片结果: [0 2 4 6 8]
arr[2:6]
表示从数组arr
中提取索引为2到5的元素(注意:切片是左闭右开区间,因此索引6处的元素不包含在结果中)。也就是说,它会提取arr[2]
到arr[5]
的元素。
arr[::2]
表示从头到尾每隔两个元素提取一次。::2
中的第一个:
表示从头到尾,2
表示步长,即每隔2个元素提取一次。
1.1.2 反向切片
切片语法也支持负索引和负步长,允许你从数组末尾开始进行切片操作。
# 使用负索引和负步长进行反向切片
sub_arr3 = arr[-5:-1]
print("负索引的切片结果:", sub_arr3)
sub_arr4 = arr[::-1]
print("数组的反向结果:", sub_arr4)
输出:
负索引的切片结果: [5 6 7 8]
数组的反向结果: [9 8 7 6 5 4 3 2 1 0]
arr[-5:-1]
使用负索引从数组的倒数第五个元素开始,提取到倒数第一个元素(不包含)。负索引从数组末尾计数,-1
表示数组的最后一个元素。
arr[::-1]
表示以步长-1
从数组末尾向前反向提取元素。这意味着整个数组将被逆序。
1.2 多维数组切片
多维数组的切片操作和一维数组类似,但你需要为每个维度指定切片。
1.2.1 二维数组的切片
# 创建一个二维数组
arr2d = np.array([[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]])
# 提取前两行和后两列的子数组
sub_arr5 = arr2d[:2, 2:]
print("前两行和后两列的切片结果:\n", sub_arr5)
# 提取每行的第2列
sub_arr6 = arr2d[:, 1]
print("每行的第二列:", sub_arr6)
# 提取中间的2x2子数组
sub_arr7 = arr2d[1:3, 1:3]
print("中间2x2的子数组:\n", sub_arr7)
输出:
前两行和后两列的切片结果:
[[2 3]
[6 7]]
每行的第二列: [ 1 5 9 13]
中间2x2的子数组:
[[ 5 6]
[ 9 10]]
arr2d[:2, 2:]
表示从二维数组arr2d
中提取前两行(行索引0和1)和后两列(列索引2和3)的元素。
arr2d[:, 1]
表示提取二维数组arr2d
的每一行中的第二列元素。这里的:
表示选择所有行,1
表示选择列索引为1的那一列。
arr2d[1:3, 1:3]
表示提取行索引为1到2的行和列索引为1到2的列(不包含3),形成一个2x2的子数组。
1.2.2 三维数组的切片
对于三维数组,切片操作同样可以扩展,适用于更多维度的数据。
# 创建一个三维数组
arr3d = np.array([[[0, 1, 2], [3, 4, 5]],
[[6, 7, 8], [9, 10, 11]],
[[12, 13, 14], [15, 16, 17]]])
# 提取第一层
sub_arr8 = arr3d[0]
print("第一层的切片结果:\n", sub_arr8)
# 提取每层的第二行
sub_arr9 = arr3d[:, 1, :]
print("每层的第二行:\n", sub_arr9)
# 提取从第一层第二行到第二层第一行的子数组
sub_arr10 = arr3d[0:2, 1, 1:]
print("复杂切片操作的结果:\n", sub_arr10)
输出:
第一层的切片结果:
[[0 1 2]
[3 4 5]]
每层的第二行:
[[ 3 4 5]
[ 9 10 11]
[15 16 17]]
复杂切片操作的结果:
[[ 4 5]
[10 11]]
arr3d[0]
表示提取三维数组arr3d
的第一层(索引为0)。这里的0
是第一个维度的索引,即三维数组的第一个二维数组。
arr3d[:, 1, :]
表示从每一层的第二行中提取所有列。这里的:
表示选择所有层,1
表示第二行,:
表示选择所有列。
arr3d[0:2, 1, 1:]
表示从第一层和第二层(索引为0和1)中提取第二行,并从第二行中提取索引为1到最后的列。
1.2.3 切片中的步长使用
步长不仅可以用于一维数组,在多维数组中同样适用,允许你以一定的间隔提取数据。
# 在二维数组中使用步长
sub_arr11 = arr2d[::2, ::2]
print("二维数组中步长为2的切片结果:\n", sub_arr11)
输出:
二维数组中步长为2的切片结果:
[[ 0 2]
[ 8 10]]
arr2d[::2, ::2]
表示从二维数组中每隔两行和两列提取一次元素。::2
中的第一个:
表示从头到尾选择所有行,但步长为2,第二个:
表示选择所有列,步长同样为2。
1.3 视图与副本
需要注意的是,NumPy切片返回的是原数组的视图(view),而不是副本(copy)。这意味着对切片的修改会影响到原数组:
# 修改切片,观察原数组的变化
sub_arr2d[0, 0] = 99
print("修改后的原数组:\n", arr2d)
输出:
修改后的原数组:
[[ 1 99 3]
[ 4 5 6]
[ 7 8 9]]
要避免这种情况,可以使用copy()
方法创建数组的副本。
# 创建副本
sub_arr2d_copy = arr2d[:2, 1:].copy()
sub_arr2d_copy[0, 0] = 77
print("修改副本后的原数组:\n", arr2d)
输出:
修改副本后的原数组:
[[ 1 99 3]
[ 4 5 6]
[ 7 8 9]]
2. 数组形状操作(Shape Operations)
NumPy数组的形状决定了数据的维度和结构。通过形状操作,你可以轻松地转换数据的结构以适应不同的分析需求。常用的形状操作包括reshape
、flatten
和transpose
。
2.1 reshape:改变数组形状
reshape
方法允许你在不改变数据的情况下重新安排数组的形状。这个操作非常有用,尤其是在你需要将一维数据转换为多维数据时。
# 创建一个一维数组
arr = np.arange(6)
# 将一维数组重塑为二维数组
reshaped_arr = arr.reshape(2, 3)
print("重塑后的数组:\n", reshaped_arr)
输出:
重塑后的数组:
[[0 1 2]
[3 4 5]]
在使用reshape
时,需要确保新形状的元素数量与原数组一致,否则会抛出错误。
2.2 flatten:展平数组
flatten
方法将多维数组展平成一维数组。这在将数据输入机器学习模型或进行其他操作时非常有用。
# 展平数组
flattened_arr = reshaped_arr.flatten()
print("展平后的数组:", flattened_arr)
输出:展平后的数组:[0 1 2 3 4 5]
2.3 transpose:转置数组
transpose
方法交换数组的维度,对于矩阵操作尤为重要。
# 创建一个二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
# 转置数组
transposed_arr = arr2d.transpose()
print("转置后的数组:\n", transposed_arr)
输出:
转置后的数组:
[[1 4]
[2 5]
[3 6]]
transpose
可以用于多维数组中,以灵活地调整数据的排列方式。
3. 统计函数(Statistical Functions)
NumPy提供了一系列的统计函数,帮助你快速计算数组的统计属性,如均值、标准差、最大值和最小值等。
####3.1 均值和中位数
# 创建一个随机数组
arr = np.random.randn(2, 3)
# 计算均值
mean_val = np.mean(arr)
print("均值:", mean_val)
# 计算中位数
median_val = np.median(arr)
print("中位数:", median_val)
输出:
均值:-0.268387
中位数:-0.133333
这些统计函数还可以沿特定轴进行计算,例如计算每列或每行的均值:
# 计算每列的均值
mean_col = np.mean(arr, axis=0)
print("每列的均值:", mean_col)
3.2 标准差和方差
标准差和方差是衡量数据分布的离散程度的两个重要指标。
# 计算标准差
std_val = np.std(arr)
print("标准差:", std_val)
# 计算方差
var_val = np.var(arr)
print("方差:", var_val)
输出:
标准差:0.823
方差:0.676
3.3 最大值和最小值
最大值和最小值函数可以帮助你快速找到数组中的极值。
# 计算最大值和最小值
max_val = np.max(arr)
min_val = np.min(arr)
print("最大值:", max_val, ",最小值:", min_val)
输出:最大值:0.975,最小值:-1.344
这些函数对于探索性数据分析(EDA)和初步了解数据分布非常有用。
4. 条件筛选与布尔索引(Conditional Selection and Boolean Indexing)
条件筛选和布尔索引允许你根据特定条件筛选数组中的元素,这是数据清洗和分析中的常用操作。
4.1 基本条件筛选
# 创建一个数组
arr = np.array([1, 2, 3, 4, 5, 6])
# 筛选出大于3的元素
filtered_arr = arr[arr > 3]
print("筛选结果:", filtered_arr)
输出:筛选结果:[4 5 6]
这种方式可以很方便地从数组中提取满足条件的子集。
4.2 组合条件筛选
你还可以组合多个条件进行复杂的筛选操作:
# 筛选出大于2且小于5的元素
filtered_arr = arr[(arr > 2) & (arr < 5)]
print("组合条件筛选结果:", filtered_arr)
输出:组合条件筛选结果:[3 4]
这种方法使你能够对数据进行更精确的操作和分析。
4.3 使用布尔索引进行赋值
布尔索引不仅可以用于条件筛选,还可以用于对数组中的特定元素进行赋值操作。这种技术在数据清洗和处理过程中非常有用,能够有效地修改数组中的数据。
通过布尔索引,你可以对满足特定条件的数组元素进行赋值。这种方式在数据预处理、异常值处理等场景下尤为实用。
# 创建一个数组
arr = np.array([1, 2, 3, 4, 5, 6])
# 将所有大于3的元素设为10
arr[arr > 3] = 10
print("赋值后的数组:", arr)
输出:赋值后的数组:[ 1 2 3 10 10 10]
在这个例子中,我们将数组中所有大于3的元素都设置为10。这种操作可以帮助你快速处理数据中的特定值,尤其是当你需要标准化数据或处理异常值时。
4.4 布尔数组的生成
有时,你可能需要生成一个布尔数组,用于后续的筛选或其他操作。你可以通过比较操作生成布尔数组:
# 生成布尔数组
bool_arr = arr > 2
print("布尔数组:", bool_arr)
输出:布尔数组:[False False True True True True]
这个布尔数组可以直接用于筛选、赋值或其他操作。
4.5 结合布尔索引与其他操作
布尔索引还可以与统计函数等其他操作结合使用,以实现更复杂的数据处理任务。
# 计算满足条件的元素的均值
mean_filtered = np.mean(arr[arr > 2])
print("筛选后元素的均值:", mean_filtered)
输出:筛选后元素的均值:10.0
这种方式允许你对筛选后的数据进一步进行统计分析,非常适合于数据清洗和预处理阶段。
总结
通过本文的学习,你现在应该对NumPy中的一些关键数组操作有了更深入的理解。我们详细探讨了数组切片与索引、数组形状操作(reshape、flatten、transpose)、统计函数以及条件筛选和布尔索引。掌握这些操作能够帮助你更加灵活、高效地处理数据,从而为后续的分析和建模打下坚实的基础。
在数据科学和机器学习的工作流程中,这些操作无处不在,是每个数据科学家和工程师都必须熟练掌握的技能。在接下来的文章中,我们将继续深入探讨NumPy的其他高级功能,如线性代数操作、随机数生成以及与其他库的整合等。
如果你有任何问题或建议,欢迎在评论区留言。继续加油,让我们一起探索NumPy的更多可能性!