『NumPy』入门概述

文章目录

1. ndarry对象

ndarry是一个多维数组对象,该对象由两部分组成:

  • 实际的数据
  • 描述这些数据的元数据

大部分的数组操作仅仅修改元数据部分,而不改变底层的实际数据

NumPy数组一般是同质的(但有一种特殊的数组类型例外,它是异质的),即数组中的所有元素类型必须是一致的。这样有一个好处:如果我们知道数组中的元素均为同一类型,该数组所需的存储空间就很容易确定下来。

1.1 元素类型

NumPy 支持的数据类型比 Python 内置的类型要多很多,基本上可以和 C 语言的数据类型对应上,其中部分类型对应为 Python 内置的类型。下表列举了常用 NumPy 基本类型。

名称
描述
bool用一位存储的布尔类型(值为True或False)
inti由所在平台决定其精度的整数(一般为int32或int64)
int8整数,范围为-128至127
int16整数,范围为-32768至32767
int32整数,范围为-231至231 -1
int64整数,范围为-263至263-1
uint8无符号整数,范围为0至255
uint16无符号整数,范围为0至65535
uint32无符号整数,范围为0至232-1
uint64无符号整数,范围为0至264-1
float16半精度浮点数(16位)
float32单精度浮点数(32位)
float64或float双精度浮点数(64位)
complex64复数,分别用两个32位浮点数表示实部和虚部
complex128或complex复数,分别用两个64位浮点数表示实部和虚部

数据类型均有对应的类型转换函数:

>>> import numpy as np
>>> np.float64(42)
42.0
>>> np.int8(42.8)
42
>>> np.bool(42)
True
>>> np.float(True)
1.0

1.2 创建数组

1.2.1 array()函数

从常规Python列表或元组创建数组。结果数组的类型是从序列中元素的类型推导出来的。

>>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int32')
>>> #####################################################
>>> b = np.array([1,2,3.5,5.1])
>>> b
array([1. , 2. , 3.5, 5.1])
>>> b.dtype
dtype('float64')
>>> #####################################################
>>> c = np.array([[1,2,3],[4,5,7],[8,9,10]])
>>> c
array([[ 1,  2,  3],
       [ 4,  5,  7],
       [ 8,  9, 10]])
>>> c.dtype
dtype('int32')

特别地,这种情况下,只会生成元素类型为object的一维数组

>>> d = np.array([[1,2],[2,3,4]])
>>> d
array([list([1, 2]), list([2, 3, 4])], dtype=object)

也可以显式指定元素类型:

>>> e = np.array([[1,2],[3,4]], dtype=complex)
>>> e
array([[1.+0.j, 2.+0.j],
       [3.+0.j, 4.+0.j]])

1.2.2 empty()函数

创建一个指定形状(shape)、数据类型(dtype)且未初始化的数组

>>> import numpy as np
>>> x = np.empty((3,2), dtype=int)
>>> x
array([[ 1892962892,       32766],
       [-1831694688,         568],
       [          0,           0]])

1.2.3 zeros()函数

创建数组,用零值填充,必选参数是shape,即每个维度的大小,是tuple类型

>>> import numpy as np
>>> a = np.zeros((3,4),dtype=int)
>>> a
array([[0, 0, 0, 0],
	   [0, 0, 0, 0],
	   [0, 0, 0, 0]])
>>> ######################################################
>>> b = np.zeros((2,3),dtype=bool)
>>> b
array([[False, False, False],
	   [False, False, False]])
>>> ######################################################
>>> c = np.zeros((2,3),dtype=str)
>>> c
array([['', '', ''],
	   ['', '', '']], dtype='<U1')

1.2.4 ones()函数

创建指定形状的数组,数组元素以 1 来填充:

>>> import numpy as np
>>> a = np.ones((5,6),dtype=int)
>>> a
array([[1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1, 1]])
>>> ######################################################
>>> b = np.ones((4,4),dtype=bool)
>>> b
array([[ True,  True,  True,  True],
       [ True,  True,  True,  True],
       [ True,  True,  True,  True],
       [ True,  True,  True,  True]])
>>> ######################################################
>>> c = np.ones((2,3),dtype=str)
>>> c
array([['1', '1', '1'],
       ['1', '1', '1']], dtype='<U1')

1.2.5 arange()函数

为了创建数字序列,NumPy提供了一个类似于range返回数组而不是列表的函数

>>> import numpy as np
>>> np.arange(10,30,5)
array([10, 15, 20, 25])
>>> np.arange(0,2,0.3) # the third argument step accepts float number
array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])

1.2.6 linspace()函数

linsapce()函数通过指定开始值、终值和元素个数来创建表示等差数列的一维数组,可以通过endpoint参数指定是否包含终值,默认为True,即包含终值。

>>> import numpy as np
>>> np.linspace( 0, 2, 9 ) #步长为1/8区间长
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  ])
>>> np.linspace(1,10,5,endpoint=False) #步长为1/5区间长
array([1. , 2.8, 4.6, 6.4, 8.2])

1.2.7 logspace()函数

logspace()和linspace()类似,不过它所创建的数组时等比数列。下面的例子产生从100到102、有5个元素的等比数列,注意,起始值0表示100,而终值2表示102

>>> import numpy as np
>>> np.logspace(0,2,5)
array([  1.        ,   3.16227766,  10.        ,  31.6227766 ,
       100.        ])

基数可以通过base参数指定,默认值为10
logspace也有endpoint参数,含义一样的

1.3 数组的属性

  • ndim
    数组的维度
  • shape
    数组的形状,是一个整数元组,依次是每个维度的大小
  • size
    数组中元素个数
  • dtype
    描述数组中元素类型的对象。可以使用标准Python类型创建或指定dtype。此外,NumPy还提供自己的类型。numpy.int32numpy.int16numpy.float64就是一些例子。
  • itemsize
    返回每个元素所占字节大小,相当于`ndarray.dtype.itemsize

1.4 一维数组存取元素

1.4.1 切片存取

可以用和列表相同的方式对数组元素进行存取

>>> import numpy as np
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a[5]
5
>>> a[3:5]
array([3, 4])
>>> a[:5]
array([0, 1, 2, 3, 4])
>>> a[:-1]
array([0, 1, 2, 3, 4, 5, 6, 7, 8])
>>> ####################################################
>>> a[1:-1:2]
array([1, 3, 5, 7])
>>> a[::-1]
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
>>> a[5:1:-2]
array([5, 3])
>>> ################下标还可以用来修改元素值################
>>> a[2:4]=100,101
>>> a
array([  0,   1, 100, 101,   4,   5,   6,   7,   8,   9])

通过切片获取的新的数组是原始数组的一个视图。它与原始数组共享一块数据存储空间。

注意与列表不同,列表的切片不能将其赋值给一个变量,因为列表的这一步是浅拷贝,只有直接利用a[2:3]这种形式在赋值号左边修改才会影响原来的数组。

l = [1, 2, 3, 4, 5]
l[2:3] = [333]
print(l)  # [1, 2, 333, 4, 5],只有这种方式才影响原始的列表

回到正题,数组的切片,打个比方,就相当于把切片区域放大了给你看,你也可以修改,但是实际上放大的只是虚像,我们修改的是在原像上。

>>> import numpy as np
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b = a[3:7]
>>> b[2] = -10
>>> a
array([  0,   1,   2,   3,   4, -10,   6,   7,   8,   9])

1.4.2 整数列表存取

当使用整数列表对数组进行存取时,将使用列表中的每个元素作为下标。

使用列表作为下标得到的数组不和原数组共享数据。

>>> import numpy as np
>>> x = np.arange(10,1,-1)
>>> x
array([10,  9,  8,  7,  6,  5,  4,  3,  2])
>>> x[[3,3,1,8]]
array([7, 7, 9, 2])
>>> x[[3,3,-3,8]]
array([7, 7, 4, 2])
>>> ##########下面代码证明用整数列表索引产生的并不是视图##########
>>> a = x[[1,3,5]]
>>> a
array([9, 7, 5])
>>> a[2] = -1
>>> a
array([ 9,  7, -1])
>>> x
array([10,  9,  8,  7,  6,  5,  4,  3,  2])
>>> ##################用整数列表存储数据######################
>>> x[[3,5,1]] = -1, -2, -3
>>> x
array([10, -3,  8, -1,  6, -2,  4,  3,  2])

1.4.3 整数数组存取

当使用整数数组作为数组下标时,将得到一个形状和下标数组相同的新数组,新数组的每个元素都是用下标数组对应位置的值作为下标从原数组获得的值。

当下标数组是一维数组时,结果和用列表作为下标的结果相同。

>>> import numpy as np
>>> x = np.arange(10,1,-1)
>>> x
array([10,  9,  8,  7,  6,  5,  4,  3,  2])
>>> x[np.array([3,3,1,8])]
array([7, 7, 9, 2])
>>> x[[3,3,1,8]]  # 显然一维数组两种存取方式是一个效果
array([7, 7, 9, 2])

当下标时多维数组时,得到的也是多维数组

>>> x[np.array([[3,3,1,8],[3,3,-3,8]])]
array([[7, 7, 9, 2],
       [7, 7, 4, 2]])

1.4.4 布尔存取

当使用布尔数组b作为下标存取数组x中的元素时,将获得数组x中与数组b中True对应的元素。

使用布尔数组作为下标获得的数组不和原始数组共享数据内存。

注意,布尔数组必须要和操作的数组shape一样,多或者少都会报错。

>>> import numpy as np
>>> x = np.arange(5,0,-1)
>>> x
array([5, 4, 3, 2, 1])
>>> # 注意,布尔数组和x必须shape相同,否则报错
>>> x[np.array([True,False,True,False,False])]
array([5, 3])
>>> # 如果传递的是布尔列表,NumPy1.10之后,会当成布尔数组,也必须满足shape相似
>>> x[[True,False,True,False,False]]
array([5, 3])

布尔数组一般不是手工产生的,而是使用布尔运算的ufunc函数产生,这个在后面有介绍。

下面举一个例子说明布尔数组下标用法:

>>> import numpy as np
>>> x = np.random.randint(0, 10, 6) # 产生一个长度为6,元素值为0~9的随机整数组
>>> x
array([8, 9, 5, 6, 2, 6])
>>> # 如果我们要收集表达式中所有大于5的数值,可以这么玩
>>> x[x>5]
array([8, 9, 6, 6])

1.5 多维数组存取元素

多维数组的存取和一维数组类似,因为多维数组有多个轴,所以它的下标需要用多个值来表示。

NumPy采用tuple作为数组的下标,元组中的每个元素和数组的每个轴对应。

为什么使用元组作为下标?

Python的下标语法(用[]存取序列中的元素)本身并不支持多维,但是可以使用任何对象作为下标,因此 NumPy 使用元组作为下标存取数组中的元素,使用元组可以很方便地表示多个轴的下标。

虽然在Python程序中经常使用圆括号将元组的元素括起来,但其实袁祖德语法只需要用逗号隔开元素即可,例如x, y = y, x就是用元组交换变量值的一个例子。因此a[1,2]a[(1,2)]完全相同,都是使用元组(1,2)作为数组a的下标。

>>> import numpy as np
>>> # 为什么行向量+列向量是二维数组,后面再说
>>> a = np.arange(0,60,10).reshape(-1,1) + np.arange(0,6)
>>> a
array([[ 0,  1,  2,  3,  4,  5],
       [10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25],
       [30, 31, 32, 33, 34, 35],
       [40, 41, 42, 43, 44, 45],
       [50, 51, 52, 53, 54, 55]])

1.5.1 存取视图

下面展示一些多维数组下标存取的用法。

>>> a[0, 3:5]
array([3, 4])
>>> a[4:,4:]
array([[44, 45],
       [54, 55]])
>>> a[:,2] # 这里我觉得奇怪,为啥不是竖着显示,明明切片出来的列向量,可能一维数组都这么显示
array([ 2, 12, 22, 32, 42, 52])
>>> a[2::2,::2]
array([[20, 22, 24],
       [40, 42, 44]])

如果下标元组只包含整数和切片,那么得到的数组和原始数据共享数据,它是原数组的视图

下面的例子中,数组b时a的视图,它们共享数据,因此修改b[0]时,数组a中对应的元素也被修改:

>>> b = a[0,3:5]
>>> b
array([3, 4])
>>> b[0] = -b[0]
>>> a[0,3:5]
array([-3,  4])

因为数组下标是一个元组,使用我们可以 将下标元组保存起来,用同一个元组存取多个数组。在下面的例子中,a[idx]a[::2,2:]相同,a[idx][idx]a[::2,2:][::2,2:]相同

>>> idx = slice(None,None,2), slice(2,None)
>>> a[idx]
array([[ 2, -3,  4,  5],
       [22, 23, 24, 25],
       [42, 43, 44, 45]])
>>> a[idx][idx]
array([[ 4,  5],
       [44, 45]])

切片(slice)对象

像上面那样创建slice镀锡对象有点麻烦,NumPy提供了一个s_对象来帮助我们创建数组下标:

>>> np.s_[::2,2:]
(slice(None, None, 2), slice(2, None, None))

1.5.2 存取副本

在多维数组的下标元组中,也可以使用整数元组或列表、整数数组和布尔数组,当下标中使用这些对象时,所获得的数据是原数据的副本(即使部分使用切片也是这样),因此修改结果数组不会改变原始数组。

>>> a[(0,1,2,3),(1,2,3,4)] # 这里注意一下,可能和想象中的结果不同
array([ 1, 12, 23, 34]) # 得到的结果为a[0,1]、a[1,2]、a[2,3]、a[3,4]
>>> a[3:, [0,2,5]]
array([[30, 32, 35],
       [40, 42, 45],
       [50, 52, 55]])
>>> mask = np.array([1,0,1,0,0,1], dtype=np.bool)
>>> # 在a[mask,2]中,第0轴的下标是一个布尔数组,他选取第0、第2和第5行
>>> # 第1轴的下标是一个整数,他选取第2列
>>> a[mask,2]
array([ 2, 22, 52])
>>> mask2 = [True,False,True,False,False,True]
>>> a[mask2,2] # 这里和一维一样,NumPy1.10之后布尔列表和布尔数组返回一样了
array([ 2, 22, 52])

当下标长度小于数组的维数时,剩余各轴对应的下标时":",即选取他们所有的数据:

>>> a[[1,2],:]
array([[10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25]])
>>> a[[1,2]]
array([[10, 11, 12, 13, 14, 15],
       [20, 21, 22, 23, 24, 25]])

1.5.3 多维数组作下标存取

当所有轴都用形状相同的整数数组作为下标时,得到的数组和下标数组形状相同:

>>> x = np.array([[0,1],[2,3]])
>>> x
array([[0, 1],
       [2, 3]])
>>> y = np.array([[-1,-2],[-3,-4]])
>>> y
array([[-1, -2],
       [-3, -4]])
>>> a[x,y]
array([[ 5, 14],
       [23, 32]])

效果等同于下面的程序:

>>> a[(0,1,2,3),(-1,-2,-3,-4)].reshape(2,2)
array([[ 5, 14],
       [23, 32]])

当不指定第1轴时,默认是":",得到的是一个三维数组:

>>> x
array([[0, 1],
       [2, 3]])
>>> a[x]
array([[[   0,    1, -123,   -3,    4,    5],
        [  10,   11,   12,   13,   14,   15]],

       [[  20,   21,   22,   23,   24,   25],
        [  30,   31,   32,   33,   34,   35]]])

1.6 结构数组

在C语言中我们可以通过struct关键字定义结构类型,结构中的字段占据连续的内存空间。类型相同的两个结构所占用的内存大小相同,因此很容易定义结构数组。和C语言一样,在NumPy中叶很容易对这种结构数组进行操作。只有NumPy中的结构定义和C语言中的结构定义相同,就可以很方便地读取C语言的结构数组的二进制数据,将其转换为NumPy的结构数组。

假设我们需要定义一个结构数组,它的每个元素都有name、age和weight字段。在NumPy中可以如下定义:

>>> import numpy as np
>>> persontype = np.dtype({
	'names':['name','age','weight'],
	'formats':['S30','i','f']},align=True)
>>> a = np.array([("zhang",32,75.5),("Wang",24,65.2)],dtype=persontype)
>>> a.dtype
dtype({'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets':[0,32,36], 'itemsize':40}, align=True)

我们先创建一个dtype对象persontype,它的参数是一个描述结构类型的各个字段的字典。字典有两个键:‘names’和’formats’。每个键对应的值都是一个列表。'names’定义结构中每个字段的名称,而’formats’则定义每个字段的类型。

还可以这样描述结构类型:

>>> import numpy as np
>>> p = np.dtype([('name','|S30'),('age','<i4'),('weight','<f4')])
>>> p
dtype([('name', 'S30'), ('age', '<i4'), ('weight', '<f4')])
>>> a = np.array([("zhang",32,75.5),("Wang",24,65.2)],dtype=p)

其中,形如"(字段名,类型描述)“的元组描述了结构中的每个字段。类型字符串前缀的”|"、"<"、">"等字符表示字段值的字节顺序:

  • |:忽视字节顺序
  • <:低位字节在前,即小端模式
  • >:高位字节在前,即大端模式

结构数组的存取方式和一般数组相同,通过下标能够取得其中的元素:

>>> a[0]
(b'zhang', 32, 75.5)
>>> a[0].dtype
dtype({'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets':[0,32,36], 'itemsize':40}, align=True)

我们可以使用字段名作为下标获取对应的字段值:

>>> a[0]['name']
b'zhang'

a[0]是一个结构元素,它和数组a共享内存数据,可以通过修改a[0]来修改数组a中的对应元素字段:

>>> c=a[1]
>>> c['name']="Li"
>>> a[1]["name"]
b'Li'

还可以直接获得结构数组的字段,返回的是原式数组的视图:

>>> d = a["age"]
>>> d[0] = 40
>>> a[0]["age"]
40

通过a.tostring()a.tofile()方法,可以将数组a以二进制的方式转换成字符串或写入文件。

1.7 内存结构

以前面的数组为例:

>>> import numpy as np
>>> a = np.array([[0,1,2],[3,4,5],[6,7,8]],dtype=np.float32)
>>> a
array([[0., 1., 2.],
       [3., 4., 5.],
       [6., 7., 8.]], dtype=float32)
>>> a.ndim
2
>>> a.shape
(3, 3)
  • 数据存储区保存着数组中所有元素的二进制数据

  • dtype对象则知道如何将元素的二进制数据转换为可用值

  • strides属性保存每个轴上相邻两个元素的地址差,即当某个轴的下标加1时,数据存储区中的指针所增加的字节数。

    • 图中的strides为(12,4),表示第0轴的下标增加1时,地址增加12字节,第1轴的下标增加1时,地址增加4字节
    • 通过切片得到的是数组的视图,但是strides属性会变化,根据实际的结果数组来计算

了解数组的内存结构,就可以解释什么时候是视图,什么时候是副本了

  • 当下标使用整数和切片时,所取得的数据在数据存储区中是等间隔分布的,所以只需修改数据结构中的dim count、dimensions、strides等属性以及指向数据存储区域的指针data,就能事项整数和切片下标,所以新数组和原数组能够共享数据存储区
  • 当使用整数序列、整数数组和布尔数组时,不能保证所取得的数据在数据存储区中是等间隔的,因此无法共享,只能复制副本

2. ufunc函数

ufuncuniversal function的缩写,它是一种能对数组的每个元素进行运算的函数。

注意,这里数组间的运算是对应位置元素的运算。

2.1 数学运算

表达式
对应的ufunc函数
y=x1 + x2np.add(x1, x2[, y])
y = x1 - x2np.subtract(x1,x2[, y])
y = x1 * x2np.multiply(x1, x2[, y])
y = x1 / x2np.divide(x1, x2[, y])如果两个数组的元素为整数,那么是整除
y = x1 / x2np.true_divide(x1, x2[, y])总是返回精确的商
y = x1 // x2np.floor_divide(x1, x2[, y])总是对返回值取整
y = -xnp.negative(x[, y])
y = x1 ** x2np.power(x1, x2[, y])
y = x1 % x2np.remainder(x1, x2[, y])np.mod(x1, x2[, y])

可选参数y是输出的变量,如果传入y,则代表把计算的结果赋值给y

a += b等价于np.add(a, b, a)

2.2 比较运算

表达式
对应的ufunc函数
y = x1 == x2np.equal(x1, x2[, y])
y = x1 != x2np.not_equal(x1, x2[, y])
y = x1 < x2np.less(x1, x2[, y])
y = x1 <= x2np.less_equal(x1, x2[, y])
y = x1 > x2np.greater(x1, x2[, y])
y = x1 >= x2np.greater_equal(x1, x2[, y])

返回的是布尔数组,结果数组每个元素值是对应位置元素比较的结果

2.3 逻辑运算

由于Python中的布尔运算使用and、or和not等关键字,它们无法被重载,因此数组的布尔运算只能通过相应的ufunc函数进行。这些函数都以**logical_**开头

  • 逻辑与:np.logical_and(x1, x2[, y])
  • 逻辑或:np.logical_or(x1, x2[, y])
  • 逻辑非:np.logical_not(x[, y])
  • 逻辑异或:np.logical_xor(x1, x2[, y])

对两个布尔数组使用and、or和not等进行布尔运算,将抛出ValueError异常。因为布尔数组中有True也有False,所以NumPy无法确定用户运算目的

2.4 any() & all()

  • np.any(x):只要数组中有一个元素值为True,就返回True
  • np.all(x):当数组元素全为True时,才返回True

2.5 位运算

表达式
对应的ufunc函数
y = x1 & x2np.bitwise_and(x1, x2[, y])
y = x1 | x2np.bitwise_or(x1, x2[, y])
y = ~xnp.bitwise_not(x[, y])
y = x1 ^ x2np.bitwise_xor(x1, x2[, y])

对于布尔数组来说,位运算和布尔运算结果相同。但在使用时注意,位运算符的优先级比比较运算高,因此需要使用括号提高比较运算符优先级,如:

>>> import numpy as np
>>> a = np.random.randint(1,50,9).reshape(3,3)
>>> b = np.random.randint(1,50,9).reshape(3,3)
>>> (a == b) | (a > b)
array([[ True,  True,  True],
 [ True, False, False],
 [False, False,  True]])

2.6 自定义ufunc函数

通过NumPy提供的标准ufunc函数,可以组合出复杂的表达式,在C语言级别对数组的每个元素进行计算。但有时这种表达式不易编写,而对每个元素进行计算的程序确很容易用Python实现,这时可以用frompyfunc()或是vectorize()将计算单个元素的函数转换成ufunc函数,这就可以方便地用所产生的ufunc函数对数组进行计算了

e.g.

我们定义一个符号函数:

def sgn(x: int) -> int:
    if x > 0:
        return 1
    elif x < 0:
        return -1
    else:
        return 0

2.6.1 frompyfunc函数

语法:frompyfunc(func, nin, nout)

  • func:计算单个元素的函数
  • nin:func的入参个数
  • nout:func的返回值个数
>>> sgn_ufunc = np.frompyfunc(sgn, 1, 1)
>>> a = np.random.randint(-50,50,9).reshape(3,3)
>>> a
array([[ 37,  -7,  22],
       [-45, -34,  39],
       [  8, -38,  -2]])
>>> sgn_ufunc(a)
array([[1, -1, 1],
       [-1, -1, 1],
       [1, -1, -1]], dtype=object)

注意,frompyfunc返回的对象是object,需要进行转换

>>> b = sgn_ufunc(a)
>>> b.dtype
dtype('O')
>>> b = b.astype(np.int32)
>>> b.dtype
dtype('int32')
>>> b
array([[ 1, -1,  1],
       [-1, -1,  1],
       [ 1, -1, -1]])

2.6.2 vectorize函数

语法:vectorize(func, otypes)

此功能与前面类似,不过,可以通过otypes参数设定自定义的ufunc的返回值

本质上是一个for循环

暂时只介绍这两个参数,后续用到其他参数再说

最好用命名关键字方式传递入参otypes,用字符串接收(类型的字符表示)或者列表接收(NumPy的类型,np.xxx)

>>> sgn_ufunc = np.vectorize(sgn,otypes=[np.int32])
>>> b = sgn_ufunc(a)
>>> b
array([[ 1, -1,  1],
       [-1, -1,  1],
       [ 1, -1, -1]])

2.7 广播

2.7.1 规则

当使用ufunc函数对两个数组进行计算时,ufunc函数会对这两个数组的对应元素进行计算,因此要求这两个数组的形状相同。如果形状不同,会进行如下广播(broadcasting)处理:

  1. 让所有输入数组都向其中维数最多的数组看齐,shape属性中不足的部分都通过在前面加1补齐
  2. 输出数组的shape属性是输入数组的shape属性的各个轴上的最大值
  3. 如果输入数组的某个轴长度为1或与输出数组的对应轴长度相同,这个数组能够用来计算。否则出错
  4. 当输入数组的某个轴长度为1时,沿着此轴运算时都用此轴上的第一组值

2.7.2 示例

上述4条规则理解起来可能比较费劲,举个栗子:

先创建一个二维数组a,其形状为(6,1):

>>> a = np.arange(0,60,10).reshape(-1,1)
>>> a
array([[ 0],
       [10],
       [20],
       [30],
       [40],
       [50]])
>>> a.shape
(6, 1)

再创建一维数组b,其形状为(5,):

>>> b = np.arange(5)
>>> b
array([0, 1, 2, 3, 4])
>>> b.shape
(5,)

计算a与b的和,得到一个加法表,它相当于计算两个数组中所有元素对的和,得到一个形状为(6,5)的数组:

>>> c = a + b
>>> c
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [20, 21, 22, 23, 24],
       [30, 31, 32, 33, 34],
       [40, 41, 42, 43, 44],
       [50, 51, 52, 53, 54]])
>>> c.shape
(6, 5)

由于a和b维数不同,根据规则①,需要让b的shape向a看齐,于是在b的shape属性前加1,补齐为(1,5)。相当于做了如下计算:

>>> b.shape = 1, 5
>>> b
array([[0, 1, 2, 3, 4]])

这样,加法运算的两个输入数组的shape属性分别为(6,1)和(1,5),根据规则②,输出数组的各个轴长度为输入数组各个轴长度的最大值,可知输出数组的shape属性为(6,5)。

由于b的第0轴长度为1,而a的第0轴的长度为6,为了让它们在第0轴上能够相加,需要将b的第0轴长度扩展为6,这相当于:

>>> b = b.repeat(6, axis=0)
>>> b
array([[0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4]])
>>> b.shape
(6,5)

这里的repeat()方法沿着axis参数指定的轴复制数组中各个元素的值。由于a的第1轴长度为1,而b的第1轴长度为5,为了让它们在第1轴能够相加,需要将a的第1轴的长度扩展为5,这相当于:

>>> a = a.repeat(5, axis=1)
>>> a
array([[ 0,  0,  0,  0,  0],
       [10, 10, 10, 10, 10],
       [20, 20, 20, 20, 20],
       [30, 30, 30, 30, 30],
       [40, 40, 40, 40, 40],
       [50, 50, 50, 50, 50]])
>>> a.shape
(6, 5)

2.7.3 broadcast_arrays函数

用法:np.broadcast_arrays(*args, **kwargs)

传入你想知道广播结果的数组,返回广播后的数组列表

2.8 ufunc的方法

ufunc函数对象本身还有一些方法函数,这些方法只对两个输入、一个输出的ufunc有效,其他的ufunc对象调用这些方法时会抛出ValueError异常。

2.8.1 reduce()方法

reduce()方法和Python的reduce()函数类似,它沿着axis参数指定的轴对数组进行操作,相当于将<op>运算符插入到沿axis轴的所有元素之间

用法<op>.reduce(a, axis=0, dtype=None, out=None, keepdims=False, initial)

  • initial:如果赋值,则代表是reduce方法起始值,否则起始值是数组第一个元素
  • keepdims:如果设置为True,则把输入数组被reduce的轴的长度压缩成1,然后保持原数组维度生成输出的数组

例如:

>>> import numpy as np
>>> r1 = np.add.reduce([1,2,3])
>>> r2 = np.add.reduce([[1,2,3],[4,5,6]], axis=1)
>>> r1
6
>>> r2
array([ 6, 15])
>>> # 指定keepdims=True
>>> r3 = np.add.reduce([[1,2,3],[4,5,6]], axis=1,keepdims=True)
>>> r3
array([[ 6],
       [15]])

2.8.2 accumulate()方法

accumulate()方法和reduce()类似,只是它返回的数组和输入数组的形状相同,保存所有的中间计算结果:

用法<op>.accumulate(array, axis=0, dtype=None, out=None)

>>> a1 = np.add.accumulate([1,2,3])
>>> a2 = np.add.accumulate([[1,2,3],[4,5,6]])
>>> a1
array([1, 3, 6], dtype=int32)
>>> a2
array([[1, 2, 3],
       [5, 7, 9]], dtype=int32)

2.8.3 reduceat()方法

reduceat()方法计算多组reduce()的结果,通过indices参数指定一系列的起始和终止位置。

用法<op>.reduceat(a, indices, axis=0, dtype=None, out=None)

它的计算有些特别,举个例子:

>>> a = np.array([1,2,3,4])
>>> result = np.add.reduceat(a, indices=[0,1,0,2,0,3,0])
>>> result
array([ 1,  2,  3,  3,  6,  4, 10], dtype=int32)
>>> # 多维数组也一样,但是注意,是针对某一轴数据
>>> x = np.linspace(0, 15, 16).reshape(4,4)
>>> np.add.reduceat(x, [0, 3, 1, 2, 0])
array([[12., 15., 18., 21.],
       [12., 13., 14., 15.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.],
       [24., 28., 32., 36.]])

对于indices参数中的每个元素都会计算出一个值,因此最终的计算结果和indices参数长度相同。

结果数组result中除最后一个元素之外,都按照如下计算得出:

if indices[i] < indices[i+1]:
	result[i] = <op>.reduce(a[indices[i]:indices[i+1]])
else:
	result[i] = a[indices[i]]

而最后一个元素计算如下:

<op>.reduce(a[indices[-1]:])

因此在上面的例子中,数组result的每个元素都按照如下计算得出:

a[0] -> 1

a[1] -> 2

a[0] + a[1] -> 1 + 2 = 3

a[2] -> 3

a[0] + a[1] + a[2] -> 1 + 2 + 3 = 6

a[3] -> 4

a[0] + a[1] + a[2] + a[3] = 1 + 2 + 3 + 4 = 10

2.8.4 outer()方法

ufunc函数对象的outer()方法等同于如下程序:

>>> # np.add.outer(a,b),等价于如下程序
>>> a.shape += (1,)*b.ndim
>>> np.add(a,b)	# 或者直接a+b,此处返回值即为outer返回的结果(此处进行了广播计算)
>>> a = a.squeeze() # 还原a数组

squeeze()函数:

从数组的shape中删除长度为1的维度,即把shape中为1的维度去掉

用法:np.squeeze(a, axis = None)

  • a表示输入的数组
  • axis用于指定需要删除的维度,但是该维度必须长度为1,否则报错
    axis的取值可以是None或int或tuple of ints;
    若axis不传则默认删除所有单维度
  • 返回值:数组
  • 不会修改原数组

3. 庞大的函数库

除了前面介绍的ndarry数组对象和ufunc函数之外,NumPy还提供了大量对数组进行处理的函数。

3.1 随机数

3.1.1 rand()函数

用法np.random.rand(d0, d1, ..., dn)

功能:返回一个shape为(d0, d1, ..., dn)的数组,其元素为**[0,1)**之间的随机值

>>> import numpy as np
>>> np.set_printoptions(precision=2) # 为了节省篇幅,只显示小数点后两位数字
>>> np.random.rand()
0.07277763243576496
>>> np.random.rand(1)
array([0.26])
>>> np.random.rand(1,2)
array([[0.09, 0.91]])
>>> np.random.rand(1,2,3)
array([[[0.66, 0.14, 0.04],
        [0.47, 0.15, 0.14]]])

3.1.2 randn()函数

用法np.random.randn(d0, d1, ..., dn)

功能:返回一个shape为(d0, d1, ..., dn)的数组,其元素为满足标准正态分布的实数域中的随机值

>>> import numpy as np
>>> np.random.randn()
0.1937885973579063
>>> np.random.randn(1)
array([-0.5])
>>> np.random.randn(1,2)
array([[-0.84, -0.12]])
>>> np.random.randn(1,2,3)
array([[[ 0.24,  0.94, -2.31],
        [-0.83,  2.05, -0.15]]])

3.1.3 randint()函数

用法np.random.randint(start, end, size)

功能:返回形状为shape的数组,其元素值为**[start, end)**之间的随机整数,注意,shape应该是元组

的确可以省略start、shape传数字等,但是有坑,还是按照完整的来吧

>>> import numpy as np
>>> np.random.randint(10)
3
>>> np.random.randint(5, 10)
9
>>> np.random.randint(1,10,3)
array([3, 7, 6])
>>> np.random.randint(1,10,(3,5))
array([[4, 5, 9, 1, 3],
       [7, 6, 5, 6, 4],
       [2, 3, 9, 1, 9]])

3.1.4 normal()函数

用法np.random.normal(μ=0.0, σ=1.0, size=None)

  • μ:期望
  • σ:标准差
  • size:想要返回的数组的形状,元组

功能:生成一个形状为shape的数组,其元素为服从正态分布N(μ,σ)的随机数

>>> import numpy as np
>>> np.random.normal(10,1)
9.90787600134799
>>> np.random.normal(10,1,1)
array([10.61])
>>> np.random.normal(10,1,(2,3))
array([[11.08,  9.56, 10.82],
       [ 9.9 ,  8.92, 10.27]])

3.1.5 uniform()函数

用法np.random.uniform(a=0.0, b=1.0, size=None)

  • a:起始值,include
  • b:终值,exclude
  • size:想要返回的数组的形状,元组

功能:生成一个形状为shape的数组,其元素为服从区间[a,b)上的均匀分布的随机数

>>> import numpy as np
>>> np.random.uniform()
0.43026979233842444
>>> np.random.uniform(2,5)
2.4114594665232914
>>> np.random.uniform(2,5,1)
array([3.26])
>>> np.random.uniform(2,5,(3,4))
array([[2.11, 3.16, 3.12, 2.1 ],
       [2.35, 4.42, 3.44, 4.88],
       [2.42, 4.51, 4.44, 3.15]])

3.1.6 poisson()函数

用法np.random.poisson(λ=1.0, size=None)

  • 泊松分布概率公式: P ( X = k ) = λ k k ! e − k , k = 0 , 1 , . . . P(X=k)=\frac{\lambda^k}{k!}e^{-k},\quad k=0,1,... P(X=k)=k!λkek,k=0,1,...

  • 这个概率公式含义是:在一段时间内,随机事件平均发生λ次的情况下,发生次数为k的概率

  • λ就是泊松分布的数学期望,也是泊松分布的方差

功能:生成一个形状为shape的数组,其元素为服从泊松分布的随机整数(因为是离散随机变量)

>>> import numpy as np
>>> np.random.poisson(4)
3
>>> np.random.poisson(4,1)
array([2])
>>> np.random.poisson(4,(2,3))
array([[9, 6, 7],
       [5, 5, 1]])

3.1.7 permutation()函数

用法np.random.permutation(x)

功能:permutation()可以用于产生一个新的乱序数组,不影响入参

  • 当参数为整数时,它返回[0,x)这n个整数的随机排列
  • 当参数为一个序列时,它返回一个随机排列之后的序列
>>> import numpy as np
>>> a = np.array([1,10,20,30,40])
>>> np.random.permutation(10)
array([1, 3, 8, 7, 6, 5, 0, 9, 2, 4])
>>> np.random.permutation(a)
array([10, 30, 40, 20,  1])
>>> np.random.permutation(c)
array([[6, 7, 8],
       [3, 4, 5],
       [0, 1, 2]])

当传入多维数组时,只打乱第1维的元素顺序,比如示例的c,第一维元素是三个list,打乱的就是三个list的顺序,但是list内部元素顺序不变。

打个比方,我的附庸的附庸,不是我的附庸。该方法打乱的是数组的元素的顺序,而不是元素的元素的顺序。

3.1.8 shuffle()函数

permutation()返回一个新数组,而shuffle()则直接将参数数组的顺序打乱

实际上permutation()就是复制传入的数组,然后对副本进行shuffle()

用法np.random.shuffle(x)

功能:修改入参数组,重新排列元素,返回值为None

>>> import numpy as np
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.random.shuffle(a)
>>> a
array([0, 3, 5, 8, 9, 1, 6, 4, 7, 2])
>>> b = np.arange(9).reshape(3,3)
>>> np.random.shuffle(b)
>>> b
array([[6, 7, 8],
       [3, 4, 5],
       [0, 1, 2]])

3.1.9 choice()函数

用法np.random.choice(a, size=None, replace=True, p=None)

  • a:给定样本,是一维数组、序列或者是int数字,int数字则代表样本为arange(a)
  • size:指定输出数组的形状
  • replace:True表示可重复抽取,False表示不重复抽取
  • p:指定每个元素对应的抽取概率,如果不指定则默认等概率

功能:生成指定形状的数组,其元素从给定样本中随机抽取

>>> import numpy as np
>>> a = np.arange(10,25,dtype=float)
>>> a
array([10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22.,
       23., 24.])
>>> c1 = np.random.choice(a, size=(4,3))
>>> c2 = np.random.choice(a,size=(4,3),replace=False)
>>> c3 = np.random.choice(a,size=(4,3),p=a/np.sum(a))
>>> c1
array([[13., 22., 17.],
       [13., 17., 22.],
       [10., 19., 20.],
       [21., 18., 23.]])
>>> c2
array([[21., 10., 12.],
       [16., 20., 11.],
       [17., 13., 22.],
       [15., 14., 24.]])
>>> c3
array([[15., 22., 19.],
       [24., 19., 22.],
       [16., 18., 24.],
       [24., 19., 20.]])

3.1.10 seed()函数

为了保证每次运行时能重现相同的随机数,可以通过seed()函数指定随机数的种子。

>>> import numpy as np
>>> r1 = np.random.randint(0,100,3)
>>> r2 = np.random.randint(0,100,3)
>>> np.random.seed(42)
>>> r3 = np.random.randint(0,100,3)
>>> np.random.seed(42)
>>> r4 = np.random.randint(0,100,3)
>>> r1
array([64, 47, 70])
>>> r2
array([98, 15, 96])
>>> r3
array([51, 92, 14])
>>> r4
array([51, 92, 14])

3.2 求和、平均值、方差

3.2.1 sum()函数

用法np.sum(a,axis=None,dtype=None,out=None,keepdims=<no value>,initial=<no value>,)

功能:计算数组元素之和

  • 简单应用
    仅仅计算所有数组元素的和,返回的是数字

    >>> import numpy as np
    >>> a = np.arange(1,6)
    >>> b = np.random.randint(0,10,size=(4,5))
    >>> b
    array([[9, 0, 4, 1, 5],
           [5, 0, 0, 1, 9],
           [8, 9, 1, 0, 8],
           [1, 0, 0, 5, 0]])
    >>> np.sum(a)
    15
    >>> np.sum(b)
    66
    
  • 指定axis参数一个轴

    实际上指定轴是计算该轴的坐标值,注意计算的方向别错了
    如果指定axis参数,则求和运算沿着指定的轴进行,结果是一个一维数组,也就是说,原数组沿axis压扁后,shape不一定和函数返回结果相同

    >>> import numpy as np
    >>> x = np.random.randint(0,10,size=(4,5))
    >>> x
    array([[2, 2, 9, 6, 1],
           [5, 1, 7, 8, 0],
           [6, 8, 8, 1, 4],
           [0, 6, 8, 7, 7]])
    >>> np.sum(x,axis=1)
    array([20, 21, 27, 28])
    >>> np.sum(x,axis=0)
    array([13, 17, 32, 22, 12])
    >>> np.sum(x)
    96
    
  • 指定axis参数轴序列

    >>> import numpy as np
    >>> z = np.random.randint(0,6,24).reshape(2,3,4)
    >>> z
    array([[[4, 0, 5, 0],
            [3, 3, 2, 2],
            [3, 5, 5, 0]],
    
           [[1, 0, 2, 1],
            [0, 5, 4, 0],
            [4, 3, 4, 1]]])
    >>> np.sum(z, axis=(1,2))
    array([32, 25])
    >>> np.sum(z, axis=1)
    array([[10,  8, 12,  2],
           [ 5,  8, 10,  2]])
    
  • 指定keepdims参数
    如果赋值为True,则返回的数组的维度和入参数组相同,只是axis指定的轴的长度变为1
    最大的好处,能和原数组进行广播运算!

    >>> import numpy as np
    >>> s = np.random.randint(0,6,24).reshape(2,3,4)
    >>> s
    array([[[3, 2, 1, 0],
            [5, 4, 4, 1],
            [1, 1, 1, 3]],
    
           [[2, 1, 2, 2],
            [3, 1, 2, 4],
            [1, 0, 1, 5]]])
    >>> np.sum(s,axis=2,keepdims=True)
    array([[[ 6],
            [14],
            [ 6]],
    
           [[ 7],
            [10],
            [ 7]]])
    >>> np.sum(s,axis=2)
    array([[ 6, 14,  6],
           [ 7, 10,  7]])
    >>> # 下面是指定轴序列的情况
    >>> np.sum(s,axis=(1,2),keepdims=True)
    array([[[26]],
    
           [[24]]])
    >>> np.sum(s,axis=(1,2))
    array([26, 24])
    

最难理解的就是keedims参数,说起来是保证返回的数组和原数组可以进行广播运算,这里举个例子帮助理解:

假设输入数组是3维的,其形状为(2,3,4),指定axis=(1,2)

  • 不指定keepdims
    返回的数组首先形状应该和入参数组相同,所以是(2,3,4),然后将轴1和轴2删除,最终出参形状应该为(2,)
  • keepdims=True
    返回的数组也一开始是(2,3,4),然后将轴1和轴2的长度压缩为1,所以最终出形状为(2,1,1)

3.2.2 product()函数

功能:和sum()很类似,参数一样,只是product用于计算数组中所有元素的乘积

3.2.3 average()函数

用法np.average(a, axis=None, weights=None, returned=False)

功能:计算平均数

  • 等权平均数

    >>> import numpy as np
    >>> a = np.random.randint(0,6,9).reshape(3,3)
    >>> a
    array([[2, 3, 1],
           [1, 5, 0],
           [5, 1, 4]])
    >>> np.average(a)
    2.4444444444444446
    
  • 加权平均数
    参数weights是每个元素的权重

    >>> import numpy as np
    >>> b = np.random.randint(0,6,9).reshape(3,3)
    >>> b
    array([[4, 3, 0],
           [0, 0, 1],
           [5, 0, 4]])
    >>> np.average(b,weights=b)
    3.9411764705882355
    
  • 指定axis,计算一部分元素的平均数

    >>> import numpy as np
    >>> a = np.random.randint(1,10,9).reshape(3,3)
    >>> b = np.random.randint(1,10,24).reshape(2,3,4)
    >>> a
    array([[2, 3, 9],
           [5, 4, 6],
           [6, 1, 1]])
    >>> b
    array([[[1, 4, 5, 3],
            [1, 5, 8, 7],
            [4, 6, 4, 5]],
    
           [[5, 3, 9, 5],
            [3, 2, 9, 4],
            [1, 3, 7, 2]]])
    >>> np.set_printoptions(precision=2) # 为方便显示2位小数
    >>> # 二维数组
    >>> np.average(a,axis=1)
    array([4.67, 5.  , 2.67])
    >>> # 三维数组
    >>> np.average(b, axis=1)
    array([[2.  , 5.  , 5.67, 5.  ],
           [3.  , 2.67, 8.33, 3.67]])
    >>> np.average(b, axis=(1,2))
    array([4.42, 4.42])
    

3.2.4 mean()、std()和var()函数*

mean()函数用于计算数学期望,std()和var()分别计算数组的标准差和方差,都有有axis、out、dtype以及keepdims等参数。方差有两种定义:

  • 偏样本方差(统计量计算方差): S 2 = 1 n ∑ i = 1 n 1 ( y i − y ‾ ) 2 S^2 = \frac{1}{n}\sum\limits_{i=1}^n \frac{1}{({y_i-\overline{y}})^2} S2=n1i=1n(yiy)21
  • 无偏样本方差(理想情况下的方差): S 2 = 1 n − 1 ∑ i = 1 n 1 ( y i − y ‾ ) 2 S^2 = \frac{1}{n-1}\sum\limits_{i=1}^n \frac{1}{({y_i-\overline{y}})^2} S2=n11i=1n(yiy)21

当ddof=0(默认)时计算偏样本方差,当ddof=1时,计算无偏样本方差

ddof其实是自由度,它实际上代表求和符号前的分数 1 n − d d o f \frac{1}{n - ddof} nddof1

举个例子,首先产生一个标准差为2.0,方差为4.0的正态分布随机数组。我们可以认为总体样本的方差为4.0.假设从总体样本中随机抽取10个样本,我们分别计算这10个样本的两种方差,重复操作100000次,然后计算所有这些方差的期望值:

>>> import numpy as np
>>> a = np.random.normal(0,2.0,(100000,10))
>>> v1 = np.var(a, axis=1)
>>> v2 = np.var(a,axis=1,ddof=1)
>>> np.mean(v1)
3.6056360308936353
>>> np.mean(v2)
4.006262256548484

可以看到无偏样本方差的期望值接近总体方差4.0,而偏样本方差比4.0小一点。

偏样本方差是正态分布随机变量的最大似然估计。如果有一个样本包含n个随机数,并且知道它们符合正态分布,通过该样本可以估算出正态分布的概率密度函数的参数。

3.3 大小与排序

3.3.1 min()、max()和ptp()函数

和sum()函数的入参类似,但是,axis不支持轴序列

>>> import numpy as np
>>> a = np.random.randint(100,size=16).reshape(4,4)
>>> a
array([[ 7, 92, 91, 70],
       [66, 46,  2, 49],
       [ 8, 67, 82,  0],
       [58, 92, 50, 24]])
>>> np.max(a)
92
>>> np.min(a)
0
>>> np.max(a,axis=0)
array([66, 92, 91, 70])
>>> np.min(a,axis=1,keepdims=True)
array([[ 7],
       [ 2],
       [ 0],
       [24]])
>>> np.ptp(a)
92
>>> np.ptp(a,axis=0)
array([59, 46, 89, 70])
>>> np.ptp(a,axis=1,keepdims=True)
array([[85],
       [64],
       [82],
       [68]])

3.3.2 minimum()和maximum()函数

功能:比较2个数组对应下标的元素大小,返回的数组形状为两参数数组广播之后的形状,元素值为比较元素中最大(小)的那个

>>> import numpy as np
>>> a = np.array([1,3,5,7])
>>> b = np.array([2,4,6]).reshape(-1,1)
>>> a
array([1, 3, 5, 7])
>>> b
array([[2],
       [4],
       [6]])
>>> np.maximum(a,b)
array([[2, 3, 5, 7],
       [4, 4, 5, 7],
       [6, 6, 6, 7]])
>>> np.maximum(b,a)
array([[2, 3, 5, 7],
       [4, 4, 5, 7],
       [6, 6, 6, 7]])

3.3.3 argmax()和argmin()函数

用argmax()和argmin()可以求最大值和最小值的下标。如果不指定axis参数,则返回平坦化(一维化)之后的数组下标,例如下面的程序找到a中最大值的下标,有多个最值时得到第一个最值的下标:

>>> import numpy as np
>>> a = np.random.randint(0,10,size=(4,5))
>>> max_pos = np.argmax(a)
>>> a
array([[8, 3, 2, 8, 4],
       [5, 4, 9, 4, 8],
       [6, 7, 3, 2, 9],
       [7, 4, 3, 1, 6]])
>>> max_pos
7
>>> a.ravel()[max_pos] # 平坦化
9
>>> np.argmax(a,axis=1) # 也可以指定轴
array([0, 2, 4, 0], dtype=int64)

3.3.4 unravel_index()函数

功能:将一维数组的下标转换为多维数组的下标,它的第一个参数为一维数组的下标,第二个参数是多维数组的形状:

>>> import numpy as np
>>> idx = np.unravel_index(13,(4,5))
>>> idx
(2, 3)

3.3.5 sort()函数

序列的sort()方法会改变数组的内容,而数组的sort()函数则返回一个新数组,不改变原数组。

它们的axis默认值都为-1,即沿着数组的最终轴进行排序。sort()函数的axis参数可以设置为None,此时它将得到平坦化字·Hi好·之后进行排序的新数组。

>>> import numpy as np
>>> a = np.random.randint(0,20,20).reshape(4,5)
>>> a
array([[ 1,  7,  2,  6, 16],
       [13, 16, 13, 11, 12],
       [ 6, 12, 11,  6,  7],
       [ 1,  4, 15,  1,  5]])
>>> np.sort(a)
array([[ 1,  2,  6,  7, 16],
       [11, 12, 13, 13, 16],
       [ 6,  6,  7, 11, 12],
       [ 1,  1,  4,  5, 15]])
>>> np.sort(a, axis=0)
array([[ 1,  4,  2,  1,  5],
       [ 1,  7, 11,  6,  7],
       [ 6, 12, 13,  6, 12],
       [13, 16, 15, 11, 16]])
>>> np.sort(a, axis=None)
array([ 1,  1,  1,  2,  4,  5,  6,  6,  6,  7,  7, 11, 11, 12, 12, 13, 13, 15, 16, 16])

3.3.6 argsort()函数

用法np.argsort(a, axis=-1, kind='quicksort', order=None)

  • kind:排序算法,可选值为{‘quicksort’, ‘mergesort’, ‘heapsort’, ‘stable’}
  • order:结构数组可以指定排序字段
>>> import nimpy as np
>>> a = np.random.randint(0,20,12).reshape(3,4)
>>> a
array([[ 8,  2, 12,  2],
       [14,  6, 10, 10],
       [ 7,  1,  9,  2]])
>>> sort_axis1 = np.argsort(a)
>>> sort_axis0 = np.argsort(a,axis=0)
>>> sort_axis1
array([[1, 3, 0, 2],
       [1, 2, 3, 0],
       [1, 3, 0, 2]], dtype=int64)
>>> sort_axis0
array([[2, 2, 2, 0],
       [0, 0, 1, 2],
       [1, 1, 0, 1]], dtype=int64)

3.3.7 lexsort()函数

类似于Excel的多列排序。它的参数是一个形状为(k,N)的数组,或者包含k个长度为N的数组的序列,可以把它理解为Excel中N行k列的表格。

lexsort()返回排序下标,注意数组中最后的列为排序的主键。

暂且知道用在哪,以后用到时我在研究,总结记录下来

3.3.8 partition()和argpartition()函数

它们用来对数组进行分割,可以很快的找出排序之后的前k个元素,由于它不需要对整个数组进行完整的排序,因此速度比调用sort()之后再取前k个元素要快许多。

  • example:
    从10万个随机数中找出前5个最小的数,注意partition()得到的前5个数值没有按照从小到大排序,如果需要,可以再调用sort()排序即可:
>>> import numpy as np
>>> r = np.random.randint(10,1000000,100000)
>>> np.sort(r)[:5]
array([ 60,  88,  95, 100, 117])
>>> np.partition(r,5)[:5]
array([ 60,  95, 100,  88, 117])

3.3.9 median()函数

获取数组中位数,如果数组长度为偶数,则返回中间两个值的平均值

它也可以指定axis和out参数

>>> import numpy as np
>>> a
array([[ 8,  2, 12,  2],
       [14,  6, 10, 10],
       [ 7,  1,  9,  2]])
>>> np.median(a,axis=1)
array([ 5. , 10. ,  4.5])
>>> np.median(a)
7.5

3.4 分段函数

3.4.1 where()函数

用法np.where(condition, [x, y])

Python自带有三目运算:x = y if condition else z,在NumPy中,where()函数可以看做判定表达式的数组版本:x = where(condition, y, z)

不过在这里,condition、y和z都是数组,它的返回值是一个形状与condition相同的数组。

当condition中某个元素为True时,x中对应下标的值从数组y获取,否则从z获取,注意,y和z必须要能广播运算

>>> import numpy as np
>>> x = np.arange(10)
>>> np.where(x<5, 9-x, x)
array([9, 8, 7, 6, 5, 5, 6, 7, 8, 9])

3.4.2 select()函数

用法np.select(condlist, choicelist, default=0)

  • conlist:判断条件列表
  • choicelist:操作列表,根据conlist条件,所要执行的操作
  • default:如果不满足条件时的默认值
  • return:返回的是一个1维数组

单条件筛选

这里是随机生成[1,50]的随机数,如果元素小于25则取相反数,否则不变

>>> import numpy as np
>>> a = np.random.randint(1,51,12).reshape(3,4)
>>> r = np.select([a < 25],[ -1 * a], a)
>>> r
array([[ -7,  27,  40,  -4],
       [-17,  27,  50, -18],
       [ 29, -19,  35,  26]])
>>> a
array([[ 7, 27, 40,  4],
       [17, 27, 50, 18],
       [29, 19, 35, 26]])

多条件筛选

>>> import numpy as np
>>> b = np.random.randint(1,20,12).reshape(3,4)
>>> b
array([[ 8, 11,  1,  9],
       [ 5, 15,  4, 13],
       [14,  8, 16, 15]])
>>> condlist = [b < 10, np.logical_and(b >=10, b <= 15), b > 15]
>>> choicelist = [b-10, -1*b, b+10]
>>> r = np.select(condlist,choicelist)
>>> r
array([[ -2, -11,  -9,  -1],
       [ -5, -15,  -6, -13],
       [-14,  -2,  26, -15]])

3.4.3 piecewise()函数

用法np.piecewise(x, condlist, funclist, *args, **kw)

  • x:保存自变量值的数组
  • condlist:判断条件列表
  • funclist:操作函数列表,满足对应判断条件执行对应的函数

比如,用piecewise()实现一个函数 f ( x ) = { x 2 + 1 ( x ≥ 0 ) − x 2 ( x < 0 ) {f(x) = \begin{cases}x^2+1 & (x \ge 0) \\ -x^2 &(x<0)\end{cases}} f(x)={x2+1x2(x0)(x<0)

>>> import numpy as np
>>> x = np.random.randint(-10,10,12).reshape(3,4)
>>> condlist = [x >= 0, x < 0]
>>> funclist = [lambda x: x**2 +1,lambda x: -x**2]
>>> r = np.piecewise(x,condlist,funclist)
>>> x
array([[ -8,   1,  -5,   4],
       [-10, -10,  -5,   0],
       [  6,  -9,  -2,   7]])
>>> r
array([[ -64,    2,  -25,   17],
       [-100, -100,  -25,    1],
       [  37,  -81,   -4,   50]])

3.5 多项式函数

多项式函数是变量的整数次幂与系数的乘积之和,可以用下面的数学公式表示:

            $$f(x) = a_nx^n + a_{n-1}x^{n-1} + \cdots + a_2x^2 + a_1x + a_0$$

由于多项式只包含加法和乘法运算,因此它很容易计算,可用于其他数学函数的近似值。

在NumPy中,多项式函数的系数可以用一维数组表示,例如下面的数组表示,其中a[0]是最高次系数,a[-1]是常数项

a = np.array([1.0, 0, -2, 1])

我们可以用polyld()将系数转换为poly1d(一元多项式)对象,此对象可以像函数一样调用,它返回多项式的函数值:

>>> import numpy as np
>>> a = np.array([1.0, 0, -2, 1])
>>> p = np.poly1d(a)
>>> type(p)
>>> p(np.linspace(0,1,5))
array([ 1.  ,  0.52,  0.12, -0.08,  0.  ])

对poly1d对象进行加减乘除运算相当于对应多项式函数进行计算

>>> p + [-2, 1] # 和p + np.poly1d([-2, 1])相同
poly1d([ 1.,  0., -4.,  2.])
>>> p * p # 两个三次多项式相乘得到一个6次多项式
poly1d([ 1.,  0., -4.,  2.,  4., -4.,  1.])
>>> p / [1, 1] # 出发返回两个多项式,分别为商式和余式
(poly1d([ 1., -1., -1.]), poly1d([2.]))

除以多项式除法,返回值第一个是商,第二个是余式,下面这个式子结果为True:

p == np.poly1d([ 1., -1., -1.]) * [1, 1] +2

多项式对象的**deriv()integ()**方法分别计算多项式函数的微分和积分:

>>> p.deriv()
poly1d([ 3.,  0., -2.])
>>> p.integ().deriv() == p
True

多项式函数的零点可以使用roots()函数计算:

>>> r = np.roots(p)
>>> r
array([-1.62,  1.  ,  0.62])
>>> # 将跟代入多项式
>>> p(r) # 近似等于0
array([2.33e-15, 4.44e-16, 1.11e-16])

poly()函数可以将根转换回多项式的系数:

>>> np.poly(r)
array([ 1.00e+00, -7.77e-16, -2.00e+00,  1.00e+00])

除了上述的直接计算,还可以使用函数达到相同效果,函数类似于np.polyXXX()

更多的多项式操作可详见多项式函数类

3.6 各种乘积运算

3.6.1 dot()函数

对于二维数组,它计算的是矩阵乘积;对于一维数组,它计算的是内积。

当需要将一维数组当做列矢量或行矢量进行矩阵运算时,先将一维数组转换为二维数组:

>>> a = np.array([1,2,3])
>>> a[:,None]
array([[1],
       [2],
       [3]])
>>> a[None,:]
array([[1, 2, 3]])

二维数组就是常规矩阵乘法,所以注意:

矩阵乘法是有顺序的,不能随意交换!!!

>>> a = np.array([[1,2], [3,4]])
>>> b = np.array([[1,-1],[-1,1]])
>>> np.dot(a,b)
array([[-1,  1],
       [-1,  1]])
>>> a.dot(b)
array([[-1,  1],
       [-1,  1]])
>>> b.dot(a)
array([[-2, -2],
       [ 2,  2]])
>>> np.dot(b,a)
array([[-2, -2],
       [ 2,  2]])

3.6.2 inner()函数

对于两个一维数组,inner()与dot()一样,求内积,简单说是行向量乘列向量

多维数组暂不考虑

3.6.3 outer()函数

outer()函数只针对一维数组计算,如果传入多维数组,则会先扁平化,它计算列向量成行向量

>>> a = np.array([1,2,3])
>>> b = np.array([4,5,6,7])
>>> np.outer(a,b)
array([[ 4,  5,  6,  7],
       [ 8, 10, 12, 14],
       [12, 15, 18, 21]])
>>> np.dot(a[:,None],b[None,:])
array([[ 4,  5,  6,  7],
       [ 8, 10, 12, 14],
       [12, 15, 18, 21]])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值