Numpy入门2——视图和副本、伪随机数、切片和索引、数组的轴操作
1、副本和视图
在NumPy中,副本(Copy)和视图(View)是两种不同的数组操作方式,它们的主要区别在于内存和数据共享的机制。理解它们的差异对高效使用NumPy非常重要。
视图(View)
- 视图:不拥有自己的数据,而是与原数组共享同一块内存数据,视图是原数组的另一种访问方式
- 不复制数据:视图和原数组指向同一数据内存,视图仅仅是创建了一个新的引用
- 视图和原数组相互影响:修改视图会影响原数组,反之亦然。
import numpy as npa = np.array([1, 2, 3, 4])b = a[1:3] # b是a的视图b[0] = 100 # 修改b会影响aprint(a) # 输出:[1, 100, 3, 4]
** 副本(Copy)**
- 独立内存:创建的一个新的数组对象,即完全独立的新数组,拥有自己的内存空间。
- 数据隔离:副本是复制数据,副本与原数组互不影响。
- 通过显式调用
copy()
方法或某些操作(如布尔索引)生成。
a = np.array([1, 2, 3, 4])c = a.copy() # c是a的副本c[0] = 99 # 修改c不会影响aprint(a) # 输出:[1, 2, 3, 4]
视图和副本的区别
特性 | 视图(View) | 副本(Copy) |
---|---|---|
内存共享 | 是(与原数组共享) | 否(独立内存) |
数据独立 | 修改互相影响 | 修改互不影响 |
生成方式 | 切片、reshape() 等 | copy() 、布尔索引等 |
内存效率 | 高(不复制数据) | 低(复制数据) |
如何判断是视图还是副本?
- 查看数组的
base
属性:副本的base一定是none
a = np.array([1, 2, 3])b = a[:2]print(b.base is a) # True → b是a的视图c = a.copy()print(c.base is None) # True → c是副本
- 使用np.shares_memory():检查内存是否共享,一定能判断出是不是视图,False表示一定不是视图,True表示一定是视图
- 使用np.may_share_memory():只能判断出不是视图关系,False表示一定不是视图,True表示可能是视图
a = np.array([1, 2, 3])
b = a.copy()print(np.shares_memory(a, b)) # False(副本,内存独立)
print(np.may_share_memory(a, b)) # False
2、索引和切片
ndarray的切片是原始数组的视图,数据不会被复制,视图上的任何修改都会直接反映到源数组上。
基本的索引和切片
- 基本的索引:一维数组中的索引,类似python列表;多维数组的索引,按照维度进行逐级索引
- 基本切片:一维的切片和python列表相同;多维数组的切片是每个维度单独切片
import numpy as npa = np.array([10, 20, 30, 40, 50])
b = np.array([[1, 2, 3],[4, 5, 6],[7, 8, 9]])print(a[0]) # 输出: 10(正索引)
print(a[-1]) # 输出: 50(负索引)
print(b[0][1]) # 输出: 2(第0行第1列)
print(b[0, 1]) # 输出: 2(第0行第1列)
# 这两种写作方式相同# 切片
print(a[1:4]) # 输出: [20, 30, 40](左闭右开)
print(a[::2]) # 输出: [10, 30, 50](步长=2)
print(b[0:2, 1:3]) # 输出第0~1行,第1~2列:# [[2, 3],# [5, 6]]
高级索引
- 整数数组索引(花式索引:fancy indexing):使用另一个整数数组作为索引
arr[:] = np.arange(1,5).reshape((4,1))
print(arr)
# 以特定顺序选取行子集,只需传入用于指定顺序的整数列表或ndarray
print(arr[[1,3,0]])
# 负数索引从某尾开始选取行
print(arr[[-3, -1]])# 复杂的花式索引 (配合连续索引)
print(arr[[1,2]][:,[0,1]]) # 选取第2、3行和第1、2列的数字
print(arr[[1,2],[0,1]]) # 选取(1,0)(2,1)位置的数字
# 对花式索引赋值,会修改被索引的值
arr[[1,2],[0,2]] = 0
print(arr)
- 布尔索引:使用布尔条件选择元素,True留下,False舍弃。
布尔数组的长度必须和被索引的轴的长度一致。
a = np.array([1, 2, 3, 4])
mask = a > 2
print(mask) # 输出:[False False True True]
print(a[mask]) # 输出: [3, 4]mask_test = np.array([True, False, True]) # 长度不一致
print(a[mask_test]) # 将报错
- 多维数组的高级索引
b = np.array([[1, 2], [3, 4], [5, 6]])
row_indices = np.array([0, 2])
col_indices = np.array([1, 0])
print(b[row_indices, col_indices]) # 输出: [2, 5]
数组的转置和轴对换
转置(Transpose)和轴对换(Axis Swapping)是调整数组维度的重要操作,常用于数据重塑、矩阵运算或广播对齐。返回视图。
.T
属性或np.transpose()
是一样的,.T
是transpose()
的缩写- 默认情况转置会将所有轴的顺序进行反转
- 可以使用
np.transpose(a, axes)
自定义轴的排列顺序。其中的[i,j,k]索引的元素,的索引变成[j,i,k] ,也就是索引为[1,2,3]位置的元素的位置变成[2,1,3]。 - 也可以使用
np.swapaxes(a,ax1,ax2)
将两个轴进行对换,原理同np.transpose(a, axes)
- 转置可能导致数组内存不连续,影响计算效率。可用
np.ascontiguousarray()
优化,确保内存连续
b = np.array([[1, 2], [3, 4]])
print(b.T) # 输出: [[1 3]# [2 4]]d = np.arange(24).reshape(2, 3, 4)
print(d.shape) # 输出: (2, 3, 4)# 将轴顺序从 (0,1,2) 改为 (1,0,2)
e = np.transpose(d, (1, 0, 2))
print(e.shape) # 输出: (3, 2, 4)f = np.arange(8).reshape(2, 2, 2)
g = np.swapaxes(f, 0, 2) # 交换第0轴和第2轴
print(g.shape) # 输出: (2, 2, 2)
伪随机数
伪随机数是指通过确定性算法生成的、看似随机但实际上可预测的数列。它们并不是真正的随机数,而是通过数学公式或计算机程序模拟的“随机”序列。
为什么需要伪随机数
-
计算机无法生成真随机:经典计算机是确定性的,需依赖外部物理熵源(如热噪声)才能生成真随机数。
-
可复现性:科学计算、仿真实验需要相同随机序列以验证结果(通过固定种子实现)。
-
高效性:伪随机数算法(如线性同余法)计算速度快,适合大规模应用。
NumPy中的伪随机数生成
- numpy中random 模块,提供了和随机数相关的函数
- random.rand(n) :返回n个0-1之间的随机数
- random.randn(d0, d1) :返回服从均值为d0,标准差为d1的正态分布的随机数
- np.random.standard_normal(size=(d0, d1)) :返回服从均值为d0,标准差为d1的正态分布的随机数
- np.random.normal(loc=0, scale=1, size=(d0, d1)) :返回服从均值为d0,标准差为d1的正态分布的随机数
- random.randint(开始,结束,总数) :返回从开始到结束共总数个随机整数
- random.normal(0, 1, 3) :根据均值0,标准差1的正态分布生成3个随机数
import numpy as np# 生成均匀分布随机数
np.random.rand(3) # [0,1) 区间,输出如 [0.42, 0.71, 0.15]
np.random.randint(1, 10, 5) # 1~9的整数,如 [3, 7, 2, 5, 8]# 生成正态分布随机数
np.random.normal(0, 1, 3) # 均值0,标准差1,如 [-0.21, 1.34, 0.56]
- random.seed()(旧版)、random.default_rng(seed=*)(新版):控制随机种子,确保复现运行结果完成测试。
种子:相同的种子可以生成相同的随机数,我们看似没有种子的时候默认生成的随机数一般都是以系统时钟等为种子计算出来的。 - random.choice(指定数组,元素个数,p=[概率设置]):从指定数组中根据指定概率抽取指定元素个数的元素
- random.shuffle(a):混洗,没有返回结果,说明传进来的ndarray本身被修改了(内容可变,但是形状和数据类型是固定的)
- np.random.permutation(a):混洗,返回一个新的数组,可以保证原数组不变
np.random.seed(42) # 旧版的固定种子写法
np.random.default_rng(seed=42) # 新版的固定种子写法,比旧版更加安全
a = np.random.rand(3) # 每次运行结果相同,如 [0.37, 0.95, 0.73]languages = ['C', 'C#', 'Python', 'C++', 'Java', 'SQL']
np.random.choice(languages, 3)# 指定抽取3个
np.random.choice(languages, 30, p=[0.7, 0.1, 0.1, 0.05, 0.02, 0.03]) # p中元素数量要和列表长度相等,元素加起来和必须是1根据概率抽取内容b = np.array([1, 2, 3, 4])
np.random.shuffle(b) # 直接修改 b
print(b) # 可能输出:[3, 1, 4, 2]