理解多维数组
在开始学习NumPy数组的索引和切片之前,让我们先来理解什么是多维数组。
从生活中理解多维数组
想象一下我们熟悉的几个场景:
- 一维数组:就像是一条线上排列的数字
- 例如:一个班级40名学生的数学成绩
- 或者:一周七天的气温数据
- 二维数组:就像一个Excel表格
- 例如:一个班级学生的多门课程成绩表
- 行代表不同的学生,列代表不同的科目
- 三维数组:就像多个表格叠在一起
- 例如:多个班级的学生成绩表
- 每一层是一个班级的成绩表
让我们用代码来看看这些例子:
import numpy as np
# 一维数组:一个班级的数学成绩
math_scores = np.array([85, 92, 78, 95, 88])
print("数学成绩:")
print(math_scores)
# 二维数组:一个班级的多门课程成绩
class_scores = np.array([
[85, 92, 78], # 第一个学生的数学、语文、英语成绩
[92, 88, 95], # 第二个学生的成绩
[78, 85, 89] # 第三个学生的成绩
])
print("\n班级成绩表(二维数组):")
print(class_scores)
# 三维数组:两个班级的成绩
two_classes = np.array([
# 第一个班
[[85, 92, 78],
[92, 88, 95],
[78, 85, 89]],
# 第二个班
[[95, 89, 92],
[88, 95, 85],
[79, 88, 92]]
])
print("\n两个班级的成绩(三维数组):")
print(two_classes)
数学成绩:
[85 92 78 95 88]
班级成绩表(二维数组):
[[85 92 78]
[92 88 95]
[78 85 89]]
两个班级的成绩(三维数组):
[[[85 92 78]
[92 88 95]
[78 85 89]]
[[95 89 92]
[88 95 85]
[79 88 92]]]
多维数组就像是数据的容器,可以按照现实世界的结构来组织数据。一维是一条线,二维是一个平面(表格),三维是一个立体(多层表格)。
ndarray对象的索引与切片
基本索引操作
NumPy数组的索引与Python列表类似,但功能更加强大。索引从0开始,可以使用负数索引从末尾开始计数。
import numpy as np
# 创建一个一维数组
arr = np.array([1, 2, 3, 4, 5])
# 基本索引
print(f"第一个元素: {arr[0]}")
print(f"最后一个元素: {arr[-1]}")
# 修改元素
arr[2] = 30
print(f"修改后的数组: {arr}")
第一个元素: 1
最后一个元素: 5
修改后的数组: [ 1 2 30 4 5]
多维数组的索引
对于多维数组,我们使用逗号分隔的索引来访问元素。
# 创建一个2x3的二维数组
arr_2d = np.array([[1, 2, 3],
[4, 5, 6]])
print("二维数组:")
print(arr_2d)
# 访问元素 [行, 列]
print(f"第1行第2列的元素: {arr_2d[0, 1]}")
print(f"第2行第3列的元素: {arr_2d[1, 2]}")
# 修改元素
arr_2d[1, 0] = 40
print("修改后的数组:")
print(arr_2d)
二维数组:
[[1 2 3]
[4 5 6]]
第1行第2列的元素: 2
第2行第3列的元素: 6
修改后的数组:
[[ 1 2 3]
[40 5 6]]
在NumPy中,索引顺序是先行后列,这与数学中的矩阵表示法一致。
数组切片
切片操作允许我们获取数组的子集。语法为start:stop:step
,其中start
是起始索引,stop
是结束索引(不包含),step
是步长。
# 一维数组切片
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 获取前5个元素
print(f"前5个元素: {arr[:5]}")
# 获取索引2到7的元素(不包含7)
print(f"索引2到7的元素: {arr[2:7]}")
# 获取所有偶数索引的元素
print(f"偶数索引的元素: {arr[::2]}")
# 反转数组
print(f"反转数组: {arr[::-1]}")
前5个元素: [0 1 2 3 4]
索引2到7的元素: [2 3 4 5 6]
偶数索引的元素: [0 2 4 6 8]
反转数组: [9 8 7 6 5 4 3 2 1 0]
多维数组的切片
对于多维数组,我们可以在每个维度上应用切片操作。
# 创建一个3x4的二维数组
arr_2d = np.arange(12).reshape(3, 4)
print("原始数组:")
print(arr_2d)
# 获取前2行
print("\n前2行:")
print(arr_2d[:2])
# 获取所有行的前3列
print("\n所有行的前3列:")
print(arr_2d[:, :3])
# 获取特定区域(第1-2行,第1-3列)
print("\n第1-2行,第1-3列:")
print(arr_2d[0:2, 0:3])
# 获取第1行和第3行
print("\n第1行和第3行:")
print(arr_2d[[0, 2]])
原始数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
前2行:
[[0 1 2 3]
[4 5 6 7]]
所有行的前3列:
[[ 0 1 2]
[ 4 5 6]
[ 8 9 10]]
第1-2行,第1-3列:
[[0 1 2]
[4 5 6]]
第1行和第3行:
[[ 0 1 2 3]
[ 8 9 10 11]]
切片操作返回的是原始数组的视图,而不是副本。这意味着修改切片会影响原始数组。如果需要副本,可以使用copy()
方法。
# 演示视图与副本的区别
arr = np.array([1, 2, 3, 4, 5])
# 创建视图
view = arr[1:4]
print(f"原始数组: {arr}")
print(f"视图: {view}")
# 修改视图
view[0] = 20
print(f"修改视图后的原始数组: {arr}")
# 创建副本
copy = arr[1:4].copy()
# 修改副本
copy[0] = 30
print(f"修改副本后的原始数组: {arr}")
原始数组: [1 2 3 4 5]
视图: [2 3 4]
修改视图后的原始数组: [ 1 20 3 4 5]
修改副本后的原始数组: [ 1 20 3 4 5]
高级索引
布尔索引
布尔索引允许我们基于条件选择元素。
# 创建一个数组
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
# 创建布尔掩码
mask = arr > 5
print(f"布尔掩码: {mask}")
# 使用布尔掩码选择元素
print(f"大于5的元素: {arr[mask]}")
# 简化写法
print(f"小于5的元素: {arr[arr < 5]}")
# 复合条件
print(f"大于3且小于8的元素: {arr[(arr > 3) & (arr < 8)]}")
布尔掩码: [False False False False False True True True True]
大于5的元素: [6 7 8 9]
小于5的元素: [1 2 3 4]
大于3且小于8的元素: [4 5 6 7]
花式索引
花式索引允许我们使用整数数组作为索引。
# 创建一个数组
arr = np.arange(10)
print(f"原始数组: {arr}")
# 使用整数数组作为索引
indices = [1, 3, 5, 7]
print(f"选定的元素: {arr[indices]}")
# 二维数组的花式索引
arr_2d = np.arange(16).reshape(4, 4)
print("\n二维数组:")
print(arr_2d)
# 选择特定行和列
rows = np.array([0, 2, 3])
cols = np.array([1, 2, 0])
print("\n选择的元素 (rows, cols):")
print(arr_2d[rows, cols]) # 选择 (0,1), (2,2), (3,0)
# 选择特定行的所有列
print("\n选择的行:")
print(arr_2d[rows]) # 选择第0, 2, 3行
原始数组: [0 1 2 3 4 5 6 7 8 9]
选定的元素: [1 3 5 7]
二维数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
选择的元素 (rows, cols):
[ 1 10 12]
选择的行:
[[ 0 1 2 3]
[ 8 9 10 11]
[12 13 14 15]]
NumPy广播机制
什么是广播?
广播(Broadcasting)是NumPy的一种强大功能,它允许不同形状的数组在算术运算中一起使用。广播机制自动扩展较小的数组,使其与较大的数组兼容,而无需实际复制数据。
广播遵循一定的规则,不是所有形状的数组都可以广播。
广播规则
- 如果两个数组的维度数不同,形状较小的数组会在前面补1
- 如果两个数组的形状在任何维度上不匹配,但其中一个维度的大小为1,则在该维度上进行广播
- 如果两个数组的形状在任何维度上不匹配,且没有一个维度的大小为1,则会引发错误
# 标量与数组的运算(最简单的广播)
arr = np.array([1, 2, 3, 4])
print(f"数组: {arr}")
print(f"数组 + 10: {arr + 10}")
# 一维数组与二维数组的广播
arr_1d = np.array([1, 2, 3])
arr_2d = np.array([[10], [20], [30]])
print("\n一维数组:")
print(arr_1d)
print("\n二维数组:")
print(arr_2d)
# 广播结果
result = arr_2d + arr_1d
print("\n广播结果:")
print(result)
数组: [1 2 3 4]
数组 + 10: [11 12 13 14]
一维数组:
[1 2 3]
二维数组:
[[10]
[20]
[30]]
广播结果:
[[11 12 13]
[21 22 23]
[31 32 33]]
广播的实际应用
数据归一化
# 创建一个表示多个样本特征的2D数组
data = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 计算每列的均值
means = data.mean(axis=0)
print("列均值:")
print(means)
# 使用广播进行归一化
normalized_data = data - means
print("\n归一化后的数据:")
print(normalized_data)
列均值:
[4. 5. 6.]
归一化后的数据:
[[-3. -3. -3.]
[ 0. 0. 0.]
[ 3. 3. 3.]]
添加偏置向量
# 创建一个表示多个样本的2D数组
samples = np.array([[1, 2, 3],
[4, 5, 6]])
# 创建一个偏置向量
bias = np.array([10, 20, 30])
# 使用广播添加偏置
result = samples + bias
print("添加偏置后的结果:")
print(result)
添加偏置后的结果:
[[11 22 33]
[14 25 36]]
广播机制使NumPy代码更加简洁高效,但也可能导致意外的行为。理解广播规则对于编写正确的NumPy代码至关重要。
结构化数组
什么是结构化数组?
结构化数组是NumPy中的一种特殊数组类型,它允许我们定义具有命名字段的复合数据类型,类似于C语言中的结构体或数据库中的表。
创建结构化数组
# 定义结构化数据类型
dt = np.dtype([
('name', 'U20'), # Unicode字符串,最大长度20
('age', 'i4'), # 32位整数
('weight', 'f4') # 32位浮点数
])
# 创建结构化数组
people = np.array([
('张三', 25, 70.5),
('李四', 35, 80.0),
('王五', 30, 65.2)
], dtype=dt)
print("结构化数组:")
print(people)
结构化数组:
[('张三', 25, 70.5) ('李四', 35, 80. ) ('王五', 30, 65.2)]
访问结构化数组的字段
# 访问单个字段
print("\n所有人的名字:")
print(people['name'])
print("\n所有人的年龄:")
print(people['age'])
# 访问单个记录
print("\n第一个人的信息:")
print(people[0])
# 访问单个记录的特定字段
print("\n第二个人的体重:")
print(people[1]['weight'])
所有人的名字:
['张三' '李四' '王五']
所有人的年龄:
[25 35 30]
第一个人的信息:
('张三', 25, 70.5)
第二个人的体重:
80.0
结构化数组的操作
# 根据字段排序
sorted_by_age = np.sort(people, order='age')
print("按年龄排序:")
print(sorted_by_age)
# 过滤数据
old_people = people[people['age'] > 30]
print("\n年龄大于30的人:")
print(old_people)
# 修改数据
people[0]['weight'] = 72.0
print("\n修改后的数组:")
print(people)
按年龄排序:
[('张三', 25, 70.5) ('王五', 30, 65.2) ('李四', 35, 80. )]
年龄大于30的人:
[('李四', 35, 80.)]
修改后的数组:
[('张三', 25, 72. ) ('李四', 35, 80. ) ('王五', 30, 65.2)]
嵌套结构化数据类型
# 定义嵌套的结构化数据类型
address_dt = np.dtype([
('city', 'U20'),
('street', 'U30')
])
person_dt = np.dtype([
('name', 'U20'),
('age', 'i4'),
('address', address_dt) # 嵌套类型
])
# 创建嵌套结构化数组
persons = np.array([
('张三', 25, ('北京', '朝阳路')),
('李四', 35, ('上海', '南京路')),
('王五', 30, ('广州', '天河路'))
], dtype=person_dt)
print("嵌套结构化数组:")
print(persons)
# 访问嵌套字段
print("\n所有人的城市:")
print(persons['address']['city'])
嵌套结构化数组:
[('张三', 25, ('北京', '朝阳路')) ('李四', 35, ('上海', '南京路'))
('王五', 30, ('广州', '天河路'))]
所有人的城市:
['北京' '上海' '广州']
小结
- NumPy的索引和切片操作提供了灵活的方式来访问和修改数组元素
- 广播机制使不同形状的数组能够进行算术运算,提高了代码的简洁性和效率
- 结构化数组允许我们在NumPy中处理复合数据类型,类似于数据库表
这些功能使NumPy成为数据处理和科学计算的强大工具。在实际应用中,理解这些概念对于高效处理大型数据集至关重要。