NumPy快速入门

1. 基础知识

NumPy 的主要对象就是同质多维数组。它是一种元素表,这些元素通常是相同类型的数值,通过一组非负整数来索引。在 NumPy 中,这些维度被称为轴。

例如,三维空间中一个点的坐标数组是[1,2,1],只有有个轴。这个轴包含3个元素,因此我们说它的长度为3。在下面的例子中,这个数组有2个轴。第一个轴长度为2,第二个轴长度为3。

python 复制代码
[[1,0,0],
[0,1,2]]

NumPy的数组类被称为ndarrayndarrayNumPy 库内置的核心类(class) ,所有 NumPy 数组都是这个类的实例对象。)。他也有另一个别名array。需要注意的是,numpy.array和python标准库直到类array.array不同,后者只能处理一维数组,并且功能相对较少。ndarray对象更重要的属性包括:

ndarray.ndim:数组的轴数(维度)。

ndarray.shape :这个数组的维度。这是一个整数元组,表示数组在每个维度的大小。对于有个有n行和m列的矩阵,shape对应(n,m)的值。因此,shape元组的长度就是轴的个数,ndim

二者核心关系:ndim = len(shape)(shape 元组的长度就等于 ndim)

python 复制代码
import numpy as np
# 例1:一维数组
a = np.array([1,2,3])
print(a.shape)  # (3,)  元组长度1
print(a.ndim)   # 1,等于len(a.shape)

# 例2:二维数组(n=2行,m=3列)
b = np.array([[1,2,3],[4,5,6]])
print(b.shape)  # (2,3) 元组长度2
print(b.ndim)   # 2,等于len(b.shape)

# 例3:三维数组
c = np.ones((2,4,5))
print(c.shape) # (2,4,5) 元组长度3
print(c.ndim)  # 3,等于len(c.shape)

ndarray.size :该数组中的元素总数。这等于shape中各元素的乘积。

python 复制代码
ndarray.size = n*m

ndarray.dtype /ndarray dtype(纯文字标题,用来阅读):这是一个用于描述数组中元素类型的对象。可以借助标准的python类型来创建或指定dtype类型。此外,NumPy还提供了自己的类型,例如numpy.int32、numpy.int16和numpy.float64。

python 复制代码
import numpy as np
arr=np.array([1,2])
print(arr.dtype) 

#运行结果为int64

ndarray.itemsize :数组中每个元素的字节大小。例如,一个类型为float64的数组包含itemsize8字节(即64/8),而类型为complex32的数组则包含itemsize4字节(即32/8)。这相当于ndarray.dtype.itemsize

python 复制代码
import numpy as np

# float64 数组
arr1 = np.array([1.1, 2.2], dtype="float64")
print(arr1.itemsize)          # 输出 8
print(arr1.dtype.itemsize)    # 输出 8
 #ndarray.itemsize等价于ndarray.dtype.itemsize
# complex32 数组
arr2 = np.array([1+2j, 3+4j], dtype="complex32")
print(arr2.itemsize)          # 输出 4
print(arr2.dtype.itemsize)    # 输出 4

ndarray.data:这个缓冲区存储了数组中的实际元素。通常,我们不需要使用这个属性,因此我们可以通过索引来访问数组中的元素。

NumPy 数组分为两部分:

  • 数据缓冲区(data):连续一维内存,存储所有原始字节数据;多维数组逻辑是靠元数据映射出来的,内存里永远是一维平铺存储。
  • 元数据(shape/dtype/strides 等):记录维度、数据类型、跨维步长,负责把多维索引翻译成内存偏移量。
python 复制代码
import numpy as np
# 创建一维数组
arr = np.array([10, 20, 30], dtype=np.int32)
# 1. 打印 data 缓冲区对象
print(arr.data)
# 输出:<memory at 0x00000213xxxxxxx>

# 2. 常规推荐方式:索引访问元素
print(arr[0])   # 10
print(arr[1:])  # [20 30]

# 3. 直接操作缓冲区(极少用,底层交互场景)
buf = arr.data
# 缓冲区转字节查看原始内存
print(bytes(buf))

一个例子

python 复制代码
>>>import numpy as np
>>>a = np.arange(15).reshape(3, 5)
>>>a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>>a.shape
(3, 5)
>>>a.ndim
2
>>>a.dtype.name
'int64'
>>>a.itemsize
8       #a 的每个元素的元素大小为 8
>>>a.size
15
>>>type(a)
<class 'numpy.ndarray'>
>>>b = np.array([6, 7, 8])
>>>b
array([6, 7, 8])
>>>type(b)
<class 'numpy.ndarray'>

Array creation(数组创建)

创建数组有多种方法。

例如,你可以使用array函数从一个普通的Python列表或元组创建一个数组。最终得到的数组的类型会由这些序列中元素的类型来决定。

python 复制代码
>>>import numpy as np
>>>a = np.array([2, 3, 4])
>>>a
[2,3,4]
>>>a.dtype
dtype('int64')
>>>b = np.array([1.2, 3.5, 5.1])
>>>b.dtype
dtype('float64')

一个常见的错误是,在调用array函数时传入多个参数,而不是只传递一个序列作为参数。

python 复制代码
>>>a = np.array(1,2,3,4) #WRONG
Traceback(most recent call last):
...
TyprError:array() takes from 1 to 2 positional arguments but 4 were given
 # 错误 回溯信息显示:... 类型错误:array()函数原本只需要 1 到 2 个位置参数,但实际上提供了 4 个参数
>>>a = np.array([1,2,3,4]) #正确

函数可以将序列的序列转换为二维数组,将序列的序列的序列转换为三维数组,以此类推。

python 复制代码
>>>b = np.array([(1.5,2,3),(4,5,6)])
>>>b
[[1.5,2.,3.],
[4.,5.,6.]]   #整个数组的数据类型为float64

在创建时,也可以明确指定数组的类型:

python 复制代码
>>>c = np.array([[1,2][3,4]],dtype = np.complex128)
>>>c
[[1.+0.J,2.+0.J]
[3.+0.J,4.+0.J]]

通常,数组的元素最初是位置的,但数组的大小是已知的。(创建数组时必须显式写明长度,内存一次性分配固定连续空间,数组容量创建后无法修改)因此,NumPy提供了多种函数,可以用来创建包含临时占位内容的数组。这些函数大大减少了需要动态扩展数组的必要------因为动态扩展数组是一个比较昂贵的操作。

函数zero生成一个全为0的数组,函数ones生成一个全为1的数组,而函数empty则生成一个初始值随机的数组,其数值取决于内存的状态。默认情况下,所创建的数组的数据类型为float64 ,但可以通过参数dtype来指定具体的数据类型。

python 复制代码
>>>np.zeros((3,4))
array([[0,0,0,0]
      [0,0,0,0]
      [0,0,0,0]])
>>>np.ones(2,3,4,dtype = int16) #2个大区块,每个区块三行,每行四个元素,指定数组元素的数据类型:16位有符号短整型
array([[[1,1,1,1]
		[1,1,1,1]
       	[1,1,1,1]]
       [[1,1,1,1]
        [1,1,1,1]
        [1,1,1,1]]])

为了生成一系列数字,NumPy提供了arange函数,该函数与python内置的range函数类似,但返回的是一个数组。

python 复制代码
>>>np.arange(10,30,5) #np.arange(start,stop,step):用于生成固定步长的一维等差 ndarray 数组,规则为左闭右开区间 [start, stop):包含起始值,不包含终止值。
array([10,15,20,25])

当讲arange与浮点型参数一起使用时,由于浮点数精度有限,通常无法预测最终会得到多少个元素。因此,最好使用函数linspace,该函数可以接受我们想要的元素数量作为参考,而不是使用固定的步长。

python 复制代码
>>>np.linspace(0,2,9) #0到2之间取9个数

array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  ])
>>>from numpy import pi
>>>x = np.linspace(0,2*pi,100) #在大量点上计算函数的值非常有用。
>>>f = np.sin(x)

printing arrays #打印数组

当你打印一个数组时,NumPy会以类似于嵌套列表的方式显示它,但布局如下:

(1)最后一个轴是从左向右依次打印出来的。

(2)倒数第二个元素是从上到下依次打印的

(3)区域的内容也是从上到下依次打印出来的,每一列数据之间都用空行分隔

一维数组会被打印成行,二维数组会被打印成矩阵,而三维数组则会被打印为矩阵列表。

python 复制代码
>>> a = np.arange(6)
>>>print(a)
[1,2,3,4,5,6]
>>>b = np.arange(12).reshape(3,4)
>>>print(b)
[[0,1,2,3]
 [4,5,6,7]
 [8,9,10,11]]
>>>c= np.arange(24).reshape(2,3,4)
>>>print(C)
[[[0,1,2,3]
  [4,5,6,7]
  8,9,10,11]
[[12,13,14,15]
 [16,17,18,19]
 [20,21,22,23]]]

请查看下面的内容,以获取关于reshape的更多详细信息。

如果数组的大小太大,NumPy会自动通过数组的中间部分,只打印出数组的四个角部分。

python 复制代码
>>>print(np.arange(10000))
[   0    1    2 ... 9997 9998 9999]
>>>print(np.arange(10000).reshape(100, 100))
[[   0    1    2 ...   97   98   99]
 [ 100  101  102 ...  197  198  199]
 [ 200  201  202 ...  297  298  299]
 ...
 [9700 9701 9702 ... 9797 9798 9799]
 [9800 9801 9802 ... 9897 9898 9899]
 [9900 9901 9902 ... 9997 9998 9999]]

要禁用此功能,并强制NumPy打印出整个数组,你可以修改打印选项。使用set_printoptions即可进行相应的设置。

python 复制代码
>>>np.set_printoptions(threshold=sys.maxsize) #必须先导入 sys 模块才能使用它。(import sys)

Basic operations #基本操作

对数组进行的算术运算是逐元素进行的。会创建一个新的数组,并将运算结果填充到该数组中。

python 复制代码
>>> a = np.array([20,30,40,50])
>>>b = np.arange(4)
>>>b
array([0,1,2,3])
>>>c = a - b
>>>c
array([20,29,38,47])
>>>b**2
array([0,1,4,9])
>>>10*np.sin(a)
rray([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>>a<35
array([True,True,False,False])

与许多矩阵编程语言不同,NumPy数组中的乘积运算符*是按元素进行操作的。而乘积可以通过@运算符(需要python3.5版本以上),或者dot函数方法来实现。

python 复制代码
>>>A = np.array([[1,1]
              [0,1]])
>>>B = np.array([[2,0]
              [3,4]])
>>>A*B  #按元素乘积
array([[2,0]
       [0,4]])
>>>A@B #矩阵乘积
array([[5,4]
       [3,4]])
>>>A.dot(B)
array([[5,4]
       [3,4]])

一些操作,比如+=和*=,是用于修改已存在的数组,而不是创建新的数组。

python 复制代码
>>>rg = np.random.default_rng(1) #创建默认随机数生成器实例
'''
np.random.default_rng(Seed):推荐随机数生成器,返回Generator对象。
seed(随机种子);如果代码中是default_rng()表示为随机种子(每次运行结果都不同),反之,default_rng(1)表示固定种子(每次运行结果都相同,numpy 的随机生成器内置了一套固定数学算法,可以生成一长串按固定顺序排好的小数,这里的1表示从第一个开始计算。
'''
>>>a = np.ones((2,3),dtype = np.int_) #int_:会自动适配当前操作系统位数(平台自适应整型)
>>>b = rg.random((2,3))
>>>a *= 3
>>>a
array([[3,3,3]
       [3,3,3]])
>>>b += a
>>>b
array([[3.51182162, 3.9504637 , 3.14415961],
       [3.94864945, 3.31183145, 3.42332645]])
a += b #变量b不会自动转换为整数类型
TraceBack(most recent call last):
    ...
    numpy._core._expections._UFuncOutputCastingError:Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'
#报错分析:整数数组+浮点数数组,运算结果自动提升为float64
		原地运算符+=强制要求same_kind安全转换。所以计算完成后,要把float64的结果直接写回原数组a(a是int64)

当处理不同类型的数组时,最终得到的数组类型会对应于更通用或精确的数组类型(这种行为被称为向上类型转换)。

python 复制代码
>>>a = np.ones(3, dtype=np.int32)
>>>b = np.linspace(0, pi, 3)
>>>b.dtype.name
'float64'
>>>c = a + b
>>>c
array([1.        , 2.57079633, 4.14159265])
>>>c.dtype.name
>>>d = np.exp(c * 1j) #np.exp(x):逐元素自然指数e^x
复数指数公式:
e^(a+jb)=e^(a)*(cosb+jsinb)
>>>d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>>d.dtype.name
'complex128'

许多一元操作,比如计算数组中所有元素的总和,都是作为ndarray类的方法来实现的。

python 复制代码
>>>a = rg.random((2, 3))
>>>a
array([[0.82770259, 0.40919914, 0.54959369],
       [0.02755911, 0.75351311, 0.53814331]])
>>>a.sum()
3.1057109529998157
>>>a.min()
0.027559113243068367
>>>a.max()
0.8277025938204418

默认情况下,这些操作适用于数组,就像它是一个数字列表一样,而不考虑数组的形状。不过,通过指定axis参数,你可以对参数的指定轴进行操作。

python 复制代码
>>>b = np.arange(12).reshape(3, 4)
>>>b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

>>>b.sum(axis=0)     #每一列求和
array([12, 15, 18, 21])

>>>b.min(axis=1)     # 每一行求最小的
array([0, 4, 8])

>>>b.cumsum(axis=1)  # 按每行计算累积和
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])#最右侧这一列为计算结果

universal function #通用函数

Numpy提供了一些常见的数学函数,比如sin,cos,exp。在NumPy中,这些函数被称为"通用函数"。在NumPy中,这些函数会对数组中的元素进行逐元素操作,最终生成一个新的数组作为输出结果。

indexing,slicing and iterating #索引,切片和迭代

一维数组也可以被索引,切片和迭代处理,就像列表和其他python序列一样。

python 复制代码
>>>a = np.arange(10)**3
>>>a
array([0,1,8,27,64,125,216,343,512,729])
>>>a[2]
8
>>>a[2:5]
8,27,64
>>>a[:6:2] = 1000 #仅在numpy数组中有效,在python列表中该方法会报错

多维数组的每个轴可以有一个索引。这些索引以逗号分隔的元组形式表示:

python 复制代码
>>>def f(x,y):
		return 10 * x + y
>>>b = np.fromfunction(f,(5,4),dtype = np.int_)#np.fromfunction(dunction,shape,dtype = float64)按照坐标索引执行自定义函数,自动生成指定形状的数组。
f:自定义函数,二维数组必须传入2个参数(i,j)
i:行索引(0~4,y因为shape的第一维是5)
j:列索引(0~3,shape第二维是4)
运行原理:不是朱哥循环调用函数,而是一次性生成坐标网格数组传给f
i网格(5,4):   j网格(5,4):
[[0 0 0 0]      [[0 1 2 3]                 
 [1 1 1 1]       [0 1 2 3]
 [2 2 2 2]       [0 1 2 3]
 [3 3 3 3]       [0 1 2 3]
 [4 4 4 4]]      [0 1 2 3]]
>>>b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
数组[行切片,列切片]-->左闭右开,都好前面控制行;逗号后面控制列
单独一个: = 取当前维度全部元素
>>>b[0:5,1] #!!!是,不是: #表示数组b第二列的每一行
array([1,11,21,31,41])
>>>b[:,1]#等同于上一个示例。
array([1,11,21,31,41])
>>>b[1:3,:]#数组b的第二列与第三列的每一列。
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

当提供的指标数量少于坐标轴的数量时,哪些缺失的指标被视为完整的切片。

python 复制代码
b[-1]#最后一行
array([40,41,42,43])

b[i]中的括号内表达式被视为一个i,然后根据需要添加多个:来表示剩余的轴。NumPy还允许你使用点来表示这种结构,即b[i,...]

这些点(...)代表了根据需要设置的列数来构成完整的索引元组。例如,如果x是一个对5个维度的数组,那么:

(1)x[1,2,...]相当于x[1,2,:,:,:],

(2)x[...,3]相当于`x:, :, :, :,3

如果需要对数组中的每个元素进行操作,可以使用flat属性,他是一个对数组所有元素的迭代器。

python 复制代码
>>>for element in b.flat:
	   print(element)
0
1
2
3
10
...

shape manipulation #形状操控

changing the shape of an array #改变数组的形状

数组的形状由各轴上的元素数量决定:

python 复制代码
>>>a = np.floor(10*rg.random((3,4))
>>>a
array([[3,7,3,4]
       [1,4,2,2]
       [7,2,4,9]])
>>>a.shape
(3,4) 

数组的形状可以通过各种命令进行更改。注意,以下三个命令都会返回一个经过修改的数组,但它们并不会改变原始数组本身:

python 复制代码
>>>a.ravel()#返回展开后的数组
array([3., 7., 3., 4., 1., 4., 2., 2., 7., 2., 4., 9.])
>>>a.reshape(6,2)#返回形状修改后的数组
array([[3., 7.],
       [3., 4.],
       [1., 4.],
       [2., 2.],
       [7., 2.],
       [4., 9.]])
>>>a.T #返回a的转置
......
>>>a.T.shape
(4, 3)
>>>a.shape
(3, 4)

在由ravel生成的数组中,元素的顺序通常是"C风格"的,也就是说,最右边的索引变化最快。因此,a[0,0]之后的元素就是a[0,1]。如果数组被重新塑性为其他形状,那么数组任然会被视为"C风格"的。NumPy通常创建以这种顺序存储的数组,所以ravel通常不需要复制其他参数。但是,如果数组是通过从另一个数组中提取部分元素或利用特殊选项创建的,那么可能需要复制该数组。此外,还可以使用可选参数来指定ravelreshape使用"FORTRAN风格"的数组,在这种模式下,最左边的索引变化最快。

reshape函数返回其参数,但参数形态会有所改变;而ndarray.resize方法则是直接修改数组本身。

换句话说,reshape函数返回新数组,不改变原数组;ndarray.resize()直接修改数组本身。

注意:np.resize(a,shape):全局函数,返回新数组,不修改a

a.resize(shape):数组方法,修改a

如果在变形操作中某个维度被指定为-1,那么其他维度将会自动计算出来。

a.reshape(3,-1):-1是占位符:让numpy自动计算列数

Stacking together different arrays #将不同的数组堆叠起来吧

多个数组可以沿着不同的轴进行堆叠:

python 复制代码
>>>a = np.floor(10 * rg.random((2, 2)))
>>>a
array([[9.,7.],
       [5.,2.]])
>>>b = np.floor(10 * rg.random((2, 2)))
>>>b
array([[1.,9.],
       [5.,1.]])
>>>np.vstack((a, b)) #竖向拼接
array([[9., 7.],
       [5., 2.],
       [1., 9.],
       [5., 1.]])
>>>np.hstack((a, b)) #横向拼接
array([[9., 7., 1., 9.],
       [5., 2., 5., 1.]])

函数column_stack将一维数组按列堆叠成二维数组。其功能等于hstack,但仅适用于二维数组的情况。

python 复制代码
>>>from numpy import newaxis
>>>np.column_stack((a,b)) #所有二维数组
array([[9., 7., 1., 9.],
       [5., 2., 5., 1.]])
>>>a = np.array([4.,2.])
>>>b = np.array([3.,8.])
>>>np.column_stack((a,b))
array([[4., 3.],
       [2., 8.]])
>>>np.hstack((a,b))
array([4.,3.,2.,8.])
>>>a[:newaxis] #将a视作二维列向量
array([[4.],
       [2.]])
>>>np.column_stack((a[:, newaxis],b[:,newaxis]))
array([[4., 3.],
       [2., 8.]])
>>>np.hstack((a[:, newaxis], b[:, newaxis])) #结果相同
array([[4., 3.],
       [2., 8.]])

一般来说,对于具有多个维度的数组,hstack会沿着第二个轴(横向)进行连接,vstack则沿着第一个轴(竖向)进行连接。而concatenate则允许提供一个可选的参数,指定连接应该发生在那个轴。

注意 :在复杂的案例中,r_和·c_非常有用,他们可以通过在某一轴上堆叠数字来创建数组。此外,他们还支持使用范围字面量:

np.r_[a,b]:一维横向拼接 / 二维上下堆叠(axis=0,类似 vstack)

np.c_[a,b]:一维拼成列、二维左右拼接(axis=1,类似 hstack)

python 复制代码
>>>np.r_[1:4,0,4] #[1,2,3] + [0] + [4] = [1,2,3,0,4]
array([1,2,3,0,4])

当作用数组的参数时,r_c_在默认行为上类似于vstackhstack。但是,他们允许提供一个可选的参数,指定进行连接的轴编号。

Splitting one array into several smaller ones #将一个数组拆分为多个较小的数组

使用hsplit,你可以按照数组的水平方向对其进行分割。这可以通过指定需要返回的相等数组的数量,或者指定分割发生的数列来实现。

python 复制代码
>>>a = np.floor(10 * rg.random((2,12))
#rg.rangdom((2,12)) 生成形状为2行,12列的二维数组。每个元素都是[0.0,1.0]之间均匀分布随机浮点数(取不到1.0)
#数组的全体元素*10
#np.floor():向下取整,舍弃小数部分。例如:floor(9.999)=9
>>>a
array([[6., 7., 6., 9., 0., 5., 4., 0., 6., 8., 5., 2.],
       [8., 5., 5., 7., 1., 8., 6., 7., 1., 8., 1., 0.]])    
>>>np.hsplit(a, 3)
[array([[6., 7., 6., 9.],
       [8., 5., 5., 7.]]), array([[0., 5., 4., 0.],
       [1., 8., 6., 7.]]), array([[6., 8., 5., 2.],
       [1., 8., 1., 0.]])]
>>>np.hsplit(a, (3, 4))#在第 3 列前面切一刀、第 4 列前面再切一刀
[array([[6., 7., 6.],
       [8., 5., 5.]]), array([[9.],
       [7.]]), array([[0., 5., 4., 0., 6., 8., 5., 2.],
       [1., 8., 6., 7., 1., 8., 1., 0.]])]

vsplit 沿着垂直轴进行分割,而 array_split 则允许指定在哪个轴上进行分割。

Copies and views #副本视图

在操作和遍历数组时,数组中的数据有时会被复制到新的数组中,有时则不会。总共有三种情况:

No copy at all #完全不需要复制啦

简单的赋值操作不会复制对象及其数据。

python 复制代码
b = a #a 和 b 是同一个 ndarray 对象的两个名称

在python中,可变对象会被当做引用传递,因此函数调用不会创建对象的副本。

引用传递:传递对象本身的别名,形参就是实参的另一个名字,二者完全等价,指向同一个变量容器。

值传递:传递变量的值副本。函数内部拿到一份拷贝。函数里修改参数,只会改副本,外面原始变量完全不受影响。

view or shallow copy #查看或浅拷贝该对象

不同的数组对象可以共享相同的数据。view方法创建一个新的数组对象,该对象同样可以访问相同的数据。

python 复制代码
c = a.view() #c是一个视图
s[:] = 10#将s的所有值变为10
deep copy #深度复制

copy该方法会完整地复制数组及其所有数据

python 复制代码
d = a.copy() #创建了一个新的数组对象,其中包含新的数据
#d并不与a共享任何内容

有时候,如果原始数组不再需要,那么在使用切片操作时,应先调用copy。例如,假设a是一个巨大的中间结果,而最终结果b仅包含a的一小部分。在通过切片构建b时,需要进行深度复制操作。

python 复制代码
a = np.arange(int(1e8))
b = a[:100]

如果改用b = a:100,那么a将会被b引用。即使执行了del aa任然会被存在于内存中。

a,b两个不同的ndarray对象,共用同一块底层数据缓冲区。del a 无法释放那么大内存,因为b还持有数据的引用。

less basic #不基础的一些内容

Broadcasting rules #广播规则

广播功能使得通用函数能够以一种有意义的方式处理哪些形状不完全相同的输入数据。

广播的第一个规则是:如果所有输入的数组的维度都不相同,那么会在叫嚣数组的维度后面不断添加"1",直到所有的数组的维度都一致为止。

广播操作的第二个规则是:在某个特定的维度上,大小为1的数组会被视为具有该维度上最大数组形状大小的数组。在"广播"过程中,该数组元素的值在该维度上会被认为与最大数组相同。

在应用广播规则之后,所有数组的大小必须保持一致。

Advance indexing and index tricks #先进的索引技巧

NumPy提供的所有功能比普通Python序列丰富的多。除了通过整数和切片进行索引之外,数组还可以用整数数组和布尔值数组来进行索引。

Indexing with arrays of indices #使用索引数组进行索引处理
python 复制代码
>>>a = np.arange(12)**2
#a = [  0   1   4   9  16  25  36  49  64  81 100 121]
>>>i = np.array([1,1,3,8,5])
>>>a[i]
array([1,1,9,64,25])

>>>j = np.array([[3, 4], [9, 7]])  # a bidimensional array of indices
>>>a[j]  # the same shape as `j`
array([[ 9, 16],
       [81, 49]])

当索引数组a是多维的时,一个索引数组就相当于指向a的第一个维度。下面的例子就展示了这种行为,它通过将标签图像转换为彩色图像来演示这一特性。

python 复制代码
>>>palette = np.array([[0, 0, 0],         # black
                    [255, 0, 0],       # red
                    [0, 255, 0],       # green
                    [0, 0, 255],       # blue
                    [255, 255, 255]])  # white
>>>image = np.array([[0, 1, 2, 0],  # 每个值代表一种颜色
                  [0, 3, 4, 0]])
>>>palette[image]  # the (2, 4, 3) color image
array([[[  0,   0,   0],
        [255,   0,   0],
        [  0, 255,   0],
        [  0,   0,   0]],
       
       [[  0,   0,   0],
        [  0,   0, 255],
        [255, 255, 255],
        [  0,   0,   0]]])

我们还可以为多个维度指定索引。每个维度的索引数组必须具有相同的形状。

python 复制代码
>>>a = np.arange(12).reshape(3, 4)#生成连续等差序列(有序)
i = np.array([[0, 1],  
              [1, 2]]) #a第一个维度的索引
a[i, j]  # 
array([[ 2,  5],
       [ 7, 11]])

在python中,arr[i,j]arr[(i,j)]其实没什么两样------因此,我们可以把ij放入tuple中,然后再用·这个组合来进行索引操作。

python 复制代码
>>>l = (i, j)
>>># equivalent to a[i, j]
>>>a[l]
array([[ 2,  5],
       [ 7, 11]])

不过我们无法通过将iJ放入数组中来实现这一点。因为该数组会被视为对a的第一维进行索引操作。

python 复制代码
>>>s = np.array([i,j]) 
>>>a[s]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index 3 is out of bounds for axis 0 with size 3
# same as `a[i, j]`
>>>a[tuple(s)]
array([[ 2,  5],
       [ 7, 11]])

在·数组中使用索引的另一个常见用途,是用于查找时间序列中的最大值。

python 复制代码
>>>time = np.linspace(20, 145, 5)  #时间尺度:生成从 20 到 145 的 5 个等间距数值,代表不同时间点。
>>>data = np.sin(np.arange(20)).reshape(5, 4) #4组时序相关序列
>>>time
array([ 20.  ,  51.25,  82.5 , 113.75, 145.  ])
>>>data
array([[ 0.        ,  0.84147098,  0.90929743,  0.14112001],
       [-0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ],
       [ 0.98935825,  0.41211849, -0.54402111, -0.99999021],
       [-0.53657292,  0.42016704,  0.99060736,  0.65028784],
       [-0.28790332, -0.96139749, -0.75098725,  0.14987721]])
>>>ind = data.argmax(axis=0) #每个序列最大值对应的索引
>>>ind 
array([2,0,3,1])
>>>time_max = time[ind]
>>>data_max = data[ind,range(data.shape[1])]
>>>time_max
array([ 82.5 ,  20.  , 113.75,  51.25])
>>>data_max
array([0.98935825, 0.84147098, 0.99060736, 0.6569866 ])
>>>np.all(data_max == data.max(axis=0))
True

你还可以将数组作为索引目标来使用,从而进行赋值操作:

python 复制代码
>>>a = np.arange(5)
>>>a
array([0, 1, 2, 3, 4])
>>>a[[1, 3, 4]] = 0
>>>a
array([0, 0, 2, 0, 0])

不过,当索引列表中存在重复的索引时,赋值操作会重复执行多次,最终只保留最后一个赋值的值。

python 复制代码
>>>a = np.arange(5)
>>>a[[0, 0, 2]] = [1, 2, 3]
>>>a
array([2, 1, 3, 3, 4])

这其实相当合理。不过,如果你想使用python的+=语法,那就得小心了,因为他的行为可能并不如你所期望的那样。

css 复制代码
>>>a = np.arange(5)
>>>a[[0, 0, 2]] += 1
>>>a
array([1, 1, 3, 3, 4])

尽管在索引列表中,数字0出现了,但第0个元素的值实际上只被更新了一次。这是因为python要求a += 1a = a + 1具有相同的含义。

indexing with boolean arrays #使用布尔数组进行索引处理

当我们使用由整数构成的索引数组来索引其他数组时,实际上是在指定要选取的元素对应的索引。而使用布尔值作为索引时,情况则有所不同:我们需要明确指定数组中哪些元素会被选中,哪些则被忽略。

对于布尔索引来说,最自然的做法就是使用与原始数组形状相同的布尔数组来表示。

python 复制代码
>>>a = np.arange(12).reshape(3, 4)
>>>b = a > 4
>>>b  # `b` is a boolean with `a`'s shape
array([[False, False, False, False],
       [False,  True,  True,  True],
       [ True,  True,  True,  True]])
>>>a[b]  # 1d array with the selected elements
array([ 5,  6,  7,  8,  9, 10, 11])

这一特性在各项任务都非常有用:

python 复制代码
>>>a[b] = 0 #a中所有大于 4 的元素变为 0
>>>a
array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

你可以参考一下示例,了解如何利用布尔索引来生成曼德博洛特集的图像:

python 复制代码
>>>import numpy as np
>>>import matplotlib.pyplot as plt
>>>def mandelbrot(h, w, maxit=20, r=2):
    """Returns an image of the Mandelbrot fractal of size (h,w)."""
    x = np.linspace(-2.5, 1.5, 4*h+1)
    y = np.linspace(-1.5, 1.5, 3*w+1)
    A, B = np.meshgrid(x, y)
    C = A + B*1j
    z = np.zeros_like(C)
    divtime = maxit + np.zeros(z.shape, dtype=np.int_)
    for i in range(maxit):
        z = z**2 + C
        diverge = abs(z) > r                    # who is diverging
        div_now = diverge & (divtime == maxit)  # who is diverging now
        divtime[div_now] = i                    # note when
        z[diverge] = r                          # avoid diverging too much
    return divtime
>>>plt.clf()
>>>plt.imshow(mandelbrot(400, 400))

使用布尔值进行索引的第二种方式与整数索引更为相似:对于数组的每个维度,我们都需要提供应该一维布尔数组来指定我们想要选取的元素范围。

python 复制代码
>>>a = np.arange(12).reshape(3,4)
>>>b1 = np.array([False,Ture,Ture])
>>>b2 = np.array([Ture,False,Ture,False])
>>>a[b1,:]
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>a[b1]
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>a[:,b2]
array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])
>>>a[b1, b2]                                
array([ 4, 10])

请注意,一维布尔数组的长度必须与您想要切分的维度或轴的长度相同。在之前的例子,b1的长度为3(即a中的行数),而b2的长度为4,因此它适合用来索引a中的第二轴(即列)。

The ix_()function #ix_()函数

ix_函数可用于将不同的向量组合起来,从而得到每个n元组的计算结果。例如,如果你想计算从向量a、b和c中选取的所有三元组所对应的a+b*c的值:

python 复制代码
>>>a = np.array([2, 3, 4, 5])
>>>b = np.array([8, 5, 4])
>>>c = np.array([5, 4, 6, 8, 3])
>>>ax, bx, cx = np.ix_(a, b, c)
>>>ax
array([[[2]],
       [[3]],
       [[4]],
       [[5]]])
>>>bx
array([[[8],
        [5],
        [4]]])
>>>cx
array([[[5, 4, 6, 8, 3]]])
>>>ax.shape, bx.shape, cx.shape
>>>result = ax + bx * cx
>>>result
array([[[42, 34, 50, 66, 26],
        [27, 22, 32, 42, 17],
        [22, 18, 26, 34, 14]],

       [[43, 35, 51, 67, 27],
        [28, 23, 33, 43, 18],
        [23, 19, 27, 35, 15]],

       [[44, 36, 52, 68, 28],
        [29, 24, 34, 44, 19],
        [24, 20, 28, 36, 16]],

       [[45, 37, 53, 69, 29],
        [30, 25, 35, 45, 20],
        [25, 21, 29, 37, 17]]])
>>>result[3, 2, 4]
17
>>>a[3] + b[2] * c[4]
17

你也可以按照以下方式来实现"reduce"功能:

python 复制代码
>>>def ufunc_reduce(ufct, *vectors):
      vs = np.ix_(*vectors)
      r = ufct.identity
      for v in vs:
          r = ufct(r, v)
      return r

然后将其用作:

python 复制代码
>>>ufunc_reduce(np.add, a, b, c)
array([[[15, 14, 16, 18, 13],
        [12, 11, 13, 15, 10],
        [11, 10, 12, 14,  9]],

       [[16, 15, 17, 19, 14],
        [13, 12, 14, 16, 11],
        [12, 11, 13, 15, 10]],

       [[17, 16, 18, 20, 15],
        [14, 13, 15, 17, 12],
        [13, 12, 14, 16, 11]],

       [[18, 17, 19, 21, 16],
        [15, 14, 16, 18, 13],
        [14, 13, 15, 17, 12]]])

与普通的ufunc.reduce相比,这个版本的reduce具有以下优势:它利用了广播机制来避免创建临时数组。而创建临时数组会占用大量内存,其大小相当于输出数组的大小乘以向量的数量。

广播是 NumPy 的一套维度自动匹配规则 :允许形状不同的数组直接进行算术运算,逻辑上把小数组 "虚拟扩展" 到大数组相同形状参与计算。

仅仅修改数组的 stride(步长)不在内存里新建一份完整数组副本。只是在读取元素时重复访问原有数据。

Tricks and tips #技巧与窍门

"Automatic" reshaping #"自动"重塑功能

要更改数组的尺寸,可以省略掉一个尺寸值,系统会自动推断出缺失的数值。

python 复制代码
>>>a = np.arange(30)
>>>b = a.reshape((2, -1, 3))  # -1 means "whatever is needed"
#维度0:2组
#维度1:每组内?块
#维度2:每块包含3个数字
>>>b.shape
(2, 5, 3)
>>>b
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8],
        [ 9, 10, 11],
        [12, 13, 14]],

       [[15, 16, 17],
        [18, 19, 20],
        [21, 22, 23],
        [24, 25, 26],
        [27, 28, 29]]])
Vector stacking #向量堆叠

如何将一组大小相同的行向量转换成二维数组呢?在MATLAB中,这非常简单:如果xy是两个长度相同的向量,那么只需执行m=[x;y]即可。在NumPy中。则可以通过column_stack,dstack,hstackvstack这些函数实现,具体使用那个函数取决于数据堆叠的维度。例如:

python 复制代码
>>>x = np.arange(0, 10, 2)
>>>y = np.arange(5)
>>>m = np.vstack([x, y])
>>>m
array([[0, 2, 4, 6, 8],
       [0, 1, 2, 3, 4]])
>>>xy = np.hstack([x, y])
>>>xy
array([0, 2, 4, 6, 8, 0, 1, 2, 3, 4])

在二维以上空间中,这些函数的运作逻辑往往相当复杂。

Histograms #直方图

将NumPy的histogram函数应用于数组时,会返回两个向量:一个是该数组的直方图,另一个是各区间边界构成的向量。需要注意的是:matlotlib也有用于生成直方图函数(在Matlab中称为hist),但该函数与NumPy中的函数有所不同。主要区别在于,pylab.hist会自动绘制直方图,而numpy.histogram仅负责生成直方图所需的数据。

python 复制代码
>>>import numpy as np
>>>rg = np.random.default_rng(1)
>>>import matplotlib.pyplot as plt
>>># Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
>>>mu, sigma = 2, 0.5
>>>v = rg.normal(mu, sigma, 10000)
>>># Plot a normalized histogram with 50 bins
>>>plt.hist(v, bins=50, density=True)       >>># matplotlib version (plot)
>>># Compute the histogram with numpy and then plot it
>>>(n, bins) = np.histogram(v, bins=50, density=True)  # NumPy version (no plot)
>>>plt.plot(.5 * (bins[1:] + bins[:-1]), n) 

如果使用的是 Matplotlib 3.4 或更高版本,也可以使用 plt.stairs(n, bins) 这个符号。

相关推荐
倒流时光三十年1 小时前
第三阶段 26 · highlight 高亮(返回命中片段)
后端·python·django
久久学姐3 小时前
Python+Playwright+Pytest+BDD,用FSM打造高效测试框架
python·pytest·bdd·playwright·fsm
Zane19944 小时前
闭包到底"闭"住了什么?一文讲透 LEGB 规则与循环里的闭包陷阱
后端·python
Lumi_Peak4 小时前
Claude思考了42秒,我的代码质量直接提升了一个档次
python·claude
m沐沐4 小时前
【机器学习】DBSCAN聚类算法——原理、参数调优与实战
人工智能·python·深度学习·算法·机器学习·聚类·dbscan
梅孔立5 小时前
推荐一个 Python 开源项目:AI 模板填充 + Markdown 转 Word,面向 Aspose 模板引擎的效率神器
人工智能·python·开源
码农小韩5 小时前
AIAgent应用开发——大模型理论基础与应用(七)
python·学习·ai·大模型·agent
CodeLinghu5 小时前
LangSmith Evaluate实战评估Agent
人工智能·python·语言模型·llm
起司喵喵5 小时前
推荐一款基于 Python 和 Rust 开发的跨平台 GUI 自动化库!
python·rust·自动化