我们已经学习了 NumPy 的基础知识。现在,让我们更深入地探索!🤿 我们将学习:
ndarray
的内部结构。ndarray
对象内部结构 🤔ufunc
高级用法 🚀ndarray
对象内部结构 🤔ndarray
将一块同质类型数据(所有元素具有相同数据类型)的内存块解释为多维数组。dtype
):数据如何被解释(浮点数、整数、布尔值等)。arr[::2, ::-1]
这样的操作,而无需复制数据!⚡️ndarray
内部结构一个 ndarray
包含:
dtype
):描述固定大小的值单元格(例如,float64、int32)。(10, 5)
表示 10x5 的数组)。ndarray
可视化 🖼️float64
)。(3, 4, 5)
)。(10, 5)
:float64
(8 字节)数组通常具有跨度 (160, 40, 8)
(C 顺序):arr_3d.strides = (160, 40, 8)
float64
大小)。np.integer
、np.floating
)和 np.issubdtype
来检查数组类型:ints = np.ones(10, dtype=np.uint16) # 创建一个 uint16 类型的全 1 数组
floats = np.ones(10, dtype=np.float32) # 创建一个 float32 类型的全 1 数组
print(np.issubdtype(ints.dtype, np.integer)) # 检查 ints.dtype 是否是 np.integer 的子类型
print(np.issubdtype(floats.dtype, np.floating)) # 检查 floats.dtype 是否是 np.floating 的子类型
True
True
.mro()
(方法解析顺序)查看父类:[numpy.float64,
numpy.floating,
numpy.inexact,
numpy.number,
numpy.generic,
float,
object]
np.float64
继承自 np.floating
、np.inexact
、…、object
。generic
是根number
, bool_
等, 是 generic
的子类reshape()
方法和一个形状元组:-1
来推断大小:ravel()
:如果值是连续的,则不复制。array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
flatten()
:始终返回副本。reshape
和 ravel
接受一个 order
参数('C'
或 'F'
)。numpy.concatenate
:沿现有轴连接数组。arr1 = np.array([[1, 2, 3], [4, 5, 6]]) # 创建一个 2x3 的数组
arr2 = np.array([[7, 8, 9], [10, 11, 12]]) # 创建一个 2x3 的数组
print(np.concatenate([arr1, arr2], axis=0)) # 沿行(axis=0)堆叠(垂直堆叠)
print(np.concatenate([arr1, arr2], axis=1)) # 沿列(axis=1)堆叠(水平堆叠)
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
[[ 1 2 3 7 8 9]
[ 4 5 6 10 11 12]]
vstack
, row_stack
:按行堆叠(axis 0)。hstack
:按列堆叠(axis 1)。column_stack
:类似于 hstack
,但首先将一维数组转换为二维列。dstack
:按“深度”堆叠(axis 2)。split
:将数组沿轴拆分为多个数组。arr = rng.standard_normal((5, 2)) # 创建一个 5x2 的标准正态分布数组
first, second, third = np.split(arr, [1, 3]) # 将数组在索引 1 和 3 处拆分
print(f"{first=}") # 打印第一个拆分结果
print(f"{second=}") # 打印第二个拆分结果
print(f"{third=}") # 打印第三个拆分结果
first=array([[-1.42382504, 1.26372846]])
second=array([[-0.87066174, -0.25917323],
[-0.07534331, -0.74088465]])
third=array([[-1.3677927 , 0.6488928 ],
[ 0.36105811, -1.95286306]])
hsplit
/vsplit
:分别在轴 0 和 1 上拆分。函数 | 描述 |
---|---|
concatenate |
通用函数,沿轴连接数组。 |
vstack , row_stack |
按行堆叠数组(axis 0)。 |
hstack |
按列堆叠数组(axis 1)。 |
column_stack |
类似于 hstack ,但将一维数组转换为二维列。 |
dstack |
按“深度”堆叠数组(axis 2)。 |
split |
在沿轴的位置拆分数组。 |
hsplit /vsplit |
分别在轴 0 和 1 上拆分。 |
r_
和 c_
r_
和 c_
使堆叠更简洁:arr = np.arange(6) # 创建一个包含 0 到 5 的一维数组
arr1 = arr.reshape((3, 2)) # 将数组重塑为 3x2
arr2 = rng.standard_normal((3, 2)) # 创建一个 3x2 的标准正态分布数组
print(np.r_[arr1, arr2]) # 类似于 row_stack
print(np.c_[np.r_[arr1, arr2], arr]) # 将 arr 作为新列连接
[[ 0. 1. ]
[ 2. 3. ]
[ 4. 5. ]
[ 2.34740965 0.96849691]
[-0.75938718 0.90219827]
[-0.46695317 -0.06068952]]
[[ 0. 1. 0. ]
[ 2. 3. 1. ]
[ 4. 5. 2. ]
[ 2.34740965 0.96849691 3. ]
[-0.75938718 0.90219827 4. ]
[-0.46695317 -0.06068952 5. ]]
tile
和 repeat
🔁repeat
:复制每个元素:arr = np.arange(3) # 创建一个包含 0 到 2 的一维数组
print(arr.repeat(3)) # 每个元素重复 3 次
print(arr.repeat([2, 3, 4])) # 每个元素分别重复 2、3、4 次
[0 0 0 1 1 1 2 2 2]
[0 0 1 1 1 2 2 2 2]
tile
:堆叠副本tile
:沿轴堆叠数组的副本:arr = rng.standard_normal((2, 2)) # 创建一个 2x2 的标准正态分布数组
print(np.tile(arr, 2)) # 沿行重复数组两次
print(np.tile(arr, (2, 1))) # 沿行重复 2 次,沿列重复 1 次
print(np.tile(arr, (3, 2))) # 沿行重复 3 次,沿列重复 2 次
[[ 1.32229806 -0.29969852 1.32229806 -0.29969852]
[ 0.90291934 -1.62158273 0.90291934 -1.62158273]]
[[ 1.32229806 -0.29969852]
[ 0.90291934 -1.62158273]
[ 1.32229806 -0.29969852]
[ 0.90291934 -1.62158273]]
[[ 1.32229806 -0.29969852 1.32229806 -0.29969852]
[ 0.90291934 -1.62158273 0.90291934 -1.62158273]
[ 1.32229806 -0.29969852 1.32229806 -0.29969852]
[ 0.90291934 -1.62158273 0.90291934 -1.62158273]
[ 1.32229806 -0.29969852 1.32229806 -0.29969852]
[ 0.90291934 -1.62158273 0.90291934 -1.62158273]]
take
和 put
进行花式索引take
:通过整数索引选择元素:arr = np.arange(10) * 100 # 创建一个 [0, 100, 200, ..., 900] 的数组
inds = [7, 1, 2, 6] # 定义索引列表
arr.take(inds) # 类似于 arr[inds]
array([700, 100, 200, 600])
put
:将值分配给索引(原地)。不接受轴参数:arr = rng.standard_normal((4, 3)) # 创建一个 4x3 的标准正态分布数组
row_means = arr.mean(1) # 计算每行的均值
print(row_means.shape) # 打印行均值的形状
# 将 row_means 重塑为 (4, 1) 以进行广播
demeaned = arr - row_means.reshape((4, 1)) # 减去行均值
print(demeaned.mean(1)) # 验证每行的均值现在为 0(或接近 0)
(4,)
[ 3.70074342e-17 -1.85037171e-17 -1.85037171e-17 0.00000000e+00]
np.newaxis
和切片添加一个长度为 1 的新轴:arr = np.zeros((4, 4)) # 创建一个 4x4 的全 0 数组
arr_3d = arr[:, np.newaxis, :] # 在中间添加一个新轴
print(arr_3d.shape) # 打印新数组的形状
arr_1d = rng.standard_normal(3) # 创建一个长度为 3 的标准正态分布数组
print(arr_1d[:, np.newaxis]) # 转换为列向量
print(arr_1d[np.newaxis, :]) # 转换为行向量
(4, 1, 4)
[[0.06114402]
[0.0709146 ]
[0.43365454]]
[[0.06114402 0.0709146 0.43365454]]
arr = np.zeros((4, 3)) # 创建一个 4x3 的全 0 数组
arr[:] = 5 # 将所有元素设置为 5
print(arr) # 打印修改后的数组
col = np.array([1.28, -0.42, 0.44, 1.6]) # 创建一个包含 4 个元素的一维数组
arr[:] = col[:, np.newaxis] # 将 col 广播到 arr 的每一行
print(arr) # 打印修改后的数组
[[5. 5. 5.]
[5. 5. 5.]
[5. 5. 5.]
[5. 5. 5.]]
[[ 1.28 1.28 1.28]
[-0.42 -0.42 -0.42]
[ 0.44 0.44 0.44]
[ 1.6 1.6 1.6 ]]
ufunc
高级用法 🚀reduce
:通过二元运算进行聚合:ufunc
方法:accumulate
和 outer
accumulate
:中间“累积”值:array([[ 0, 1, 3, 6, 10],
[ 5, 11, 18, 26, 35],
[10, 21, 33, 46, 60]])
outer
:成对的叉积:ufunc
方法:reduceat
reduceat
:“局部”归约(数组分组):array([10, 18, 17])
ufunc
方法表方法 | 描述 |
---|---|
accumulate(x) |
聚合,保留部分聚合。 |
at(x, i, b=None) |
在索引 i 处对 x 进行原地操作。 |
reduce(x) |
通过连续操作进行聚合。 |
reduceat(x, bins) |
“局部”归约/分组;归约切片以生成聚合数组。 |
outer(x, y) |
将操作应用于所有对;结果具有形状 x.shape + y.shape 。 |
numba.jit
编译:import numba as nb # 导入 Numba 库
numba_mean_distance = nb.jit(mean_distance) # 使用 jit 装饰器编译 mean_distance 函数
# 或者,使用装饰器:
@nb.jit # 使用 jit 装饰器
def numba_mean_distance(x, y): # 定义一个计算平均距离的函数
nx = len(x) # 获取 x 的长度
result = 0.0 # 初始化结果
count = 0 # 初始化计数器
for i in range(nx): # 遍历 x
result += x[i] - y[i] # 累加差值
count += 1 # 计数器加 1
return result / count # 返回平均值
numba_mean_distance
速度快得多(甚至可能比 NumPy 的版本还要快!)。ufunc
numba.vectorize
创建编译的 NumPy ufunc
:nb_add
充当 ufunc
。ndarray
通常是同质的。ndarray.sort()
:原地排序:[-0.79501746 0.27748366 0.30003095 0.53025239 0.53672097 0.61835001]
numpy.sort()
:创建一个新的、排序的副本。[-1.60270159 -1.26162378 -0.07127081 0.26679883 0.47404973]
axis
参数argsort
和 lexsort
argsort()
:返回将对数组进行排序的索引:lexsort
:多个键lexsort()
:对多个键进行字典序排序(最后一个数组是主键):first_name = np.array(['Bob', 'Jane', 'Steve', 'Bill', 'Barbara']) # 创建一个包含名字的数组
last_name = np.array(['Jones', 'Arnold', 'Arnold', 'Jones', 'Walters']) # 创建一个包含姓氏的数组
sorter = np.lexsort((first_name, last_name)) # 先按姓氏排序,再按名字排序
print(list(zip(last_name[sorter], first_name[sorter]))) # 打印排序后的姓名
[(np.str_('Arnold'), np.str_('Jane')), (np.str_('Arnold'), np.str_('Steve')), (np.str_('Jones'), np.str_('Bill')), (np.str_('Jones'), np.str_('Bob')), (np.str_('Walters'), np.str_('Barbara'))]
quicksort
(默认)、mergesort
、heapsort
、timsort
。mergesort
是稳定的)。种类 | 速度 | 稳定性 | 工作空间 | 最坏情况 |
---|---|---|---|---|
‘quicksort’ | 1 | 否 | 0 | O(n^2) |
‘mergesort’ | 2 | 是 | ~n/2 | O(n log n) |
‘heapsort’ | 3 | 否 | 0 | O(n log n) |
‘timsort’ | 4 | 是 | ~n/2 | O(n log n) |
timsort
也是稳定的,并且通常非常高效。numpy.partition
, np.argpartition
:围绕第 k 个最小元素进行分区。rng = np.random.default_rng(12345) # 创建一个随机数生成器
arr = rng.standard_normal(20) # 创建一个包含 20 个标准正态分布元素的数组
np.partition(arr, 3) # 前 3 个是最小的(未排序)
array([-1.95286306, -1.42382504, -1.3677927 , -1.25666813, -0.87066174,
-0.75938718, -0.74088465, -0.46695317, -0.25917323, -0.07534331,
-0.06068952, 0.36105811, 0.57585751, 0.6488928 , 0.78884434,
0.90219827, 0.96849691, 1.26372846, 1.39897899, 2.34740965])
np.argpartition
返回索引:array([-1.95286306, -1.42382504, -1.3677927 , -1.25666813, -0.87066174,
-0.75938718, -0.74088465, -0.46695317, -0.25917323, -0.07534331,
-0.06068952, 0.36105811, 0.57585751, 0.6488928 , 0.78884434,
0.90219827, 0.96849691, 1.26372846, 1.39897899, 2.34740965])
numpy.searchsorted
:查找元素 🔎searchsorted
:在排序数组上进行二分查找;返回插入索引。arr = np.array([0, 1, 7, 12, 15]) # 创建一个排序数组
print(arr.searchsorted(9)) # 在哪里插入 9?
print(arr.searchsorted([0, 8, 11, 16])) # 查找多个值的插入位置
3
[0 3 3 5]
side='right'
更改相等值的行为。searchsorted
示例:分箱data = np.floor(rng.uniform(0, 10000, size=50)) # 生成 50 个 0 到 10000 之间的随机整数
bins = np.array([0, 100, 1000, 5000, 10000]) # 定义分箱边界
labels = bins.searchsorted(data) # 对每个数据点进行分箱
print(labels) # 打印每个数据点所属的箱子标签
[2 3 3 3 3 4 3 3 2 4 4 4 4 4 4 4 4 4 3 3 3 4 3 4 3 3 3 3 1 4 3 2 4 3 3 3 3
3 3 3 3 3 3 3 3 3 3 3 4 3]
groupby
结合使用以获取箱子统计信息。memmap
:NumPy 的类似 ndarray 的对象。读取/写入段,而无需加载整个文件。np.memmap
创建:指定路径、dtype、形状、模式:flush()
写入:mmap = np.memmap('mymmap', dtype='float64', shape=(10000, 10000)) # 打开现有的内存映射文件
print(mmap) # 打印内存映射对象
[[-0.90738246 -1.09542531 0.00714569 ... 0.27528689 -1.164065
0.85209933]
[-0.01030507 -0.06457559 -1.06146483 ... -1.10033268 0.25046196
0.58323566]
[ 0.45830978 1.2992377 1.71366921 ... 0.86913463 -0.78886549
-0.24314164]
...
[ 0. 0. 0. ... 0. 0.
0. ]
[ 0. 0. 0. ... 0. 0.
0. ]
[ 0. 0. 0. ... 0. 0.
0. ]]
memmap
适用于结构化数据类型。flags
检查:arr_c = np.ones((100, 10000), order='C') # 创建一个 C 连续数组
arr_f = np.ones((100, 10000), order='F') # 创建一个 Fortran 连续数组
print(arr_c.flags) # C_CONTIGUOUS: True, F_CONTIGUOUS: False
print(arr_f.flags) # C_CONTIGUOUS: False, F_CONTIGUOUS: True
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
copy()
和 'C'
或 'F'
: C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
flags.contiguous
。ndarray
内部结构:dtype
、形状、跨度。ufunc
方法。