美文网首页码农的世界Python可以做的那些事!我爱编程
利用Python进行数据分析(二十)之numpy高级应用

利用Python进行数据分析(二十)之numpy高级应用

作者: BrainZou | 来源:发表于2018-05-23 23:02 被阅读26次

    ndarray对象的内部机理

    NumPy的ndarray提供了一种将同质数据块(可以是连续或跨越的,稍后将详细讲解)解释为多维数组对象的方式。正如你之前所看到的那样,数据类型(dtype)决定了数据的解释方式,比如浮点数、整数、布尔值等。
    ndarray如此强大的部分原因是所有数组对象都是数据块的一个跨度视图(stridedview)。你可能想知道数组视图arr[::2,::-1]不复制任何数据的原因是什么。简单地说,ndarray不只是一块内存和一个dtype,它还有跨度信息,这使得数组能以各种步幅(step size)在内存中移动译注2.更准确地讲,ndarray内部由以下内容组成:

    1. 一个指向数组(一个系统内存块)的指针。
    2. 数据类型或dtype。
    3. 一个表示数组形状(shape)的元组,例如,一个10×5的数组,其形状为(10,5)。
    np.ones((10,5)).shape
    (10,5)
    
    1. 一个跨度元组(stride),其中的整数指的是为了前进到当前维度下一个元素需要“跨过”的字节数,例如,一个典型的(C顺序,稍后将详细讲解)3×4×5的float64(8个字节)数组,其跨度为(160,40,8)。
    np. ones((3,4,5), dtype=np. float64). strides 
    (160,40,8)
    

    虽然NumPy用户很少会对数组的跨度信息感兴趣,但它们却是构建非复制式数组视图的重要因素。跨度甚至可以是负数,这样会使数组在内存中后向移动,比如在切片obj[::-1]或obj[:,::-1]中就是这样的。


    NumPy数据类型体系

    你可能偶尔需要检查数组中所包含的是否是整数、浮点数、字符串或Python对象。因为浮点数的种类很多,判断dtype是否属于某个大类的工作非常繁琐。幸运的是,dtype都有一个超类(比如np.integer和np.floating),它们可以跟np.issubdtype函数结合使用:

    ints=np.ones(10, dtype=np.uint16) 
    loats=np.ones(10, dtype=np.float32) 
    np.issubdtype(ints.dtype, np.integer) 
    True 
    np.issubdtype(floats.dtype, np.floating) 
    True
    

    调用dtype的mro方法即可查看其所有的父类:

    np.float64.mro()
    [numpy. float64,
     numpy. floating, 
    numpy. inexact, 
    numpy. number, 
    numpy. generic, 
    float,
    object]
    
    NumPy的dtype体系

    高级数组操作

    除花式索引、切片、布尔条件取子集等操作之外,数组的操作方式还有很多。虽然pandas中的高级函数可以处理数据分析工作中的许多重型任务,但有时你还是需要编写一些在现有库中找不到的数据算法。

    数组重塑

    鉴于我们已经学过的有关NumPy数组的知识,当你知道“无需复制任何数据,数组就能从一个形状转换为另一个形状”时应该会感到有一点吃惊。只需向数组的实例方法reshape传入一个表示新形状的元组即可实现该目的。假设有一个一维数组,我们希望将其重新排列为一个矩阵:

    arr=np.arange(8)
    arr
    array([0,1,2,3,4,5,6,7])
    arr.reshape((4,2))
    array([[0,1], [2,3], [4,5], [6,7]])
    

    多维数组也能被重塑:

    arr.reshape((4,2)).reshape((2,4))
    array([[0,1,2,3], [4,5,6,7]])
    

    作为参数的形状的其中一维可以是-1,它表示该维度的大小由数据本身推断而来:

    arr=np.arange(15)
    arr.reshape((5,-1))
    array([[0,1,2], [3,4,5], 6,7,8], [9,10,11], [12,13,14]])
    

    由于数组的shape属性是一个元组,因此它也可以被传入reshape:

    other_arr=np.ones((3,5))
    other arr.shape 
    (3,5)
    arr.reshape(other_arr.shape)
    array([[ 0,1,2,3,4],[5,6,7,8,9],[10,11,12,13,14]])
    

    与reshape将一维数组转换为多维数组的运算过程相反的运算通常称为扁平化(flattening)或散开(raveling):

    arr=np.arange(15).reshape((5,3))
    arr
    array([[0,1,2], [3,4,5], [6,7,8], [9,10,11], [12,13,14]])
    arr.ravel()
    array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14])
    

    如果没有必要,rave1不会产生源数据的副本(下面将详细介绍).flatten方法的行为类似于rave1,只不过它总是返回数据的副本:

    arr. flatten() array([o,1,2,3,4,5,6,7,8,9,10,11,12,13,14])
    

    数组可以被重塑或散开为别的顺序。

    C和Fortran顺序

    与其他科学计算环境相反(如R和MATLAB),NumPy允许你更为灵活地控制数据在内存中的布局。默认情况下,NumPy数组是按行优先顺序创建的。在空间方面,这就意味着,对于一个二维数组,每行中的数据项是被存放在相邻内存位置上的。另一种顺序是列优先顺序,它意味着(猜到了吧)每列中的数据项是被存放在相邻内存位置上的。由于一些历史原因,行和列优先顺序又分别称为C和Fortran顺序。在FORTRAN77中(前辈们的语言),矩阵全都是列优先的。像reshape和reval这样的函数,都可以接受一个表示数组数据存放顺序的order参数。一般可以是‘C’或‘F’(还有‘A’和‘K’等不常用的选项,具体请参考NumPy的文档)。

    arr=np. arange(12). reshape((3,4)) 
    arr 
    [[0,1,2,3], [4,5,6,7], [8,9,10,11]])
    arr.ravel() 
    array([0,1,2,3,4,5,6,7,8,9,10,11]) 
    arr. ravel('F') 
    array([0,4,8,1,5,9,2,6,10,3,7,11])
    

    二维或更高维数组的重塑过程比较令人费解.C和Fortran顺序的关键区别就是维度的行进顺序:

    1. C/行优先顺序:先经过更高的维度(例如,轴1会先于轴0被处理)。
    2. Fortran/列优先顺序:后经过更高的维度(例如,轴0会先于轴1被处理)。

    数组的合并和拆分

    numpy.concatenate可以按指定轴将一个由数组组成的序列(如元组、列表等)连接到一起。

    arr1=np. array([[1,2,3],[4,5,6]])
    arr2=np. array([[7,8,9],[10,11,12]])
    np. concatenate([ arr1, arr2], axis=0) 
    array([[1,2,3], [4,5,6], [7,8,9], [10,11,12]])
    np. concatenate([ arr1, arr2], axis=1)
    array([[ 1,2,3,7,8,9], [4,5,6,10,11,12]])
    

    对于常见的连接操作,NumPy提供了一些比较方便的方法(如vstack和hstack)。因此,上面的运算还可以表达为:

    np.vstack((arr1,arr2))
    array([[1,2,3],[4,5,6], [7,8,9], [10,11,12]])
    np.hstack((arr1,arr2))
    array([[1,2,3,7,8,9], [4,5,6,10,11,12]])
    

    与此相反,split用于将一个数组沿指定轴拆分为多个数组:

    from numpy.random import randn arr=randn(5,2)
    arr
    array([[ 0.1689,0.3287], [0.4703,0.8989], 
    [0.1535,0.0243], [-0.2832,1.1536], [0.2707,0.8075]])
    first,second,third =np.split(arr,[1,3]) 
    first 
    array([[ 0.1689,0.3287]])
    second
    array([[0.4703,0.8989], [0.1535,0.0243]])
    third
    array([[-0.2832,1.1536], [0.2707,0.8075]])
    
    数组连接函数

    堆叠辅助类:r_和c_

    NumPy命名空间中有两个特殊的对象一r_和c_,它们可以使数组的堆叠操作更为简洁:

    arr=np. arange(6) 
    arr1=arr. reshape((3,2))
    arr2=randn(3,2)
    np.r_[arr1,arr2]
    array([[0.,1.], [2.,3.], [4.,5.], 
    [0.7258,-1.5325], [-0.4696,-0.2127], [-0.1072,1.2871]])
    np.c_[np.r_[arr1,arr2],arr]
    array([[o.,1.,0.], [2.,3.,1.], [4.,5.,2.], [0.7258,-1.5325,3.], [-0.4696,-0.2127,4.][-0.1072,1.2871,5.]])
    #此外,它还可以将切片翻译为数组:
    np.c_[1:6,-10:-5]
    array([[1,-10], [2,-9], [3,-8],[4,-7],[5,-6]])
    

    元素的重复操作:title和repeat

    对数组进行重复以产生更大数组的工具主要是repeat和tile这两个函数。repeat会将数组中的各个元素重复一定次数,从而产生一个更大的数组:

    arr=np. arange(3) 
    arr. repeat(3) 
    array([0,0,0,1,1,1,2,2,2])
    

    默认情况下,如果传入的是一个整数,则各元素就都会重复那么多次。如果传入的是一组整数,则各元素就可以重复不同的次数:

    arr. repeat([2,3,4]) 
    array([0,0,1,1,1,2,2,2,2])
    

    对于多维数组,还可以让它们的元素沿指定轴重复。

    arr=randn(2,2)
    arr
    array([[0.7157,-0.6387], [0.3626,0.849]]) 
    arr. repeat(2, axis=0)
    array([[0.7157,-0.6387], [0.7157,-0.6387], [0.3626,0.849], [0.3626,0.849]])
    

    注意,如果没有设置轴向,则数组会被扁平化,这可能不会是你想要的结果。同样,在对多维进行重复时,也可以传入一组整数,这样就会使各切片重复不同的次数:

    arr. repeat([2,3], axis=o) 
    array([[0.7157,-0.6387], [0.7157,-0.6387], [0.3626,0.849], [0.3626,0.849], [0.3626,0.849]]) arr. repeat([2,3], axis=1)
    array([[0.7157,0.7157,-0.6387,-0.6387,-0.6387], [0.3626,0.3626,0.849,0.849,0.849]])
    

    tile的功能是沿指定轴向堆叠数组的副本。你可以形象地将其想象成“铺瓷砖”:

    arr 
    array([[0.7157,-0.6387], [0.3626,0.849]]) 
    np.tile(arr,2)
    array([[0.7157,-0.6387,0.7157,-0.6387], [0.3626,0.849,0.3626,0.849]])
    

    第二个参数是瓷砖的数量。对于标量,瓷砖是水平铺设的,而不是垂直铺设。它可以是一个表示“铺设”布局的元组:

    arr
    array([[ 0.7157,-0.6387], [0.3626,0.849]])
    np.tile(arr,(2,1))
    array([[o.7157,-0.6387], [0.3626,0.849], [0.7157,-0.63871],[0.3626,0.849]])
    np.tile(arr,(3,2))
    array([[0.7157,-0.6387,0.7157,-0.6387], [0.3626,0.849,0.3626,0.849], [0.7157,-0.6387,0.7157,-0.6387], [0.3626,0.849,0.3626,0.849], [0.7157,-0.6387,0.7157,-0.6387], [0.3626,0.849,0.3626,0.849]])
    

    花式索引的等价函数:take和put

    在第4章中我们讲过,获取和设置数组子集的一个办法是通过整数数组使用花式索引:

    arr=np.arange(10)*100
    inds=[7,1,2,6]
    arr[inds]
    array([700,100,200,600])
    

    ndarray有两个方法专门用于获取和设置单个轴向上的选区:

    arr.take(inds) 
    array([70o,100,200,600]) 
    arr.put(inds,42) 
    arr 
    array([0,42,42,300,400,500,42,42,800,900]) arr.put(inds,[40,41,42,43]) 
    arr 
    array([0,41,42,300,400,500,43,40,800,900])
    

    要在其他轴上使用take,只需传入axis关键字即可:

    inds=[2,0,2,1]
    arr=randn(2,4)
    arr
    array([[-0.8237,2.6047,-0.4578,-1.], [2.3198,-1.0792,0.518,0.2527]]) 
    arr.take(inds, axis=1)
    array([[-0.4578,-0.8237,-0.4578,2.6047], [0.518,2.3198,0.518,-1.0792]])
    

    put不接受axis参数,它只会在数组的扁平化版本(一维,C顺序)上进行索引(这一点今后应该是会有所改善的)。因此,在需要用其他轴向的索引设置元素时,最好还是使用花式索引。

    广播

    广播(broadcasting)指的是不同形状的数组之间的算术运算的执行方式。它是一种非常强大的功能,但也容易令人误解,即使是经验丰富的老手也是如此。将标量值跟数组合并时就会发生最简单的广播:

    arr=np. arange(5) 
    arr 
    array([0,1,2,3,4])
    arr*4 
    array([0,4,8,12,16])
    

    这里我们说:在这个乘法运算中,标量值4被广播到了其他所有的元素上。
    再来看一个例子,我们可以通过减去列平均值的方式对数组的每一列进行距平化处理。

    arr=randn(4,3) 
    arr.mean(0) 
    array([ 0.12959986,  0.0541326 ,  0.28520378])
    demeaned=arr-arr. mean(0) 
    demeaned
    array([[-0.26539865,  0.56553254, -0.46160397],
           [ 0.24474686, -0.17869761,  0.62222595],
           [-0.36335486, -0.26365681,  0.26070088],
           [ 0.38400665, -0.12317812, -0.42132286]])
    demeaned.mean(0)
    array([  0.00000000e+00,  -6.93889390e-18,   2.77555756e-17])
    

    广播的原则: 如果两个数组的后缘维度(trailing dimension,即从末尾开始算起的维度)的轴长度相符或其中一方的长度为1,则认为它们是广播兼容的。广播会在缺失和(或)长度为1的维度上进行。

    ufunc高级应用

    虽然许多NumPy用户只会用到通用函数所提供的快速的元素级运算,但通用函数实际上还有一些高级用法能使我们丢开循环而编写出更为简洁的代码。

    ufunc实例方法

    NumPy的各个二元ufunc都有一些用于执行特定矢量化运算的特殊方法。reduce接受一个数组参数,并通过一系列的二元运算对其值进行聚合(可指明轴向)。例如,我们可以用np.add.reduce对数组中各个元素进行求和:

    arr=np.arange(10)
    np.add.reduce(arr)
    45 
    arr.sum()
    45
    

    起始值取决于ufunc(对于add的情况,就是0)。如果设置了轴号,约简运算就会沿该轴向执行。这就使你能用一种比较简洁的方式得到某些问题的答案。


    ufunc的方法

    自定义ufunc

    有两个工具可以让你将自定义函数像ufunc那样使用。numpy.frompyfunc接受一个Python函数以及两个分别表示输入输出参数数量的整数。例如,下面是一个能够实现元素级加法的简单函数:

    def add_elements(x,y):
            return x+y
    add_them=np.frompyfunc(add_elements,2,1)
    add them(np. arange(8), np. arange(8)) array([0,2,4,6,8,10,12,14], dtype=object)
    

    用frompyfunc创建的函数总是返回Python对象数组,这一点很不方便。幸运的是,还有另一个办法,即numpy.vectorize。虽然没有rompyfunc那么强大,但它在类型推断方面要更智能一些:

    add_them=np.vectorize(add_elements,otypes=[np.float64]) 
    add_them(np. arange(8), np.arange(8)) array([0.,2.,4.,6.,8.,10.,12.,14.])
    

    虽然这两个函数提供了一种创建ufunc型函数的手段,但它们非常慢,因为它们在计算每个元素时都要执行一次Python函数调用,这自然会比NumPy自带的基于C的ufunc慢很多:

    arr=randn(10000) 
    %timeit add_them(arr, arr) 
    100 loops, best of 3:2.12 ms per loop
    % timeit np. add(arr, arr) 
    100000 loops, best of 3:11.6 us per loop
    

    微信公众号:BrainZou
    欢迎关注,一起学习。
    回复“资料”,有本人精心收集的Python,Java,Android,小程序,后端,算法等等近1T的网盘资源免费分享给你。

    相关文章

      网友评论

        本文标题:利用Python进行数据分析(二十)之numpy高级应用

        本文链接:https://www.haomeiwen.com/subject/gprhjftx.html