NumPy 数组操作
本章将详细介绍 NumPy 中数组的各种操作,包括形状变换、数组合并分割、元素操作等。这些操作是数据处理中最常用的功能。
形状变换
reshape - 改变数组形状
reshape 函数可以在不改变数据的情况下改变数组的形状:
import numpy as np
# 创建一维数组
arr = np.arange(12)
print(f"原始数组: {arr}")
print(f"原始形状: {arr.shape}")
# 改为 3 行 4 列
reshaped = arr.reshape((3, 4))
print(f"\nreshape((3, 4)):\n{reshaped}")
# 使用 -1 自动推断维度
reshaped2 = arr.reshape((3, -1))
print(f"\nreshape((3, -1)): {reshaped2.shape}") # (3, 4)
reshaped3 = arr.reshape((-1, 3))
print(f"reshape((-1, 3)): {reshaped3.shape}") # (4, 3)
关键点:
reshape返回的是原数组的视图,修改视图会影响原数组- 使用
-1让 NumPy 自动计算该维度的大小 - 总元素数量必须匹配
# 验证视图共享数据
arr = np.arange(12)
reshaped = arr.reshape((3, 4))
reshaped[0, 0] = 100
print(f"原数组: {arr[0]}") # 100,说明是视图
# 如需复制,使用 copy()
reshaped_copy = arr.reshape((3, 4)).copy()
reshaped_copy[0, 0] = 200
print(f"原数组: {arr[0]}") # 100,未改变
ravel 和 flatten - 展平数组
两者都用于将多维数组转换为一维,但有重要区别:
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
# ravel 返回视图(如果可能)
raveled = arr.ravel()
print(f"ravel: {raveled}")
raveled[0] = 100
print(f"修改 ravel 后原数组: {arr[0, 0]}") # 100
# flatten 返回副本
flattened = arr.flatten()
print(f"flatten: {flattened}")
flattened[0] = 200
print(f"修改 flatten 后原数组: {arr[0, 0]}") # 100,不变
性能对比:对于大数组,ravel 通常更快(因为可能避免复制),但如果不确定是否需要修改原数组,使用 flatten 更安全。
transpose 和 T - 转置
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(f"原数组:\n{arr}")
# 转置
print(f"\ntranspose:\n{np.transpose(arr)}")
print(f"\nT 属性:\n{arr.T}")
# 高维数组转置
arr3d = np.arange(24).reshape((2, 3, 4))
print(f"\n3D 数组形状: {arr3d.shape}")
print(f"转置后形状: {arr3d.transpose().shape}") # (4, 3, 2)
# 指定轴顺序
print(f"交换轴 0 和 2: {arr3d.transpose(2, 1, 0).shape}") # (4, 3, 2)
维度变换
expand_dims - 增加维度
import numpy as np
arr = np.array([1, 2, 3])
print(f"原始形状: {arr.shape}") # (3,)
# 在轴 0 添加维度
expanded = np.expand_dims(arr, axis=0)
print(f"axis=0: {expanded.shape}") # (1, 3)
# 在轴 1 添加维度
expanded2 = np.expand_dims(arr, axis=1)
print(f"axis=1: {expanded2.shape}") # (3, 1)
# 使用 np.newaxis 效果相同
print(f"使用 newaxis: {arr[np.newaxis, :].shape}") # (1, 3)
print(f"使用 newaxis: {arr[:, np.newaxis].shape}") # (3, 1)
squeeze - 移除单维度
import numpy as np
# 有一个维度为 1 的数组
arr = np.array([[[1], [2], [3]]])
print(f"形状: {arr.shape}") # (1, 3, 1)
# 移除所有为 1 的维度
squeezed = np.squeeze(arr)
print(f"squeeze 后: {squeezed.shape}") # (3,)
# 指定移除特定维度
arr2 = np.array([[[1, 2, 3]]])
print(f"形状: {arr2.shape}") # (1, 1, 3)
squeezed2 = np.squeeze(arr2, axis=0) # 不能移除 axis=1
print(f"axis=0: {squeezed2.shape}") # (1, 3)
atleast_1d/2d/3d - 确保维度
import numpy as np
# 将输入转换为至少指定维度的数组
a = 5 # 标量
print(f"原始: {a}, 类型: {type(a)}")
arr1 = np.atleast_1d(a)
print(f"atleast_1d: {arr1.shape}") # (1,)
arr2 = np.atleast_2d(a)
print(f"atleast_2d: {arr2.shape}") # (1, 1)
arr3 = np.atleast_3d(a)
print(f"atleast_3d: {arr3.shape}") # (1, 1, 1)
数组合并
concatenate - 沿轴连接
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
# 默认沿 axis=0(垂直方向拼接)
c1 = np.concatenate((a, b))
print(f"axis=0 (默认):\n{c1}")
# 沿 axis=1(水平方向拼接)
c2 = np.concatenate((a, b.T), axis=1)
print(f"\naxis=1:\n{c2}")
# axis=None 先展平再拼接
c3 = np.concatenate((a, b), axis=None)
print(f"\naxis=None: {c3}")
重要规则:除拼接轴外,其他轴的形状必须一致。
# 正确示例
a = np.array([[1, 2], [3, 4]]) # shape: (2, 2)
b = np.array([[5, 6]]) # shape: (1, 2)
print(np.concatenate((a, b), axis=0).shape) # (3, 2)
# 错误示例 - 形状不兼容
# c = np.array([[7, 8, 9]]) # shape: (1, 3)
# np.concatenate((a, c), axis=0) # 错误!形状不匹配
stack - 创建新轴堆叠
与 concatenate 不同,stack 会创建一个新轴:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 默认沿新轴 0 堆叠
s1 = np.stack((a, b))
print(f"stack 默认:\n{s1}")
print(f"形状: {s1.shape}") # (2, 3)
# 沿 axis=1 堆叠
s2 = np.stack((a, b), axis=1)
print(f"\nstack axis=1:\n{s2}")
print(f"形状: {s2.shape}") # (3, 2)
vstack / hstack / dstack
便捷函数,用于特定方向的堆叠:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
c = np.array([[7, 8], [9, 10]])
# vstack: 垂直堆叠(行方向)
v = np.vstack((a, c))
print(f"vstack:\n{v}")
# hstack: 水平堆叠(列方向)
h = np.hstack((c, c))
print(f"\nhstack:\n{h}")
# dstack: 深度方向堆叠
d1 = np.array([[1, 2], [3, 4]])
d2 = np.array([[5, 6], [7, 8]])
d = np.dstack((d1, d2))
print(f"\ndstack:\n{d}")
print(f"形状: {d.shape}") # (2, 2, 2)
block - 分块合并
import numpy as np
# 使用嵌套列表构建复杂结构
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
c = np.array([[7], [8]])
# 类似 MATLAB 的块矩阵
result = np.block([
[a, b.T], # 第一行
[c, 9] # 第二行
])
print(f"block 结果:\n{result}")
数组分割
split - 分割数组
import numpy as np
arr = np.arange(12)
# 均匀分割为 3 份
s1 = np.split(arr, 3)
print(f"split(arr, 3): {s1}")
# 按位置分割
s2 = np.split(arr, [3, 7, 10])
print(f"split(arr, [3, 7, 10]): {s2}")
# [0,1,2], [3,4,5,6], [7,8,9], [10,11]
hsplit / vsplit / dsplit
import numpy as np
arr = np.arange(20).reshape((4, 5))
# 水平分割(按列)
print(f"hsplit(arr, 5):\n{np.hsplit(arr, 5)}")
# 垂直分割(按行)
print(f"vsplit(arr, 2):\n{np.vsplit(arr, 2)}")
# 深度分割(按第三维)
arr3d = np.arange(60).reshape((2, 3, 10))
print(f"dsplit 形状: {np.dsplit(arr3d, 2)[0].shape}")
元素添加与删除
append / insert / delete
import numpy as np
arr = np.array([1, 2, 3, 4])
# append: 末尾添加
a1 = np.append(arr, 5)
print(f"append: {a1}")
a2 = np.append(arr, [5, 6])
print(f"append 多个: {a2}")
# 沿轴添加
arr2d = np.array([[1, 2], [3, 4]])
a3 = np.append(arr2d, [[5, 6]], axis=0)
print(f"\nappend 行: \n{a3}")
a4 = np.append(arr2d, [[5], [6]], axis=1)
print(f"\nappend 列:\n{a4}")
# insert: 指定位置插入
i1 = np.insert(arr, 2, 100) # 在索引 2 前插入
print(f"\ninsert: {i1}")
# delete: 删除元素
d1 = np.delete(arr, 2) # 删除索引 2 的元素
print(f"delete: {d1}")
resize - 调整大小
import numpy as np
arr = np.array([1, 2, 3])
# 增大数组(重复填充)
r1 = np.resize(arr, (6,))
print(f"resize 到 6: {r1}") # [1, 2, 3, 1, 2, 3]
# 减小数组
r2 = np.resize(arr, (2,))
print(f"resize 到 2: {r2}") # [1, 2]
# 也可以用 ndarray.resize(原地修改)
arr2 = np.array([1, 2, 3, 4, 5])
arr2.resize((3,))
print(f"原地 resize: {arr2}")
元素重排
flip - 翻转元素
import numpy as np
arr = np.arange(12).reshape((3, 4))
print(f"原数组:\n{arr}")
# 沿所有轴翻转
print(f"\nflip:\n{np.flip(arr)}")
# 沿指定轴翻转
print(f"\nflip axis=0:\n{np.flip(arr, axis=0)}")
print(f"\nflip axis=1:\n{np.flip(arr, axis=1)}")
# fliplr: 左右翻转
print(f"\nfliplr:\n{np.fliplr(arr)}")
# flipud: 上下翻转
print(f"\nflipud:\n{np.flipud(arr)}")
roll - 滚动元素
import numpy as np
arr = np.arange(10)
# 滚动
print(f"roll 3: {np.roll(arr, 3)}") # [7,8,9,0,1,2,3,4,5,6]
print(f"roll -3: {np.roll(arr, -3)}") # [3,4,5,6,7,8,9,0,1,2]
# 沿轴滚动
arr2d = np.arange(12).reshape((3, 4))
print(f"\n2D roll axis=1, shift=2:\n{np.roll(arr2d, 2, axis=1)}")
rot90 - 旋转90度
import numpy as np
arr = np.arange(9).reshape((3, 3))
print(f"原数组:\n{arr}")
# 逆时针旋转 90 度
print(f"\nrot90 1次:\n{np.rot90(arr)}")
# 旋转 2 次(180度)
print(f"\nrot90 2次:\n{np.rot90(arr, 2)}")
# 顺时针旋转(k=-1)
print(f"\nrot90 -1:\n{np.rot90(arr, -1)}")
tile 和 repeat - 重复元素
import numpy as np
arr = np.array([1, 2])
# tile: 重复整个数组
t1 = np.tile(arr, 3)
print(f"tile(arr, 3): {t1}") # [1, 2, 1, 2, 1, 2]
t2 = np.tile(arr, (2, 2))
print(f"tile(arr, (2,2)):\n{t2}")
# repeat: 重复每个元素
r1 = np.repeat(arr, 3)
print(f"repeat(arr, 3): {r1}") # [1,1,1,2,2,2]
# 二维数组 repeat
arr2d = np.array([[1, 2], [3, 4]])
r2 = np.repeat(arr2d, 2, axis=0)
print(f"\nrepeat axis=0:\n{r2}")
实战示例:图像处理基础
这些数组操作在图像处理中非常常见:
import numpy as np
# 模拟一张简单的灰度图像(8x8 像素)
image = np.random.randint(0, 256, (8, 8), dtype=np.uint8)
print(f"原始图像形状: {image.shape}")
# 垂直翻转(镜像)
flipped = np.flipud(image)
# 旋转 90 度
rotated = np.rot90(image)
# 拼接多张图像(组合图)
top_half = image[:4, :]
bottom_half = image[4:, :]
combined = np.vstack((top_half, bottom_half))
# 扩展维度(添加通道)
# 假设添加一个通道维度用于 RGB
grayscale_3d = np.expand_dims(image, axis=2)
print(f"扩展后形状: {grayscale_3d.shape}") # (8, 8, 1)
print("\n图像处理示例完成")
性能优化技巧
避免不必要的复制
import numpy as np
# 错误的做法:创建不必要的副本
arr = np.arange(10000).reshape((100, 100))
_ = arr.reshape((50, 200)) # 如果不需要保存,可以不用赋值
# 正确的做法:使用视图
view = arr.reshape((50, 200)) # 这是视图
view[0, 0] = 0 # 会修改原数组
# 当需要副本时显式使用
copy = arr.reshape((50, 200)).copy()
# 或使用 np.reshape(总是返回视图,如果可能)
copy = np.reshape(arr, (50, 200))
连续性
import numpy as np
arr = np.arange(20).reshape((4, 5))
# 检查是否连续
print(f"C 连续: {arr.flags['C_CONTIGUOUS']}")
print(f"F 连续: {arr.flags['F_CONTIGUOUS']}")
# 非连续切片
sliced = arr[:, :3]
print(f"切片后 C 连续: {sliced.flags['C_CONTIGUOUS']}")
# 转换为连续
contiguous = np.ascontiguousarray(sliced)
print(f"转换后 C 连续: {contiguous.flags['C_CONTIGUOUS']}")
连续数组在进行某些操作时性能更好。
小结
本章介绍了 NumPy 中数组操作的核心函数:
- 形状变换:
reshape、ravel、flatten、transpose - 维度变换:
expand_dims、squeeze、atleast_*d - 合并数组:
concatenate、stack、vstack、hstack、block - 分割数组:
split、hsplit、vsplit、dsplit - 增删元素:
append、insert、delete、resize - 元素重排:
flip、roll、rot90、tile、repeat
熟练掌握这些操作,能够高效地处理各种数据结构和进行数据预处理。
练习
- 创建一个 4x4 的矩阵,使用
reshape将其转换为 2x8 - 使用
vstack合并两个不同形状的数组 - 将一张图像水平翻转并垂直翻转
- 使用
split将数组按 [2, 5, 8] 位置分割 - 创建一个"九宫格"图像拼接(3x3 张小图合成一张大图)