数组索引和切片
在这一章中,我们将深入学习 NumPy 数组的索引和切片操作,这是数据操作和分析的基础技能。
一维数组索引
基本索引
python
import numpy as np
# 创建一维数组
array = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
print("原始数组:", array)
print("数组长度:", len(array))
print()
# 正向索引(从0开始)
print("第一个元素 array[0]:", array[0])
print("第三个元素 array[2]:", array[2])
print("第五个元素 array[4]:", array[4])
print()
# 反向索引(从-1开始)
print("最后一个元素 array[-1]:", array[-1])
print("倒数第二个元素 array[-2]:", array[-2])
print("倒数第五个元素 array[-5]:", array[-5])输出结果:
原始数组: [ 10 20 30 40 50 60 70 80 90 100]
数组长度: 10
第一个元素 array[0]: 10
第三个元素 array[2]: 30
第五个元素 array[4]: 50
最后一个元素 array[-1]: 100
倒数第二个元素 array[-2]: 90
倒数第五个元素 array[-5]: 60一维数组切片
python
import numpy as np
array = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print("原始数组:", array)
print()
# 基本切片语法:array[start:stop:step]
print("前5个元素 array[:5]:", array[:5])
print("后5个元素 array[5:]:", array[5:])
print("中间元素 array[2:8]:", array[2:8])
print("全部元素 array[:]:", array[:])
print()
# 使用步长
print("每隔一个元素 array[::2]:", array[::2])
print("每隔两个元素 array[::3]:", array[::3])
print("从索引1开始每隔一个 array[1::2]:", array[1::2])
print()
# 反向切片
print("反向全部元素 array[::-1]:", array[::-1])
print("反向每隔一个 array[::-2]:", array[::-2])
print("从后往前取前5个 array[-5:]:", array[-5:])
print("从后往前取中间部分 array[-8:-2]:", array[-8:-2])输出结果:
原始数组: [0 1 2 3 4 5 6 7 8 9]
前5个元素 array[:5]: [0 1 2 3 4]
后5个元素 array[5:]: [5 6 7 8 9]
中间元素 array[2:8]: [2 3 4 5 6 7]
全部元素 array[:]: [0 1 2 3 4 5 6 7 8 9]
每隔一个元素 array[::2]: [0 2 4 6 8]
每隔两个元素 array[::3]: [0 3 6 9]
从索引1开始每隔一个 array[1::2]: [1 3 5 7 9]
反向全部元素 array[::-1]: [9 8 7 6 5 4 3 2 1 0]
反向每隔一个 array[::-2]: [9 7 5 3 1]
从后往前取前5个 array[-5:]: [5 6 7 8 9]
从后往前取中间部分 array[-8:-2]: [2 3 4 5 6 7]二维数组索引
基本二维索引
python
import numpy as np
# 创建二维数组
array_2d = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
])
print("二维数组:")
print(array_2d)
print(f"数组形状: {array_2d.shape}")
print()
# 访问单个元素
print("第一行第一列 array_2d[0, 0]:", array_2d[0, 0])
print("第二行第三列 array_2d[1, 2]:", array_2d[1, 2])
print("最后一行最后一列 array_2d[-1, -1]:", array_2d[-1, -1])
print("第三行第二列 array_2d[2, 1]:", array_2d[2, 1])
print()
# 访问整行
print("第一行 array_2d[0, :]:", array_2d[0, :])
print("第三行 array_2d[2, :]:", array_2d[2, :])
print("最后一行 array_2d[-1, :]:", array_2d[-1, :])
print()
# 访问整列
print("第一列 array_2d[:, 0]:", array_2d[:, 0])
print("第三列 array_2d[:, 2]:", array_2d[:, 2])
print("最后一列 array_2d[:, -1]:", array_2d[:, -1])输出结果:
二维数组:
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]
[13 14 15 16]]
数组形状: (4, 4)
第一行第一列 array_2d[0, 0]: 1
第二行第三列 array_2d[1, 2]: 7
最后一行最后一列 array_2d[-1, -1]: 16
第三行第二列 array_2d[2, 1]: 10
第一行 array_2d[0, :]: [1 2 3 4]
第三行 array_2d[2, :]: [ 9 10 11 12]
最后一行 array_2d[-1, :]: [13 14 15 16]
第一列 array_2d[:, 0]: [ 1 5 9 13]
第三列 array_2d[:, 2]: [ 3 7 11 15]
最后一列 array_2d[:, -1]: [ 4 8 12 16]二维数组切片
python
import numpy as np
array_2d = np.arange(20).reshape(4, 5)
print("原始数组:")
print(array_2d)
print()
# 行切片
print("前两行 array_2d[:2, :]:")
print(array_2d[:2, :])
print()
print("后两行 array_2d[2:, :]:")
print(array_2d[2:, :])
print()
# 列切片
print("前三列 array_2d[:, :3]:")
print(array_2d[:, :3])
print()
print("后两列 array_2d[:, -2:]:")
print(array_2d[:, -2:])
print()
# 行列同时切片
print("中间2x3子数组 array_2d[1:3, 1:4]:")
print(array_2d[1:3, 1:4])
print()
# 使用步长
print("每隔一行 array_2d[::2, :]:")
print(array_2d[::2, :])
print()
print("每隔一列 array_2d[:, ::2]:")
print(array_2d[:, ::2])
print()
# 复杂切片
print("每隔一行每隔一列 array_2d[::2, ::2]:")
print(array_2d[::2, ::2])输出结果:
原始数组:
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]
前两行 array_2d[:2, :]:
[[0 1 2 3 4]
[5 6 7 8 9]]
后两行 array_2d[2:, :]:
[[10 11 12 13 14]
[15 16 17 18 19]]
前三列 array_2d[:, :3]:
[[ 0 1 2]
[ 5 6 7]
[10 11 12]
[15 16 17]]
后两列 array_2d[:, -2:]:
[[ 3 4]
[ 8 9]
[13 14]
[18 19]]
中间2x3子数组 array_2d[1:3, 1:4]:
[[ 6 7 8]
[11 12 13]]
每隔一行 array_2d[::2, :]:
[[ 0 1 2 3 4]
[10 11 12 13 14]]
每隔一列 array_2d[:, ::2]:
[[ 0 2 4]
[ 5 7 9]
[10 12 14]
[15 17 19]]
每隔一行每隔一列 array_2d[::2, ::2]:
[[ 0 2 4]
[10 12 14]]高级索引
整数数组索引
python
import numpy as np
# 一维数组的整数数组索引
array = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
print("原始数组:", array)
# 使用索引数组
indices = np.array([0, 2, 4, 6])
print("索引数组:", indices)
print("选择的元素:", array[indices])
print()
# 可以重复索引
repeated_indices = np.array([1, 1, 3, 3, 5])
print("重复索引:", repeated_indices)
print("选择的元素:", array[repeated_indices])
print()
# 二维数组的整数数组索引
array_2d = np.arange(12).reshape(3, 4)
print("二维数组:")
print(array_2d)
print()
# 选择特定的行
row_indices = np.array([0, 2])
print("选择第0行和第2行:")
print(array_2d[row_indices])
print()
# 选择特定位置的元素
row_idx = np.array([0, 1, 2])
col_idx = np.array([1, 2, 3])
print("选择位置 (0,1), (1,2), (2,3) 的元素:")
print(array_2d[row_idx, col_idx])输出结果:
原始数组: [ 10 20 30 40 50 60 70 80 90 100]
索引数组: [0 2 4 6]
选择的元素: [10 30 50 70]
重复索引: [1 1 3 3 5]
选择的元素: [20 20 40 40 60]
二维数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
选择第0行和第2行:
[[ 0 1 2 3]
[ 8 9 10 11]]
选择位置 (0,1), (1,2), (2,3) 的元素:
[ 1 6 11]布尔索引
python
import numpy as np
# 创建数组
array = np.array([1, 5, 3, 8, 2, 9, 4, 7, 6])
print("原始数组:", array)
print()
# 创建布尔条件
condition = array > 5
print("条件 array > 5:", condition)
print("满足条件的元素:", array[condition])
print()
# 多个条件
condition_and = (array > 3) & (array < 8)
print("条件 (array > 3) & (array < 8):", condition_and)
print("满足条件的元素:", array[condition_and])
print()
condition_or = (array < 3) | (array > 7)
print("条件 (array < 3) | (array > 7):", condition_or)
print("满足条件的元素:", array[condition_or])
print()
# 二维数组的布尔索引
array_2d = np.random.randint(1, 10, (4, 4))
print("二维随机数组:")
print(array_2d)
print()
# 找出大于5的元素
mask = array_2d > 5
print("大于5的元素位置:")
print(mask)
print("大于5的元素值:", array_2d[mask])
print()
# 找出每行的最大值位置
max_in_rows = array_2d == array_2d.max(axis=1, keepdims=True)
print("每行最大值位置:")
print(max_in_rows)
print("每行最大值:", array_2d[max_in_rows])输出结果:
原始数组: [1 5 3 8 2 9 4 7 6]
条件 array > 5: [False False False True False True False True True]
满足条件的元素: [8 9 7 6]
条件 (array > 3) & (array < 8): [False True False False False False True True True]
满足条件的元素: [5 4 7 6]
条件 (array < 3) | (array > 7): [ True False False True True True False False False]
满足条件的元素: [1 8 2 9]
二维随机数组:
[[6 1 4 4]
[8 4 6 3]
[5 8 7 9]
[2 6 3 1]]
大于5的元素位置:
[[ True False False False]
[ True False True False]
[False True True True]
[False True False False]]
大于5的元素值: [6 8 6 8 7 9 6]
每行最大值位置:
[[ True False False False]
[ True False False False]
[False False False True]
[False True False False]]
每行最大值: [6 8 9 6]花式索引
使用索引数组
python
import numpy as np
# 创建二维数组
array_2d = np.arange(24).reshape(6, 4)
print("原始数组:")
print(array_2d)
print()
# 选择特定的行(按顺序)
row_indices = [1, 3, 5]
print(f"选择行 {row_indices}:")
print(array_2d[row_indices])
print()
# 选择特定的行(改变顺序)
row_indices_reorder = [5, 1, 3]
print(f"选择行 {row_indices_reorder} (重新排序):")
print(array_2d[row_indices_reorder])
print()
# 同时选择行和列
row_idx = [0, 2, 4]
col_idx = [1, 3, 2]
print(f"选择位置 {list(zip(row_idx, col_idx))}:")
print(array_2d[row_idx, col_idx])
print()
# 使用网格索引选择子矩阵
rows = np.array([1, 3])
cols = np.array([0, 2])
print("选择行1,3和列0,2的交叉位置:")
print(array_2d[np.ix_(rows, cols)])输出结果:
原始数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]
选择行 [1, 3, 5]:
[[ 4 5 6 7]
[12 13 14 15]
[20 21 22 23]]
选择行 [5, 1, 3] (重新排序):
[[20 21 22 23]
[ 4 5 6 7]
[12 13 14 15]]
选择位置 [(0, 1), (2, 3), (4, 2)]:
[ 1 11 18]
选择行1,3和列0,2的交叉位置:
[[ 4 6]
[12 14]]组合不同类型的索引
python
import numpy as np
array_2d = np.arange(20).reshape(4, 5)
print("原始数组:")
print(array_2d)
print()
# 组合整数索引和切片
print("第2行的前3列 array_2d[2, :3]:")
print(array_2d[2, :3])
print()
# 组合布尔索引和切片
row_mask = np.array([True, False, True, False])
print("选择第1和第3行的后3列:")
print(array_2d[row_mask, -3:])
print()
# 组合整数数组索引和切片
row_indices = [0, 2]
print("选择第0和第2行的中间3列:")
print(array_2d[row_indices, 1:4])
print()
# 复杂的组合索引
print("选择满足条件的行的特定列:")
row_condition = array_2d[:, 0] % 2 == 0 # 第一列是偶数的行
col_indices = [1, 3, 4] # 选择第1, 3, 4列
result = array_2d[row_condition][:, col_indices]
print(f"行条件: {row_condition}")
print(f"列索引: {col_indices}")
print("结果:")
print(result)输出结果:
原始数组:
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]
第2行的前3列 array_2d[2, :3]:
[10 11 12]
选择第1和第3行的后3列:
[[ 2 3 4]
[12 13 14]]
选择第0和第2行的中间3列:
[[ 1 2 3]
[11 12 13]]
选择满足条件的行的特定列:
行条件: [ True False True False]
列索引: [1, 3, 4]
结果:
[[ 1 3 4]
[11 13 14]]数组元素的修改
通过索引修改元素
python
import numpy as np
# 一维数组修改
array_1d = np.array([1, 2, 3, 4, 5])
print("原始一维数组:", array_1d)
# 修改单个元素
array_1d[2] = 99
print("修改索引2的元素:", array_1d)
# 修改多个元素
array_1d[[0, 4]] = [88, 77]
print("修改索引0和4的元素:", array_1d)
print()
# 二维数组修改
array_2d = np.arange(12).reshape(3, 4)
print("原始二维数组:")
print(array_2d)
# 修改单个元素
array_2d[1, 2] = 999
print("修改位置(1,2)的元素:")
print(array_2d)
# 修改整行
array_2d[0, :] = [100, 101, 102, 103]
print("修改第0行:")
print(array_2d)
# 修改整列
array_2d[:, 1] = [200, 201, 202]
print("修改第1列:")
print(array_2d)输出结果:
原始一维数组: [1 2 3 4 5]
修改索引2的元素: [ 1 2 99 4 5]
修改索引0和4的元素: [88 2 99 4 77]
原始二维数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
修改位置(1,2)的元素:
[[ 0 1 2 3]
[ 4 5 999 7]
[ 8 9 10 11]]
修改第0行:
[[100 101 102 103]
[ 4 5 999 7]
[ 8 9 10 11]]
修改第1列:
[[100 200 102 103]
[ 4 201 999 7]
[ 8 202 10 11]]通过布尔索引修改元素
python
import numpy as np
array = np.array([1, 5, 3, 8, 2, 9, 4, 7, 6])
print("原始数组:", array)
# 将大于5的元素设为0
array[array > 5] = 0
print("将大于5的元素设为0:", array)
# 重新创建数组
array = np.array([1, 5, 3, 8, 2, 9, 4, 7, 6])
print("\n重新创建数组:", array)
# 将偶数元素乘以10
array[array % 2 == 0] *= 10
print("将偶数元素乘以10:", array)
# 条件替换
array = np.array([1, 5, 3, 8, 2, 9, 4, 7, 6])
print("\n重新创建数组:", array)
# 使用 np.where 进行条件替换
result = np.where(array > 5, array, 0) # 大于5保持原值,否则为0
print("使用np.where条件替换:", result)
# 复杂条件替换
result = np.where((array > 3) & (array < 8), array * 2, array)
print("3<元素<8时乘以2:", result)输出结果:
原始数组: [1 5 3 8 2 9 4 7 6]
将大于5的元素设为0: [1 5 3 0 2 0 4 0 0]
重新创建数组: [1 5 3 8 2 9 4 7 6]
将偶数元素乘以10: [ 1 5 3 80 20 9 40 7 60]
重新创建数组: [1 5 3 8 2 9 4 7 6]
使用np.where条件替换: [0 0 0 8 0 9 0 7 6]
3<元素<8时乘以2: [ 1 10 3 8 2 9 8 14 12]实际应用示例
示例1:数据清洗
python
import numpy as np
# 模拟包含异常值的数据
np.random.seed(42)
data = np.random.normal(50, 10, 20) # 正常数据
data[5] = 200 # 异常值
data[15] = -50 # 异常值
print("原始数据:")
print(data)
print(f"数据统计: 均值={np.mean(data):.2f}, 标准差={np.std(data):.2f}")
print()
# 识别异常值(超过3个标准差)
mean_val = np.mean(data)
std_val = np.std(data)
threshold = 3 * std_val
outliers_mask = np.abs(data - mean_val) > threshold
print("异常值位置:", np.where(outliers_mask)[0])
print("异常值:", data[outliers_mask])
print()
# 清洗数据:移除异常值
clean_data = data[~outliers_mask]
print("清洗后的数据:")
print(clean_data)
print(f"清洗后统计: 均值={np.mean(clean_data):.2f}, 标准差={np.std(clean_data):.2f}")
print()
# 或者用中位数替换异常值
data_replaced = data.copy()
median_val = np.median(data[~outliers_mask])
data_replaced[outliers_mask] = median_val
print("用中位数替换异常值后:")
print(data_replaced)
print(f"替换后统计: 均值={np.mean(data_replaced):.2f}, 标准差={np.std(data_replaced):.2f}")示例2:图像处理
python
import numpy as np
# 创建一个简单的"图像"(8x8灰度图)
np.random.seed(42)
image = np.random.randint(0, 256, (8, 8), dtype=np.uint8)
print("原始图像:")
print(image)
print()
# 提取图像的某个区域(ROI - Region of Interest)
roi = image[2:6, 2:6] # 提取中心4x4区域
print("感兴趣区域 (ROI):")
print(roi)
print()
# 图像阈值处理
threshold = 128
binary_image = (image > threshold).astype(np.uint8) * 255
print(f"二值化图像 (阈值={threshold}):")
print(binary_image)
print()
# 图像掩码操作
# 创建一个圆形掩码
y, x = np.ogrid[:8, :8]
center_y, center_x = 4, 4
radius = 3
circle_mask = (x - center_x)**2 + (y - center_y)**2 <= radius**2
print("圆形掩码:")
print(circle_mask.astype(int))
print()
# 应用掩码
masked_image = image.copy()
masked_image[~circle_mask] = 0 # 圆形外的像素设为0
print("应用圆形掩码后的图像:")
print(masked_image)示例3:数据分析
python
import numpy as np
# 模拟学生成绩数据
np.random.seed(42)
students = 100
subjects = 5
scores = np.random.normal(75, 15, (students, subjects)) # 均值75,标准差15
scores = np.clip(scores, 0, 100) # 限制在0-100范围内
print(f"成绩数据形状: {scores.shape}")
print(f"成绩范围: {scores.min():.1f} - {scores.max():.1f}")
print()
# 找出优秀学生(平均分>85)
average_scores = np.mean(scores, axis=1)
excellent_students = average_scores > 85
print(f"优秀学生数量: {np.sum(excellent_students)}")
print(f"优秀学生平均分: {average_scores[excellent_students][:5]}")
print()
# 找出需要帮助的学生(任何科目<60分)
failing_mask = scores < 60
students_need_help = np.any(failing_mask, axis=1)
print(f"需要帮助的学生数量: {np.sum(students_need_help)}")
print()
# 分析各科目表现
subject_names = ['数学', '语文', '英语', '物理', '化学']
for i, subject in enumerate(subject_names):
subject_scores = scores[:, i]
print(f"{subject}: 平均分={np.mean(subject_scores):.1f}, "
f"最高分={np.max(subject_scores):.1f}, "
f"最低分={np.min(subject_scores):.1f}, "
f"及格率={np.sum(subject_scores >= 60)/len(subject_scores)*100:.1f}%")
print()
# 找出每个学生的最强和最弱科目
best_subjects = np.argmax(scores, axis=1)
worst_subjects = np.argmin(scores, axis=1)
print("前5名学生的最强/最弱科目:")
for i in range(5):
print(f"学生{i+1}: 最强-{subject_names[best_subjects[i]]}, "
f"最弱-{subject_names[worst_subjects[i]]}")性能考虑
视图 vs 副本
python
import numpy as np
array = np.arange(10)
print("原始数组:", array)
# 切片创建视图(共享内存)
slice_view = array[2:8]
print("切片视图:", slice_view)
print("是否共享内存:", np.shares_memory(array, slice_view))
# 修改视图会影响原数组
slice_view[0] = 999
print("修改视图后的原数组:", array)
print()
# 花式索引创建副本(独立内存)
array = np.arange(10) # 重新创建
fancy_copy = array[[1, 3, 5, 7]]
print("花式索引结果:", fancy_copy)
print("是否共享内存:", np.shares_memory(array, fancy_copy))
# 修改副本不会影响原数组
fancy_copy[0] = 888
print("修改副本后的原数组:", array)
print("修改后的副本:", fancy_copy)输出结果:
原始数组: [0 1 2 3 4 5 6 7 8 9]
切片视图: [2 3 4 5 6 7]
是否共享内存: True
修改视图后的原数组: [ 0 1 999 3 4 5 6 7 8 9]
花式索引结果: [1 3 5 7]
是否共享内存: False
修改副本后的原数组: [0 1 2 3 4 5 6 7 8 9]
修改后的副本: [888 3 5 7]本章小结
在这一章中,我们深入学习了:
- 一维和多维数组的基本索引方法
- 数组切片的各种语法和技巧
- 整数数组索引和布尔索引
- 花式索引的高级用法
- 通过索引修改数组元素
- 实际应用中的索引技巧
- 视图和副本的性能考虑
下一步
在下一章中,我们将学习 NumPy 的数学运算和函数,包括基本运算、统计函数、三角函数等。
练习题
- 创建一个 5x5 的数组,提取其对角线元素
- 从一个二维数组中提取所有偶数元素
- 使用布尔索引找出数组中的局部最大值
- 实现一个函数,交换二维数组的指定行和列
- 创建一个图像掩码,只保留图像中心的圆形区域