Numpy知识点复习总结大全


(hbworld) #1

目录

  1. Numpy简介
  2. ndarray
  3. ndarray数据类型
  4. 修改ndarray的形状
  5. ndarray索引和切片
  6. ndarray的转置与轴变换
  7. 通用函数
  8. ndarray与标量的运算
  9. ndarray的“三目运算符”
  10. 统计函数
  11. 随机
  12. 线性代数
  13. 广播
  14. 排序
  15. 结构化和记录式数组
  16. 用于数组的文件输入输出

Numpy简介

NumPy是用Python进行科学计算的基本软件包。它包含以下内容:

  • 一个强大的N维数组对象
  • 复杂的(广播)功能
  • 用于集成C / C ++和Fortran代码的工具
  • 有用的线性代数,傅里叶变换和随机数能力
  • 用于读写磁盘数据以及操作内存映射文件的工具

NumPy的优势在于NumPy底层使用C语言编写,其对数组的操作速度不受Python解释器的限制,效率远高于纯Python代码。并且结合Python脚本语言的特点,使得代码比起其他语言更简洁易读。除了明显的科学用途外,NumPy还可以用作通用数据的高效多维容器。任意的数据类型可以被定义。这使得NumPy能够与各种各样的数据库无缝,快速地整合。

ndarray

ndarray是一个N维齐次同构数组对象,每个数组都有一个shapedtype 属性,shape 描述的是ndarray的形状,而dtype则描述ndarray里面元素的数据类型。

创建ndarray

通常创建ndarray会用如下几种方法

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘674’%20height='381’>)

下面是创建ndarray的例子

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='228’>)

可以看到numpy根据输入的嵌套列表,生成了一个二维数组,每一维有三个元素,而且我们可以看到该数组元素的数据类型均为int64(因为没有显式的指定dtype参数,所以numpy自己推断了int64 这个数据类型,int64 为64位整型)

ndarray数据类型

dtype 含有将ndarray解释为特定数据类型的所需的信息,数据的类型命名有规律,均为类型名+位宽(一个字节有8个位宽)如上面例子中的int64。dytpe 有整型、浮点数、字符串、复数、布尔值、Python对象等等。

如果你没有在创建ndarray数组时显式的指明dtype,你仍可以通过astype(type) 方法修改数据类型为type,但是使用astype会返回一个新的数组,浪费额外的内存,因此建议在创建ndarray数组的时候指定数据对象。(注意浮点数转化为整型数会把小数部分截断)

修改ndarray的形状

shape 属性描述了ndarray数组的形状ndarray.shape 会返回描述形状而元祖,元祖内元素的个数表示数组有多少个维度,元素的值描述了某个维度的长度。在创建ndarray数组的时候我们可以用一个元祖指定shape 参数的取值来控制数组的形状。或者在创建数组之后,直接对数组的shape 属性赋值或者使用reshape() 方法来返回一个指定形状的新数组。

在使用reshape的时候若你指定传入-1为参数,那么返回的数组将会是1维,

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='67’>)

当你指定了n-1维的值后,剩下一维的大小可以传入-1,由numpy自己计算出该维度的大小

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='214’>)

reshape() 方法虽然可以改变数组的形状,但由于数组元素的个数是恒定不变的,而且显然数组元素个数为各个维度大小的乘积。但在有些时候,我们需要给数组添加一些新的元素。numpy中有column_stack()row_stack() 方法,可以把两个数组合并,达到增加元素的效果。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='89’>)

column_stack() 为例,该函数接受一个元祖作为参数将元祖内的数组按列堆叠,上例将形状为(2,1)的数组堆叠到array,显然按列堆叠必须要行数必须相等。大于二维的数组若想要成功按列堆叠,则需要除列以外所有维度的大小相等。

类似的函数或类还有concatenate()、vstack()、r_、c_ 等等,其中concatenate() 为最一般的连接方法,它可以指定按某一个轴进行数组的合并。

除了合并,还可以用split() 函数传入指定轴以及切分点的索引值拆分数组(传入n 个切分索引则返回n+1 个数组),类似的方法还有hsplit() 、vsplit() 、dsplit() 有兴趣的朋友可以去查看官方文档。

ndarray索引和切片

Python列表的索引和切片操作在ndarray上仍然适用。但是要注意ndarray的切片返回的是数组的视图而非副本,如果你想要获得副本,这需要显式的调用copy()方法

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='84’>)

以上代码可以选取除最后一列的其他元素,可以看到在numpy里,所有维度的索引可以写在一个中括号内,并且用逗号隔开(分别写在多个中括号也是可以的,这种写法和Python列表索引的写法相同)。

ndarray还有着特殊的索引方式,分布是布尔型索引花式索引。

布尔型索引就是在中括号内放入布尔型数组,数组会根据布尔型数组的真值来选择数据。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='164’>)

上例中在索引里加入布尔表达式,可以生成与原数组形状相同的布尔型数组,如上图所示array>3返回的是是一个与原数组形状相同的布尔型数组。原数组对应位置的元素的值为该元素对于给定布尔表达式的真值。因此只有** 4,5,6** 对应的位置应位置的取值才为True。若把布尔型数组放入索引,就可以按照布尔数组来索引元素了。(注意布尔型数组的长度必须与被索引轴的长度相等)

花式索引 就是把一个指定顺序的整数顺序的列表或者ndarray数组放入中括号内,就可以按照特定顺序选取子集例如我想选第0列和第2列的全部元素,那么就可以向下面那样进行索引。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='86’>)

或者我想先选取第2列再选取第0列,只须修改列表里元素的顺序即可。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='89’>)

numpy索引的方法有很多,但是各种索引方式之间的效率和空间是不一样的。例如我们现在有一个比较大的数组,我们打算每隔10个行选取1行,则可以有以下两种方式。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='212’>)

可以看到使用切片会比花式数组快两个数量级,这是因为切片返回的是原数组的视图,修改视图会修改原数组。但是花式索引返回的是却是一个新的数组。实际上使用切片再显式的拷贝也比使用花式数组要快。故等步长选取数据时优先使用切片会有更好的性能。

但你选取的数据不是按等步长的选择时,可以使用np.take() 方法,他接受一个数组,索引数组以及轴作为参数,如下例所示

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='154’>)

使用take()方法可以比使用花式索引快那么一点点。在使用布尔型索引按某一轴的时候,使用np.compress()的效率会更高。(np.compress()用法和take一样)。

ndarray的转置与轴变换

若想实现数组的转置可以使用transpose() 方法接受描述轴顺序的元祖作为参数,并根据这一元祖对元素重新排序。例如一个二维数组的轴原来为(0,1),当你改变轴的顺序为

(1,0)即可实现转置。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='105’>)

也可以直接使用**.T**简单转置

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='102’>)

在处理二维数组的时候使用**.T.transpose()** 并没有差异,但是**.transpose()** 可以实现更复杂的轴变换,而.T 值能实现轴的按逆序来进行轴变换。(注意一维数组的转置为本身)

通用函数

通用函数是一种对ndarray 中的数据进行元素级 运算的函数。这些函数非常好上手,都是一些看到名字就知道作用的函数,例如absfbs、sqrt

、exp等等。numpy的数学函数库非常的丰富,在此就不一一说明,以下是例子。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='86’>)

可以看到每一个元素都被开平方。

ndarray与标量之间运算

ndarray支持数组直接用运算符来与标量运算。运算的效果为数组的每一个元素都与标量进行运算,这一点符合和我们正常的使用习惯。并且对于形状相同的两个数组,使用运算符直接运算,会转化为对应位置元素之间的运算,一下是例子。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='86’>)

可以看到原数组的每一个元素的值都加1了。

ndarray的“三目运算符”

在numpy中有一个where 函数,常用于对特定数据进行修改或者选取。其用法有点像C语言中的三目运算符。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='132’>)

where() 函数接受三个参数,第一个是逻辑表达式,若逻辑表达式为真则选取第二个参数,否则选取第三个参数。第二个和第三个参数可以是标量也可以是数组。本质上就是一个if…else… 语句快。

如上例所示,但arr1arr2对应位置的元素进行比较,若arr1中对应位置的元素大于arr2对应位置的元素,则选取arr1中的元素,否则选取arr2中的元素。故结果为 array( [ 3,2,3] )

统计函数

numpy的函数库中也包括用于统计的函数

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘672’%20height='320’>)

表格里前4行函数在使用时如果没有显式而指名某一条轴的话,则默认对象为所有元素。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='91’>)

指定按1轴

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='71’>)

并且使用的时候必须要留意数组中是否有缺失值(numpy.NAN),若数组有缺失值,则结果也会是nan。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='79’>)

表格的最后两个函数的元素的返回值为一维数组,其元素为原数组逐个累加(乘)的结果。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='72’>)

若数组中存在缺失值,则自第一个缺失值之后的值都是nan

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='72’>)

有些时候即使数组中有缺失值,但是这些缺失值实际上可以忽略。numpy还有一些忽略缺失值的统计函数,他们都是以nan开头,例如**.nansum()可以忽略缺失值进行求和,用法和sum**是一样的。

有些时候,我们需要判断有没有缺失值,要知道在数据很多的情况下判断有没有缺失值靠肉眼是不可能的(数据太多,Python也不会将数据全部显示),因此我们可以用**.isnan()**函数来进行判断数据中是否存在缺失值。虽说可以直接使用nan开头的统计函数,但nan开头的统计函数的执行效率比普通统计函数慢得多。

随机

numpy中也提供了random模块,总的来说提供了随机整型数,随机浮点数,随机正态分布,随机选择等类型的随机数。以下是例子

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='81’>)

用以上方法给以生成**[ -10,10)**的随机整型数,若不指定下限,下限默认为0,若不指定size默认返回单个数据。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='90’>)

以上方法可以生成**[ 0 , 1 )**的随机浮点数,若不传size参数,则默认返回单个数据。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='89’>)

**.random.randn()**返回一组符合标准正态分布的的随机数。若不指定size则默认返回单个数据。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='54’>)

.random.choice()方法接受一维数组或者int类型,当接受的参数是一维数组,则可按照数组元素生成随机数,若传入的整型数效果与传入np.arange(int) 效果相同。

还有其他的随机函数如下

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘672’%20height='334’>)

shuffle()和permutation()相似,但shuffle()只能接受数组作为参数,而permutation()的参数可以是数组也可以是int,但参数为int时,等价是打乱**np.arrage(int)**的顺序。**shuffle()就地打乱数组且返回值为None,permutation()**返回一个打乱后的数组,不对原数组进行修改。在对多维数组进行随机排列的时候,只按照第一个维度进行随机排列。

seed( ) 用于指定随机数生成时所用算法开始的整数值。 如过每次传进去的随机整数一样,那么随机数的生成结果相同。如果不传,则系统会自动根据时间选择这个值。

线性代数

numpy线性代数相关的计算。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘670’%20height='427’>)

使用线性代数要注意线性代数里的一些规则,例如求行列式必须要求是方阵等等。并且要记得矩阵的乘法是**.dot()函数,而不是使用***运算符。

numpy中还有一个matrix类,使用的方法更接近MATLAB中的矩阵,例如即可实现矩阵乘法,并且矩阵还有一个 I 属性(矩阵的逆)。matrix的泛用性没有ndarray强,但是对于个别带有大量线性代数运算的函数,也可以先转为matrix类型,在返回之前用np.asarray*(不会复制任何数据)将其转化为ndarray

广播

**广播(broadcasting)**是指不同形状的数组之间的算术运算的执行方式。标量和数组运算时就是一种比较简单的广播

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='86’>)

这里我们说:在这个乘法中,标量值4广播到其他所有的元素上

广播的规则如下:

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

例如下面是一个**(2,3)维的和(3, )** 数组的加法,根据以上的对齐规则,arr2和arr1的后缘维度的长度相同(都为3),符合广播规则,因此可以在0轴进行广播,arr2的形状变为**(1,3)**

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='190’>)

因此上面的结果相当于arr1每行元素都与arr2元素对应相加。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='254’>)

上图就是广播过程的一个图解,由于广播使得形状不同的数组得以运算。以下是沿其他轴广播时而例子,若我们想每一行减去每一行的平均数。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='225’>)

arr.mean(1)的尺寸为(4, ) 显然与不符合广播规则,正确的代码应该如下

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='108’>)

由于较小数组广播维必须为1,因此用reshape改变数组的形状为**(4 , 1)**。

但是如果reshape要增加一个维度的话,那么我必须知道各个维度的长度,这是一件非常麻烦的事,因此我们还有另外一种方法来增加一个维度

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='100’>)

使用全切片的好处就是我们不必知道数组的维度是多少,就可以增加一个维度。而且你会发现Nonenp.newaxis是等价的。

我们还可以通过广播来赋值,例子如下

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='160’>)

上例中将赋值广播到所有的元素,更复杂而例子如下:

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='162’>)

这里将**[1,2,3,4]沿着第1**轴进行广播。

排序

ndarrayPython的列表一样,ndarraysort实例方法也是就地排序(不产生新数组),注意,如果目标数组只是某个数组的一个视图,那么原数组也会被修改。若要返回一个已排序数组的副本,则可以使用numpy.sort()。sort可以接受某个维度作为参数,即可按照该维度进行排序。虽然sort方法只能实现升序排序,但是我们只需调整切片的步长为**-1**即可返回数组的逆序视图。

我们还可以使用argsortlexsort实现间接排序。argsort对数组排好序后,返回索引数组。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='147’>)

可以看到indexer按顺序存储了排序结果的索引,当原数组使用花式索引的时候就可以得到有序结果。但是原数组并没有被修改。

lexsort也是返回一个索引数组,但是lexsort是一种多关键字排序。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='84’>)

上例lexsort以最后一行开始,首先对a进行排序,可以发现a中有几个元素是相同的,有着相同元素的按照索引小的在前,因此a排序后的索引数组为**[ 0, 2, 4, 3, 5, 6, 1 ]。然后我们对b进行排序,排序结果为[ 2, 4, 6, 5, 3, 1, 0 ],最后的排序结果为[ 2, 0, 4, 6, 5, 3, 1 ]**,我们知道a中索引0和2数值相等,但是在b中索引为2的元素比索引为0的元素小,所以最终的比较结果为第二列比第0列小,其他列同理。

其实上面相当于是对 19,54,10,44,30,42,41进行排序,带下划线的元素是主关键字,主关键字来自a(最后一行 ),当主关键字比较不出结果,则参考次关键字(b对应行)的比较结果得出最后的排序结果

另外对有序数组中,我们可以用searchsorted找到某个元素合适的位置。这里不一定会找到查找成功,但是如果待查找元素存在则一定在那个的位置上。因此searchsorted方法可以轻松找到某一个区间的临界点的索引。(注意如果元素存在且不止一个,则默认返回最左边的索引)

结构化和记录式数组

ndarray是一种同质的数据容器,所以在他表示的内存块中,各元素占用字节相同(取决于dtype)。但是我们可以通过设定更复杂的dtype使得数组能存储更多样的数据。这类似C语言中的结构体,以下是一个例子。

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='190’>)

这是一个二维数组,第一列数据为浮点型,第二列数据为整型。并且可以用**‘x’**来索引第一列。

在定义结构化dtype时,我们还可以设置一个形状(整数或者元祖)

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='175’>)

如上例,各个记录的x字段所表示的是一个长度为3的数组。访问字段x 即可访问到二维数组。

我们还可以通过嵌套来实现更复杂的结构化数组

![](data:image/svg+xml;utf8,<svg%20xmlns=‘http://www.w3.org/2000/svg’%20width=‘640’%20height='158’>)

ndarray 每个元素在内存中被解释为固定的字节数,所以在ndarray的取址速度非常快(计算地址的速度更快了),因而使用结构体数组能够提供非常快速高效的磁盘数据读写速度。

用于数组的文件输入输出

np.loadnp.save 是读写磁盘数据的两个主要函数,默认情况下,数组以未压缩的原始二进制格式保存在扩展名为**.npy** 的文件中。使用savez 可以将多个数组保存在同一个压缩文件中,如

np.savez('xxx.npz,a = arr1,b = arr2 )

在加载上述文件时,会得到类似字典的对象,通过关键字可以访问到对应的数组。

性能建议

  • 将Python循环和条件逻辑转化转换为数组运算和布尔数组运算
  • 尽量使用广播
  • 避免复制数据,尽量使用数组视图(切片)
  • 利用ufunc及其各种方法

选自《AI遇上机器学习》