您的当前位置:首页正文

Python3.1-标准库之Numpy

2024-11-28 来源:个人技术集锦

这系列用来介绍Python的标准库的支持Numpy部分。资料来自http://wiki.scipy.org/Tentative_NumPy_Tutorial,页面有许多链接,这里是直接翻译,所以会无法链接。可以大致看完该博文,再去看英文版。

1、先决条件

    想要运行numpy,首先最小安装的有:、。:a、 是一个增强的交互式python shell,它对于探索numpy的特性是非常方便的;b、可以让你进行plot 图表;c、提供许多工作在numpy顶端的科学的例子。

2、基础知识

    numpy的主要对象是同质多维数组。也就是在一个元素(通常是数字)表中,元素的类型都是相同的。其中可以通过正整数的元组来对元素进行索引。在numpy中数组的维度被称为轴(axes),轴的数量称为秩(rank)。例如,在3维空间中一个点[1,2,1]的坐标就是秩为1的数组,因为它只有一个轴。这个轴的长度为3。下面的例子中,数组的秩为2(因为是2维的)。第一个维度(轴)的大小是2,第二个维度的大小是3:

numpy的数组类被成为ndarray。别名为array。numpy.array与标准python库类array.array不一样,标准库类中的那个只能处理一维数组并且功能更少。ndarray对象的重要的属性有:

                ndarray.ndim:数组的轴(维度)的数量。在python中,维度的数量通常被称为rank。

                ndarray.shape:数组的维度。为一个整数元组,表示每个维度上的大小。对于一个n行m列的矩阵来说,shape就是(n,m)。shape元组的长度就是秩(或者维度的数量)ndim。

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

               ndarray.dtype:用来描述数组中元素类型的对象。可以用标准python类型来创建或指定dtype;或者在后面,加上numpy的类型。numpy.int32;numpy.int16;numpy.float64等等

               ndarray.itermsize:数组的每个元素的字节大小。例如,一个类型为float64的元素的数组itemsize 为8(=64/8),而一个complex32的数组itersize为4(=32/8)。该属性等价于ndarray.dtype.itemsize。

               ndarray.data:该缓冲区包含了数组的实际元素。通常情况下,我们不需要使用这个属性,因为我们会使用索引的方式来访问数组中的元素。

    1)例子

    2)构建数组

    有几种方法来构建数组。例如可以通过一个常规的python列表或者使用array函数的元组来构建。可以通过调用生成后数组的属性dtype来了解该数组的元素类型:

经常犯得的错误:调用array的时候,传递了多个数值参数,而不是使用单一的数字列表:

array会将序列的序列转换成2维的数组,序列的序列的序列会转换成3维的数组,等等:

数组的类型同样可以在创建的时侯显式指定:

通常来说,数组中元素的值在开始的时候是未知的,不过数组的大小是已知的。所以numpy提供几个函数在创建数组的时候对元素的值进行占位。这样就可以避免后续需要对数组进行动态增长的操作(数组的动态增长的代价较大)。函数zeros生成一个全为0的数组;函数ones生成一个全为1的数组,函数empty生成一个初始化内容为随机并且依赖于内存状态的数组。默认情况下,生成的数组的dtype是float64:

为了生成数字序列。numpy提供一个类似于range的函数,返回一个列表,参数为(起始,结束,步长):

当传递的参数是浮点数时,因为浮点数精度是有限的,所以通常不会有稳定的元素个数的保证。所以,使用函数linspace是一个更好的选择,因为我们可以对该函数指定需要创建多少个元素,参数为(起始,结束,元素个数):

see also:, , , , , , , , , , ,, 

    3)打印数组

        当需要打印数组的时候,numpy通过类似列表嵌套的形式来显示结果。其中的布局为:a、在数组的最后维度(轴)上,元素是从左到右打印的,比如下面,c的[8 9 10 11]:b、其他维度上都是自顶向下打印的;当维度较多时,每个分片之间通过一个空行隔开:一维数组按行打印,二维按矩阵打印,三维按矩阵的列表打印:

 更多信息可以查看reshape。如果一个数组太大以至于无法打印,numpy自动跳过中间部分只打印角部分:

如果想关闭该功能,并强制numpy打印整个数组,可以改变打印的选项:通过使用set_printoptions:

    4)基本操作

        在数组上的算术操作符是逐元素的elementwise。得到的是一个重新创建的数组,然后将结果写入新数组中:

不同于其他的一些语言对矩阵的操作,在numpy中乘积操作符×是逐元素进行的。矩阵的积可以通过使用dot 函数或者创建matrix对象:

许多操作,例如+= 和×=,支持原处修改现有的数组,而不是生成一个新的数组:

当涉及到不同类型的数组时,是会将更通用或者精确度更高的那个类型(upcasting)作为新数组的类型:

例如计算数组中所有元素的和的这类一元运算都是以ndarray类的方法来实现的:

默认情况下,这些操作的前提是数组中的元素是数字。通过指定axie参数,可以指定对数组的哪个轴进行操作:

    5)通用函数

    numpy提供常见的数学函数,例如sin,cos,和exp。在numpy中,这些被称为“通用函数 universal  functions(ufunc)”。在numpy中,这些函数是逐元素操作的,输出的就是新的数组:

see also:, , , ,  ,  ,  ,  ,  ,  ,  ,  , ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  , ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  ,  , ,, , , , , , 

    6)索引,分片和迭代

     一维数组可以被索引,分片和迭代。就像列表和其它python序列一样:

    多维数组可以每个轴都有一个索引,通过逗号隔开的元组来指定不同的轴上的索引操作:

如果提供的索引比轴的个数要少,缺省的索引表示的轴上会被认为是一个完整的分片:

放在括号中的表达式b【i】表示一个i 后面跟着许多的值:根据需要来表示其他的轴,numpy同样可以使用三个点省略:点(...)表示根据需要来代替未指定的轴的所有元素的索引。例如。如果x 是秩为5的数组(即有5个轴),那么

基于多维数组的迭代只是在第一个轴上进行操作(也就是最上层的轴):

然而如果想要在数组中每个元素上都有操作,需要使用flat属性,这是基于数组所有元素的一个迭代器(  ):

see also:], , , , , 

3、形状操作

    1)改变数组的形状

   当指定数组每个轴上元素个数的时候,就形成了shape:

数组的shape可以通过其他命令来改变:

从ravel()中生成的数组中元素的顺序是标准的“C-风格”,也就是,最右边的索引“改变的最快”。所以元素a【0,0】之后就是a【0,1】.如果数字是被reshaped到一些其他的shape,该数组还是被认为“C-风格”。numpy通常生成的数组都是这种顺序存储的,所以ravel()不需要复制它的参数,不过如果数组是通过另一个数组的分片或者不平常的选项生成的,它也许需要被复制。函数ravel()和reshape()可以通过一个选项参数来指定是否使用FORTRAN-style的存储,即最左边的索引变得最快。函数返回的是修改过的shape的参数,因而 方法会修改数组本身:

如果在reshape操作中,某个维度的值为-1,那么就会被自动计算

see also:, , , 

    2)将不同的数组堆叠在一起

        可以在不同的轴上将几个数组堆叠起来:

函数 可以将1维数组按列的方式堆叠成2维的数组(下图第5个命令)。注意下面的函数:

另一方面,函数 ,按照行的方式将1维数组堆叠成2维数组。对于超过2维的数组,会沿着第二轴进行堆叠,会沿着第一轴堆叠, 允许一个可选的参数,指定在第几个轴上进行concatenation操作。

Note:在复数情况中,r_[] 和c_[] 可以如下来生成数组。它们允许范围标识(“:”)的使用:

当将数组作为参数使用时,[]   和[] 的操作类似于和 在默认参数下的结果,同时也可以通过一个可选参数来指定连接(concatenate)哪个轴。

see also:, , , ,, , 。

    3)将一个数组划分成几个小的数组

         使用函数,可以沿着水平轴划分数组。可以进行等同划分,或者指定列划分:

沿着竖直轴进行划分, 允许指定沿着哪个轴划分。

4、复制和视图

    当对数组进行操作的时候,它们的数据有时候会被复制到一个新的数组中,有时候又不会。这通常是一个对于初学者来说很困惑的地方。有三种情况:

    1)完全不复制

    简单的赋值不会复制数组对象或者它们的数值:

python将可变的对象作为引用,所以函数调用是没有复制的:

    2)浅层复制

        不同的数组对象可以共享相同的数据。view方法生成一个新的数组对象,其中的数据看上去是一样的:

    3)深层复制

    copy方法会生成一个数组和它的数据的复制版本:

    4)函数和方法概述

    这里列出numpy的函数列表和方法。名字是与相关联的:

            创建数组:, ,  ,  ,  ,  ,  ,  ,  ,  ,  , ,  ,  ,  ,  , ,  

            转换:, , , , 。

           操作(manipulations):, ,  ,  , ,  , , ,  , ,,  ,  , , , , , , , 

           询问:, , , 。

           排序:, , , , , , , 。

           操作(operations:, , , , , , , , , , , 

           基本的统计:, , , 。

           基本的线性代数:, , , , 。

5、更少的基础

    1)广播(broadcasting)的规则

          广播允许通用函数中的矩阵的shape不完全一致:a、广播的第一个规则就是如果所有的输入数组没有相同数量的维度,“1”会被重复的预考虑到更小的数组的形状上,直到所有的数组有着相同的维度数量;b、广播的第二个规则就是确保有着size为1的数组可以沿着特定的维度进行操作,就好像沿着这个维度上数组有着最大的形状(shape)。数组元素的值被假设成沿着这个维度有着一样的“广播”数组(个人:这里和matlab中bsxfun的行为类似)。在广播规则的应用之后,所有的数组的size必须可以匹配。更多的可以查看 。

6、索引技巧

        numpy提供比常规python序列更多的索引方法。除了整合和分片,数组可以被整数数组和布尔数组索引。

    1)索引数组的索引

      

当索引数组是多维度的时候,索引数组中的每个值指向的是原来数组中的第一维(也就是顶层)。下面例子就是,image(2*4)索引的是palette(5*3的矩阵)中第一维上不同的子数组,结果为2*4*3的矩阵:

下面就是给出多个索引数组,这时候每个索引数组必须shape一致,这时候可以看成是坐标点的形式:

当然,我们可以将i 和 j  放入一个序列中(一个列表)然后对该列表进行索引:

然而,我们没法将 i 和 j 放入数组中,因为这个数组会被解释成 a 的第一维索引:  

下面是另一个使用索引数组的例子:

可以使用索引数组来对指定元素进行更新:

然而,当索引的列表里面有重复的时候,同一个元素会被更新好几次,并保留最后的更新:

当想要使用python的+=来构建的时候,它的结果也许不像你期望的那样(没有重复更新):

即使0在索引列表中发生了两次。第0个元素只更新一次。这是因为python要求“a+=1”等效于“a = a+1”.

    2)布尔数组的索引

         当我们通过索引(整数)数组来对数组进行索引,我们提供了索引的列表去进行挑选,显式的选择数组中的项是否是我们想要的。对布尔索引的最自然的方式就是使用与原始数组一样shape的布尔数组:

这个特性在赋值的时候很有用:

可以通过查看来了解如何使用布尔索引来生成 的图像的。

第二种布尔索引的方式更类似于整数索引,对于数组的每一维来说,我们给它一个1维布尔数组来选择我们想要的分片:

注意到1维布尔数组的长度必须与你想要分片的维度(或者轴)的长度一致。在之前的例子中,b1 是一个 1-rank 的数组,长度为3(在a 中行的数量),和b2 (长度为4)是匹配a 的第二个秩(列)的索引的。

    3)ix_()函数

    ix_function可以组合不同维度的向量,然后按照参数的个数来决定进行多少维度的升级,下面是三个参数,所以ax有三维,如果是四个参数,那么ax就是四维了。例如,按照式子a+b*c来计算向量a,b,c 的所有元素的组合,得到的是一个4*3*5的矩阵:

或者定义函数:

然后像下面这样使用:

与ufunc.reduce(numpy中的方法)相比较,该版本的优势在于它会使用广播规则( )来避免生成参数数组等。

    4)字符串的索引

见.

7、线性代数

    基本的线性代数都包含在这里。

    1)单一数组操作

    numpy的文件夹内的linalg.py的代码可以更细致的介绍原理:

    2)矩阵类

     对矩阵类的简短介绍:

    3)索引:矩阵和2D数组的比较

    注意到在 数组和矩阵之间不同的。 提供了两种基本的对象:一个N维的数组对象和一个通用的函数对象。其他对象都是建立在这些上面的。特别的,矩阵是2维的数组对象,继承自 数组对象。对于数组和矩阵来说,索引必须包含合适的一个或多个的组合:整数标量、省略号、整数或布尔值列表、整数或布尔值元组、整数或布尔值的1维数组。一个矩阵可以用来作为矩阵的索引,但是通常的数组、列表或其他的形式需要在给定的任务下完成。

    在python中,索引是从0开始的。传统的,我们将一个2D数组或矩阵表示成使用行和列的常规数组,沿着轴0移动就是沿着行移动,沿着轴1移动就是沿着列移动。

如下例子:

现在,让我们考虑一些简单的分片。基本的分片使用分片对象或者整数。例如,A[:]和M[:]在python中很相似,然而必须注意到分片Numpy数组不是对数据进行拷贝;分片提供对同一个数据的一个新的视图:

现在,对于python索引中的一些不同,你也许会使用逗号隔开的索引对多个轴进行索引:

注意到上面两个结果的不同。使用一个2D数组的复制会生成一个1维的数组,而对于一个矩阵来说,会生成一个2维的数组。一个矩阵的分片总是会生成一个矩阵。例如,一个分片M[2,:]生成一个shape(1,4)的矩阵。相反地,一个数组的分片总是生成一个最低可能维度的数组。例如,如果C是一个3维的数组,C[...,1]生成一个2D数组,同时C[1,...,1]会生成一个1维数组。我们显示的结果就能看出在数组分片的结果和对应的矩阵分片的结果是不一样的。

    这里假设我们想要数组的第一和第三列。一种分片的方法是使用列表:

较为老练的方法是使用take()方法:

如果我们想要跳过第一行,我们可以:

或者简单的使用A[1:,[1,3]]。然而,另一个方法是使用交叉结果:

为了读者的方便,再次打印数组:

现在,让我们做一些更复杂的事情。假设我们想要保留所有的那些第一行大于1的列。一种方法是生成一个布尔索引:

正如我们想要的!但是索引矩阵不是很方便:

问题就是分片矩阵的块会生成一个矩阵。但是矩阵有一个方便的“A”属性,它的值就是数组的表征,所以我们可以如下:

如果想要有条件的在两个方向上分片矩阵。如下:

需要使用交叉积(cross product)“ix_”:

8、技巧和提示

    这里给出一些短的但是有用的提示。   

    1)“自动”reshaping  

使用-1可以让机器自己计算该维度上是多少:

    2)向量堆叠

     如何从一个相同size的行向量列表中构建一个2D数组?在matlab中这是相当容易的:如果 x 和 y 是两个有着相同大小的向量,你只需要 m = [x:y]就行。在Numpy中这是通过函数 column_stackdstackhstack 和 vstack,取决于哪个维度上的需要被堆叠。例如:


在超过2维情况下这些函数背后的逻辑是相当奇怪的。可以详细的观看  。

    3)直方图

    应用在一个数组上的numpy的histogram函数会返回一个向量对:数组的直方图和bins的向量。注意:matplotlib同样有个函数来建立直方图(叫做hist,和matlab中一样)这不同于numpy中的这个函数。主要的区别在于pylab.hist会自动画直方图,而numpy.histogram只是生成数据:

9、参考资料

 

    • The .

    • The .

    • The nonexistent  at scipy.org, where we can find the old Numeric documentation.

    • The  book.

    • The  and a 

    • .

    • A .

显示全文