考研那年年末学习和梳理了numpy和matplotlib这两个模块,该文档主要就是基于那时的笔记,matplotlib没有时间重新整理了,见原始笔记(matplotlib初级教程3D绘图patches与path

Numpy主要提供了同构(所有元素类型相同)多维(维度也被称为“轴”)数组对象ndarray,具有矢量化运算能力,除了支持多维数组与矩阵运算,此外还针对数组运算提供大量的数学函数库

首先看看核心对象ndarray的主要属性:

  • ndim:轴的数量,即数组维数
  • shape:数组的形状,一个整数元组,长度即为数组维数,从左到右依次是每个维度的轴长,譬如一个nm列的矩阵,其形状为(n,m),表示第0维(轴)的长度为n、第1维(轴)的长度为m
  • dtype:数组元素类型,如np.int32np.int16np.float64npnumpy的缩写)
  • flags:数组的内存布局信息
  • realimag:分别是数组的实部和虚部(即时非虚数类型也有这两个属性)
  • itemsize:数组元素大小,以字节为单位,如元素为float64类型的数组itemsize为8(64/8,一个字节占8位),而complex32类型的数组itemsize为4(32/8),其等于ndarray.dtype.itemsize
  • size:数组中的元素个数(可用于判断数组是否为空,np.array([]).size=0
  • T:数组转置,仅针对二维数组(或矩阵matrix对象)
  • strides:跨度元组,其中的整数是指为了前进到当前维度下一个元素需要“跨过”的字节数
  • flat:数组的元素迭代器,返回一个flatiter对象,可以用作一维数组“视图”
数据类型

内置数据类型包括(括号中是数据类型的格式字符或格式字符串):
np.bool?b1)、np.int(默认为np.int32)、np.int8bi1int8)、np.int16hi2int16)、np.int32ii4int32)、np.int64qi8int64)、np.uint8Bu1uint8)、np.uint16Hu2uint16)、np.uint32Iu4uint32)、np.uint64Qu8uint64)、np.float(默认为np.float64)、np.float16ef2float16)、np.float32ff4float32)、np.float64df8float64)、np.complex(默认为np.complex128)、np.complex64Fc8complex64)、np.complex128Dc16complex128)、np.straS,可以在aS后面添加数字,表示字符串长度,比如S3表示长度为三的字符串,不写则为最大长度)、np.unicodeU)、np.objectO)、np.voidV)。可以用格式字符(串)作为np.dtype()的参数来创建相应的数据类型对象,或作为np.array()等函数的参数用于创建数组对象,譬如np.dtype('i1')==np.int8np.array([1,2,3,4],dtype='d')

注1:可以通过np.sctypeDict.keys()获取所有的字符代码,dtype对象有一个char属性用于获取对应的字符代码(另见str属性),还有一个type属性获取对应的数据类型
注2:数组的类型转换可以通过astype()方法实现,譬如np.array([1,2,3],dtype='i4').astype('float')(将数据类型从int32转换成float64

自定义结构化数据类型

Numpy允许我们自定义结构化数据类型和结构化数组对象(numpy structured arrays),这些数组既可以通过标签索引也可以通过命名字段索引,看一个简单的示例:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
>>> a=np.array([[[('a',1),('b',2),('c',3)],[('d',4),('e',5),('f',6)],[('g',7),('h',8),('i',9)]],[[('j',10),('k',11),('l',12)],[('m',13),('n',14),('o',15)],[('p',16),('q',17),('r',18)]]],dtype=[('char','S3'),('int',np.int)])
>>> a.shape
(2, 3, 3)
>>> a.dtype
dtype([('char', 'S3'), ('int', '<i4')])
>>> a[1]
array([[(b'j', 10), (b'k', 11), (b'l', 12)],
[(b'm', 13), (b'n', 14), (b'o', 15)],
[(b'p', 16), (b'q', 17), (b'r', 18)]],
dtype=[('char', 'S3'), ('int', '<i4')])
>>> _.shape
(3, 3)
>>> a[1,1,1] #等同于a[1][1][1]
(b'n', 14)
>>> c=a['char'] #切片和索引得到的可变对象都是浅拷贝,所以对c的修改将如实反映在a中
>>> c
array([[[b'a', b'b', b'c'],
[b'd', b'e', b'f'],
[b'g', b'h', b'i']],

[[b'j', b'k', b'l'],
[b'm', b'n', b'o'],
[b'p', b'q', b'r']]], dtype='|S3')
>>> c.shape
(2, 3, 3)
>>> c[1,1,1]='dy'
>>> a[1,1,1]
(b'dy', 14)
>>> a['int']
array([[[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9]],

[[10, 11, 12],
[13, 14, 15],
[16, 17, 18]]])
>>> _.dtype
dtype('int32')
>>> a[0]['int']
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> a[['int','char']] #可以使用字段名称列表一次访问多个字段,类似于花式索引
array([[[( 1, b'a'), ( 2, b'b'), ( 3, b'c')],
[( 4, b'd'), ( 5, b'e'), ( 6, b'f')],
[( 7, b'g'), ( 8, b'h'), ( 9, b'i')]],

[[(10, b'j'), (11, b'k'), (12, b'l')],
[(13, b'm'), (14, b'n'), (15, b'o')],
[(16, b'p'), (17, b'q'), (18, b'r')]]],
dtype=[('int', '<i4'), ('char', 'S3')])

上面这个数组a的形状是(2,3,3),只不过每一个元素都是结构化数据,其由两个字段构成,字段名称分别为charint,类型分别为“长度为3的字符串”和“32位整数”,可以像访问字典那样将某一字段的数据从结构化数组中剥离出来,如a['int'],也可以同时访问多个字段,如a[['int','char']]。结构化数据类型同样可以用于创建特定数组,譬如:

1
2
3
4
5
6
>>> z=np.zeros(3,dtype=np.dtype([('first','i4'),('second','f')]))
>>> z
array([(0, 0.), (0, 0.), (0, 0.)],
dtype=[('first', '<i4'), ('second', '<f4')])
>>> z.shape
(3,)

上述结构化数据类型的定义形如[(字段名,类型),...](二元组列表),其中“类型”既可以是dtype对象,也可以是类型格式字符(串),且类型格式字符(串)前面还可以添加可选的“形状”信息,用于描述数组形状,换句话说,字段类型除了可以是整型、布尔型、字符串、复数类型等外,还可以是数组类型,譬如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> z=np.zeros(3,dtype=np.dtype([('first','(2,3)i4'),('second','f')]))
>>> z.shape
(3,)
>>> z['first']
array([[[0, 0, 0],
[0, 0, 0]],

[[0, 0, 0],
[0, 0, 0]],

[[0, 0, 0],
[0, 0, 0]]])
>>> _.shape
(3, 2, 3)

事实上,还有其他三种定义结构化数据类型的方式:

  • 以逗号分隔的类型格式字符串
    每个类型字符串各自代表一个字段,但是此种情况下无法自由指定字段名,默认的字段名为f0f1、…,譬如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    >>> z=np.zeros(3,dtype='3int8, float32, (2,3)float64') #包含三个字段,字段1为(3,)形单字节整数类型数组,字段2为32位浮点数,字段3为(2,3)形64位浮点数类型数组
    >>> z.shape
    (3,)
    >>> z['f0']
    array([[0, 0, 0],
    [0, 0, 0],
    [0, 0, 0]], dtype=int8)
    >>> z['f1']
    array([0., 0., 0.], dtype=float32)
    >>> z['f2']
    array([[[0., 0., 0.],
    [0., 0., 0.]],

    [[0., 0., 0.],
    [0., 0., 0.]],

    [[0., 0., 0.],
    [0., 0., 0.]]])
    >>> _.dtype
    dtype('float64')
  • 字典,形如{'names':[字段名1,...],'formats':[类型1,...]}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> z=np.zeros(3,{'names':['first','second'],'formats':[np.float32,'2i4']})
    >>> z.shape
    (3,)
    >>> z.dtype
    dtype([('first', '<f4'), ('second', '<i4', (2,))])
    >>> z['first']
    array([0., 0., 0.], dtype=float32)
    >>> z['second']
    array([[0, 0],
    [0, 0],
    [0, 0]])
  • 这个似乎比较特殊,我也不知道具体有什么应用,看个例子吧:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> z=np.zeros(3, dtype=('i4',[('b1','u1'), ('b2','u1'), ('b3','u1'), ('b4','u1')]))
    >>> z.shape
    (3,)
    >>> z
    array([0, 0, 0],
    dtype=(numpy.int32, [('b1', 'u1'), ('b2', 'u1'), ('b3', 'u1'), ('b4', 'u1')]))
    >>> z['b1']
    array([0, 0, 0], dtype=uint8)
    >>> z['b1']=[1,2,3]
    >>> z['b2']=[11,12,13]
    >>> z
    array([2817, 3074, 3331],
    dtype=(numpy.int32, [('b1', 'u1'), ('b2', 'u1'), ('b3', 'u1'), ('b4', 'u1')]))

    简单解释一下,('i4',[('b1','u1'), ('b2','u1'), ('b3','u1'), ('b4','u1')])表示将一个4字节的整型划分成了四个部分,每部分都是一个单字节的无符号整型,分别对应字段b1(最低字节)、b2b3b4(最高字节),此时有z[i]==(z['b4'][i]<<24)+(z['b3'][i]<<16)+(z['b2'][i]<<8)+z['b1'][i]成立,以z[1]为例,z['b1'][1]=2(十进制的2对应二进制的00000010),z['b2'][1]=12(十进制的12对应二进制的00001100),z['b3'][1]=0(十进制的0对应二进制的00000000),z['b4'][1]=0(十进制的0对应二进制的00000000),于是将这四个字节拼接得到00000000,00000000,00001100,00000010,再转换成十进制即可得到3074

    1
    2
    3
    4
    5
    >>> 0b00000000_00000000_00001100_00000010
    3074
    >>> i=1
    >>> z[i]==(z['b4'][i]<<24)+(z['b3'][i]<<16)+(z['b2'][i]<<8)+z['b1'][i]
    True

    另外还可以这样定义:z=np.zeros(3, dtype=('i4','u1,u1,u1,u1'))z=np.zeros(3, dtype=('i4',{'names':['b1','b2','b3','b4'],'formats':['u1','u1','u1','u1']})),都是一样的

可以通过names属性访问和修改结构化数据类型的字段名,还有一个类似于字典的fields只读属性,键名为字段名,非结构化数据类型的namesfields都是None,因而可以据此判断一个dtype对象是否是结构化数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
>>> d=np.dtype('i1,f,S10')
>>> d.type
<class 'numpy.void'>
>>> d.names
('f0', 'f1', 'f2')
>>> d.names=('field1','field2','field3')
>>> d
dtype([('field1', 'i1'), ('field2', '<f4'), ('field3', 'S10')])
>>> d.fields #键值为字段类型和偏移量构成的元组,我也不知道这个偏移量是什么鬼,官方文档里我没有找到相关解释啊
mappingproxy({'f0': (dtype('int8'), 0), 'f1': (dtype('float32'), 1), 'f2': (dtype('S10'), 5)})
>>> np.dtype('i4').names is None
True

可以按行、按列或者按字段对结构化数组进行填充:

1
2
3
4
5
6
7
8
9
10
11
12
>>> z=np.zeros((2,3),dtype=[('f1','i4'),('f2','f')])
>>> z
array([[(0, 0.), (0, 0.), (0, 0.)],
[(0, 0.), (0, 0.), (0, 0.)]], dtype=[('f1', '<i4'), ('f2', '<f4')])
>>> z[0]=[(1,2),(3,4),(5,6)] #按行填充
>>> z[:,0]=[(11,12),(13,14)] #按列填充
>>> z['f1']=[[21,22,23],[24,25,26]] #按字段填充
>>> z[1,1]=(31,32) #注意只能使用元组填充某一个元素而不能使用列表,否则报错ValueError: setting an array element with a sequence.
>>> z
array([[(21, 12.), (22, 4.), (23, 6.)],
[(24, 14.), (31, 32.), (26, 0.)]],
dtype=[('f1', '<i4'), ('f2', '<f4')])

这里介绍的并不全面,实际上每个字段还可以指定偏移量和标题等(我也没大看懂,具体有什么用我也不知道),并且构造方式也不止上述几种形式,具体请参阅官方文档

创建结构化数组时需要注意的地方:

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
>>> L=[[[0, 0], [0, 1], [0, 2], [0, 3]], [[1, 0], [1, 1], [1, 2], [1, 3]], [[2, 0], [2, 1], [2, 2], [2, 3]]]
>>> LT=[[(0, 0), (0, 1), (0, 2), (0, 3)], [(1, 0), (1, 1), (1, 2), (1, 3)], [(2, 0), (2, 1), (2, 2), (2, 3)]]
>>> np.array(L,dtype='i4,i4')
array([[[(0, 0), (0, 0)],
[(0, 0), (1, 1)],
[(0, 0), (2, 2)],
[(0, 0), (3, 3)]],

[[(1, 1), (0, 0)],
[(1, 1), (1, 1)],
[(1, 1), (2, 2)],
[(1, 1), (3, 3)]],

[[(2, 2), (0, 0)],
[(2, 2), (1, 1)],
[(2, 2), (2, 2)],
[(2, 2), (3, 3)]]], dtype=[('f0', '<i4'), ('f1', '<i4')])
>>> _.shape
(3, 4, 2)
>>> np.array(LT,dtype='i4,i4')
array([[(0, 0), (0, 1), (0, 2), (0, 3)],
[(1, 0), (1, 1), (1, 2), (1, 3)],
[(2, 0), (2, 1), (2, 2), (2, 3)]],
dtype=[('f0', '<i4'), ('f1', '<i4')])
>>> _.shape
(3, 4)

我写了个函数(list2structarray())来解决该问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np

#给定结构化数据类型将一个(嵌套)列表无差别转换成结构化数组对象
def list2structarray(a,dtype):
a=np.array(a)
t=[tuple(i) for i in a.reshape(-1,a.shape[-1])]
return np.array(t,dtype=dtype).reshape(a.shape[:-1])

a=[[[0, 0], [0, 1], [0, 2], [0, 3]], [[1, 0], [1, 1], [1, 2], [1, 3]], [[2, 0], [2, 1], [2, 2], [2, 3]]]
s=list2structarray(a,'i4,i4')
print(s)
print(s.shape)
print(s.dtype)

'''OUTPUT
[[(0, 0) (0, 1) (0, 2) (0, 3)]
[(1, 0) (1, 1) (1, 2) (1, 3)]
[(2, 0) (2, 1) (2, 2) (2, 3)]]
(3, 4)
[('f0', '<i4'), ('f1', '<i4')]
'''

创建数组

np.array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)
参数说明:

  • object:可以是python原生序列类型,如(嵌套)列表、(嵌套)元组等,也可以是ndarray类型及其子类,函数默认行为总是会返回object的(深)拷贝副本,两者占据完全不同的内存
  • dtype:设置数组元素的数据类型,缺省时会根据object的类型自行确定
  • copy:控制当objectndarray类型或其子类matrix时是否进行(深)复制的行为,设为False,如果objectndarray类型,将直接返回object自身(即满足is判断),如果objectmatrix类型,则浅拷贝,修改任意一方都会体现在对方,且只有当subok置为True时才会返回object自身
  • order:指定数组在内存中的布局,有两种不同的风格(C或Fortran),C为行主方向(譬如一个二维数组,在内存中依次存储数组的每一行),F为列主方向(譬如一个二维数组,在内存中依次存储数组的每一列),如果objectndarray类型,可取值就是C(默认)或者F,否则,还可以取值AK,所创建的数组order由以下规则确定:
    ordercopy=Falsecopy=True
    'K'objectorder一致F & C order preserved, otherwise most similar order(没懂)
    'A'objectorder一致objectorderF且非C,则orderF,反之为C
    'C'orderCorderC
    'F'orderForderF
  • subok:默认情况下将创建的数组强制转换为基类数组对象(ndarray),设为True则返回子类(譬如矩阵类型matrixndarray的子类,m=np.matrix('1 2 3; 4 5 6');a=np.array(m,subok=True)type(a)numpy.matrixlib.defmatrix.matrix,且issubclass(type(a),np.ndarray)True
  • ndmin:设置最低维度,创建的多维数组至少是ndmin维(譬如object的形状为(2,3),而ndmin为4,则创建的数组形状为(1,1,2,3)),当object的维度大于此值时,则不管,维度同object

注(order参数),给定一个二维数组a=[[1,2,3],[4,5,6]],如果按照C顺序创建x=np.array(a,order='C'),此时默认的dtypenp.int32x.itemsize为4,形状x.shape(2,3),其在内存中的存储为:|1|2|3|4|5|6|(因为内存总是连续的),每个格子占据4字节大小,其跨度元组x.strides(12,4),表示第0维前进到下一个元素需要“跨过”12个字节(如内存中1到4之间的距离),第1维前进到下一个元素需要“跨过”4个字节(如内存中1到2之间的距离),如果是按照F顺序创建y=np.array(a,order='F')y.itemsize为4,形状y.shape(2,3),其在内存中的存储为:|1|4|2|5|3|6|,其跨度元组y.strides(4,8)

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
30
31
32
>>> import numpy as np
>>> a=[[1,2,3],[4,5,6]]
>>> b=np.array(a)
>>> b[0,0]=888
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> c=np.array(b)
>>> c[0,0]=999
>>> b
array([[888, 2, 3],
[ 4, 5, 6]])
>>> d=np.array(b,copy=False)
>>> d is b
True
>>> d[0,0]=111
>>> b
array([[111, 2, 3],
[ 4, 5, 6]])
>>> e=np.matrix(a)
>>> e
matrix([[1, 2, 3],
[4, 5, 6]])
>>> e[0,0]=222
>>> a
[[1, 2, 3], [4, 5, 6]]
>>> f=np.array(e,copy=False)
>>> f[0,0]=333
>>> e
matrix([[333, 2, 3],
[ 4, 5, 6]])
>>> f is e
False
np.copy()

np.copy(a, order='K')
函数说明:
深拷贝输入对象a(可以是ndarray也可以是python原生列表等),尽管np.array()可以达到相同的功能,但是np.copy()的函数名更加直截了当,即复制一个数组对象,通常直接在ndarray对象上调用(ndarray.copy()

np.asarray(a, dtype=None, order=None)
函数说明:
作用也是将输入a转换为一个ndarray对象,除了比np.array()少了很多控制参数,最大的区别是,当andarray对象时,将返回自身,如果a是列表、元组等,则还是深拷贝,但当amatrix对象,则浅拷贝,此时虽然不满足is判断,但是修改任意一方,都会体现在对方,换句话说,np.asarray()相当于np.array()的参数copy置为False时的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> import numpy as np
>>> a=[[1,2,3],[4,5,6]]
>>> b=np.array(a)
>>> c=np.asarray(b)
>>> b is c
True
>>> d=np.matrix('1 2 3;4 5 6')
>>> e=np.asarray(d)
>>> d is e
False
>>> e[0,0]=666
>>> d
matrix([[666, 2, 3],
[ 4, 5, 6]])

np.fromiter(iterable, dtype, count=-1)
函数说明:
将一个一维的可迭代(序列)对象iterable转为一维数组并返回,如果(序列)元素也是序列,则只能自定义结构化数据类型,否则出错。注意参数dtype是必填参数,count参数用于决定读取序列的多少个元素,默认值-1表示全部读取。须知,np.array()仅能将array_like对象转换为ndarray,如列表等,如果是生成器等或其他自定义可迭代对象则无法转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> g=(i for i in range(5))
>>> np.array(g)
array(<generator object <genexpr> at 0x00000239D28B0728>, dtype=object)
>>> np.fromiter(g,dtype='i',count=3)
array([0, 1, 2], dtype=int32)
>>> n=[(1,2,3),(4,5,6)]
>>> np.array(n)
array([[1, 2, 3],
[4, 5, 6]])
>>> np.fromiter(n,dtype='i')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: setting an array element with a sequence.
>>> np.fromiter(n,dtype=np.dtype([('col1','i1'),('col2','i1'),('col3','i1')])) #参见numpy structured arrays
array([(1, 2, 3), (4, 5, 6)],
dtype=[('col1', 'i1'), ('col2', 'i1'), ('col3', 'i1')])

np.fromfunction(function, shape, dtype=float)
函数说明:
function函数可接受参数个数应等于shape形状元组的长度(假设shape长度为n,则表示function是一个n元函数),同时shape也是将要创建的数组的形状。以二维为例,shape=(m,n),则function是一个二元函数,创建的数组记作a,则有a[i,j]=function(i,j)。更一般的,对shape=(k1,k2,k3...kn)来说,fromfunction()将返回一个n维数组对象a,第i维的长度为kia[i1,i2,...,in]的值就是function(i1,i2,...,in)

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
30
31
32
33
#(3,4)形二维数组位置坐标
y,x=np.meshgrid(range(4),range(3))
print(list2structarray(np.rollaxis(np.stack([x,y]),0,3),'i4,i4')) #list2structarray()函数定义见numpy structured arrays

#设二元函数f(x,y)=x*y
def f(x,y):
return x*y
print(np.fromfunction(f,(3,4)))

'''OUTPUT
[[(0, 0) (0, 1) (0, 2) (0, 3)]
[(1, 0) (1, 1) (1, 2) (1, 3)]
[(2, 0) (2, 1) (2, 2) (2, 3)]]
[[0. 0. 0. 0.]
[0. 1. 2. 3.]
[0. 2. 4. 6.]]
'''

'''间隙等于1太大?希望缩小至1/m,则:
def f(i1,i2,...,in):
i1/=m
...
in/=m
...
相应的shape也应成倍增大,fromfunction(f,(m*k1,m*k2,...,m*kn))
'''
#举个一维的例子,设一维函数:f(x)=x^2,要求画出在x轴[0,8]区间上的图像,采样间隔设为1/5,相较于间隔为1时的图像将显得更为光滑
y=np.fromfunction(lambda x:(x/5)**2,(8*5,))
x=[i/5 for i in range(8*5)]

import matplotlib.pyplot as plt
plt.plot(x,y)
plt.show()
np.arange()

np.arange([start,] stop[, step,], dtype=None)
函数说明:
类似于python内置的range()函数,区别在于,range()不接受浮点数作为参数,只接受整数类型,而np.arange()允许浮点数作为参数,一般说来两者都是在左闭右开的数值范围内产生均匀间隔的值,但np.arange()由于允许接受浮点型参数,受计算精度影响得到的结果有可能是左闭右闭的,譬如np.arange(3,3.2,0.1)的结果是[3.0,3.1,3.2],而np.arange(3,3.3,0.1)的结果却又是[3.0,3.1,3.2],具体原因不明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> np.arange(5)
array([0, 1, 2, 3, 4])
>>> np.arange(2,7)
array([2, 3, 4, 5, 6])
>>> np.arange(2.3,4.1,0.3)
array([2.3, 2.6, 2.9, 3.2, 3.5, 3.8])
>>> np.arange(3,3.2,0.1)
array([3. , 3.1, 3.2])
>>> range(5)
range(0, 5)
>>> list(_)
[0, 1, 2, 3, 4]
>>> list(range(2,7))
[2, 3, 4, 5, 6]
>>> list(range(2.3,4.1,0.3))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
np.linspace()

np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
函数说明:
np.arange()的区别:np.arange()通过指定开始值、终值和步长创建一个等差的一维数组,得到的结果中是不包含终值的,而np.linspace()则是通过指定开始值、终值和元素个数创建等差的一维数组,并且可以通过endpoint参数指定是否包含终值,默认值为True即表示包含终值,参数retstepTrue时,将返回一个元组,第一个元素是生成的等差数组,第二个元素则是差值或者说间距

1
2
3
4
5
6
7
8
>>> np.linspace(0,1,12)
array([0. , 0.09090909, 0.18181818, 0.27272727, 0.36363636,
0.45454545, 0.54545455, 0.63636364, 0.72727273, 0.81818182,
0.90909091, 1. ])
>>> np.linspace(0,1,12,endpoint=False,retstep=True)
(array([0. , 0.08333333, 0.16666667, 0.25 , 0.33333333,
0.41666667, 0.5 , 0.58333333, 0.66666667, 0.75 ,
0.83333333, 0.91666667]), 0.08333333333333333)
np.logspace()

np.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None)
函数说明:
该函数用于创建一个一维的对数等比数组,计算过程是这样的:首先将除base以外的参数(暂且假设endpointTrue)全部传递给np.linspace()函数创建一个等差数列[start,start+Δ,start+2·Δ,...,start+(num-1)·Δ](其中Δ为差值,满足条件start+(num-1)·Δ=stop),然后据此创建等比数列:[base^start,base^(start+Δ),base^(start+2·Δ),...base^(stop)],公比为base^Δ

1
2
3
4
5
6
7
8
>>> np.logspace(0,5,5)
array([1.00000000e+00, 1.77827941e+01, 3.16227766e+02, 5.62341325e+03,
1.00000000e+05])
>>> np.linspace(0,5,5)
array([0. , 1.25, 2.5 , 3.75, 5. ])
>>> 10**_
array([1.00000000e+00, 1.77827941e+01, 3.16227766e+02, 5.62341325e+03,
1.00000000e+05])
np.geomspace()

np.geomspace(start, stop, num=50, endpoint=True, dtype=None)
函数说明:
该函数用于创建一个一维的指数等比数组,其返回(暂且假设endpointTrue):[start^1,start^(1+Δ),start^(1+2·Δ),...,start^(1+(num-1)·Δ)](其中Δ满足start^(1+(num-1)·Δ)=stop),公比为start^Δ,可以对比np.logspace()np.logspace()需要指定等比数列取对数后的等差数列的首项和末项,而np.geomspace()则需要指定等比数列本身的首项和末项

1
2
3
4
5
6
7
#np.geomspace()的输出取底为start的对数后将得到一个等差数列[1,1+Δ,1+2·Δ,...,1+(num-1)·Δ]
#设start=3,Δ=1,num=10,则stop=start^(1+(num-1)·Δ)=59049
>>> np.geomspace(3,59049,10)
array([3.0000e+00, 9.0000e+00, 2.7000e+01, 8.1000e+01, 2.4300e+02,
7.2900e+02, 2.1870e+03, 6.5610e+03, 1.9683e+04, 5.9049e+04])
>>> np.log10(_)/np.log10(3) #换底公式:log_a(b)=log_10(b)/log_10(a),含义为:以a为底b的对数等于以10为底b的对数除以以10为底a的对数
array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
np.empty()

np.empty(shape, dtype=float, order='C')
函数说明:
创建形状为shape的空数组,并以随机值填充(所谓“空”是指数组中没有任何有效值,这和np.zeros()不同,0是有效值)

1
2
3
4
5
6
>>> np.empty((2,3))
array([[6.23042070e-307, 5.11798224e-307, 1.37961370e-306],
[4.22795269e-307, 9.34609790e-307, 1.06101441e-312]])
>>> np.empty((2,3)) #同一形状每次生成的结果是相同的
array([[6.23042070e-307, 5.11798224e-307, 1.37961370e-306],
[4.22795269e-307, 9.34609790e-307, 1.06101441e-312]])
np.empty_like()

np.empty_like(a, dtype=None, order='K', subok=True)
函数说明:
创建形状、类型同a的空数组,但仍可通过dtype参数指定类型

np.zeros()

np.zeros(shape, dtype=float, order='C')
函数说明:
创建形状为shape的零数组

np.zeros_like()

np.zeros_like(a, dtype=None, order='K', subok=True)
函数说明:
创建形状、类型同a的零数组,但仍可通过dtype参数指定类型

np.ones()

np.ones(shape, dtype=None, order='C')
函数说明:
创建形状为shape的全1数组

np.ones_like()

np.ones_like(a, dtype=None, order='K', subok=True)
函数说明:
创建形状、类型同a的全1数组,但仍可通过dtype参数指定类型

np.full()

np.full(shape, fill_value, dtype=None, order='C')
函数说明:
用指定的值fill_value填充一个形状为shape的多维数组

1
2
3
>>> np.full((2,3),8)
array([[8, 8, 8],
[8, 8, 8]])
np.full_like()

np.full_like(a, fill_value, dtype=None, order='K', subok=True)
函数说明:
用指定的值填充与给定数组a形状、类型相同的多维数组,但仍可通过dtype参数指定类型

np.eye()

np.eye(N, M=None, k=0, dtype=<class 'float'>, order='C')
函数说明:
能够用于创建单位矩阵(二维ndarray对象,对角线元素值为1,其余为0),N指定行数,M指定列数(缺省情况下M=N),参数k(整型)用于指定“主对角线”的偏移量,当为负数时,向左方偏移,为正数时向右方偏移

1
2
3
4
5
6
7
8
9
10
11
12
>>> np.eye(3)
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
>>> np.eye(3,5)
array([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.]])
>>> np.eye(3,5,k=2)
array([[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])
np.identity()

np.identity(n, dtype=None)
函数说明:
专门用于创建形状为(n,n)的单位矩阵

如果希望能够重现生成的伪随机数,只需要设置相同的随机种子np.random.seed(seed)(参数seed是一个整数,范围限于[0,2321][0,2^{32}-1]),如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> np.random.seed(20210527)
>>> np.random.rand(2,3)
array([[0.94230071, 0.7680507 , 0.41849498],
[0.23640405, 0.28777521, 0.53928407]])
>>> np.random.rand(2,3)
array([[0.03641101, 0.3279482 , 0.48352279],
[0.77410674, 0.50404311, 0.4773902 ]])
>>> np.random.seed(20210527) #种下同一种子,随机函数结果一致
>>> np.random.rand(2,3)
array([[0.94230071, 0.7680507 , 0.41849498],
[0.23640405, 0.28777521, 0.53928407]])
>>> np.random.rand(2,3)
array([[0.03641101, 0.3279482 , 0.48352279],
[0.77410674, 0.50404311, 0.4773902 ]])
np.random.rand()

np.random.rand(d0, d1, ..., dn)
函数说明:
生成形状为(d0,d1,...,dn)的数组,并使用[0.0, 1.0)上均匀分布的随机样本(浮点数)填充它,如果没有给定任何参数,则返回单个浮点数

1
2
3
4
5
6
7
>>> np.random.rand()
0.6258827101037749
>>> np.random.rand(5)
array([0.62735357, 0.88232692, 0.23067855, 0.59308295, 0.22785762])
>>> np.random.rand(2,3)
array([[0.98092414, 0.32172215, 0.94662153],
[0.27608697, 0.89694515, 0.55163334]])

如果希望生成的范围区间介于[a,b),可以通过(b - a) * np.random.rand(...) + a得到:

1
2
3
>>> a,b=3,5
>>> (b - a) * np.random.rand(5) + a
array([3.55059992, 4.37930213, 3.03409387, 4.17120523, 3.04866055])
np.random.sample()

np.random.random_sample(size=None)
函数说明:
作用同np.random.rand(),只不过传入的是size形状参数,若sizeNone,则返回单个随机浮点数

np.random.randn()

np.random.randn(d0, d1, ..., dn)
函数说明:
生成形状为(d0,d1,...,dn)的数组,并使用服从标准正态分布(均值为0,方差为1)的随机样本(浮点数,钟型曲线在-1.96~+1.96范围下的面积等于0.95)填充它,如果没有给定任何参数,则返回单个浮点数

如果你希望构造服从均值为μ\mu、方差为σ2\sigma^2的随机数(组),记作XN(μ,σ2)X\sim N(\mu,\sigma^2),根据标准化有XμσN(0,1)\frac{X-\mu}{\sigma}\sim N(0,1),设Z=XμσZ=\frac{X-\mu}{\sigma},即有ZN(0,1)Z\sim N(0,1)X=σZ+μN(μ,σ2)X=\sigma Z+\mu\sim N(\mu,\sigma^2),因此可以通过σ*np.random.randn(...)+μ来获取这样的XX

1
2
3
4
5
6
7
8
import numpy as np
import matplotlib.pyplot as plt
μ,σ=2,3
X=σ*np.random.randn(10000)+μ #或者X=np.random.normal(loc=μ, scale=σ,size=(10000,))
print((μ-3*σ,μ+3*σ)) #(-7, 11) #“3σ原则”
plt.hist(X,80,density=True,color='g',edgecolor='k')
plt.title(f'$X\sim N(\mu,\sigma^2),\mu={μ},\sigma^2={σ**2}$')
plt.show()
np.random.standard_normal()

np.random.standard_normal(size=None)
函数说明:
作用同np.random.randn(),区别好像仅在于np.standard_normal()允许接受形状元组作为参数

np.random.normal()

np.random.normal(loc=0.0, scale=1.0, size=None)
函数说明:
生成服从均值为locμ\mu)、标准差为scaleσ\sigma)的正态分布的形状为size的数组

np.random.randint()

np.random.randint(low, high=None, size=None, dtype='l')
函数说明:
生成一个数值介于[low,high)左闭右开区间的形状为size的整型随机数组。特别地,当只传入参数low且参数highNone时,生成的随机整数将处于[0,low)区间,当size形状为None时,将返回一个标量,而当size是一个整数时,将返回一个长度为size的一维数组

1
2
3
4
>>> np.random.randint(1) #显然此时总是返回0
0
>>> np.random.randint(-10,10,size=(5,))
array([ 1, -6, -8, -9, 5])
np.random.choice()

np.random.choice(a, size=None, replace=True, p=None)
函数说明:
用于从给定的一组数据a中产生随机样本(或依照指定概率p),采样数目由size参数决定(数目为np.cumprod(a.shape)[-1]),并组织成形状为size的数组返回
参数说明:

  • a:可以是一维数组(也可以是python列表)或者整型标量,特别地,当其为标量时,相当于a=np.arange(a),表示从[0,a)中随机采样
  • size:指定输出数组形状,也可以指定为一个整型标量,如n,相当于size=(n,),如果sizeNone,函数将输出单个样本
  • replace:决定采样中是否有重复值(True表示可以重复),只有当size小于等于len(a)的时候(采样数小于全部样本数),才能置为False
  • p:一个一维序列,对应着a中每个样本的概率分布,如果没有指定,则使用均匀分布
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> np.random.choice([3,5,7,9,12,67,1,19],size=10)
array([ 1, 3, 1, 3, 67, 19, 67, 12, 5, 3])
>>> np.random.choice(5, 3)
array([1, 2, 2])
>>> np.random.choice(5, 3, p=[0.1, 0, 0.3, 0.6, 0])
array([0, 3, 3], dtype=int64)
>>> np.random.choice(5, 3, replace=False)
array([2, 1, 0])
>>> np.random.choice(5, 8, replace=False)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "mtrand.pyx", line 1166, in mtrand.RandomState.choice
ValueError: Cannot take a larger sample than population when 'replace=False'
>>> np.random.choice(['pooh', 'rabbit', 'piglet', 'Christopher'], 5, p=[0.5, 0.1, 0.1, 0.3])
array(['pooh', 'rabbit', 'Christopher', 'pooh', 'rabbit'], dtype='<U11')
np.random.shuffle()

np.random.shuffle(x)
函数说明:
沿第0维对数组xx还可以是python列表,shuffle操作并不会改变其类型)进行“洗牌”(打乱顺序),且为原址操作,函数无返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> a=np.arange(10)
>>> np.random.shuffle(a)
>>> a
array([5, 0, 1, 8, 4, 6, 9, 7, 2, 3])
>>> a=np.arange(9).reshape((3,3))
>>> a
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
>>> np.random.shuffle(a)
>>> a
array([[0, 1, 2],
[6, 7, 8],
[3, 4, 5]])
>>> a=['Christopher','pooh','rabbit','pooh','pooh']
>>> np.random.shuffle(a)
>>> a
['pooh', 'rabbit', 'Christopher', 'pooh', 'pooh']
>>> type(a)
<class 'list'>
np.random.permutation()

np.random.permutation(x)
函数说明:
功能同np.random.shuffle(),但非原址操作,函数将返回打乱后的数组,x除了可以是python列表,还可以是一个整型参数,相当于对数组np.arange(x)进行shuffle

1
2
3
4
5
6
7
>>> a=['Christopher','pooh','rabbit','pooh','pooh']
>>> np.random.permutation(a)
array(['pooh', 'Christopher', 'rabbit', 'pooh', 'pooh'], dtype='<U11')
>>> a
['Christopher', 'pooh', 'rabbit', 'pooh', 'pooh']
>>> np.random.permutation(10)
array([9, 1, 3, 4, 6, 5, 8, 0, 2, 7])

形状重塑

np.reshape(a, newshape, order='C')
函数说明:
将多维数组a变形为newshape,一般是直接在ndarray对象上调用该方法,形式为ndarray.reshape(newshape,order='C')(此时既可以直接传递形状元组,也可以将其解包为多个位置参数传递), 需要注意的是,reshape()函数返回的新数组其实是原多维数组对象a的“视图”,所谓“视图”,即你对视图的修改将原封不动地反映在原始数据中,因此对reshape()函数返回的“新数组”的修改将影响到a(如果a是python对象自然不会影响),如果不希望变形操作影响原数组,则采用np.resize()替代,或者先进行一次深拷贝(np.copy()
参数说明:

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
30
31
32
>>> np.reshape([[1,2,3],[4,5,6]],(-1,))
array([1, 2, 3, 4, 5, 6])
>>> a=np.arange(6)
>>> a.reshape((2,3))
array([[0, 1, 2],
[3, 4, 5]])
>>> a.reshape(2,3) #手动将newshape解包为多个位置参数传递
array([[0, 1, 2],
[3, 4, 5]])
>>> a.reshape(*(2,3)) #利用*自动将newshape解包为多个位置参数传递
array([[0, 1, 2],
[3, 4, 5]])
>>> _[0,0]=666 #对视图的修改将反映在原数组上,反之对原数组的修改也可以通过视图实时显示
>>> a
array([666, 1, 2, 3, 4, 5])
>>> a = np.array([[0, 1],
... [2, 3],
... [4, 5]])
>>> o=np.reshape(a, (2, 3), order='F') #第一步,按照列主序读取数组a,得到|0|2|4|1|3|5|,然后第二步,按照列主序顺序将其依次存储在(2,3)形的新数组中,对于第二步的实施其实很简单,首先新数组在内存中连续存储的就是|0|2|4|1|3|5|,只需要再设置一下flags为列主序以及跨度元组strides即可,而strides显然为(4,8),这也变相告诉了计算机新数组各维度大小,即(2,3)
>>> o
array([[0, 4, 3],
[2, 1, 5]])
>>> o.flags
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
>>> o.strides
(4, 8)

需要注意的是,如果reshape()order参数为C,只有在a为C-contiguous(a.flags['C_CONTIGUOUS']True)时才会返回视图,否则返回深拷贝,反之,若orderF,则只有在a为F-contiguous(a.flags['F_CONTIGUOUS']True)时才会返回视图,否则返回深拷贝

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
>>> a=np.array([[1,2,3],[4,5,6]],order='F')
>>> a
array([[1, 2, 3],
[4, 5, 6]])
>>> a.flags
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
>>> a.reshape(3,2)
array([[1, 2],
[3, 4],
[5, 6]])
>>> _[0,0]=666
>>> a
array([[1, 2, 3],
[4, 5, 6]])
>>> a.reshape(3,2,order='F')
array([[1, 5],
[4, 3],
[2, 6]])
>>> _[0,0]=666
>>> a
array([[666, 2, 3],
[ 4, 5, 6]])

np.resize(a, newshape)
函数说明:
作用同np.reshape(),最大的区别在于np.resize()返回的是一个完全独立的新数组,对其所做的一切修改不会影响原数组,且此时允许指定错误的或者说不匹配的newshape,多余的部分会被丢弃,不足的部分则会复用已有的数据补全(描述能力有限,具体见示例),另外,虽然也可以直接在ndarray对象上调用resize()方法,但ndarray.resize(...)是原址操作,无返回值(返回值为无意义的None),此时newshape形状元组中禁止使用代表缺省维数的-1np.resize(...)虽然不直接报错,但是结果也不正确,也必须禁止),不匹配的newshape则会导致错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> a=np.arange(10)
>>> np.resize(a,(2,-1))
array([[0, 1, 2, 3],
[4, 5, 6, 7]])
>>> _[0,0]=666
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a.resize((2,-1))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: negative dimensions not allowed
>>> np.resize(a,(8,)) #丢弃部分
array([0, 1, 2, 3, 4, 5, 6, 7])
>>> np.resize(a,(2,9)) #不足补全,不足部分将不断重复ndarray.flat中的数据
array([[0, 1, 2, 3, 4, 5, 6, 7, 8],
[9, 0, 1, 2, 3, 4, 5, 6, 7]])
>>> a.resize((2,9))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: cannot resize an array that references or is referenced
by another array in this way. Use the resize function

np.swapaxes(a, axis1, axis2)
函数说明:
用于交换多维数组的某两个维度,函数返回原数组的“视图”,可以直接在ndarray上调用ndarray.swapaxes(axis1,axis2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> x = np.array([[1,2,3]])
>>> np.swapaxes(x,0,1)
array([[1],
[2],
[3]])
>>> _[0,0]=666
>>> x
array([[666, 2, 3]])
>>> x = np.array([[[0,1],[2,3]],[[4,5],[6,7]]])
>>> np.swapaxes(x,0,2) #同np.swapaxes(x,2,0)
array([[[0, 4],
[2, 6]],

[[1, 5],
[3, 7]]])

以二维或三维数组为例,将其分别放到二维平面和三维空间坐标系中,swapaxes()操作相当于交换某两个轴的名称

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
30
31
32
33
34
35
36
37
38
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection,Line3DCollection
import numpy as np
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus']=False

x=np.array([[[0,1],[2,3]],[[4,5],[6,7]]])
y=np.swapaxes(x,0,2)

points=[(1,1,0),(0,1,0),(0,0,0),(1,0,0),(1,1,1),(0,1,1),(0,0,1),(1,0,1)]
faces=[(0,4,7,3,0),(0,1,5,4,0),(1,2,6,5,1),(2,3,7,6,2),(4,5,6,7,4),(0,1,2,3,0)]
plots=[[points[point_num] for point_num in face] for face in faces]

line3d=Line3DCollection(plots)
ax1=plt.subplot(121,projection='3d')
ax1.add_collection3d(line3d)
plt.xlabel('轴0')
plt.ylabel('轴1')
plt.gca().set_zlabel('轴2')
plt.xticks([])
plt.yticks([])
plt.gca().set_zticks([])

line3d=Line3DCollection(plots)
ax2=plt.subplot(122,projection='3d')
ax2.add_collection3d(line3d)
plt.xlabel('轴2') #只需要交换两根轴的名称即可
plt.ylabel('轴1')
plt.gca().set_zlabel('轴0')
plt.xticks([])
plt.yticks([])
plt.gca().set_zticks([])

for pos in points:
ax1.text(*pos,f'{x[pos]}:{pos}',color='red' if pos==(0,0,0) else 'black')
ax2.text(*pos,f'{x[pos]}:{pos}',color='red' if pos==(0,0,0) else 'black')
plt.suptitle(f'交换数组x的0轴和2轴:y=np.swapaxes(x,0,2)\n x={x}\n y={y}')
plt.show()

np.transpose(a, axes=None)
函数说明:
区别于np.swapaxes()只能一次性交换两个维度,该操作用于同时变换n个轴的名称(n≤a.ndim),axes即是一个长度为a.ndim的元组,里面是要转换的维度序号,当其为None时,相当于np.transpose(a,axes=list(reversed(range(len(a.shape))))),此时可以用于二维数组的转置操作,函数返回的是原数组的“视图”(ndarray对象的T属性返回的也是“视图”),也可以直接在ndarray上调用ndarray.transpose(axes=None)。另外轴变换后得到的数组内存布局将会发生变化,譬如一个二维的列主序数组,transpose后会变成行主序

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
>>> x=np.array([[[0,1],[2,3]],[[4,5],[6,7]]])
>>> x.transpose() #显然对于三维数组,axes为None时等同于x.swapaxes(0,2)
array([[[0, 4],
[2, 6]],

[[1, 5],
[3, 7]]])
>>> x.transpose((2,1,0))
array([[[0, 4],
[2, 6]],

[[1, 5],
[3, 7]]])
>>> a=np.array([[0,1],[2,3],[4,5]])
>>> a.transpose()
array([[0, 2, 4],
[1, 3, 5]])
>>> a.T #多维数组的.T属性返回的也是“视图”,所有视图之间实时共享数据
array([[0, 2, 4],
[1, 3, 5]])
>>> _[0,0]=666
>>> a
array([[666, 1],
[ 2, 3],
[ 4, 5]])

np.rollaxis(a, axis, start=0)
函数说明:
向前或向后滚动特定的轴axis到一个特定位置start,在滚动过程中,其它轴的相对位置不会改变,默认是将某根轴向前滚动到位置0,即变成轴0,向前滚动时要滚动到位置几就将start置为几即可,譬如将轴2滚动到轴1位置处,即为np.rollaxis(a,2,1),但是向后滚动时需要人为加1,譬如一个三维数组,要将轴0滚动到原本的轴2位置处,应当是np.rollaxis(a,0,3)而非np.rollaxis(a,0,2),函数返回的仍是“视图”,但是ndarray对象上并无rollaxis()方法属性

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
>>> a=np.arange(24).reshape(2,3,4)
>>> a
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]]])
>>> np.rollaxis(a,0,3)
array([[[ 0, 12],
[ 1, 13],
[ 2, 14],
[ 3, 15]],

[[ 4, 16],
[ 5, 17],
[ 6, 18],
[ 7, 19]],

[[ 8, 20],
[ 9, 21],
[10, 22],
[11, 23]]])
>>> _.shape
(3, 4, 2)
>>> np.rollaxis(a,0,2)
array([[[ 0, 1, 2, 3],
[12, 13, 14, 15]],

[[ 4, 5, 6, 7],
[16, 17, 18, 19]],

[[ 8, 9, 10, 11],
[20, 21, 22, 23]]])
>>> _.shape
(3, 2, 4)
>>> np.rollaxis(a,2,1)
array([[[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]],

[[12, 16, 20],
[13, 17, 21],
[14, 18, 22],
[15, 19, 23]]])
>>> _.shape
(2, 4, 3)

ndarray.flatten(order='C')
函数说明:
用于“一维化”多维数组(降维),返回的是原数组的深拷贝

1
2
3
4
5
>>> a=np.array([[0,1,2],[3,4,5]])
>>> a.flatten()
array([0, 1, 2, 3, 4, 5])
>>> a.flatten('F')
array([0, 3, 1, 4, 2, 5])
np.ravel()

np.ravel(a, order='C')
函数说明:
作用同ndarray.flatten(),区别在于np.ravel()(或ndarray.ravel())返回的是原数组的“视图”,不过我更倾向于使用ndarray.reshape(-1)

1
2
3
4
5
6
7
8
>>> a=np.arange(6).reshape(2,3)
>>> b=a.ravel()
>>> b[0]=666
>>> b
array([666, 1, 2, 3, 4, 5])
>>> a
array([[666, 1, 2],
[ 3, 4, 5]])

需要注意的是,如果ravel()order参数为C,只有在aC-contiguous时才会返回视图,否则返回深拷贝,反之,如果orderF,则只有在aFortran-contiguous时才会返回视图,否则返回深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> a=np.arange(6).reshape(2,3,order='F')
>>> a
array([[0, 2, 4],
[1, 3, 5]])
>>> b=a.ravel()
>>> b[0]=666
>>> b
array([666, 2, 4, 1, 3, 5])
>>> a
array([[0, 2, 4],
[1, 3, 5]])
>>> a.flags['C_CONTIGUOUS']
False
>>> a.flags['F_CONTIGUOUS']
True
>>> b=a.ravel(order='F')
>>> b[0]=888
>>> b
array([888, 1, 2, 3, 4, 5])
>>> a
array([[888, 2, 4],
[ 1, 3, 5]])

np.expand_dims(a, axis)
函数说明:
该函数用于扩展数组a的维度,一次只能增加一个维度,新增维度的轴长为1,函数将返回原数组的“视图”,比较常见的是扩充第一个或最后一个维度,但一般会用a[None,...]a[...,None]简写达到同样的效果

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
>>> a=np.arange(6).reshape(2,-1)
>>> a
array([[0, 1, 2],
[3, 4, 5]])
>>> np.expand_dims(a,0)
array([[[0, 1, 2],
[3, 4, 5]]])
>>> _.shape
(1, 2, 3)
>>> np.expand_dims(a,1)
array([[[0, 1, 2]],

[[3, 4, 5]]])
>>> _.shape
(2, 1, 3)
>>> np.expand_dims(a,2)
array([[[0],
[1],
[2]],

[[3],
[4],
[5]]])
>>> _.shape
(2, 3, 1)
>>> np.expand_dims(a,-1) #扩充最后一个维度时,axis参数可以写-1
array([[[0],
[1],
[2]],

[[3],
[4],
[5]]])
>>> _.shape
(2, 3, 1)
>>> a[None,...]
array([[[0, 1, 2],
[3, 4, 5]]])
>>> _.shape
(1, 2, 3)
>>> a[None,:,:]
array([[[0, 1, 2],
[3, 4, 5]]])
>>> _.shape
(1, 2, 3)
>>> a[:,None,:]
array([[[0, 1, 2]],

[[3, 4, 5]]])
>>> _.shape
(2, 1, 3)
>>> a[...,None]
array([[[0],
[1],
[2]],

[[3],
[4],
[5]]])
>>> _.shape
(2, 3, 1)
>>> a[:,:,None]
array([[[0],
[1],
[2]],

[[3],
[4],
[5]]])
>>> _.shape
(2, 3, 1)
>>> a=np.arange(24).reshape(2,3,4)
>>> a[:,None,:,None,:] #还可以同时写多个None,用于同时扩充多个维度
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]]]]])

np.squeeze(a, axis=None)
函数说明:
该函数默认用于删除多维数组a中的所有轴长为1的维度(axisNone),函数将返回原数组的“视图”,也可以直接在ndarray对象上调用该方法,也可以指定axis参数,即删除指定的维度(但该维度的轴长必须为1)

1
2
3
4
5
6
>>> a=np.arange(24).reshape(2,3,4)
>>> b=a[:,None,:,None,:]
>>> b.squeeze().shape
(2, 3, 4)
>>> b.squeeze(1).shape
(2, 3, 1, 4)

数组切片和索引

先说说(数值)索引,对数组(单个)元素的索引是通过数字下标进行定位的,譬如a[i][j]表示沿着数组a的第0轴获取第i个位置上的元素,记作b=a[i],然后再沿着数组b的第0轴获取第j个位置上的元素,就得到a[i][j],简写形式为a[i,j],其中逗号用于分隔不同的维度(事实上,不管是此处的数值索引还是后面的切片索引,对于不足的维度说明,譬如a[i]就是只给出了第0维的索引说明,总是会自动在最后使用...对其他维度的索引说明进行补全,譬如a[i]即等同于a[i,...]...含义后面会说),而切片索引可以同时访问多个元素,有两种使用方式(同原生python),一是创建切片对象slice(start,end,stride)(切片范围不包括end在内),二是使用冒号语法start:end:stride(同样切片范围不包括end在内)替代slice对象,譬如a[i:j],同样,可以使用逗号作为不同维度的间隔,譬如a[i:j,i:j]表示沿着数组a的第0轴获取第i个位置到第j个位置之前的所有元素,记作b=a[i:j],然后再沿着数组b的第1轴获取第i个位置到第j个位置之前的所有元素,即b[:,i:j],要知道每进行一次数值索引,返回的数组维数都会减一,而切片索引的返回结果则始终保持维数不变(许多numpy函数都有一个叫做keepdims的参数,就是这个意思),因此上面a[i][j]的描述也可以改成:首先沿着数组a的第0轴获取第i个位置上的元素且保持维数不变,即得到b=a[i:i+1],然后再沿着数组b的第1轴获取第j个位置上的元素且保持维数不变,即b[:,j:j+1],所得相当于[[a[i,j]]],数值索引和切片索引自然可以混用,譬如a[:,i](表示获取数组a的第i列),另外,numpy索引还支持...ellipse对象,须知其在索引中只能出现一次,原生python索引不支持)用于替代一个或多个(逗号间隔且连续的):,譬如a[i:j,:,:]可以替换成a[i:j,...]a[:,:,i:j]可以替换成a[...,i:j]a[:]可以替换成a[...]a[:,i]可以替换成a[...,i]

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
30
31
32
33
34
35
36
37
>>> a=list(range(10))
>>> a[::-1] #原生python切片索引示例
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> a[2:10:2]
[2, 4, 6, 8]
>>> a[5:7]
[5, 6]
>>> a=np.arange(12).reshape(3,4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[1,2]
6
>>> a[0:2:1]
array([[0, 1, 2, 3],
[4, 5, 6, 7]])
>>> a[::-1]
array([[ 8, 9, 10, 11],
[ 4, 5, 6, 7],
[ 0, 1, 2, 3]])
>>> a[:2,1:-1] #numpy切片索引,由于是多个维度,可以通过逗号间隔不同维度的切片
array([[1, 2],
[5, 6]])
>> a[slice(0,2),slice(1,-1)]
array([[1, 2],
[5, 6]])
>>> a[...,2]
array([ 2, 6, 10])
>>> a[...,2:2+1] #要保持维度一致,有时我会这样写:a[...,[2]],但这并不总是正确,譬如a[0:0+1,[1]]和a[[0],[1]]结果就有区别,后者的维度依旧没有保持住,可以参见高级索引,前者属于花式索引(一维花式索引和切片索引混用),后者则属于全坐标索引,实际上,在所有维度都是数值索引的情况下,不能全都用括号括起来以进行维度保持,因为全坐标索引返回的结果总是一维数组,除非你在进行全坐标索引的时候指定形状信息,a[[[0]],[[1]]],索引坐标放在一个(1,1)形状的数组中,返回的索引结果也会按照相应形状组织
array([[ 2],
[ 6],
[10]])
>>> a[0,::]
array([0, 1, 2, 3])
>>> a[0,...]
array([0, 1, 2, 3])

Numpy索引还支持一个特殊对象None,之前在介绍np.expand_dims()时顺带展示了相关用法,这里再简单回顾一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a=np.arange(10).reshape((2,-1))
>>> _=a[...,None]
>>> _.shape
(2, 5, 1)
>>> _=a[:,None] #None是多出来的一个维度,而a是一个二维数组,因此维度说明应有两处,此处仅出现一处,因此在最后加上...变成a[:,None,...],也就是a[:,None,:]
>>> _.shape
(2, 1, 5)
>>> _=a[:,None,:]
>>> _.shape
(2, 1, 5)
>>> _=a[:,None,...]
>>> _.shape
(2, 1, 5)
全坐标索引

这算是最简单的一种索引方式,需要将全部待索引元素的坐标按不同维度分别组织起来(过程相当于zip(坐标点1,坐标点2,...)),称为坐标分量(此时是一维的序列),然后将其以逗号间隔用于索引,索引结果将被存放于一个一维数组中,譬如一个二维数组,全坐标索引形式为:ndarray[行索引序列,列索引序列]

1
2
3
4
5
>>> a=np.array([[1,2,3],[6,7,8],[11,12,13]]) #要索引第一行第一个元素、第二行第二个元素以及第三行第三个元素,这三个元素的位置坐标分别为:(0,0)、(1,1)、(2,2)
>>> x=[0,1,2] #行索引序列
>>> y=[0,1,2] #列索引序列
>>> a[x,y] #全坐标索引总是返回一个一维数组
array([ 1, 7, 13])

上述坐标分量被放在一个一维列表(数组)中,返回的结果也是一维的,事实上,你可以将坐标分量放在嵌套列表(多维数组)中,索引结果也会按照相应的形状结构组织:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a=np.arange(1,28).reshape((3,3,-1)) #这是一个(3,3,3)形状的数组,八个顶点上的坐标分别为:(0,0,0),(0,2,0),(0,0,2),(0,2,2),(2,0,0),(2,2,0),(2,0,2),(2,2,2),下面索引之
>>> x=[0,0,0,0,2,2,2,2] #0轴分量
>>> y=[0,2,0,2,0,2,0,2] #1轴分量
>>> z=[0,0,2,2,0,0,2,2] #2轴分量
>>> a[x,y,z]
array([ 1, 7, 3, 9, 19, 25, 21, 27])
>>> x=[[[0,0],[0,0]],[[2,2],[2,2]]] #如果我们希望返回的8个顶点仍以原始相对位置展示,即索引返回(2,2,2)的三维数组,只需要将坐标分量组织为(2,2,2)形状
>>> y=[[[0,0],[2,2]],[[0,0],[2,2]]]
>>> z=[[[0,2],[0,2]],[[0,2],[0,2]]]
>>> a[x,y,z]
array([[[ 1, 3],
[ 7, 9]],

[[19, 21],
[25, 27]]])

假设给出了待索引的所有坐标点,可以通过zip()函数简化全坐标分量序列的构造:

1
2
3
4
5
6
7
8
>>> a=np.arange(1,28).reshape((3,3,-1))
>>> points=[(0,0,0),(0,2,0),(0,0,2),(0,2,2),(2,0,0),(2,2,0),(2,0,2),(2,2,2)]
>>> x,y,z=zip(*points)
>>> a[x,y,z]
array([ 1, 7, 3, 9, 19, 25, 21, 27])
>>> pos=zip(*points)
>>> a[[*pos]] #标准的全坐标索引形式为ndarray[轴0坐标分量,轴1坐标分量,...],实践发现在这些所有分量外面再套一层中括号,结果不变,即可以写成ndarray[[轴0坐标分量,轴1坐标分量,...]],因此此处,[*pos](*的简化容器添加功能)返回[(0, 0, 0, 0, 2, 2, 2, 2), (0, 2, 0, 2, 0, 2, 0, 2), (0, 0, 2, 2, 0, 0, 2, 2)],可以直接将其用于全坐标索引,为什么不a[*pos]呢?因为该语法非法。套一层括号结果不变,套两层乃至更多层呢?结果是会变的,实际将变成一种花式索引
array([ 1, 7, 3, 9, 19, 25, 21, 27])
布尔索引

布尔索引通过构建一个布尔型多维数组(称为“掩码数组”),用于数组元素的过滤(过滤出的元素会被放在一个一维数组中,假设过滤元素是标量,那么返回的将是一维数组,如果过滤元素是向量,则会返回二维数组),譬如条件过滤a[a>0]将返回数组a中所有大于0的元素(a>0可以得到一个与a同形的掩码数组,因而过滤出的元素是标量,最终该布尔索引将返回一个一维数组),须知掩码数组的形状(记为ms,设长度为m)应和待索引数组的形状(记为ns,设长度为n)具有“前向一致性”(我胡诌的名词,一般掩码数组应是ndarray对象,如果是python列表则有可能会出错),即ms应构成ns的前m项,且满足m≤n,譬如ns=(2,3,4),那么ms只能是(2)(2,3)(2,3,4)的其中一种,特别地,当进行一维布尔索引时(指布尔掩码数组是一维的),可以以逗号间隔多个维度,且可以和(单个)数值索引、切片索引混用,但注意只能在其中一个维度上使用布尔掩码(长度需和当前维度的轴长一致),和全坐标索引自然不能混用,另外和(一维)花式索引也不能混用,否则结果错误,具体见下示例:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
>>> a=np.arange(10).reshape((2,-1)) #现给定一个二维数组,输出其中所有大于3的元素
>>> a
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
>>> def f(a): #手动构建与待索引数组同形的二维布尔数组:对待索引数组中的每一个元素进行判断,符合条件为True,不符合为False
... s=a.shape
... return np.array([True if i>3 else False for i in a.reshape((-1))]).reshape(s)
...
>>> a[f(a)]
array([4, 5, 6, 7, 8, 9])
>>> a>3 #ndarray对象上的关系运算将返回一个布尔型数组,满足关系的为True,不满足为False
array([[False, False, False, False, True],
[ True, True, True, True, True]])
>>> a[a>3]
array([4, 5, 6, 7, 8, 9])
>>> b=[True,False] #待索引数组形状为(2,5),此掩码数组的形状为(2,),这是允许的,含义是沿待索引二维数组的0轴进行元素过滤选取,具体的,此处表示第一行符合条件,而第二行不符合条件
>>> a[b]
array([[0, 1, 2, 3, 4]])
>>> a[0,[True,False,False,False,True]] #以逗号间隔不同维度的索引,其中第0维是(单个)数值索引,第1维是(一维)布尔索引,如果改成a[[True,True],[True,False,False,False,True]],结果是没意义的,尽管不会直接报错,因为布尔索引在不同维度上至多只能使用一次,再譬如改成a[[0,1],[True,False,False,False,True]],返回array([0, 9])结果也是没意义的,第0维上其实是(一维)花式索引,和布尔索引不能混用
array([0, 4])
>>> a[[True,False]][:,[True,False,False,False,True]] #如果要对不同维度分别进行布尔索引,应像这样使用,交换顺序也正确,a[:,[True,False,False,False,True]][[True,False]]
array([[0, 4]])
>>> a=np.arange(24).reshape(2,3,4)
>>> a
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]]])
>>> a[np.array([[True,False,False],[False,True,True]])] #待索引数组形状为(2,3,4),布尔掩码数组形状是(2,3),这是允许的,表示沿着0轴和1轴构成的“面”进行过滤,过滤出的元素是长度为4的一维数组,过滤出的元素将被放在一个一维数组中,因此返回的结果将是(n,4)形状的,其中n是过滤出的元素个数
array([[ 0, 1, 2, 3],
[16, 17, 18, 19],
[20, 21, 22, 23]])
>>> a[[[True,False,False],[False,True,True]]] #同上,只不过掩码数组是list类型,此时出错,而之前如一维的布尔掩码数组则允许是list类型,有点莫名
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: boolean index did not match indexed array along dimension 0; dimension is 2 but corresponding boolean dimension is 3
>>> a[[True,True],:,[True,True,False,False]] #注意这是一个错误示例,输出不符合预期(因为一维布尔索引只能出现一次,不能出现在多个维度上),按理说,由于数组a轴0长度为2,该索引应该相当于a[...,[True,True,False,False]],观察输出,后者正常
array([[ 0, 4, 8],
[13, 17, 21]])

使用布尔索引可以“较为方便”地操作“表”结构(其实就是二维或三维的numpy数组,三维即表示“多层叠加的表”),为此,还需要准备有字段数组,用于标识“表”不同维度所代表的信息,譬如下面我们预备构建一个(2,3,3)形的三维数组,其中(3,3)的子数组记录的是所有3个学生3门科目的成绩信息,第0维的2表示期中和期末两个阶段的成绩,另构建三个字段数组,第一个表示“时间”维度信息,第二个表示“学生”维度信息,第三个表示“科目”维度信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'''学生成绩单
语文 数学 英语
期中:Daiyang: 80 78 70
Muggle: 90 82 86
Huamulan: 87 78 83
期末:Daiyang: 78 76 85
Muggle: 88 91 79
Huamulan: 92 89 90
'''
scores=np.array([[[80,78,70],[90,82,86],[87,78,83]],
[[78,76,85],[88,91,79],[92,89,90]]])

periods=np.array(['Midterm','Endterm'])
names=np.array(['Daiyang','Muggle','Huamulan'])
subjects=np.array(['Chinese','Math','English'])

要获取Muggle期中的三科成绩:

1
2
3
4
5
6
>>> scores[periods=='Midterm'].squeeze()[names=='Muggle']
array([[90, 82, 86]])
>>> scores[periods=='Midterm'][:,names=='Muggle'] #这种方式看起来更好一点
array([[[90, 82, 86]]])
>>> scores[:,names=='Muggle'][periods=='Midterm'] #交换顺序也正确
array([[[90, 82, 86]]])

要获取所有人的期末英语成绩:

1
2
3
4
5
6
7
8
9
10
>>> scores[periods=='Endterm'][:,:,subjects=='English']
array([[[85],
[79],
[90]]])
>>> scores[...,subjects=='English'][periods=='Endterm']
array([[[85],
[79],
[90]]])
>>> scores[periods=='Endterm'].squeeze().T[subjects=='English']
array([[85, 79, 90]])

要获取Muggle期中期末的数学成绩:

1
2
3
4
5
6
>>> scores[:,names=='Muggle'][...,subjects=='Math']
array([[[82]],

[[91]]])
>>> scores.swapaxes(0,1)[names=='Muggle'].squeeze().T[subjects=='Math']
array([[82, 91]])

要获取Daiyang和Huamulan期末的语文成绩:

1
2
3
>>> scores[periods=='Endterm'][:,(names=='Daiyang')|(names=='Huamulan')][...,subjects=='Chinese']
array([[[78],
[92]]])
花式索引

何为花式索引?设待索引数组a为二维,给定两个整数序列s1s2,含义上,s1选中了二维数组中的某些行,s2选中了二维数组中的某些列,现要获取这些行列交线的交点所在位置的元素,这就是花式索引,很简单,通过a[np.ix_(s1,s2)]即可获得,所得结果数组形状为(len(s1),len(s2)),换句话说,这两个整数序列s1s2的笛卡尔积就是将要定位的全部元素的二维位置坐标,再譬如三维数组的花式索引,给定三个整数序列s1s2s3,这三个整数序列的笛卡尔积就是将要定位的全部元素的三维位置坐标,可以通过a[np.ix_(s1,s2,s3)]快速获取,返回的数组形状显然就是(len(s1),len(s2),len(s3)),事实上,对于任意一个待索引的n维数组,传入np.ix_()的整数序列数量≤n即可,仍以三维待索引数组a为例(将其视为由诸多一维数组元素构成的二维数组,数组元素a[i][j]即是一个一维数组),a[np.ix_(s1,s2)]表示在轴0和轴1构成的“面”上进行花式索引,由于索引的元素本身就是一维数组,因此该花式索引将返回三维数组,形状为(len(s1),len(s2),a.shape[2]),特别地,当传入np.ix_()的整数序列数量为1时,表示特殊的一维花式索引(由于np.ix_(s,)将返回s本身,所以一维花式索引a[np.ix_(s,)]可以简写成a[s]),此时可以以逗号间隔不同的维度,但是只能在其中一个维度上使用(一维)花式索引,其可以和(单个)数值索引、切片索引混用,和全坐标索引自然不能混用,另外也不能和(一维)布尔索引混用,否则结果错误(整体十分类似于上面介绍的布尔索引)

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
>>> a=np.arange(12).reshape(3,4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[np.ix_([0,1],[1,3])]
array([[1, 3],
[5, 7]])
>>> b=np.arange(24).reshape(2,3,4)
>>> 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]]])
>>> b[np.ix_([0,1],[1],[1,3])]
array([[[ 5, 7]],

[[17, 19]]])
>>> b[np.ix_([1],[0,1])] #在三维数组轴0和轴1构成的“面”上进行花式索引
array([[[12, 13, 14, 15],
[16, 17, 18, 19]]])
>>> np.rollaxis(b,1,3)[np.ix_([0,1],[2,3])] #如果要在轴0和轴2构成的“面”上进行花式索引的话,需要先变换数组的轴,使原来的轴2滚动到轴1的位置上
array([[[ 2, 6, 10],
[ 3, 7, 11]],

[[14, 18, 22],
[15, 19, 23]]])
>>> a[np.ix_([0,2])]
array([[ 0, 1, 2, 3],
[ 8, 9, 10, 11]])
>>> a[[0,2]] #同上,因为np.ix_([0,2])将返回np.array([0,2]),其实也就是[0,2]。这是一维花式索引,允许以逗号为间隔指定多个维度的索引方式,其等同于a[[0,2],...]
array([[ 0, 1, 2, 3],
[ 8, 9, 10, 11]])
>>> a[:,[0,2]] #一维花式索引可以和切片索引混用
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
>>> a[1,[0,2]] #一维花式索引可以和(单个)数值索引混用
array([4, 6])
>>> b[:,1,[0,3]] #同上
array([[ 4, 7],
[16, 19]])
>>> b[[0,1],:,[0,1]] #注意这是一个错误示例,输出不符合预期(因为一维花式索引只能出现一次,不能出现在多个维度上),按理说,由于数组b轴0长度为2,此处不应该相当于b[...,[0,1]]么?观察两者输出,后者正常
array([[ 0, 4, 8],
[13, 17, 21]])
笛卡尔积计算

废话不多说,直接看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'''任意数量的序列的笛卡尔积,递归法求解
编写思路:
1.函数返回的是什么?返回的形式总为:[(),(),...,()]
2.一般步骤是什么?对于N个序列的“笛卡尔积”,只要求出前N-1个,其结果记为t(形如[(),(),...,()],设其长度为s),那么与第N个序列([e0,e1,...,en])再做一次“笛卡尔积”的结果将为:[(*t[0],e0),...,(*t[s],e0),(*t[0],e1),...,(*t[s],e1),(*t[0],e2),...,(*t[s],e2),......,(*t[0],en),...,(*t[s],en)]
3.递归的终点是什么(问题分解的最小规模)?只有一个序列(记为seq)的“笛卡尔积”,返回结果就是:[(e,) for e in seq]
'''
def cross_product(*arrs):
ret=[]
if len(arrs)==1:
return [(i,) for i in arrs[0]]
t=cross_product(*arrs[:-1])
for i in t:
for j in arrs[-1]:
ret.append((*i,j))
return ret

print(cross_product([1,2,3])) #个数3
print(cross_product([1,2],[3,4,5])) #个数2*3=6
print(cross_product([1,2,3],[4,5,6],[7,8,9])) #个数3*3*3=27
特别地,np.ix_()还允许接收布尔型序列boolean_sequence,它将被自动转化为np.nonzero(boolean_sequence)),也就是序列中为真值True的下标,还是以之前的“学生成绩单”为案例,再要获取Muggle期中的三科成绩:
1
2
3
4
>>> scores[np.ix_(periods=='Midterm',names=='Muggle')]
array([[[90, 82, 86]]])
>>> scores[np.ix_(periods=='Midterm',names=='Muggle',subjects=='Math')] #进一步获取Muggle的期中数学成绩
array([[[82]]])
再要获取所有人的期末英语成绩:
1
2
>>> np.rollaxis(scores,1,3)[np.ix_(periods=='Endterm',subjects=='English')]
array([[[85, 79, 90]]])
掌握技巧就会很容易写:将要进行条件选择的字段提前,不需要的一律置后,譬如上面的例子,periodssubjects两个字段需要进行条件选择,而names字段(“所有人”嘛)不需要,因此将names字段滚动到最后一个轴位置处,其他两个字段也就相对提前了,再举个例子,要获取Muggle的所有成绩,只有names字段需要条件选择,因此只需要将该字段提到最前即可:
1
2
3
>>> np.rollaxis(scores,1)[np.ix_(names=='Muggle')] #或np.rollaxis(scores,1)[names=='Muggle']
array([[[90, 82, 86],
[88, 91, 79]]])
再要获取Muggle期中期末的数学成绩:
1
2
>>> np.rollaxis(scores,0,3)[np.ix_(names=='Muggle',subjects=='Math')]
array([[[82, 91]]])
再要获取Daiyang和Huamulan期末的语文成绩:
1
2
3
>>> scores[np.ix_(periods=='Endterm',(names=='Daiyang')|(names=='Huamulan'),subjects=='Chinese')]
array([[[78],
[92]]])

最后再看一种特殊的(一维)花式索引,在上述一维花式索引使用的基础上,譬如a[s],其中s是一个一维序列,事实上,s可以reshape成任意形状(必须是ndarray对象),此时a[s]返回的结果相当于t=a[s.flatten()];a[s]=t.reshape(*s.shape,*t.shape[1:]),再譬如a[:,s]的返回结果相当于t=a[:,s.flatten()];a[:,s]=t.reshape(*t.shape[:-1],*s.shape),看个示例:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
>>> a=np.arange(6)
>>> a[np.array([[0,3],[1,4],[2,5]])]
array([[0, 3],
[1, 4],
[2, 5]])
>>> s=np.array([[0,3],[1,4],[2,5]]);t=a[s.flatten()];t.reshape(*s.shape,*t.shape[1:]) #验证
array([[0, 3],
[1, 4],
[2, 5]])
>>> a=np.arange(12).reshape(3,4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[np.array([[0,1,2],[2,1,0]])]
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],

[[ 8, 9, 10, 11],
[ 4, 5, 6, 7],
[ 0, 1, 2, 3]]])
>>> _.shape
(2, 3, 4)
>>> a[:,np.array([[0,1,2],[2,1,0]])]
array([[[ 0, 1, 2],
[ 2, 1, 0]],

[[ 4, 5, 6],
[ 6, 5, 4]],

[[ 8, 9, 10],
[10, 9, 8]]])
>>> s=np.array([[0,1,2],[2,1,0]]);t=a[:,s.flatten()];t.reshape(*t.shape[:-1],*s.shape) #验证
array([[[ 0, 1, 2],
[ 2, 1, 0]],

[[ 4, 5, 6],
[ 6, 5, 4]],

[[ 8, 9, 10],
[10, 9, 8]]])

看一个利用该原理的示例,构建循环队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def circular_queue(arr):
t=list(range(len(arr)))
inds=list(zip(*[(t+t[:-1])[i:] for i in range(len(t))]))
return np.array(arr)[np.array(inds)]

print(circular_queue([4,4,6,3,2,7]))

'''OUTPUT
[[4 4 6 3 2 7]
[4 6 3 2 7 4]
[6 3 2 7 4 4]
[3 2 7 4 4 6]
[2 7 4 4 6 3]
[7 4 4 6 3 2]]
'''

数组元素迭代

np.nditer(a, order, flags, op_flags, ...)
函数说明:
对一个多维数组使用for循环迭代只能遍历第0维上的元素,要迭代所有元素,需使用flatten()等函数展平(一维化)数组,并且可以指定访问风格,行主序或列主序,numpy为我们提供了一个专门的函数nditer(),通过该函数将创建一个在多维数组上的元素迭代器
参数说明(部分):

  • order:指定数组元素的迭代风格,行主序或列主序
  • flags参数(部分)可取值(一个字符串序列,可以同时指定以下多个值):
    • c_index:表示跟踪C顺序的索引(从0开始计数),索引值通过函数返回的迭代器的index属性获取,即便实际是以F风格(order参数)迭代数组元素,给出的也是C风格索引。注意c_indexf_index两者只能取其一
    • f_index:表示跟踪F顺序的索引,同上
    • multi_index:表示跟踪迭代元素的全(位置)坐标,通过函数返回的迭代器的multi_index属性获取
    • external_loop(没懂):其将多维数组a视为由诸多一维数组元素构成的数组,若指定order='F',那么迭代次数将为reduce(lambda x,y:x*y,a.shape[1:]),此时第0维上的向量被视为单个元素,若指定order='C',那么迭代次数将为1,访问的元素就是np.flatten(a),一个一维数组,这有何用?
  • op_flags:默认取值readonly,即视待遍历的数组为只读对象,为了在遍历数组的同时实现对数组元素值的修改,需指定为readwritewriteonly
元素迭代器的属性和方法

函数所返回的迭代器上的(部分)属性和方法:

  • finished:判断元素是否迭代完毕,也可以通过迭代器对象的iternext()方法的返回值来判断
  • iterindex:返回当前迭代位置处元素的索引,至于是C风格还是F风格由order参数决定
  • has_index:如果返回True,表示迭代器是指定c_indexf_index标志(flags参数)创建的,此时属性index才是可获得的
  • index:同iterindex,只不过具体的风格是由flags参数决定,且只有当has_index属性为True时,可以使用该属性,否则引发ValueError异常
  • has_multi_index:如果返回True,表示迭代器是指定multi_index标志(flags参数)创建的,此时属性multi_index才是可获得的
  • multi_index:返回当前迭代位置处元素的全坐标(即该元素在原始数组中的位置坐标),只有当has_multi_index属性为True时,可以使用该属性,否则引发ValueError异常
  • itersize:返回迭代器中元素的个数(还有ndimshape属性,我还没弄明白它们的输出,譬如nditer(np.arange(12).reshape(3,-1),flags=['f_index']).shape为什么是(4,3),而将其中的f_index改成c_index输出又变成(12,)?)
  • iternext():表示执行一次内部迭代,但不返回迭代元素,只返回TrueFalse,用于判断当前迭代器是否已尽,返回False表示没有元素可供迭代了(继续执行iternext()方法仍会返回False,调用iterable[0]则会出错,除非调用reset()方法重置状态以从头开始迭代),而要获取当前迭代位置处的元素,可以通过下标0索引该迭代器(即iterator[0],事实上,你可以使用nditer()并行迭代多个多维数组对象,于是第二个多维数组的元素可以通过iterable[1]获取,以此类推。注意在使用nditer()创建迭代器后,立即就可调用iterable[0]来获取第一个迭代元素,而无需先调用iternext()方法)的方式获取。除了使用iternext(),常规的迭代方法则是使用for循环遍历迭代器(自动调用迭代器对象的__next__()方法),当然也可以手动调用__next__()方法(此时需自行捕捉StopIteration异常)
  • reset():重置迭代器的状态
  • copy():返回迭代器在当前状态下的一个副本
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import numpy as np

print('### nditer() ###')
a=np.arange(6).reshape((2,3),order='F')
print(a)
for i in np.nditer(a,order='C'): #默认order为'K',其将会按照a的布局进行访问,打印0,1,2,3,4,5,
print(i,end=',')
print()

print('### read_write mode ###')
#读写模式
for i in np.nditer(a,op_flags=('readwrite',),order='C'):
i*=2
print(i,end=',')
print()
print(a) #可以看到原始数组也已经被修改了

print('### while+iternext() ###')
#通过while循环不断输出迭代器对象的索引属性值
a=np.array([1,3,5,7,2,4,6,8]).reshape((2,2,-1))
print(a)
it=np.nditer(a,flags=['f_index'])
print(it.ndim,it.shape,it.itersize)
while not it.finished:
print("[F]index-%d: %d [C]index-%d" % (it.index,it[0],it.iterindex)) #index属性跟踪F顺序的索引,即使当前实际是以C风格访问数组元素
it.iternext()
it=np.nditer(a,flags=['multi_index'])
while not it.finished:
print("index-%s: %d" % (it.multi_index,it[0])) #全位置坐标
it.iternext()

print('### external_loop ###')
for i in np.nditer(a,flags=['external_loop'],order='F'):
print(i,end=',')
print()
for i in np.nditer(a,flags=['external_loop'],order='C'): #有什么用?
print(i,end=',')
print()

print('### copy() ###')
it=np.nditer(np.arange(24).reshape(2,3,4))
print(it.__next__())
print(it.__next__())
it2=it.copy() #副本保存了原件的状态
print(it2.__next__())
print(it.__next__())

print('### return of iternext() ###')
it=np.nditer(np.array([12,13]))
print(it.iternext())
print(it.__next__())
print(it.iternext())
print(it.iternext())
print(it.iternext())

'''OUTPUT
### nditer() ###
[[0 2 4]
[1 3 5]]
0,2,4,1,3,5,
### read_write mode ###
0,4,8,2,6,10,
[[ 0 4 8]
[ 2 6 10]]
### while+iternext() ###
[[[1 3]
[5 7]]

[[2 4]
[6 8]]]
3 (2, 2, 2) 8
[F]index-0: 1 [C]index-0
[F]index-4: 3 [C]index-1
[F]index-2: 5 [C]index-2
[F]index-6: 7 [C]index-3
[F]index-1: 2 [C]index-4
[F]index-5: 4 [C]index-5
[F]index-3: 6 [C]index-6
[F]index-7: 8 [C]index-7
index-(0, 0, 0): 1
index-(0, 0, 1): 3
index-(0, 1, 0): 5
index-(0, 1, 1): 7
index-(1, 0, 0): 2
index-(1, 0, 1): 4
index-(1, 1, 0): 6
index-(1, 1, 1): 8
### external_loop ###
[1 2],[5 6],[3 4],[7 8],
[1 3 5 7 2 4 6 8],
### copy() ###
0
1
2
2
### return of iternext() ###
True
13
False
False
False
'''

并行迭代,如果两个数组是同形的(弱化条件为两者可以广播至同一形状),那么nditer()能够同时迭代它们:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import numpy as np
a=np.arange(0,60,5).reshape(3,4)
b=np.array([1, 2, 3, 4])
print(a)
print(b)
#b(形状为(1,4))可以广播至与a的相同形状(3,4)
for ia,ib in np.nditer([a,b]):
print(ia,',',ib)

it=np.nditer([a,b],flags=['multi_index'])
while not it.finished:
print(f'pos:{it.multi_index},item(a):{it[0]},item(b):{it[1]}')
it.iternext()

'''OUTPUT
[[ 0 5 10 15]
[20 25 30 35]
[40 45 50 55]]
[1 2 3 4]
0 , 1
5 , 2
10 , 3
15 , 4
20 , 1
25 , 2
30 , 3
35 , 4
40 , 1
45 , 2
50 , 3
55 , 4
pos:(0, 0),item(a):0,item(b):1
pos:(0, 1),item(a):5,item(b):2
pos:(0, 2),item(a):10,item(b):3
pos:(0, 3),item(a):15,item(b):4
pos:(1, 0),item(a):20,item(b):1
pos:(1, 1),item(a):25,item(b):2
pos:(1, 2),item(a):30,item(b):3
pos:(1, 3),item(a):35,item(b):4
pos:(2, 0),item(a):40,item(b):1
pos:(2, 1),item(a):45,item(b):2
pos:(2, 2),item(a):50,item(b):3
pos:(2, 3),item(a):55,item(b):4
'''

该属性返回多维数组的元素迭代器(一个flatiter对象,由于是迭代器对象,用完即弃),遵循C风格,也可以作为一个一维数组“视图”使用,支持切片索引、花式索引等,flatiter对象上有一个copy()方法将其转为(一维的)ndarray对象(深拷贝),其还有三个主要属性:

  • base:返回flatiter对象的原始数组引用
  • index:返回当前迭代元素的索引(从0开始计数)
  • coords:返回当前迭代元素在原数组中的全坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
a=np.arange(10).reshape((2,-1))
b=a.flat
print(b.base is a)
for i in b:
print('元素:',i,'索引:',b.index,'位置坐标:',b.coords)
print()

'''OUTPUT
True
元素: 0 索引: 1 位置坐标: (0, 1)
元素: 1 索引: 2 位置坐标: (0, 2)
元素: 2 索引: 3 位置坐标: (0, 3)
元素: 3 索引: 4 位置坐标: (0, 4)
元素: 4 索引: 5 位置坐标: (1, 0)
元素: 5 索引: 6 位置坐标: (1, 1)
元素: 6 索引: 7 位置坐标: (1, 2)
元素: 7 索引: 8 位置坐标: (1, 3)
元素: 8 索引: 9 位置坐标: (1, 4)
元素: 9 索引: 10 位置坐标: (2, 0)
'''

上述输出不正确,这里不建议通过for循环的方式去获取索引或坐标,因为flatiter只有调用一次__next__()方法后才能获取到(第一个)迭代元素,而调用后其indexcoords属性又会立即更新(变成第二个迭代元素的索引和坐标),因此必须在next()前获取indexcoordsfor循环无法控制next()调用的时机,因此可以通过while方式并手动调用next()迭代元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
from itertools import count
a=np.arange(10).reshape((2,-1))
b=a.flat
for i in count():
s=f'{b.index},{b.coords}'
try:
print(s,b.__next__())
except StopIteration as e:
break

'''OUTPUT
0,(0, 0) 0
1,(0, 1) 1
2,(0, 2) 2
3,(0, 3) 3
4,(0, 4) 4
5,(1, 0) 5
6,(1, 1) 6
7,(1, 2) 7
8,(1, 3) 8
9,(1, 4) 9
'''

ndarray.flat本身也可以作为展平后的一维数组“视图”使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np
a=np.arange(10).reshape((2,-1))
print(a)
print(a.flat[[2,5,7]]) #对一维数组的花式索引
a.flat[[3,6]]=55 #对“视图”的修改将影响原数组
print(a)
a.flat.copy()[0]=78 #copy()深复制,不影响原数组
print(a)
a.flat=[i for i in range(100,200)] #可以将一个一维数组或列表赋给flat属性(长度不一致时会自动截取前n项),这常用于一个多维数组元素的修改
print(a)

'''OUTPUT
[[0 1 2 3 4]
[5 6 7 8 9]]
[2 5 7]
[[ 0 1 2 55 4]
[ 5 55 7 8 9]]
[[ 0 1 2 55 4]
[ 5 55 7 8 9]]
[[100 101 102 103 104]
[105 106 107 108 109]]
'''

广播机制

广播机制是处理不同维度数组间操作的一个非常重要的手段,要知道两个不同的多维数组能够进行操作的基本条件是其形状相同,当形状不同时,但符合广播的条件,则会自动对这两个或其中一个多维数组进行维度补齐并进行元素复制(补齐维数)
广播的规则:

  1. 让所有输入数组都向其中维数最大(shape最长)的数组看齐,shape中不足的部分在前面加1补齐维度
  2. 输出数组的shape根据所有输入数组shape的各个轴上的最大值得到
  3. 如果输入数组的某个轴和输出数组的对应轴的长度相同或者不同但其中一方长度为1时,这些输入数组被认为能够广播至输出数组的形状,即符合广播的条件,否则抛出异常
  4. 当输入数组的某个轴的长度为1且输出数组对应轴长大于1时,将沿着此轴复制第一个元素来进一步补齐维数
1
2
3
4
5
6
7
8
9
a=np.arange(24).reshape((2,3,4))
b1=np.arange(4).reshape((-1))
b2=np.arange(12).reshape((3,-1))
c1=a+b1 #首先维度补齐:b1.reshape((1,1,4)),确定输出数组形状:(2,3,4),判断能够进行广播,最后进行元素复制
c2=a+b2 #同上,维度补齐后得到b2.reshape((1,3,4)),输出数组形状仍是(2,3,4),且符合广播的条件

a=np.arange(8).reshape((1,2,1,4))
b=np.arange(12).reshape((1,1,1,3,-1))
c=a+b #a补齐维度a.reshape((1,1,2,1,4)),b补齐维度b.reshape((1,1,1,3,4)),再确定输出数组形状(1,1,2,3,4),显然也符合广播条件

np.broadcast(*arrs)
函数说明:
函数可以接受任意数量(设为n)的输入数组,并产生基于这些输入数组的“广播对象”作为函数返回值,这个广播对象有一些属性和方法,其中shapendim(或nd)分别获取经广播机制作用后的输出数组的形状和维数,而iters属性返回一个长度为n(数量可由numiter属性获取)的由诸多flatiter对象构成的元组,这些flatiter(用完即弃)分别代表那些输入数组经广播(维度补齐和元素复制)后的数据内容,此外,广播对象本身也是一个迭代器(用完即弃),相当于并行迭代前面的flatiter对象,为了复用广播对象,其有一个reset()方法能够重置初始的迭代状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> x=np.array([[1], [2], [3]])
>>> y=np.array([4, 5, 6, 7, 8])
>>> broadcast_obj=np.broadcast(x,y) #两个输入数组x和y的形状分别为(3,1)和(5,),于是输出数组的形状可以确定为(3,5),并且符合广播条件,于是x和y分别沿着第1轴和第0轴按列复制和按行复制元素
>>> type(broadcast_obj)
<class 'numpy.broadcast'>
>>> broadcast_obj.shape
(3, 5)
>>> broadcast_obj.nd
2
>>> a,b=broadcast_obj.iters
>>> list(a)
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]
>>> list(b) #之后再执行list(a)或list(b)只会得到空列表[],除非执行重置reset()
[4, 5, 6, 7, 8, 4, 5, 6, 7, 8, 4, 5, 6, 7, 8]
>>> broadcast_obj.reset()
>>> [(i,j) for i,j in broadcast_obj]
[(1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8)]
手动实现两个多维数组的相加操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> x=np.array([[1], [2], [3]])
>>> y=np.array([4, 5, 6, 7, 8])
>>> x+y
array([[ 5, 6, 7, 8, 9],
[ 6, 7, 8, 9, 10],
[ 7, 8, 9, 10, 11]])
>>> bo=np.broadcast(x,y)
>>> z=np.empty(bo.shape,dtype='i4') #创建一个空的结果数组
>>> z.flat=[x+y for i,j in bo]
>>> z
array([[ 5, 6, 7, 8, 9],
[ 6, 7, 8, 9, 10],
[ 7, 8, 9, 10, 11]])

np.broadcast_to(array, shape, subok=False)
函数说明:
用于将一个数组广播至一个指定的形状

1
2
3
4
5
6
7
8
>>> np.broadcast_to(np.array([[1], [2], [3]]),(3,5))
array([[1, 1, 1, 1, 1],
[2, 2, 2, 2, 2],
[3, 3, 3, 3, 3]])
>>> np.broadcast_to(np.array([4, 5, 6, 7, 8]),(3,5))
array([[4, 5, 6, 7, 8],
[4, 5, 6, 7, 8],
[4, 5, 6, 7, 8]])

数组拼接与分割

np.concatenate()

np.concatenate((a1, a2, ...), axis=0, out=None)
函数说明:
用于沿指定轴(axis)连接多个数组对象(a1a2,…),a1a2等数组的形状完全相同固然最好(此时,可以沿任意轴进行连接),即使不相同,也必须满足“除了将要连接的那个轴的长度可以不一致外,其余轴长必须完全相同”的条件(举例:shape1=(2,3,4,5)shape2=(2,3,7,5),则连接轴只能设为2轴,否则出错,且显然输出形状为(2,3,11,5)),特别地,axis置为None时,a1a2等数组首先会被“展平”(一维化)开来,然后沿0轴连接,out参数指定结果存放的数组

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
>>> a=np.array([[1,2],[3,4]]) #形状为(2,2)
>>> b=np.array([[5,6]]) #形状为(1,2),显然这两个数组只能沿第0轴连接
>>> np.concatenate((a,b),axis=0)
array([[1, 2],
[3, 4],
[5, 6]])
>>> np.concatenate((a,b),axis=None)
array([1, 2, 3, 4, 5, 6])
>>> np.concatenate((a,b.T),axis=1) #b转置后形状变为(2,1),此时只能沿着第1轴连接
array([[1, 2, 5],
[3, 4, 6]])
>>> a=np.arange(24).reshape((2,3,4))
>>> b=np.arange(24,56).reshape((2,4,4)) #显然a和b只能沿着第1轴连接,输出数组形状为(2,7,4)
>>> np.concatenate((a,b),axis=1)
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[24, 25, 26, 27],
[28, 29, 30, 31],
[32, 33, 34, 35],
[36, 37, 38, 39]],

[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23],
[40, 41, 42, 43],
[44, 45, 46, 47],
[48, 49, 50, 51],
[52, 53, 54, 55]]])
np.stack()

np.stack(arrays, axis=0, out=None)
函数说明:
沿新轴堆叠数组。用于堆叠的各数组形状必须完全一致,注意stack()总是会增加一个维度(最简单的就是二维数组的堆叠,结果将变成三维数组),“新轴”由参数axis指定,axis的取值为0~len(shape)+1,若取值len(shape)+1表示在最后新增一个维度,譬如有两个待堆叠数组,形状为(2,3,4),若指定axis为3,返回的新数组形状将为(2,3,4,2)

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
>>> a=np.arange(12).reshape(3,-1)
>>> b=np.arange(12,24).reshape(3,-1)
>>> np.stack((a,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]]])
>>> _.shape
(2, 3, 4)
>>> np.stack((a,b),axis=2)
array([[[ 0, 12],
[ 1, 13],
[ 2, 14],
[ 3, 15]],

[[ 4, 16],
[ 5, 17],
[ 6, 18],
[ 7, 19]],

[[ 8, 20],
[ 9, 21],
[10, 22],
[11, 23]]])
>>> _.shape
(3, 4, 2)
np.vstack()

np.vstack((a1,a2,...))
函数说明:
不同于np.stack()vstack()垂直堆叠(不改变维数)总是沿着第0轴连接(concatenate())多个数组,a1a2等除0轴外其余轴长必须保持一致,之所以叫“垂直”堆叠,是假设a1a2等都是二维数组时,第0轴正是那根垂直的轴,特别地,要对一维数组进行“垂直堆叠”,等同于np.vstack((a1[None,:],a2[None,:],...)),因此这些一维数组的长度必须相同(只有在这种特殊情况下,堆叠后的数组维数发生改变)

1
2
3
4
5
6
7
8
9
10
11
12
>>> a=np.array((1,2,3))
>>> b=np.array((2,3,4))
>>> np.vstack((a,b))
array([[1, 2, 3],
[2, 3, 4]])
>>> np.vstack((a[None,:],b[None,:]))
array([[1, 2, 3],
[2, 3, 4]])
>>> np.vstack((np.arange(8).reshape(2,4),np.arange(4).reshape(1,4)))
array([[0, 1, 2, 3],
[4, 5, 6, 7],
[0, 1, 2, 3]])
np.row_stack()

np.row_stack((a1,a2,...))
函数说明:
按行堆叠,等同于np.vstack((a1,a2,...))

np.hstack()

np.hstack((a1,a2,...))
函数说明:
类似于np.vstack()hstack()水平堆叠(不改变维数)总是沿着第1轴连接(concatenate())多个数组,a1a2等除1轴外其余轴长必须保持一致,特别地,要对一维数组进行“水平”堆叠,仍等同于np.hstack((a1[None,:],a2[None,:],...)),因此对这些一维数组的长度没有限制

1
2
3
4
5
6
7
8
9
>>> a=np.array((1,2,3))
>>> b=np.array((4,5,6,7))
>>> np.hstack((a,b))
array([1, 2, 3, 4, 5, 6, 7])
>>> c=np.arange(4,10).reshape(3,2,order='F')
>>> np.hstack((a[:,None],c))
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
np.column_stack()

np.column_stack((a1,a2,...))
函数说明:
按列堆叠,等同于np.hstack((a1,a2,...))

np.dstack()

类似于np.vstack()np.hstack()dstack()深度堆叠(不改变维数)总是沿着第2轴连接(concatenate())多个数组,a1a2等除2轴外其余轴长必须保持一致,特别地,要对一维数组进行“深度”堆叠,等同于np.dstack((a1[None,:,None],a2[None,:,None],...)),因此这些一维数组的长度必须相同(只有在这种特殊情况下,堆叠后的数组维数发生改变)

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
>>> a=np.arange(24).reshape(3,4,2)
>>> b=np.arange(12).reshape(3,4,1)
>>> np.dstack((a,b))
array([[[ 0, 1, 0],
[ 2, 3, 1],
[ 4, 5, 2],
[ 6, 7, 3]],

[[ 8, 9, 4],
[10, 11, 5],
[12, 13, 6],
[14, 15, 7]],

[[16, 17, 8],
[18, 19, 9],
[20, 21, 10],
[22, 23, 11]]])
>>> a=np.arange(3)
>>> b=np.arange(3,6)
>>> np.dstack((a,b))
array([[[0, 3],
[1, 4],
[2, 5]]])
>>> np.dstack((a[None,:,None],b[None,:,None]))
array([[[0, 3],
[1, 4],
[2, 5]]])
np.split()

np.split(arr, indices_or_sections, axis=0)
函数说明:
用于沿指定的轴axis将数组分割为多个子数组。参数indices_or_sections可以是整数,也可以是一个整数列表,分别表示不同的切割方式,当为整数n时,沿指定轴axis将数组arr平均切割为n份,如果该轴长度不足以均分为n等份时,将抛出异常,当为整数列表时,其中每一个整数表示一处切割的位置,譬如[2,5,7]表示分别在轴长刻度为2、5、7处进行切割(并得到四个子数组),具体的,第一份从刻度0开始但不包括刻度2,第二份从刻度2开始但不包括刻度5,第三份从刻度5开始但不包括刻度7,最后一份从刻度7开始直至最后(有可能是空数组),特别地,如果指定的切割位置超出轴长,则返回空的子数组而不会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a=np.arange(9)
>>> np.split(a,3)
[array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8])]
>>> b=np.arange(16).reshape(2,-1)
>>> b
array([[ 0, 1, 2, 3, 4, 5, 6, 7],
[ 8, 9, 10, 11, 12, 13, 14, 15]])
>>> np.split(b,[2,5,7],axis=1)
[array([[0, 1],
[8, 9]]), array([[ 2, 3, 4],
[10, 11, 12]]), array([[ 5, 6],
[13, 14]]), array([[ 7],
[15]])]
np.vsplit()

np.vsplit(arr, indices_or_sections)
函数说明:
垂直分割,特指沿0轴切割,注意不能对一维数组执行该操作(至少是二维)

1
2
3
4
5
>>> a=np.arange(8).reshape((4,-1))
>>> np.vsplit(a,2)
[array([[0, 1],
[2, 3]]), array([[4, 5],
[6, 7]])]
np.hsplit()

np.hsplit(arr, indices_or_sections)
函数说明:
水平分割,特指沿1轴切割,特别地,要对一维数组进行“水平”切割,相当于但不完全等同于np.hsplit(arr[None,:], indices_or_sections)(此处返回的子数组是二维的),返回的子数组仍是一维的

1
2
3
4
5
6
7
>>> np.hsplit(np.arange(8).reshape(2,-1),[2,3])
[array([[0, 1],
[4, 5]]), array([[2],
[6]]), array([[3],
[7]])]
>>> np.hsplit(np.arange(8),4)
[array([0, 1]), array([2, 3]), array([4, 5]), array([6, 7])]
np.dsplit()

np.dsplit(arr, indices_or_sections)
函数说明:
深度分割,特指沿2轴切割,注意不对对一维或二维数组执行该操作(至少是三维)

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
>>> a=np.arange(24).reshape(4,3,-1)
>>> a
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]]])
>>> np.dsplit(a,2)
[array([[[ 0],
[ 2],
[ 4]],

[[ 6],
[ 8],
[10]],

[[12],
[14],
[16]],

[[18],
[20],
[22]]]), array([[[ 1],
[ 3],
[ 5]],

[[ 7],
[ 9],
[11]],

[[13],
[15],
[17]],

[[19],
[21],
[23]]])]
>>> _[0].shape
(4, 3, 1)

数组增删改查

np.append(arr, values, axis=None)
函数说明:
用于沿指定轴axis向数组中追加数据values(多维数组对象),该操作基本同np.concatenate()函数,要追加的子数组values的形状和arr的形状除axis轴长度可以不一致外,其余轴长必须一致,另外当axis置为None时(默认值),会将一维化的arrvalues连接成一个一维数组并返回

1
2
3
4
5
>>> np.append(np.arange(4).reshape(2,-1),np.arange(6).reshape(2,3),axis=1)
array([[0, 1, 0, 1, 2],
[2, 3, 3, 4, 5]])
>>> np.append(np.arange(4).reshape(2,-1),np.arange(6).reshape(2,3),axis=None)
array([0, 1, 2, 3, 0, 1, 2, 3, 4, 5])

np.delete(arr, obj, axis=None)
函数说明:
用于沿指定轴axis删除特定位置处的子数组。obj参数可以是整数(指定要删除的刻度位置处的元素)、切片或整数列表(指定要删除的多个刻度对应位置处的元素),特别地,当axis置为None时(默认值),将会首先“展平”(一维化)arr数组

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
>>> arr=np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
>>> arr
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
>>> np.delete(arr,[0,2]) #axis为None,则先展平再删除
array([ 2, 4, 5, 6, 7, 8, 9, 10, 11, 12])
>>> np.delete(arr,[0,2],axis=0) #沿着第0轴删除第0个元素和第2个元素
array([[5, 6, 7, 8]])
>>> np.delete(arr,[0,2],axis=1)
array([[ 2, 4],
[ 6, 8],
[10, 12]])
>>> np.delete(arr,slice(0,2),axis=0)
array([[ 9, 10, 11, 12]])
>>> np.delete(arr,np.s_[:2],axis=0) #这种切片方式更简便,效果同上
array([[ 9, 10, 11, 12]])
>>> np.delete(arr,np.s_[::],axis=1) #表示全部删除,返回一个空数组
array([], shape=(3, 0), dtype=int32)
>>> np.delete(arr,np.s_[::2],axis=1)
array([[ 2, 4],
[ 6, 8],
[10, 12]])
>>> np.s_[::2]
slice(None, None, 2)
>>> np.delete(arr,np.ix_(np.max(arr,axis=0)>10),axis=1) #删除arr数组中那些列中最大值大于10的列
array([[ 1, 2],
[ 5, 6],
[ 9, 10]])

np.insert(arr, obj, values, axis=None)
函数说明:
此操作类似于np.append(),可以认为append()insert()的一个特例(在末尾插入子数组),参数obj可以是整数(在指定刻度位置处插入子数组values)、切片对象或整数列表(在指定的多个刻度位置处插入子数组values),具体的,当obj为切片或整数列表时,values的形状和arr的形状除了满足“除axis所在轴长度可以不一致外其余轴长必须相同”的条件外(基本原则),还需要满足“valuesaxis所在轴长要么是1,要么是切片或整数列表的长度”的条件,当obj为整数时,values的形状除了需要满足基本原则,还需要满足“valuesaxis所在轴长必须为1”的条件,最后还要删除这个多余的长度为1的axis维(squeeze()),当obj为单整数序列时,values的形状只需要满足基本原则即可。特别地,axis参数可以为None时(默认值),表示会将arr一维化展开,其余的,正常按照前述规则,就像在一维数组中插入数据一样

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
>>> a=np.arange(12).reshape(2,2,3)
>>> a
array([[[ 0, 1, 2],
[ 3, 4, 5]],

[[ 6, 7, 8],
[ 9, 10, 11]]])
>>> np.insert(a,[0,1],np.arange(12,18).reshape(2,1,3),1) #由于obj为整数列表,根据规则,插入数据values的形状只能是(2,1,3)或(2,2,3),当其为(2,1,3)时,表示沿1轴来看,values相当于单个元素,当沿着1轴在刻度0位置处插入数据values后,似乎没有元素可供插入刻度1位置处了,这时会复制一份一模一样的数据插在刻度1位置处
array([[[12, 13, 14],
[ 0, 1, 2],
[12, 13, 14],
[ 3, 4, 5]],

[[15, 16, 17],
[ 6, 7, 8],
[15, 16, 17],
[ 9, 10, 11]]])
>>> np.arange(12,24).reshape(2,2,3)
array([[[12, 13, 14],
[15, 16, 17]],

[[18, 19, 20],
[21, 22, 23]]])
>>> np.insert(a,[0,1],_,1) #当values形状为(2,2,3)时,沿1轴来看,values相当于有两个元素,于是分别插在原数组的刻度0和刻度1位置处
array([[[12, 13, 14],
[ 0, 1, 2],
[15, 16, 17],
[ 3, 4, 5]],

[[18, 19, 20],
[ 6, 7, 8],
[21, 22, 23],
[ 9, 10, 11]]])
>>> np.insert(a,1,np.arange(12,18).reshape(2,3),1) #此处obj是单整数,根据规则,插入数据values的形状只能是(2,1,3),且要删除axis所在轴(长度为1),于是形状只能是(2,3),为什么要减去这个维度呢?之前在介绍数值索引时说过,数值索引总是会导致维度减1,譬如给定一个二维数组,a=np.arange(12).reshape(3,4),a[:,0]或a[0]的结果总是一维的,因此在向二维数组a的某列或某行赋值时,也必须是一维的,即a[:,0]=np.array([666,777,888]),如果写成a[:,0]=np.array([666,777,888])[:,None]就会出错,同理,此处的insert()函数在obj为单整数时候,也是这个道理
array([[[ 0, 1, 2],
[12, 13, 14],
[ 3, 4, 5]],

[[ 6, 7, 8],
[15, 16, 17],
[ 9, 10, 11]]])
>>> np.insert(np.arange(12).reshape(3,4),1,np.array([666,777,888]),1) #根据规则,插入数据values的形状只能是(3,1),去除axis所在轴(长度为1),于是形状只能是(3,)
array([[ 0, 666, 1, 2, 3],
[ 4, 777, 5, 6, 7],
[ 8, 888, 9, 10, 11]])
>>> np.insert(a,[1],np.arange(12,18).reshape(2,1,3),1) #如果obj是单整数序列呢?这个比较特殊,此时插入数据values的形状为(2,x,3)即可,其中x任意
array([[[ 0, 1, 2],
[12, 13, 14],
[ 3, 4, 5]],

[[ 6, 7, 8],
[15, 16, 17],
[ 9, 10, 11]]])
>>> np.insert(a,[1],np.arange(12,42).reshape(2,5,3),1)
array([[[ 0, 1, 2],
[12, 13, 14],
[15, 16, 17],
[18, 19, 20],
[21, 22, 23],
[24, 25, 26],
[ 3, 4, 5]],

[[ 6, 7, 8],
[27, 28, 29],
[30, 31, 32],
[33, 34, 35],
[36, 37, 38],
[39, 40, 41],
[ 9, 10, 11]]])
>>> a.flatten()
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> np.insert(a,[2,5],[88])
array([ 0, 1, 88, 2, 3, 4, 88, 5, 6, 7, 8, 9, 10, 11])
>>> np.insert(a,[2,5],[66,88])
array([ 0, 1, 66, 2, 3, 4, 88, 5, 6, 7, 8, 9, 10, 11])
>>> np.insert(a,[2,5,7],[66,88,99])
array([ 0, 1, 66, 2, 3, 4, 88, 5, 6, 99, 7, 8, 9, 10, 11])
>>> np.insert(a,np.s_[::3],[66,77,88,99])
array([66, 0, 1, 2, 77, 3, 4, 5, 88, 6, 7, 8, 99, 9, 10, 11])
>>> np.insert(a,[2],[66,99,100])
array([ 0, 1, 66, 99, 100, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11])
>>> np.insert(a,2,[66,99,100]) #这里也比较特殊,对于一维,obj为单整数,不仅可以插入[666](长度为1的一维数组),也可以插入[666,777,888](任意长度)
array([ 0, 1, 66, 99, 100, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11])
>>> np.insert(a,2,[66])
array([ 0, 1, 66, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

np.unique(arr, return_index=False, return_inverse=False, return_counts=False, axis=None)
函数说明:
用于去除数组arr中的重复元素,返回以升序排序的去重值数组(由于axis默认为None,因此会首先一维化arr,并最终返回一个去重的一维数组)
参数说明:

  • axis:沿axis轴对数组元素进行去重,举个例子,当arr为二维数组,axis1,表示要去重的元素是二维数组中的列向量,反之,axis为0,表示要去重的元素是二维数组中的行向量。axis置为None时,表示将arr一维化
  • return_index:若为True,同时返回去重数组中元素在原始数组中的位置下标
  • return_inverse:若为True,同时返回原始数组中元素在去重数组中的位置下标
  • return_counts:若为True,同时返回去重数组中元素在原始数组中出现的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> a=np.array([6,7,6,6,5,2,9,8,2,5]).reshape(2,-1)
>>> a
array([[6, 7, 6, 6, 5],
[2, 9, 8, 2, 5]])
>>> np.unique(a)
array([2, 5, 6, 7, 8, 9])
>>> r,ind=np.unique(a,axis=1,return_index=True)
>>> r
array([[5, 6, 6, 7],
[5, 2, 8, 9]])
>>> ind
array([4, 0, 2, 1], dtype=int64)
>>> r,ind,cs=np.unique(a,axis=1,return_inverse=True,return_counts=True)
>>> r
array([[5, 6, 6, 7],
[5, 2, 8, 9]])
>>> ind
array([1, 3, 2, 1, 0], dtype=int64)
>>> cs
array([1, 2, 1, 1], dtype=int64)
>>> r[:,ind] #(一维)花式索引重构原始数组
array([[6, 7, 6, 6, 5],
[2, 9, 8, 2, 5]])

np.sort(a, axis=-1, kind='quicksort', order=None)
函数说明:
用于沿指定轴对输入数组进行排序。默认是沿着最后一个轴(axis=-1)进行排序,如果axis置为None,则先一维化展平数组a,然后再进行排序(最终将返回一个一维数组),kind参数指定排序算法,还可取值mergesort(归并排序)和heapsort(堆排序),此处的order参数并非有关内存布局,而是用于自定义结构化数据类型时,设置不同字段参与排序时的优先级(order列表中字段的优先级从左至右依次降低),没有出现在order的字段,其优先级低于order中的字段,且按照定义结构化数据类型dtype时的字段顺序依次降低

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
30
31
32
>>> a = np.array([[1,4],[3,1]])
>>> a
array([[1, 4],
[3, 1]])
>>> np.sort(a)
array([[1, 4],
[1, 3]])
>>> np.sort(a,axis=1)
array([[1, 4],
[1, 3]])
>>> np.sort(a,axis=0)
array([[1, 1],
[3, 4]])
>>> np.sort(a,axis=None)
array([1, 1, 3, 4])
>>> dtype = [('name', 'S10'), ('height', float), ('age', int)]
>>> values = [('Arthur', 1.8, 41), ('Lancelot', 1.9, 38), ('Galahad', 1.7, 38)]
>>> a = np.array(values, dtype=dtype)
>>> np.sort(a, order='height')
array([(b'Galahad', 1.7, 38), (b'Arthur', 1.8, 41),
(b'Lancelot', 1.9, 38)],
dtype=[('name', 'S10'), ('height', '<f8'), ('age', '<i4')])
>>> np.sort(a, order=['age', 'height'])
array([(b'Galahad', 1.7, 38), (b'Lancelot', 1.9, 38),
(b'Arthur', 1.8, 41)],
dtype=[('name', 'S10'), ('height', '<f8'), ('age', '<i4')])
>>> np.sort(a)
array([(b'Arthur', 1.8, 41), (b'Galahad', 1.7, 38),
(b'Lancelot', 1.9, 38)],
dtype=[('name', 'S10'), ('height', '<f8'), ('age', '<i4')])
>>> b'Arthur'<b'Galahad'<b'Lancelot'
True
np.argsort()

np.argsort(a, axis=-1, kind='quicksort', order=None)
函数说明:
函数返回的是数组值从小到大排列的下标序列,参数同np.sort()

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
30
31
32
>>> x=np.random.randint(0,24,6)
>>> y = np.argsort(x)
>>> x[y] #(一维)花式索引构造升序排列的结果
array([ 4, 13, 16, 16, 21, 23])
>>> x = np.array([[0, 3], [2, 2]])
>>> x
array([[0, 3],
[2, 2]])
>>> np.argsort(x, axis=0)
array([[0, 1],
[1, 0]], dtype=int64)
>>> x[_,np.broadcast_to(np.arange(x.shape[1]),x.shape)] #全坐标索引构造二维数组沿0轴升序排列的结果
array([[0, 2],
[2, 3]])
>>> np.argsort(x, axis=1)
array([[0, 1],
[0, 1]], dtype=int64)
>>> x[np.broadcast_to(np.arange(x.shape[0])[:,None],x.shape),_] #全坐标索引构造二维数组沿1轴升序排列的结果
array([[0, 3],
[2, 2]])
>>> np.argsort(x, axis=None)
array([0, 2, 3, 1], dtype=int64)
>>> x.flatten()[_]
array([0, 2, 2, 3])
>>> pos = np.unravel_index(np.argsort(x, axis=None), x.shape)
>>> x[pos] #全坐标索引
array([0, 2, 2, 3])
>>> x = np.array([(1, 0), (0, 1)], dtype=[('x', '<i4'), ('y', '<i4')])
>>> np.argsort(x, order=('x','y'))
array([1, 0], dtype=int64)
>>> np.argsort(x, order=('y','x'))
array([0, 1], dtype=int64)
上面使用了一个叫做unravel_index(indices, dims, order='C')的函数,是干嘛用的?将一个多维数组按C或F风格(order参数)“展平”为一维数组后,可以通过取值范围为[0,len(arr.flat)-1]的下标i来索引任意元素,那么现在的下标索引i(指定参数indices,其也可以是一个整数列表)所指向的数组元素在原数组(指定形状参数dims)中的位置坐标又是几何?函数np.unravel_index()就是为了解决这个问题的,看个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a=np.arange(6).reshape(2,3)
>>> a
array([[0, 1, 2],
[3, 4, 5]])
>>> a.flatten(order='C') #按C风格展平,其中第1、3、5个元素在原始数组中的坐标为:np.unravel_index([1,3,5],(2,3))
array([0, 1, 2, 3, 4, 5])
>>> a.flatten(order='F') #按F风格展平,其中第1、3、5个元素在原始数组中的坐标为:np.unravel_index([1,3,5],(2,3),order='F')
array([0, 3, 1, 4, 2, 5])
>>> np.unravel_index(3,(2,3))
(1, 0)
>>> np.unravel_index([1,3,5],(2,3))
(array([0, 1, 1], dtype=int64), array([1, 0, 2], dtype=int64))
>>> np.unravel_index([1,3,5],(2,3),order='F')
(array([1, 1, 1], dtype=int64), array([0, 1, 2], dtype=int64))
上面列举了一维数组和二维数组的情况下如何根据argsort()的结果构造sort()的结果,下面再看看三维数组的情况:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
>>> x=np.random.randint(0,100,24).reshape(2,3,4)
>>> np.sort(x,axis=-1) #等同于np.sort(x,axis=2)
array([[[39, 57, 60, 99],
[26, 35, 38, 85],
[ 8, 12, 35, 85]],

[[30, 66, 86, 88],
[21, 49, 66, 80],
[ 3, 45, 47, 70]]])
>>> np.argsort(x,axis=-1)
array([[[3, 2, 0, 1],
[3, 2, 1, 0],
[0, 2, 1, 3]],

[[2, 3, 1, 0],
[3, 0, 2, 1],
[0, 3, 2, 1]]], dtype=int64)
>>> x[np.broadcast_to(np.arange(2)[:,None,None],x.shape),np.broadcast_to(np.arange(3)[None,:,None],x.shape),_] #全坐标索引构造三维数组沿2轴升序排列的结果
array([[[39, 57, 60, 99],
[26, 35, 38, 85],
[ 8, 12, 35, 85]],

[[30, 66, 86, 88],
[21, 49, 66, 80],
[ 3, 45, 47, 70]]])
>>> np.sort(x,axis=1)
array([[[ 8, 35, 12, 26],
[60, 38, 35, 39],
[85, 99, 57, 85]],

[[ 3, 70, 30, 21],
[49, 80, 47, 45],
[88, 86, 66, 66]]])
>>> np.argsort(x,axis=1)
array([[[2, 2, 2, 1],
[0, 1, 1, 0],
[1, 0, 0, 2]],

[[2, 2, 0, 1],
[1, 1, 2, 2],
[0, 0, 1, 0]]], dtype=int64)
>>> x[np.broadcast_to(np.arange(2)[:,None,None],x.shape),_,np.broadcast_to(np.arange(4)[None,None,:],x.shape)] #全坐标索引构造三维数组沿1轴升序排列的结果
array([[[ 8, 35, 12, 26],
[60, 38, 35, 39],
[85, 99, 57, 85]],

[[ 3, 70, 30, 21],
[49, 80, 47, 45],
[88, 86, 66, 66]]])
>>> np.sort(x,axis=0)
array([[[60, 86, 30, 39],
[49, 38, 35, 21],
[ 3, 35, 12, 45]],

[[88, 99, 57, 66],
[85, 80, 66, 26],
[ 8, 70, 47, 85]]])
>>> np.argsort(x,axis=0)
array([[[0, 1, 1, 0],
[1, 0, 0, 1],
[1, 0, 0, 1]],

[[1, 0, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 0]]], dtype=int64)
>>> x[_,np.broadcast_to(np.arange(3)[None,:,None],x.shape),np.broadcast_to(np.arange(4)[None,None,:],x.shape)] #全坐标索引构造三维数组沿0轴升序排列的结果
array([[[60, 86, 30, 39],
[49, 38, 35, 21],
[ 3, 35, 12, 45]],

[[88, 99, 57, 66],
[85, 80, 66, 26],
[ 8, 70, 47, 85]]])
上述写法还是太麻烦,可以借助np.meshgrid()简化坐标分量的构造(试问,还有更好更简便的写法吗?):
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
>>> y,x,z=np.meshgrid(range(3),range(2),range(4)) #三个坐标分量,要通过meshgrid()构造任意形状多维数组的坐标分量,譬如shape=(s0,s1,s2,...,sn),则有axis1,axis0,axis2,...,axisn=meshgrid(range(s1),range(s0),range(s3),...,range(sn)),即第0轴和第1轴比较特殊,是顺序反过来的
>>> np.sort(a,axis=-1)
array([[[33, 71, 90, 90],
[41, 51, 56, 71],
[23, 66, 70, 87]],

[[32, 41, 62, 86],
[48, 71, 75, 80],
[54, 73, 83, 92]]])
>>> np.argsort(a,axis=-1)
array([[[2, 0, 1, 3],
[2, 3, 1, 0],
[2, 0, 1, 3]],

[[3, 1, 2, 0],
[0, 2, 1, 3],
[2, 1, 3, 0]]], dtype=int64)
>>> a[x,y,_]
array([[[33, 71, 90, 90],
[41, 51, 56, 71],
[23, 66, 70, 87]],

[[32, 41, 62, 86],
[48, 71, 75, 80],
[54, 73, 83, 92]]])
>>> np.sort(a,axis=1)
array([[[66, 56, 23, 51],
[71, 70, 33, 87],
[71, 90, 41, 90]],

[[48, 41, 54, 32],
[86, 73, 62, 80],
[92, 75, 71, 83]]])
>>> np.argsort(a,axis=1)
array([[[2, 1, 2, 1],
[0, 2, 0, 2],
[1, 0, 1, 0]],

[[1, 0, 2, 0],
[0, 2, 0, 1],
[2, 1, 1, 2]]], dtype=int64)
>>> a[x,_,z]
array([[[66, 56, 23, 51],
[71, 70, 33, 87],
[71, 90, 41, 90]],

[[48, 41, 54, 32],
[86, 73, 62, 80],
[92, 75, 71, 83]]])
>>> np.sort(a,axis=0)
array([[[71, 41, 33, 32],
[48, 56, 41, 51],
[66, 70, 23, 83]],

[[86, 90, 62, 90],
[71, 75, 71, 80],
[92, 73, 54, 87]]])
>>> np.argsort(a,axis=0)
array([[[0, 1, 0, 1],
[1, 0, 0, 0],
[0, 0, 0, 1]],

[[1, 0, 1, 0],
[0, 1, 1, 1],
[1, 1, 1, 0]]], dtype=int64)
>>> a[_,y,z]
array([[[71, 41, 33, 32],
[48, 56, 41, 51],
[66, 70, 23, 83]],

[[86, 90, 62, 90],
[71, 75, 71, 80],
[92, 73, 54, 87]]])
np.msort()

np.msort(a)
函数说明:
用于沿数组a的第0轴进行排序,相当于np.sort(a, axis=0)

1
2
3
4
5
6
7
8
9
>>> a=np.random.randint(0,24,12).reshape(3,-1)
>>> a
array([[15, 8, 23, 12],
[14, 1, 17, 13],
[ 5, 4, 14, 8]])
>>> np.msort(a)
array([[ 5, 1, 14, 8],
[14, 4, 17, 12],
[15, 8, 23, 13]])
np.lexsort()

np.lexsort(keys, axis=-1)
函数说明:
参数keys是一个元组(越靠后优先级越高),包含多个(至少两个)排序键(一维序列),lexsort()将会执行多重排序,其实就相当于sorted(zip(keys[::-1]),key=lambda x:x),只不过lexsort()返回的是排序后的下标索引列表

看个例子,现有一个学生姓名列表,另外还有这些学生对应的语文成绩列表、数学成绩列表、英语成绩列表以及语数英总分列表,要对学生进行优劣排序,规则:首先按照总分降序排序,当总分相同时按照语文成绩降序排序,如果又出现相同,再依次按照数学、英语成绩降序排序,根据这些成绩排序结果,最终将得到学生的优劣先后,代码如下:
1
2
3
4
5
6
7
8
9
10
11
>>> names=['daiyang','daixiaodong','zhouqifei','dongjiazhou','liushaoliang','zhouzifeng']
>>> totals=[337,333,345,345,337,333]
>>> chinese=[103,90,108,108,103,86]
>>> math=[156,162,155,135,156,160]
>>> english=[78,81,82,102,78,87]
>>> inds=np.lexsort((english,math,chinese,totals))
>>> inds
array([5, 1, 0, 4, 3, 2], dtype=int64)
>>> np.array(names)[inds] #花式索引,这里其实是升序排列
array(['zhouzifeng', 'daixiaodong', 'daiyang', 'liushaoliang',
'dongjiazhou', 'zhouqifei'], dtype='<U12')
也可以自定义结构化数组,并利用`np.argsort()`实现:
1
2
3
4
5
6
7
8
9
10
t=list2structarray(list(zip(totals,chinese,math,english)),dtype='i4,i4,i4,i4')
inds=np.argsort(t)[::-1] #按降序排列的下标索引
print(inds)
print(np.array(names)[inds])

'''OUTPUT
[2 3 4 0 1 5]
['zhouqifei' 'dongjiazhou' 'liushaoliang' 'daiyang' 'daixiaodong'
'zhouzifeng']
'''
np.sort_complex()

np.sort_complex(a)
函数说明:
对复数数组按照先实部后虚部的顺序进行排序,注意是沿最后一维进行排序

1
2
3
4
5
6
7
8
9
10
11
>>> a = np.sort_complex([1 + 2j, 2 - 1j, 3 - 2j, 3 - 3j, 3 + 5j])
>>> np.random.shuffle(a)
>>> a
array([3.-2.j, 2.-1.j, 3.+5.j, 6.+2.j, 6.+1.j, 3.-3.j])
>>> b=a.reshape(2,-1)
>>> b
array([[3.-2.j, 2.-1.j, 3.+5.j],
[6.+2.j, 6.+1.j, 3.-3.j]])
>>> np.sort_complex(b)
array([[2.-1.j, 3.-2.j, 3.+5.j],
[3.-3.j, 6.+1.j, 6.+2.j]])

np.max(a, axis=None, out=None, keepdims=False)
函数说明:
该操作返回数组的最大值(axis=None的默认行为)或者沿轴的最大值(结果数组的维数可能会减1,以(m,n)形二维数组为例,若沿0轴获取最大值,即获取数组中每个列向量中的最大值,所得结果数组形状将变为(n,),除非keepdims设为True,此时结果数组维数将变为(1,n),仍为二维),另外也可以直接在ndarray对象上调用该方法。out参数用于存放结果数组,keepdims参数表示是否维持数组的维数不变。实际上,axis还可以是一个元组,以(m,n,q)形三维数组为例说明,若沿着axis=(0,1)获取最大值,即获取数组中(沿2轴的)每个“面”中的最大值,所得结果数组形状将变为(q,),除非keepdimsTrue,此时结果数组维数将变为(1,1,q),仍保持三维。最后提醒一下,np.max()还可以写成np.amax(),两个函数就是一回事

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
30
31
32
33
34
35
36
>>> a=np.random.randint(0,24,12).reshape(3,-1)
>>> a
array([[12, 11, 6, 21],
[12, 17, 5, 16],
[10, 14, 13, 17]])
>>> np.max(a)
21
>>> a.max()
21
>>> np.max(a,axis=0)
array([12, 17, 13, 21])
>>> np.max(a,axis=1,keepdims=True)
array([[21],
[17],
[17]])
>>> a=np.random.randint(0,24,24).reshape(2,3,4)
>>> a
array([[[ 7, 2, 0, 19],
[20, 22, 17, 23],
[11, 23, 14, 14]],

[[11, 16, 16, 12],
[13, 9, 17, 0],
[ 8, 5, 15, 18]]])
>>> np.max(a,axis=(0,1))
array([20, 23, 17, 23])
>>> np.max(a,axis=(0,1),keepdims=True)
array([[[20, 23, 17, 23]]])
>>> np.max(a[:,:,0]) #沿2轴的首个“面”
20
>>> np.max(a[:,:,1])
23
>>> np.max(a[:,:,2])
17
>>> np.max(a[:,:,3]) #沿2轴的最后一个“面”
23
np.argmax()

np.argmax(a, axis=None, out=None)
函数说明:
返回数组或沿轴最大值的下标索引,参数见np.max(),只要说的是,当axisNone时(默认值),表示先将a展平,然后在一维化的数组a中寻找最大值并返回此时的下标,另外也可以直接在ndarray对象上调用该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a=np.array([[30,40,70],[80,20,10],[50,90,60],[12,56,3]])
>>> a.argmax()
7
>>> a.flat[_]
90
>>> a.max()
90
>>> a.argmax(axis=0)
array([1, 2, 0], dtype=int64)
>>> a[_,np.arange(a.shape[1])] #全坐标索引
array([80, 90, 70])
>>> a.max(axis=0)
array([80, 90, 70])
np.maximum()

np.maximum(arr1, arr2, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature, extobj])
函数说明:
用于比较两个数组对象(arr1arr2这两个数组必须是同形状或者可以广播至同一形状)并返回一个新数组,其中存储了两个数组对应位置上的较大值。其余参数暂时先不管了

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
30
31
32
33
34
35
36
37
38
39
40
>>> a=np.random.randint(20,size=(2,3,4))
>>> b=np.random.randint(20,size=(2,3,4))
>>> a
array([[[ 8, 12, 1, 16],
[ 6, 17, 5, 2],
[ 8, 9, 18, 11]],

[[15, 15, 12, 1],
[ 8, 4, 2, 3],
[16, 12, 6, 0]]])
>>> b
array([[[18, 12, 2, 18],
[ 7, 16, 7, 2],
[ 2, 3, 11, 2]],

[[ 6, 17, 5, 17],
[ 3, 4, 14, 0],
[ 3, 15, 2, 11]]])
>>> s=np.empty_like(a)
>>> np.maximum(a,b,out=s)
array([[[18, 12, 2, 18],
[ 7, 17, 7, 2],
[ 8, 9, 18, 11]],

[[15, 17, 12, 17],
[ 8, 4, 14, 3],
[16, 15, 6, 11]]])
>>> s
array([[[18, 12, 2, 18],
[ 7, 17, 7, 2],
[ 8, 9, 18, 11]],

[[15, 17, 12, 17],
[ 8, 4, 14, 3],
[16, 15, 6, 11]]])
>>> a=np.eye(2)
>>> b=[0.5, 2]
>>> np.maximum(a,b) #b形状为(2,),而a形状为(2,2),因此b发生广播,首先补齐维度变为(1,2),然后沿0轴复制元素进一步补齐维数,于是b相当于[[0.5,2],[0.5,2]],可通过np.broadcast_to(b,(2,2))验证
array([[1. , 2. ],
[0.5, 2. ]])
np.nanmax()

np.nanmax(a, axis=None, out=None, keepdims=False)
函数说明:
只要数组中有一个元素为NaN(通常是无意义的异常值),则相应的np.max()比较得到的最大值也将为NaN,若要忽略NaN值,则使用np.nanmax()代替np.max(),该函数具体的用法参照np.max()即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a=np.random.randint(0,24,12).reshape(3,-1).astype('float')
>>> a[1,2]=np.nan
>>> a[2,0]=np.nan
>>> a
array([[ 9., 19., 5., 23.],
[ 6., 3., nan, 13.],
[nan, 15., 17., 2.]])
>>> np.max(a)
nan
>>> np.max(a,axis=1)
array([23., nan, nan])
>>> np.nanmax(a)
23.0
>>> np.nanmax(a,axis=1)
array([23., 13., 17.])

np.min(axis=None, out=None, keepdims=False)
函数说明:
等同于函数np.amin(),具体参见np.max(),只不过一个求最大一个求最小而已

np.argmin()

np.argmin(a, axis=None, out=None)
函数说明:
具体参见np.argmax()

np.minimum()

np.numpy.minimum(x1, x2, /, out=None, *, where=True, casting='same_kind', order='K', dtype=None, subok=True[, signature, extobj]) = <ufunc 'minimum'>
函数说明:
具体参见np.maximum()

np.nanmin()

np.nanmin(a, axis=None, out=None, keepdims=False)
函数说明:
具体参见np.nanmax()

np.sum(a, axis=None, dtype=None, out=None, keepdims=False)
函数说明:
求和运算,类似于np.max()np.min(),都是“聚合”操作,参数含义也都一样,就不多说了。可以直接在ndarray对象上调用该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a=np.arange(24).reshape(2,3,4)
>>> a.sum()
276
>>> a.sum(axis=0)
array([[12, 14, 16, 18],
[20, 22, 24, 26],
[28, 30, 32, 34]])
>>> a.sum(axis=(1,2))
array([ 66, 210])
>>> a[0].sum()
66
>>> a[1].sum()
210
np.cumsum()

np.cumsum(a, axis=None, dtype=None, out=None)
函数说明:
计算数组或沿轴元素的累加,当axisNone时,会先展平一维化数组,然后再计算累加,所得也会是一个一维数组。可以直接在ndarray对象上调用该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a.flatten() #继续上面的代码
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])
>>> a.cumsum()
array([ 0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78,
91, 105, 120, 136, 153, 171, 190, 210, 231, 253, 276], dtype=int32)
>>> a.cumsum(axis=2)
array([[[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]],

[[12, 25, 39, 54],
[16, 33, 51, 70],
[20, 41, 63, 86]]], dtype=int32)

np.cumprod(a, axis=None, dtype=None, out=None)
函数说明:
计算数组或沿轴元素的累乘,当axisNone时,会先展平一维化数组,然后再计算累乘,所得也会是一个一维数组。可以直接在ndarray对象上调用该方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> a=np.random.randint(0,5,12).reshape(3,4)
>>> a
array([[3, 1, 1, 0],
[3, 3, 3, 1],
[1, 3, 0, 4]])
>>> a.flatten()
array([3, 1, 1, 0, 3, 3, 3, 1, 1, 3, 0, 4])
>>> np.cumprod(a)
array([3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int32)
>>> np.cumprod(a,axis=0)
array([[3, 1, 1, 0],
[9, 3, 3, 0],
[9, 9, 0, 0]], dtype=int32)
>>> np.cumprod(a,axis=1)
array([[ 3, 3, 3, 0],
[ 3, 9, 27, 27],
[ 1, 3, 0, 0]], dtype=int32)

np.nonzero(a)
函数说明:
用于返回数组a中非零元素的下标索引(按坐标分量返回),根据此下标索引可以获取所有非零元素并以一维数组返回,若要直接返回数组中的非零元素,直接a[a!=0]。该函数常用于查找条件为真的数组元素索引,譬如,给定数组a,条件a>3返回一个布尔数组,由于False就是整数0(有False==0成立),所以np.nonzero(a>3)将返回符合条件的数组元素的下标索引。另外也可以直接在ndarray对象上调用nonzero()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a=np.random.randint(0,24,12).reshape(3,-1)
>>> a
array([[20, 12, 1, 0],
[11, 3, 13, 4],
[ 4, 11, 22, 21]])
>>> np.nonzero(_)
(array([0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2], dtype=int64), array([0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3], dtype=int64))
>>> a[_] #全坐标索引获取非零元素
array([20, 12, 1, 11, 3, 13, 4, 4, 11, 22, 21])
>>> a[a!=0] #布尔索引获取非零元组
array([20, 12, 1, 11, 3, 13, 4, 4, 11, 22, 21])
>>> a[np.nonzero(a>3)] #等同于:a[a>3]
array([20, 12, 11, 13, 4, 4, 11, 22, 21])

np.where(condition, [x, y])
函数说明:
分两种情况(其中condition将返回一个布尔数组):

  1. np.where(condition):此时等同于condition.nonzero()
  2. np.where(condition,x,y):首先创建一个空的结果数组,形状同condition,如果condition某个位置的元素为True,表示符合条件,则从x中对应位置处取出元素并放到结果数组中的相应位置,如果condition某个位置的元素为False,表示不符合条件,则从y中对应位置处取出元素并放到结果数组中的相应位置。并不要求xycondition的形状完全一致,但是其应能广播至同一形状
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> np.where([[0, 1],[1, 0]])
(array([0, 1], dtype=int64), array([1, 0], dtype=int64))
>>> np.array([[0, 1],[1, 0]]).nonzero()
(array([0, 1], dtype=int64), array([1, 0], dtype=int64))
>>> np.where([[True, False],
... [True, True]],
... [[1, 2],
... [3, 4]],
... [[9, 8],
... [7, 6]])
array([[1, 8],
[3, 4]])
>>> a=np.array([1,5,2,7])
>>> x=[[11,12,13,14],[15,16,17,18]]
>>> y=[-1]
>>> ret=np.where(a>3,x,y) #这里发生了广播
>>> ret
array([[-1, 12, -1, 14],
[-1, 16, -1, 18]])
>>> a = np.arange(9.).reshape(3, 3)
>>> np.where(a < 5, a, -1) #大于等于5的元素被置为-1,不过最简单的做法还是:a[a>=5]=-1
array([[ 0., 1., 2.],
[ 3., 4., -1.],
[-1., -1., -1.]])

np.extract(condition, arr)
函数说明:
用于从数组a中提取满足条件的元素。这不就是a[condition]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> arr = np.arange(12).reshape((3, 4))
>>> arr
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> condition = np.mod(arr, 3)==0
>>> condition
array([[ True, False, False, True],
[False, False, True, False],
[False, True, False, False]])
>>> np.extract(condition, arr)
array([0, 3, 6, 9])
>>> arr[condition]
array([0, 3, 6, 9])

np.place(arr, mask, vals)
函数说明:
将数组arr中满足条件mask(掩码数组)的元素用数据vals进行替换(原址操作)。这不就是arr[mask]=vals么。注意vals参数是一个一维数组或一维序列,指定用于替换的元素,设其长度为N,而符合条件的元素个数记为M,若N>M,则将只使用vals序列的前M个元素用于替换,若N<=M,则将循环重复使用这个序列的元素,譬如vals=[1,2,3],且符合条件的元素有5个,则用于替换的元素为:[1,2,3,1,2]

1
2
3
4
5
6
7
8
9
10
>>> arr = np.arange(12).reshape(3, 4)
>>> arr
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> np.place(arr, arr>2, [66, 88]) #相当于arr[arr>2]=[66,88],不过此句实际会报错,因为等号两边替换的元素和符合条件的元素数量不一致
>>> arr
array([[ 0, 1, 2, 66],
[88, 66, 88, 66],
[88, 66, 88, 66]])

np.partition(a, kth, axis=-1, kind='introselect', order=None)
函数说明:
指定一个或多个数,对数组进行分区。这玩意儿常用来求序列的第k项最大(小)值,特别是前几项最大(小)值,效率较高,因为它不对分区元素排序
参数说明:

  • kth:可以是单个整数,也可以是多个整数的序列,单个整数值时,kth表示第k个元素,k是指按升序排序后的数组(记作s,参数axis决定了具体沿哪个轴进行排序)的下标索引,axis所在轴长也就是kth参数的合法取值(实际取值区间为[-len(arr),len(arr)-1],负数索引譬如-1表示的是最后一个元素的索引,这里姑且假设axis取0,于是0轴长度就是len(arr)),函数的功能是将原数组中小于s[k]的元素放到(s[k]的)左边区域,将大于或等于s[k]的元素放到(s[k]的)右边区域,但分区区域中元素并无顺序。显而易见的是,由于第k个元素指的是排序后的数组(s)的第k个元素,因此s本身就是一个符合条件的函数返回值,因为s[k]左边的元素的确都比s[k]小,s[k]右边的元素的确都比s[k]大或等啊。当kth参数是多值序列时(设为[k0,k1,,k2,...]),序列中的每一个整数都要在“合法取值”内,只需记住:作为函数返回值的数组的第k0k1k2、…个元素就是按升序排列后的数组的第k0k1k2、…个元素,将这些元素视为一个一个的分界点,左边的数总是小于它,右边的数总是大于等于它
  • axis:指定要排序的轴,缺省为-1,表示沿最后一根轴排序,若指定为None,则首先将数组“展平”为一维,然后再排序,同np.sort()
  • order:非内存布局,而是用于自定义结构化数据类型时,指定字段排序优先级,仍同np.sort()
kth为整数时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> a=[3,4,5,2,1] #这里有五个数,使用np.sort()按升序排序得到:[1,2,3,4,5],记作s
>>> np.partition(a,0) #以s的第0个元素为分界点,比它小的在前面,比它大的或相等的在后面,前后区域中的元素未排序
array([1, 4, 5, 2, 3])
>>> np.partition(a,-5)
array([1, 4, 5, 2, 3])
>>> np.partition(a,1) #以s的第1个元素为分界点,比它小的在前面,比它大的或相等的在后面,前后区域中的元素未排序
array([1, 2, 5, 4, 3])
>>> np.partition(a,-4)
array([1, 2, 5, 4, 3])
>>> np.partition(a,2) #以s的第2个元素为分界点,比它小的在前面,比它大的或相等的在后面,前后区域中的元素未排序
array([1, 2, 3, 4, 5])
>>> np.partition(a,-3)
array([1, 2, 3, 4, 5])
>>> np.partition(a,3) #以s的第3个元素为分界点,比它小的在前面,比它大的或相等的在后面,前后区域中的元素未排序
array([2, 1, 3, 4, 5])
>>> np.partition(a,-2)
array([2, 1, 3, 4, 5])
>>> np.partition(a,4) #以s的第4个元素为分界点,比它小的在前面,比它大的或相等的在后面,前后区域中的元素未排序
array([2, 1, 3, 4, 5])
>>> np.partition(a,-1)
array([2, 1, 3, 4, 5])
kth为多个整数序列时:
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
a=[2,6,10,1,5,2,4,8,1,9,7,7,13]
print('kth参数是多值序列时:如[3,5,8],函数返回的数组的第3个元素(从0开始计数,第0个元素...)、第5个元素、第8个元素分别是原数组按升序排列后的第3、5、8个元素:')
b=np.frombuffer(b' '*len(a),dtype='S1')
b=b.copy() # 通过frombuffer()得到的将是一个只允许“读”模式的数组对象,op_flags参数无法开启“写”模式,只好拷贝一份副本而丢弃原数组对象
it=np.nditer(b,op_flags=['readwrite'])
while not it.finished:
if it.iterindex in [3,5,8]:
it[0]=b'|'
#print(it[0])
it.iternext()
print(' 分区 界点 分区 界点 分区 界点 分区')
print(' ',end='')
for i in np.nditer(b):
print(str(i, encoding='utf-8'),end=' ')
print()
print(np.sort(a),end=' -- sort(a)\n')
print(np.partition(a,(3,5,8)),end=' -- partition(a,[3,5,8])\n')

'''OUTPUT
kth参数是多值序列时:如[3,5,8],函数返回的数组的第3个元素(从0开始计数,第0个元素...)、第5个元素、第8个元素分别是原数组按升序排列后的第3、5、8个元素:
分区 界点 分区 界点 分区 界点 分区
| | |
[ 1 1 2 2 4 5 6 7 7 8 9 10 13] -- sort(a)
[ 1 1 2 2 4 5 6 7 7 9 8 10 13] -- partition(a,[3,5,8])
'''
该函数常用于求数组的前几个最大或最小值:
1
2
3
4
5
6
7
>>> a=[2,6,10,1,5,2,4,8,1,9,7,7,13]
>>> np.partition(a,[-1,-2,-3]) #求数组的前三大元素(由于默认是升序,因此是取最后三项,使用负数索引),另外当kth为多值序列时,其中的索引无需计较先后顺序
array([ 2, 2, 1, 1, 4, 6, 7, 5, 7, 8, 9, 10, 13])
>>> np.partition(a,[-2,-3,-1])
array([ 2, 2, 1, 1, 4, 6, 7, 5, 7, 8, 9, 10, 13])
>>> np.partition(a,[-1,-2,-3])[[-1,-2,-3]] #花式索引
array([13, 10, 9])
再看一个自定义结构化类型和多维情况下的例子:
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
30
31
32
33
34
35
36
37
38
39
40
41
>>> dtype = [('name', 'S10'), ('height', float), ('age', int)]
>>> values = [('Lancelot', 1.9, 38), ('Arthur', 1.8, 41), ('muggledy', 1.8, 22), ('Galahad', 1.7, 38)] #尝试将'Arthur'改成'rthur',看看结果会有什么不同(该实验将说明“未在order参数中出现的字段仍将影响排序结果”,对于order='height'来说,'height'字段出于最高优先级,其次是在dtype中先定义的'name'字段,最后是'age'字段)
>>> a = np.array(values, dtype=dtype)
>>> np.sort(a, order='height')
array([(b'Galahad', 1.7, 38), (b'Arthur', 1.8, 41),
(b'muggledy', 1.8, 22), (b'Lancelot', 1.9, 38)],
dtype=[('name', 'S10'), ('height', '<f8'), ('age', '<i4')])
>>> np.partition(a, kth=2, order='height')
array([(b'Galahad', 1.7, 38), (b'Arthur', 1.8, 41),
(b'muggledy', 1.8, 22), (b'Lancelot', 1.9, 38)],
dtype=[('name', 'S10'), ('height', '<f8'), ('age', '<i4')])
>>> np.sort(a, order=['age', 'height'])
array([(b'muggledy', 1.8, 22), (b'Galahad', 1.7, 38),
(b'Lancelot', 1.9, 38), (b'Arthur', 1.8, 41)],
dtype=[('name', 'S10'), ('height', '<f8'), ('age', '<i4')])
>>> np.partition(a, kth=2, order=['age', 'height'])
array([(b'muggledy', 1.8, 22), (b'Galahad', 1.7, 38),
(b'Lancelot', 1.9, 38), (b'Arthur', 1.8, 41)],
dtype=[('name', 'S10'), ('height', '<f8'), ('age', '<i4')])
>>> a=np.array([[3,7,1],[4,2,5],[2,3,2]])
>>> a
array([[3, 7, 1],
[4, 2, 5],
[2, 3, 2]])
>>> np.partition(a,kth=1,axis=0)
array([[2, 2, 1],
[3, 3, 2],
[4, 7, 5]])
>>> a=np.array([[[3,1],[6,5]],[[4,7],[2,3]]])
>>> a
array([[[3, 1],
[6, 5]],

[[4, 7],
[2, 3]]])
>>> np.partition(a,kth=1,axis=1)
array([[[3, 1],
[6, 5]],

[[2, 3],
[4, 7]]])
np.argpartition()

np.argpartition(a, kth, axis=-1, kind='introselect', order=None)
函数说明:
功能同np.partition(),只不过返回的是np.partition()结果数组中的元素在原数组a中的下标索引

1
2
3
4
5
6
7
8
9
>>> a=[2,6,10,1,5,2,4,8,1,9,7,7,13]
>>> s1=np.argpartition(a,5)
>>> s2=np.partition(a,5)
>>> s1
array([ 5, 0, 8, 3, 6, 4, 1, 10, 11, 9, 7, 2, 12], dtype=int64)
>>> s2
array([ 2, 2, 1, 1, 4, 5, 6, 7, 7, 9, 8, 10, 13])
>>> np.array(a)[s1]
array([ 2, 2, 1, 1, 4, 5, 6, 7, 7, 9, 8, 10, 13])

数学运算

np.dot(a, b, out=None)
函数说明:
矩阵乘法(可以在ndarray对象上直接调用该方法),等同于np.matmul(a,b)或使用@运算符:a @ b,注意区别于np.multiply()方法,这是用于两个数组之间的逐元素乘法操作,对应运算符*,即a * b

  • ab都是标量,表示数值间的普通乘法
  • ab都是一维时,表示计算这两个向量的内积
  • ab都是二维或其中一个允许是一维,表示矩阵乘法
  • aN维(N>2)而b是一维,则结果是ab的最后一个轴上元素的和积,因此需满足条件:ab最后一个轴的长度相同,等同于:reduce(lambda last,now:last+now[0]*now[1],zip(np.rollaxis(a,a.ndim-1),b),0),结果数组形状为a.shape[:-1]
  • aN维(N>2)而bM维(M>=2),则结果是a的最后一个轴和b的倒数第二个轴上元素的和积,需满足条件:a的最后一个轴长等于b的倒数第二个轴长,且有:dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m]),这条没看懂
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
30
31
32
33
34
35
>>> a=np.random.randint(0,10,3).reshape(3)
>>> b=np.random.randint(0,10,3).reshape(3)
>>> a
array([3, 4, 0])
>>> b
array([8, 9, 4])
>>> a.dot(b)
60
>>> c=np.arange(6).reshape(2,3)
>>> c.dot(a) #c形状为(2,3),a形状为(3,),允许这样,而并不一定要求a形状为(3,1)
array([ 4, 25])
>>> c.dot(a[:,None])
array([[ 4],
[25]])
>>> a=np.arange(24).reshape(3,4,2)
>>> a
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]]])
>>> b=np.array([1,2])
>>> a.dot(b)
array([[ 2, 8, 14, 20],
[26, 32, 38, 44],
[50, 56, 62, 68]])
np.out()

np.outer(a, b, out=None)
函数说明:
用于计算两个一维向量的外积,其尺寸各自随意,等同于a[:,None]@b[None,:]

1
2
3
4
5
6
7
8
9
10
>>> a=np.random.randint(0,10,3)
>>> a
array([4, 9, 3])
>>> b=np.random.randint(0,10,5)
>>> b
array([9, 2, 1, 8, 4])
>>> np.outer(a,b)
array([[36, 8, 4, 32, 16],
[81, 18, 9, 72, 36],
[27, 6, 3, 24, 12]])
np.inner()

np.inner(a, b)
函数说明:
用于计算两个一维向量的内积,其尺寸必须一致,等同于a[None,:]@b[:,None]

1
2
3
4
5
>>> a=np.random.randint(0,10,3)
>>> a
array([9, 6, 3])
>>> np.inner(a,a)
126

np.log(a)
函数说明:
逐元素的以e为底的对数计算,满足log(exp(x)) = x(以e为底exp(x)的对数就是x),还有np.log2()np.log10(),而基于其他底数的对数计算,通常可以采用换底公式转换为以10为底的对数(也就是ln)计算:logab=log10blog10a=lnblna\log_a b=\frac{\log_{10}b}{\log_{10}a}=\frac{\ln b}{\ln a}

1
2
3
4
5
6
>>> np.log(np.exp(12))
12.0
>>> np.log2(12)
3.584962500721156
>>> np.log10(12)/np.log10(2)
3.5849625007211565

np.sqrt(a)
函数说明:
逐元素计算平方根。要计算任意次方根,可以使用**运算符,如a**(1/2)a**(1/3),其等同于调用np.power()函数(或math库中的pow()),如np.power(a,1/2)np.power(a,1/3)

1
2
3
4
>>> np.sqrt([1,4,9])
array([1., 2., 3.])
>>> np.array([1,4,9])**(1/2)
array([1., 2., 3.])
立方根

np.cbrt(a)
函数说明:
逐元素计算立方根。不建议使用a**(1/3)方式,譬如当a是负数时将返回复数类型,a**(1/5)等也是如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> np.array([-1,-8,-27])**(1/3) #之所以返回nan,是因为np.array([-1,-8,-27])是int64整型,无法支持复数运算
array([nan, nan, nan])
>>> np.array([-1,-8,-27],dtype='complex')**(1/3)
array([0.5+0.8660254j , 1. +1.73205081j, 1.5+2.59807621j])
>>> np.power(np.array([-1,-8,-27],dtype="complex"),1/3)
array([0.5+0.8660254j , 1. +1.73205081j, 1.5+2.59807621j])
>>> (-27)**(1/3)
(1.5000000000000004+2.598076211353316j)
>>> -27**(1/3) #由于**运算符的优先级大于-,所以相当于-(27**(1/3)),因为有-(-a)^{1/3}=a^{1/3},a>0成立
-3.0
>>> np.cbrt(-27) #立方根(cube root)
-3.0
>>> import math
>>> x=-27
>>> math.pow(abs(x),float(1)/3) * (1,-1)[x<0] #参见https://stackoverflow.com/questions/1361740/cubic-root-of-the-negative-number-on-python
-3.0
>>> math.pow(-27,1/3) #参见https://vevurka.github.io/cs/python/cubic_root/
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: math domain error

np.mod(a,b)
函数说明:
逐元素的取模运算

1
2
3
4
5
6
7
>>> a=np.arange(8).reshape(2,4)
>>> a
array([[0, 1, 2, 3],
[4, 5, 6, 7]])
>>> np.mod(a,2)
array([[0, 1, 0, 1],
[0, 1, 0, 1]], dtype=int32)

np.poly1d(c_or_r, r=False, variable='x')
函数说明:
用于创建一个一元多项式函数,有两种模式,一是rFalse(默认),此时c_or_r指定多项式的系数,譬如c_or_r=[1,2,3],创建的多项式函数为f(x)=1x2+2x1+3x0f(x)=1x^2+2x^1+3x^0,二是rTrue,此时c_or_r指定多项式的根,仍以c_or_r=[1,2,3]为例,创建的多项式函数为f(x)=(x1)(x2)(x3)=x36x2+11x6f(x)=(x-1)(x-2)(x-3)= x^3 - 6x^2 + 11x -6,实际poly1d是一个类,若返回的类实例记作f=np.poly1d([1,2,3]),可以直接用()调用之(f()),返回多项式的具体值,variable参数指定未知变量字母,控制多项式函数的打印结果,默认为'x'f有一些属性,r属性返回多项式的根,c属性返回多项式的系数数组,order属性返回多项式的最高次方,f可以用[i]进行索引,获取xix^i的系数,f还有两个方法,deriv([m])表示求导,可选的参数m表示求几次导,默认求一次导,返回的仍是一个多项式函数,integ([m,k])表示求积分,可选的参数m表示积几次分,k表示积分后的常数项的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> f=np.poly1d([1,2,3])
>>> print(f)
2
1 x + 2 x + 3
>>> f.r
array([-1.+1.41421356j, -1.-1.41421356j])
>>> f(_)
array([4.4408921e-16+0.j, 4.4408921e-16+0.j])
>>> f.c
array([1, 2, 3])
>>> f[2]
1
>>> f.deriv()
poly1d([2, 2])
>>> print(_)
2 x + 2