Python最牛逼数据分析库!月薪35K大牛:整理的NumPy详细教程!
NumPy在数据分析就像Django在web开发中那样出名,就是老大哥,也是在工作中最常用的库,没有之一,今天给大家详细的讲解一下这个库的妙用!
103456743
ndarray.ndim
数组轴的个数,在python的世界中,轴的个数被称作秩
ndarray.shape
数组的维度。这是一个指示数组在每个维度上大小的整数元组。例如一个n排m列的矩阵,它的shape属性将是(2,3),这个元组的长度显然是秩,即维度或者ndim属性
ndarray.size
数组元素的总个数,等于shape属性中元组元素的乘积。
ndarray.dtype
一个用来描述数组中元素类型的对象,可以通过创造或指定dtype使用标准Python类型。另外NumPy提供它自己的数据类型。
ndarray.itemsize
数组中每个元素的字节大小。例如,一个元素类型为float64的数组itemsiz属性值为8(=64/8),又如,一个元素类型为complex32的数组item属性为4(=32/8).
ndarray.data
包含实际数组元素的缓冲区,通常我们不需要使用这个属性,因为我们总是通过索引来使用数组中的元素。
x[1,2,…] 等同于 x[1,2,:,:,:],
x[…,3] 等同于 x[:,:,:,:,3]
x[4,…,5,:] 等同 x[4,:,:,5,:].
进阶
广播法则(rule)
广播法则能使通用函数有意义地处理不具有相同形状的输入。
广播第一法则是,如果所有的输入数组维度不都相同,一个“1”将被重复地添加在维度较小的数组上直至所有的数组拥有一样的维度。
广播第二法则确定长度为1的数组沿着特殊的方向表现地好像它有沿着那个方向最大形状的大小。对数组来说,沿着那个维度的数组元素的值理应相同。
应用广播法则之后,所有数组的大小必须匹配。更多细节可以从这个文档找到。
第二种通过布尔来索引的方法更近似于整数索引;对数组的每个维度我们给一个一维布尔数组来选择我们想要的切片。
>>> a = arange(12).reshape(3,4)>>> b1 = array([False,True,True]) # first dim selection>>> b2 = array([True,False,True,False]) # second dim selection>>>>>> a[b1,:] # selecting rowsarray([[ 4, 5, 6, 7], [ 8, 9, 10, 11]])>>>>>> a[b1] # same thingarray([[ 4, 5, 6, 7], [ 8, 9, 10, 11]])>>>>>> a[:,b2] # selecting columnsarray([[ 0, 2], [ 4, 6], [ 8, 10]])>>>>>> a[b1,b2] # a weird thing to doarray([ 4, 10])
注意一维数组的长度必须和你想要切片的维度或轴的长度一致,在之前的例子中,b1是一个秩为1长度为三的数组(a的行数),b2(长度为4)与a的第二秩(列)相一致。7
ix_()函数
ix_函数可以为了获得多元组的结果而用来结合不同向量。例如,如果你想要用所有向量a、b和c元素组成的三元组来计算a+b*c:
>>> a = array([2,3,4,5])>>> b = array([8,5,4])>>> c = array([5,4,6,8,3])>>> ax,bx,cx = ix_(a,b,c)>>> axarray([[[2]], [[3]], [[4]], [[5]]])>>> bxarray([[[8], [5], [4]]])>>> cxarray([[[5, 4, 6, 8, 3]]])>>> ax.shape, bx.shape, cx.shape((4, 1, 1), (1, 3, 1), (1, 1, 5))>>> result = ax+bx*cx>>> resultarray([[[42, 34, 50, 66, 26], [27, 22, 32, 42, 17], [22, 18, 26, 34, 14]], [[43, 35, 51, 67, 27], [28, 23, 33, 43, 18], [23, 19, 27, 35, 15]], [[44, 36, 52, 68, 28], [29, 24, 34, 44, 19], [24, 20, 28, 36, 16]], [[45, 37, 53, 69, 29], [30, 25, 35, 45, 20], [25, 21, 29, 37, 17]]])>>> result[3,2,4]17>>> a[3]+b[2]*c[4]17
你也可以实行如下简化:
def ufunc_reduce(ufct, *vectors): vs = ix_(*vectors) r = ufct.identity for v in vs: r = ufct(r,v) return r
然后这样使用它:
>>> ufunc_reduce(add,a,b,c)array([[[15, 14, 16, 18, 13], [12, 11, 13, 15, 10], [11, 10, 12, 14, 9]], [[16, 15, 17, 19, 14], [13, 12, 14, 16, 11], [12, 11, 13, 15, 10]], [[17, 16, 18, 20, 15], [14, 13, 15, 17, 12], [13, 12, 14, 16, 11]], [[18, 17, 19, 21, 16], [15, 14, 16, 18, 13], [14, 13, 15, 17, 12]]])
这个reduce与ufunc.reduce(比如说add.reduce)相比的优势在于它利用了广播法则,避免了创建一个输出大小乘以向量个数的参数数组。8
用字符串索引
参见RecordArray。
线性代数
继续前进,基本线性代数包含在这里。
简单数组运算
参考numpy文件夹中的linalg.py获得更多信息
>>> from numpy import *>>> from numpy.linalg import *>>> a = array([[1.0, 2.0], [3.0, 4.0]])>>> print a[[ 1. 2.][ 3. 4.]]>>> a.transpose()array([[ 1., 3.], [ 2., 4.]])>>> inv(a)array([[-2. , 1. ], [ 1.5, -0.5]])>>> u = eye(2) # unit 2x2 matrix; "eye" represents "I">>> uarray([[ 1., 0.], [ 0., 1.]])>>> j = array([[0.0, -1.0], [1.0, 0.0]])>>> dot (j, j) # matrix productarray([[-1., 0.], [ 0., -1.]])>>> trace(u) # trace2.0>>> y = array([[5.], [7.]])>>> solve(a, y)array([[-3.], [ 4.]])>>> eig(j)(array([ 0.+1.j, 0.-1.j]),array([[ 0.70710678+0.j, 0.70710678+0.j], [ 0.00000000-0.70710678j, 0.00000000+0.70710678j]]))Parameters: square matrixReturns The eigenvalues, each repeated according to its multiplicity. The normalized (unit "length") eigenvectors, such that the column ``v[:,i]`` is the eigenvector corresponding to the eigenvalue ``w[i]`` .
矩阵类
这是一个关于矩阵类的简短介绍。
>>> A = matrix("1.0 2.0; 3.0 4.0")>>> A[[ 1. 2.][ 3. 4.]]>>> type(A) # file where class is defined<class "numpy.matrixlib.defmatrix.matrix">>>> A.T # transpose[[ 1. 3.][ 2. 4.]]>>> X = matrix("5.0 7.0")>>> Y = X.T>>> Y[[5.][7.]]>>> print A*Y # matrix multiplication[[19.][43.]]>>> print A.I # inverse[[-2. 1. ][ 1.5 -0.5]]>>> solve(A, Y) # solving linear equationmatrix([[-3.], [ 4.]])
索引:比较矩阵和二维数组
注意NumPy中数组和矩阵有些重要的区别。NumPy提供了两个基本的对象:一个N维数组对象和一个通用函数对象。其它对象都是建构在它们之上 的。特别的,矩阵是继承自NumPy数组对象的二维数组对象。对数组和矩阵,索引都必须包含合适的一个或多个这些组合:整数标量、省略号 (ellipses)、整数列表;布尔值,整数或布尔值构成的元组,和一个一维整数或布尔值数组。矩阵可以被用作矩阵的索引,但是通常需要数组、列表或者 其它形式来完成这个任务。
现在有些和Python索引不同的了:你可以同时使用逗号分割索引来沿着多个轴索引。
>>> print A[:,1]; print A[:,1].shape[1 5 9](3,)>>> print M[:,1]; print M[:,1].shape[[1][5][9]](3, 1)
>>> A[:,[1,3]]array([[ 1, 3], [ 5, 7], [ 9, 11]])
稍微复杂点的方法是使用take()方法(method):
>>> A[:,].take([1,3],axis=1)array([[ 1, 3], [ 5, 7], [ 9, 11]])
如果我们想跳过第一行,我们可以这样:
>>> A[1:,].take([1,3],axis=1)array([[ 5, 7], [ 9, 11]])
或者我们仅仅使用A[1:,[1,3]]。还有一种方法是通过矩阵向量积(叉积)。
>>> A[ix_((1,2),(1,3))]array([[ 5, 7], [ 9, 11]])
为了读者的方便,在次写下之前的矩阵:
>>> A[ix_((1,2),(1,3))]array([[ 5, 7], [ 9, 11]])
现在让我们做些更复杂的。比如说我们想要保留第一行大于1的列。一种方法是创建布尔索引:
>>> A[0,:]>1array([False, False, True, True], dtype=bool)>>> A[:,A[0,:]>1]array([[ 2, 3], [ 6, 7], [10, 11]])
就是我们想要的!但是索引矩阵没这么方便。
>>> M[0,:]>1matrix([[False, False, True, True]], dtype=bool)>>> M[:,M[0,:]>1]matrix([[2, 3]])
技巧和提示
下面我们给出简短和有用的提示。
“自动”改变形状
更改数组的维度,你可以省略一个尺寸,它将被自动推导出来。
>>> a = arange(30)>>> a.shape = 2,-1,3 # -1 means "whatever is needed">>> a.shape(2, 5, 3)>>> aarray([[[ 0, 1, 2], [ 3, 4, 5], [ 6, 7, 8], [ 9, 10, 11], [12, 13, 14]], [[15, 16, 17], [18, 19, 20], [21, 22, 23], [24, 25, 26], [27, 28, 29]]])
2 NumPy-快速处理数据
标准安装的Python中用列表(list)保存一组值,可以用来当作数组使用,不过由于列表的元素可以是任何对象,因此列表中所保存的是对象的指针。这样为了保存一个简单的[1,2,3],需要有3个指针和三个整数对象。对于数值运算来说这种结构显然比较浪费内存和CPU计算时间。
此外Python还提供了一个array模块,array对象和列表不同,它直接保存数值,和C语言的一维数组比较类似。但是由于它不支持多维,也没有各种运算函数,因此也不适合做数值运算。
NumPy的诞生弥补了这些不足,NumPy提供了两种基本的对象:ndarray(N-dimensional array object)和 ufunc(universal function object)。ndarray(下文统一称之为数组)是存储单一数据类型的多维数组,而ufunc则是能够对数组进行处理的函数。
数组的大小可以通过其shape属性获得:
>>> a.shape(4,)>>> c.shape(3, 4)
数组a的shape只有一个元素,因此它是一维数组。而数组c的shape有两个元素,因此它是二维数组,其中第0轴的长度为3,第1轴的长度为4。还可以通过修改数组的shape属性,在保持数组元素个数不变的情况下,改变数组每个轴的长度。下面的例子将数组c的shape改为(4,3),注意从(3,4)改为(4,3)并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变:
数组的元素类型可以通过dtype属性获得。上面例子中的参数序列的元素都是整数,因此所创建的数组的元素类型也是整数,并且是32bit的长整型。可以通过dtype参数在创建时指定元素类型:
>>> np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.float)array([[ 1., 2., 3., 4.], [ 4., 5., 6., 7.], [ 7., 8., 9., 10.]])>>> np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]], dtype=np.complex)array([[ 1.+0.j, 2.+0.j, 3.+0.j, 4.+0.j], [ 4.+0.j, 5.+0.j, 6.+0.j, 7.+0.j], [ 7.+0.j, 8.+0.j, 9.+0.j, 10.+0.j]])
上面的例子都是先创建一个Python序列,然后通过array函数将其转换为数组,这样做显然效率不高。因此NumPy提供了很多专门用来创建数组的函数。下面的每个函数都有一些关键字参数,具体用法请查看函数说明。
arange函数类似于python的range函数,通过指定开始值、终值和步长来创建一维数组,注意数组不包括终值:
>>> np.arange(0,1,0.1)array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
linspace函数通过指定开始值、终值和元素个数来创建一维数组,可以通过endpoint关键字指定是否包括终值,缺省设置是包括终值:
>>> 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. ])
logspace函数和linspace类似,不过它创建等比数列,下面的例子产生1(10^0)到100(10^2)、有20个元素的等比数列:
>>> np.logspace(0, 2, 20)array([ 1. , 1.27427499, 1.62377674, 2.06913808, 2.6366509 , 3.35981829, 4.2813324 , 5.45559478, 6.95192796, 8.8586679 , 11.28837892, 14.38449888, 18.32980711, 23.35721469, 29.76351442, 37.92690191, 48.32930239, 61.58482111, 78.47599704, 100. ])
使用布尔数组
当使用布尔数组b作为下标存取数组x中的元素时,将收集数组x中所有在数组b中对应下标为True的元素。使用布尔数组作为下标获得的数组不和原始数组共享数据空间,注意这种方式只对应于布尔数组,不能使用布尔列表。
.1.3 多维数组
多维数组的存取和一维数组类似,因为多维数组有多个轴,因此它的下标需要用多个值来表示,NumPy采用组元(tuple)作为数组的下标。如图2.1所示,a为一个6x6的数组,图中用颜色区分了各个下标以及其对应的选择区域。
组元不需要圆括号
虽然我们经常在Python中用圆括号将组元括起来,但是其实组元的语法定义只需要用逗号隔开即可,例如 x,y=y,x 就是用组元交换变量值的一个例子。
a[(0,1,2,3,4),(1,2,3,4,5)] : 用于存取数组的下标和仍然是一个有两个元素的组元,组元中的每个元素都是整数序列,分别对应数组的第0轴和第1轴。从两个序列的对应位置取出两个整数组成下标: a[0,1], a[1,2], ..., a[4,5]。
a[3:, [0, 2, 5]] : 下标中的第0轴是一个范围,它选取第3行之后的所有行;第1轴是整数序列,它选取第0, 2, 5三列。
a[mask, 2] : 下标的第0轴是一个布尔数组,它选取第0,2,5行;第1轴是一个整数,选取第2列。
2.1.4 结构数组
在C语言中我们可以通过struct关键字定义结构类型,结构中的字段占据连续的内存空间,每个结构体占用的内存大小都相同,因此可以很容易地定义结构数组。和C语言一样,在NumPy中也很容易对这种结构数组进行操作。只要NumPy中的结构定义和C语言中的定义相同,NumPy就可以很方便地读取C语言的结构数组的二进制数据,转换为NumPy的结构数组。
假设我们需要定义一个结构数组,它的每个元素都有name, age和weight字段。在NumPy中可以如下定义
内存对齐
C语言的结构体为了内存寻址方便,会自动的添加一些填充用的字节,这叫做内存对齐。例如如果把下面的name[32]改为name[30]的话,由于内存对齐问题,在name和age中间会填补两个字节,最终的结构体大小不会改变。因此如果numpy中的所配置的内存大小不符合C语言的对齐规范的话,将会出现数据错位。为了解决这个问题,在创建dtype对象时,可以传递参数align=True,这样numpy的结构数组的内存对齐和C语言的结构体就一致了。
用下面的字典参数也可以定义结构类型,字典的关键字为结构中字段名,值为字段的类型描述,但是由于字典的关键字是没有顺序的,因此字段的顺序需要在类型描述中给出,类型描述是一个组元,它的第二个值给出字段的字节为单位的偏移量,例如age字段的偏移量为25个字节:
>>> np.dtype({"surname":("S25",0),"age":(np.uint8,25)})dtype([("surname", "|S25"), ("age", "|u1")])
2.1.5 内存结构
下面让我们来看看ndarray数组对象是如何在内存中储存的。如图2.3所示,关于数组的描述信息保存在一个数据结构中,这个结构引用两个对象:一块用于保存数据的存储区域和一个用于描述元素类型的dtype对象。
sin函数的第二个参数也是x,那么它所做的事情就是对x中的每给值求正弦值,并且把结果保存到x中的对应的位置中。此时函数的返回值仍然是整个计算的结果,只不过它就是x,因此两个变量的id是相同的(变量t和变量x指向同一块内存区域)。
我用下面这个小程序,比较了一下numpy.math和P
ython标准库的math.sin的计算速度::
请注意numpy.sin的计算速度只有math.sin的1/5。这是因为numpy.sin为了同时支持数组和单个值的计算,其C语言的内部实现要比math.sin复杂很多,如果我们同样在Python级别进行循环的话,就会看出其中的差别了。此外,numpy.sin返回的数的类型和math.sin返回的类型有所不同,math.sin返回的是Python的标准float类型,而numpy.sin则返回一个numpy.float64类型:
由于Python的操作符重载功能,计算两个数组相加可以简单地写为a+b,而np.add(a,b,a)则可以用a+=b来表示。下面是数组的运算符和其对应的ufunc函数的一个列表,注意除号"/"的意义根据是否激活__future__.division有所不同。
y = x1 + x2:add(x1, x2 [, y])y = x1 - x2:subtract(x1, x2 [, y])y = x1 * x2:multiply (x1, x2 [, y])y = x1 / x2:divide (x1, x2 [, y]), 如果两个数组的元素为整数,那么用整数除法y = x1 / x2:true divide (x1, x2 [, y]), 总是返回精确的商y = x1 // x2:floor divide (x1, x2 [, y]), 总是对返回值取整y = -x:negative(x [,y])y = x1**x2:power(x1, x2 [, y])y = x1 % x2:remainder(x1, x2 [, y]), mod(x1, x2, [, y])
值得注意的是用frompyfunc得到的函数计算出的数组元素的类型为object,因为frompyfunc函数无法保证Python函数返回的数据类型都完全一致。因此还需要再次 y2.astype(np.float64)将其转换为双精度浮点数组。
2.2.1 广播
当我们使用ufunc函数对两个数组进行计算时,ufunc函数会对这两个数组的对应元素进行计算,因此它要求这两个数组有相同的大小(shape相同)。如果两个数组的shape不同的话,会进行如下的广播(broadcasting)处理:
让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分都通过在前面加1补齐
输出数组的shape是输入数组shape的各个轴上的最大值
如果输入数组的某个轴和输出数组的对应轴的长度相同或者其长度为1时,这个数组能够用来计算,否则出错
当输入数组的某个轴的长度为1时,沿着此轴运算时都用此轴上的第一组值
上述4条规则理解起来可能比较费劲,让我们来看一个实际的例子。
先创建一个二维数组a,其shape为(6,1):
这样加法运算的两个输入数组的shape分别为(6,1)和(1,5),根据规则2,输出数组的各个轴的长度为输入数组各个轴上的长度的最大值,可知输出数组的shape为(6,5)。
由于b的第0轴上的长度为1,而a的第0轴上的长度为6,因此为了让它们在第0轴上能够相加,需要将b在第0轴上的长度扩展为6,这相当于:
ogrid是一个很有趣的对象,它像一个多维数组一样,用切片组元作为下标进行存取,返回的是一组可以用来广播计算的数组。其切片下标有两种形式:
开始值:结束值:步长,和np.arange(开始值, 结束值, 步长)类似
开始值:结束值:长度j,当第三个参数为虚数时,它表示返回的数组的长度,和np.linspace(开始值, 结束值, 长度)类似:
.2.2 ufunc的方法
ufunc函数本身还有些方法,这些方法只对两个输入一个输出的ufunc函数有效,其它的ufunc对象调用这些方法时会抛出ValueError异常。
reduce 方法和Python的reduce函数类似,它沿着axis轴对array进行操作,相当于将<op>运算符插入到沿axis轴的所有子数组或者元素当中。
<op>.reduce (array=, axis=0, dtype=None)
例如:
if indices[i] < indices[i+1]:result[i] = np.reduce(a[indices[i]:indices[i+1]])else:result[i] = a[indices[i]
而最后一个元素如下计算:
np.reduce(a[indices[-1]:])
因此上面例子中,结果的每个元素如下计算而得:
2.3 矩阵运算
NumPy和Matlab不一样,对于多维数组的运算,缺省情况下并不使用矩阵运算,如果你希望对数组进行矩阵运算的话,可以调用相应的函数。
matrix对象
numpy库提供了matrix类,使用matrix类创建的是矩阵对象,它们的加减乘除运算缺省采用矩阵方式计算,因此用法和matlab十分类似。但是由于NumPy中同时存在ndarray和matrix对象,因此用户很容易将两者弄混。这有违Python的“显式优于隐式”的原则,因此并不推荐在较复杂的程序中使用matrix。下面是使用matrix的一个例子:
使用numpy.savetxt和numpy.loadtxt可以读写1维和2维的数组:
- 中国最牛书生,日本商界大亨说:没有他,就没有强盛的大和民族!
- 最牛麻将馆,交警都笑毁了!
- 为什么说 Python 和 Pygame 最适合编程初学者 | Linux 中国
- 白手起家,年入80万亿,他们才是史上是最牛创业团队!
- 洛阳——河南最牛的城市!创造人类历史上130个世界之最,你服么?
- 中国轨道交通最牛城市, 让人有身处纽约的错觉, 连北上广都做不到
- 中国近20年最牛基金经理:兵无常势陈一峰
- 中国最牛小镇,百年前走出5位总统、9位总理、数十位将军
- 中国近20年最牛基金经理:博采众长的傅鹏博
- 中国近20年最牛基金经理:邱国鹭战胜三次股灾