美文网首页我爱编程
【Chapter 4】 NumPy基础:数组和矢量计算

【Chapter 4】 NumPy基础:数组和矢量计算

作者: 蜘蛛的梦呓 | 来源:发表于2018-05-20 20:54 被阅读0次

    【Chapter 4】 NumPy基础:数组和矢量计算

    使用 Python 进行科学计算:NumPy入门

    NumPy本身并没有提供多么高级的数据分析功能,理解NumPy数组以及面向数组的计算将有助于你更加高效地使用诸如pandas之类的工具。

    对于大部分数据分析应用而言,我最关注的功能主要集中在:

    • 用于数据整理和清理、子集构造和过滤、转换等快速的矢量化数组运算。
    • 常用的数组算法,如排序、唯一化、集合运算等。
    • 高效的描述统计和数据聚合/摘要运算。
    • 用于异构数据集的合并/连接运算的数据对齐和关系型数据运算。
    • 将条件逻辑表述为数组表达式(而不是带有if-elif-else分支的循环)。
    • 数据的分组运算(聚合、转换、函数应用等)。。

    虽然NumPy提供了通用的数值数据处理的计算基础,但大多数读者可能还是想将pandas作为统计和分析工作的基础,尤其是处理表格数据时。pandas还提供了一些NumPy所没有的更加领域特定的功能,如时间序列处理等。

    NumPy之于数值计算特别重要的原因之一,是因为它可以高效处理大数组的数据。这是因为:

    • NumPy是在一个连续的内存块中存储数据,独立于其他Python内置对象。NumPy的C语言编写的算法库可以操作内存,而不必进行类型检查或其它前期工作。比起Python的内置序列,NumPy数组使用的内存更少。
    • NumPy可以在整个数组上执行复杂的计算,而不需要Python的for循环。

    要搞明白具体的性能差距,考察一个包含一百万整数的数组,和一个等价的Python列表:

    In [7]: import numpy as np
    
    In [8]: my_arr = np.arange(1000000)
    
    In [9]: my_list = list(range(1000000))
    

    各个序列分别乘以2:

    In [10]: %time for _ in range(10): my_arr2 = my_arr * 2
    CPU times: user 20 ms, sys: 50 ms, total: 70 ms
    Wall time: 72.4 ms
    
    In [11]: %time for _ in range(10): my_list2 = [x * 2 for x in my_list]
    CPU times: user 760 ms, sys: 290 ms, total: 1.05 s
    Wall time: 1.05 s
    

    基于NumPy的算法要比纯Python快10到100倍(甚至更快),并且使用的内存更少。

    4.1 NumPy的ndarray:一种多维数组对象

    NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器。你可以利用这种数组对整块数据执行一些数学运算,其语法跟标量元素之间的运算一样。

    要明白Python是如何利用与标量值类似的语法进行批次计算,我先引入NumPy,然后生成一个包含随机数据的小数组:

    import numpy as np
    
    data = np.random.randn(2,3)
    
    data
    Out[210]: 
    array([[-1.08335704,  0.10430985,  0.1606809 ],
           [ 1.34537474, -1.19032319, -1.14856331]])
    
    #进行计算
    
    data * 10
    Out[212]: 
    array([[-10.8335704 ,   1.04309853,   1.60680899],
           [ 13.45374739, -11.90323192, -11.48563308]])
    
    data + data
    Out[213]: 
    array([[-2.16671408,  0.20861971,  0.3213618 ],
           [ 2.69074948, -2.38064638, -2.29712662]])
    

    第一个例子中,所有的元素都乘以10。第二个例子中,每个元素都与自身相加。

    笔记:在本章及全书中,我会使用标准的NumPy惯用法import numpy as np。你当然也可以在代码中使用from numpy import *,但不建议这么做。numpy的命名空间很大,包含许多函数,其中一些的名字与Python的内置函数重名(比如min和max)。

    ndarray是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个数组的形状(shape)是指它有多少行和列 和一个dtype(一个用于说明数组数据类型的对象):

    In [214]: data.shape
    Out[214]: (2, 3)
    
    In [215]: data.dtype
    Out[215]: dtype('float64')
    

    本章将会介绍NumPy数组的基本用法,这对于本书后面各章的理解基本够用。虽然大多数数据分析工作不需要深入理解NumPy,但是精通面向数组的编程和思维方式是成为Python科学计算牛人的一大关键步骤。

    笔记:当你在本书中看到“数组”、“NumPy数组”、"ndarray"时,基本上都指的是同一样东西,即ndarray对象。

    Greating ndarrays (创建n维数组)

    创建数组最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。以一个列表的转换为例:

    In [219]: data1 = [6,7.5,8,0,1]
    
    In [220]: arr1=np.array(data)
    
    In [221]: arr1
    Out[221]: array([6. , 7.5, 8. , 0. , 1. ])
    

    嵌套序列能被转换为多维数组:

    data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
    arr2 = np.array(data2)
    arr2
    
    array([[1, 2, 3, 4],
           [5, 6, 7, 8]])
    

    因为data2是一个list of lists, 所以arr2维度为2。我们能用ndim和shape属性来确认一下:

    #'ndim' 属性是指数组有多少维
    In [25]: arr2.ndim
    Out[25]: 2
    
    In [26]: arr2.shape
    Out[26]: (2, 4)
    

    除np.array之外,还有一些函数也可以新建数组。比如,zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可:

    In [223]: np.zeros(10)
    Out[223]: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
    
    In [224]: np.zeros((3,6))
    Out[224]: 
    array([[0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0.]])
    
    In [225]: np.empty((2,3,2))
    Out[225]: 
    array([[[8.38955552e-312, 3.16202013e-322],
            [0.00000000e+000, 0.00000000e+000],
            [0.00000000e+000, 4.42436408e-062]],
    
           [[2.58453781e-057, 2.37900058e+184],
            [1.86386951e+160, 3.40558559e+175],
            [3.40609302e-057, 6.28850449e-066]]])
    

    注意:认为np.empty会返回全0数组的想法是不安全的。很多情况下(如前所示),它返回的都是一些未初始化的垃圾值。

    arange是Python内置函数range的数组版:

    np.arange(15)
    Out[227]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
    

    2. ndarray的数据类型

    dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息:

    
    arr1 = np.array([1,2,3],dtype=np.float64)
    
    array2 = np.array([1,2,3],dtype=np.int32)
    
    arr1.dtype
    Out[232]: dtype('float64')
    
    array2.dtype
    Out[234]: dtype('int32')
    
    
    
    #用astype把string里的数字变为实际的数字:
    
    In [236]: numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
         ...: numeric_strings
         ...: 
    Out[236]: array([b'1.25', b'-9.6', b'42'], dtype='|S4')
    
    In [237]: numeric_strings.astype(float)
    Out[237]: array([ 1.25, -9.6 , 42.  ])
    

    要十分注意numpy.string_类型,这种类型的长度是固定的,所以可能会直接截取部分输入而不给警告。

    如果转换(casting)失败的话,会给出一个ValueError提示。

    数组的dtype还有另一个属性:

    In [240]: int_array = np.arange(10)
    
    In [241]: calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)
    
    In [242]: int_array.astype(calibers.dtype)
    Out[242]: array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
    

    还可以利用类型的缩写,比如u4就代表unit32:

    empty_unit32 = np.empty(8, dtype='u4')
    empty_unit32
    
    array([0, 0, 0, 0, 0, 0, 0, 0], dtype=uint32)
    

    记住,astype总是会返回一个新的数组

    笔记:记不住这些NumPy的dtype也没关系,新手更是如此。通常只需要知道你所处理的数据的大致类型是浮点数、复数、整数、布尔值、字符串,还是普通的Python对象即可。当你需要控制数据在内存和磁盘中的存储方式时(尤其是对大数据集),那就得了解如何控制存储类型。

    3. NumPy数组的运算

    数组很重要,因为它使你不用编写循环即可对数据执行批量运算。NumPy用户称其为矢量化(vectorization)。任何两个大小相等的数组之间的运算,都是element-wise(点对点):

    arr = np.array([[1., 2., 3.], [4., 5., 6.]])
    
    arr
    Out[245]: 
    array([[1., 2., 3.],
          [4., 5., 6.]])
          
    arr * arr
    Out[246]: 
    array([[ 1.,  4.,  9.],
          [16., 25., 36.]])
          
    arr - arr
    Out[247]: 
    array([[0., 0., 0.],
          [0., 0., 0.]])
    

    数组与标量的算术运算会将标量值传播到各个元素:

    In [55]: 1 / arr
    Out[55]: 
    array([[ 1.    ,  0.5   ,  0.3333],
          [ 0.25  ,  0.2   ,  0.1667]])
    
    In [56]: arr ** 0.5
    Out[56]: 
    array([[ 1.    ,  1.4142,  1.7321],
          [ 2.    ,  2.2361,  2.4495]])
    
    

    大小相同的数组之间的比较会生成布尔值数组:

    In [57]: arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
    
    In [58]: arr2
    Out[58]: 
    array([[  0.,   4.,   1.],
          [  7.,   2.,  12.]])
    
    In [59]: arr2 > arr
    Out[59]:
    array([[False,  True, False],
          [ True, False,  True]], dtype=bool)
    
    

    4. Basic Indexing and Slicing(基本的索引和切片)

    NumPy数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有很多。一维数组很简单。从表面上看,它们跟Python列表的功能差不多:

    In [60]: arr = np.arange(10)
    
    In [61]: arr
    Out[61]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    In [62]: arr[5]
    Out[62]: 5
    
    In [63]: arr[5:8]
    Out[63]: array([5, 6, 7])
    
    In [64]: arr[5:8] = 12
    
    In [65]: arr
    Out[65]: array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])
    
    

    这里把12赋给arr[5:8],其实用到了broadcasted(我觉得应该翻译为广式转变)。这里有一个比较重要的概念需要区分,python内建的list与numpy的array有个明显的区别,这里array的切片后的结果只是一个views(视图),用来代表原有array对应的元素,而不是创建了一个新的array。但list里的切片是产生了一个新的list:

    arr
    Out[65]: array([ 0,  1,  2,  3,  4, 12, 12, 12,  8,  9])
    
    arr_slice = arr[5:8]
    arr_slice
    
    array([12, 12, 12])
    #如果我们改变arr_slice的值,会反映在原始的数组arr上:
    arr_slice[1] = 12345
    arr
    
    array([    0,     1,     2,     3,     4,    12, 12345,    12,     8,     9])
    #[:]这个赋值给所有元素:
    arr_slice[:] = 64
    arr
    
    array([ 0,  1,  2,  3,  4, 64, 64, 64,  8,  9])
    

    之所以这样设计是出于性能和内存的考虑,毕竟如果总是复制数据的话,会很影响运算时间。当然如果想要复制,可以使用copy()方法,比如arr[5:8].copy()

    在一个二维数组里,单一的索引指代的是一维的数组:

    arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    arr2d[2]
    
    Out[252]: array([7, 8, 9])
    

    有两种方式可以访问单一元素:

    Out[252]: array([7, 8, 9])
    
    arr2d[0][2]
    Out[253]: 3
    
    arr2d[0,2]
    Out[254]: 3
    

    对于多维数组,如果省略后面的索引,返回的将是一个低纬度的多维数组。比如下面一个2 x 2 x 3数组:

    
    arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
    arr3d
    Out[267]: 
    array([[[ 1,  2,  3],
            [ 4,  5,  6]],
    
           [[ 7,  8,  9],
            [10, 11, 12]]])
    

    arr3d[0]是一个2x3数组:

    arr3d[0]
    Out[265]: 
    array([[1, 2, 3],
           [4, 5, 6]])
    

    标量和数组都能赋给arr3d[0]:

    old_values = arr3d[0].copy()
    
    arr3d[0] = 42
    
    arr3d
    Out[268]: 
    array([[[42, 42, 42],
            [42, 42, 42]],
    
           [[ 7,  8,  9],
            [10, 11, 12]]])
    
    
    arr3d[0] = old_values
    arr3d
    
    Out[269]: 
    array([[[ 1,  2,  3],
            [ 4,  5,  6]],
    
           [[ 7,  8,  9],
            [10, 11, 12]]])
    

    arr3d[1, 0]会给你一个(1, 0)的一维数组:

    arr3d[1,0]
    Out[270]: array([7, 8, 9])
    

    上面的一步等于下面的两步:

    x = arr3d[1]
    x
    
    Out[271]: 
    array([[ 7,  8,  9],
           [10, 11, 12]])
    
    x[0]
    Out[272]: array([7, 8, 9])
    

    一定要牢记这些切片后返回的数组都是views

    Indexing with slices(用切片索引)

    一维的话和python里的list没什么差别, 二维的话,数组的切片有点不同:

    arr2d[:2]
    Out[274]: 
    array([[1, 2, 3],
           [4, 5, 6]])
    arr2d
    Out[275]: 
    array([[1, 2, 3],
           [4, 5, 6],
           [7, 8, 9]])
    

    可以看到,切片是沿着axis 0(行)来处理的。所以,数组中的切片,是要沿着设置的axis来处理的。我们可以把arr2d[:2]理解为“选中arr2d的前两行”。

    当然,给定多个索引后,也可以使用复数切片

    
    arr2d
    Out[276]: 
    array([[1, 2, 3],
           [4, 5, 6],
           [7, 8, 9]])
    
    arr2d[:2,1:]# 前两行,第二列之后
    Out[277]: 
    array([[2, 3],
           [5, 6]])
    

    记住,选中的是array view。通过混合整数和切片,能做低维切片。比如,我们选中第二行的前两列:

    arr2d[1,:2]
    Out[278]: array([4, 5])
    #选中第三列的前两行: 
    arr2d[:2, 2]
    Out[279]: array([3, 6])
    #冒号表示提取整个axis(轴):
    arr2d[:, :1]
    Out[280]: 
    array([[1],
           [4],
           [7]])
    
    
    img

    自然,对切片表达式的赋值操作也会被扩散到整个选区:

    arr2d[:2,1:] = 0
    
    arr2d
    Out[283]: 
    array([[1, 0, 0],
           [4, 0, 0],
           [7, 8, 9]])
    
    5. 布尔型索引

    假设我们的数组数据里有一些重复。这里我们用numpy.random里的randn函数来随机生成一些离散数据:

    names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
    names
    
    Out[285]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4')
    
    data =np.random.randn(7,4)
    data
    Out[289]: 
    array([[ 0.79464532,  1.82956019, -0.72248023, -0.95578708],
           [ 0.74989722,  0.68546362,  1.85957048,  0.62219052],
           [-0.25333906,  0.83874759, -0.19498459,  0.03106703],
           [-0.88922371, -1.11102412,  0.00290218,  0.49031693],
           [-0.59301418,  0.09401758,  0.3187404 ,  1.75425578],
           [ 0.94548973, -0.74021661,  0.25142927,  1.45700803],
           [-0.52268701,  1.02549695, -0.62408623, -0.46731474]])
    

    假设每个名字都对应data数组中的一行,而我们想要选出对应于名字"Bob"的所有行。跟算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对names和字符串"Bob"的比较运算将会产生一个布尔型数组:

    names == 'Bob'
    Out[290]: array([ True, False, False,  True, False, False, False])
    
    #这个布尔型数组可用于数组索引: 
    data[names == 'Bob']
    Out[292]: 
    array([[ 0.79464532,  1.82956019, -0.72248023, -0.95578708],
          [-0.88922371, -1.11102412,  0.00290218,  0.49031693]])
    

    注意:布尔数组和data数组的长度要一样。

    我们可以选中names=='Bob'的行,然后索引了列:

    data[names == 'Bob', 2:]
    Out[293]: 
    array([[-0.72248023, -0.95578708],
           [ 0.00290218,  0.49031693]])
     
    data[names == 'Bob', 3]
    Out[294]: array([-0.95578708,  0.49031693])
    

    选中除了'Bob'外的所有行,可以用!=或者~

    names != 'Bob'
    Out[296]: array([False,  True,  True, False,  True,  True,  True])
    

    要选择除"Bob"以外的其他值,既可以使用不等于符号(!=),也可以通过~对条件进行否定:

    In [106]: names != 'Bob'
    Out[106]: array([False,  True,  True, False,  True,  True,  True], dtype=bool)
    
    In [107]: data[~(names == 'Bob')]
    Out[107]:
    array([[ 1.0072, -1.2962,  0.275 ,  0.2289],
           [ 1.3529,  0.8864, -2.0016, -0.3718],
           [ 3.2489, -1.0212, -0.5771,  0.1241],
           [ 0.3026,  0.5238,  0.0009,  1.3438],
           [-0.7135, -0.8312, -2.3702, -1.8608]])
    

    ~操作符用来反转条件很好用:

    In [108]: cond = names == 'Bob'
    
    In [109]: data[~cond]
    Out[109]: 
    array([[ 1.0072, -1.2962,  0.275 ,  0.2289],
           [ 1.3529,  0.8864, -2.0016, -0.3718],
           [ 3.2489, -1.0212, -0.5771,  0.1241],
           [ 0.3026,  0.5238,  0.0009,  1.3438],
           [-0.7135, -0.8312, -2.3702, -1.8608]])
    

    选取这三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可:

    In [110]: mask = (names == 'Bob') | (names == 'Will')
    
    In [111]: mask
    Out[111]: array([ True, False,  True,  True,  True, False, False], dtype=bool)
    
    In [112]: data[mask]
    Out[112]: 
    array([[ 0.0929,  0.2817,  0.769 ,  1.2464],
           [ 1.3529,  0.8864, -2.0016, -0.3718],
           [ 1.669 , -0.4386, -0.5397,  0.477 ],
           [ 3.2489, -1.0212, -0.5771,  0.1241]])
    

    通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回一模一样的数组也是如此。

    注意:Python关键字and和or在布尔型数组中无效。要使用&与|。

    通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0,我们只需:

    In [113]: data[data < 0] = 0
    
    In [114]: data
    Out[114]: 
    array([[ 0.0929,  0.2817,  0.769 ,  1.2464],
           [ 1.0072,  0.    ,  0.275 ,  0.2289],
           [ 1.3529,  0.8864,  0.    ,  0.    ],
           [ 1.669 ,  0.    ,  0.    ,  0.477 ],
           [ 3.2489,  0.    ,  0.    ,  0.1241],
           [ 0.3026,  0.5238,  0.0009,  1.3438],
           [ 0.    ,  0.    ,  0.    ,  0.    ]])
    

    通过一维布尔数组设置整行或列的值也很简单:

    In [115]: data[names != 'Joe'] = 7
    
    In [116]: data
    Out[116]: 
    array([[ 7.    ,  7.    ,  7.    ,  7.    ],
           [ 1.0072,  0.    ,  0.275 ,  0.2289],
           [ 7.    ,  7.    ,  7.    ,  7.    ],
           [ 7.    ,  7.    ,  7.    ,  7.    ],
           [ 7.    ,  7.    ,  7.    ,  7.    ],
           [ 0.3026,  0.5238,  0.0009,  1.3438],
           [ 0.    ,  0.    ,  0.    ,  0.    ]])
    

    后面会看到,这类二维数据的操作也可以用pandas方便的来做。

    6. Fancy Indexing(花式索引)

    通过整数数组来索引。假设我们有一个8 x 4的数组:

    arr = np.empty((8,4))
    
    for i in range(8):
       arr[i] = i
       
    
    arr
    Out[303]: 
    array([[0., 0., 0., 0.],
          [1., 1., 1., 1.],
          [2., 2., 2., 2.],
          [3., 3., 3., 3.],
          [4., 4., 4., 4.],
          [5., 5., 5., 5.],
          [6., 6., 6., 6.],
          [7., 7., 7., 7.]])
    

    为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可:

    arr[[4,3,0,6]]
    Out[305]: 
    array([[4., 4., 4., 4.],
          [3., 3., 3., 3.],
          [0., 0., 0., 0.],
          [6., 6., 6., 6.]])
    

    这段代码确实达到我们的要求了!使用负数索引将会从末尾开始选取行:

    arr[[-3,-5,-7]]
    Out[306]: 
    array([[5., 5., 5., 5.],
          [3., 3., 3., 3.],
          [1., 1., 1., 1.]])
    

    一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组:

    arr = np.arange(32).reshape((8,4))
    
    arr
    Out[309]: 
    array([[ 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, 30, 31]])
     
    arr[[1,5,7,2],[0,3,1,2]]
    Out[310]: array([ 4, 23, 29, 10])
    

    7. 数组转置和轴交换

    转置也是返回一个view,而不是新建一个数组。有两种方式,一个是transpose方法,一个是T属性:

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

    做矩阵计算的时候,这个功能很常用,计算矩阵乘法的时候,用np.dot:

    In [129]: arr = np.random.randn(6, 3)
    
    In [130]: arr
    Out[130]: 
    array([[-0.8608,  0.5601, -1.2659],
           [ 0.1198, -1.0635,  0.3329],
           [-2.3594, -0.1995, -1.542 ],
           [-0.9707, -1.307 ,  0.2863],
           [ 0.378 , -0.7539,  0.3313],
           [ 1.3497,  0.0699,  0.2467]])
    
    In [131]: np.dot(arr.T, arr)
    Out[131]:
    array([[ 9.2291,  0.9394,  4.948 ],
           [ 0.9394,  3.7662, -1.3622],
           [ 4.948 , -1.3622,  4.3437]])
    
    

    对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子):

    In [132]: arr = np.arange(16).reshape((2, 2, 4))
    
    In [133]: arr
    Out[133]: 
    array([[[ 0,  1,  2,  3],
            [ 4,  5,  6,  7]],
           [[ 8,  9, 10, 11],
            [12, 13, 14, 15]]])
    
    In [134]: arr.transpose((1, 0, 2))
    Out[134]: 
    array([[[ 0,  1,  2,  3],
            [ 8,  9, 10, 11]],
           [[ 4,  5,  6,  7],
            [12, 13, 14, 15]]])
    

    这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。

    简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray还有一个swapaxes方法,它需要接受一对轴编号:

    In [135]: arr
    Out[135]: 
    array([[[ 0,  1,  2,  3],
            [ 4,  5,  6,  7]],
           [[ 8,  9, 10, 11],
            [12, 13, 14, 15]]])
    
    In [136]: arr.swapaxes(1, 2)
    Out[136]: 
    array([[[ 0,  4],
            [ 1,  5],
            [ 2,  6],
            [ 3,  7]],
           [[ 8, 12],
            [ 9, 13],
            [10, 14],
            [11, 15]]])
    

    swapaxes也是返回源数据的视图(不会进行任何复制操作)。

    4.2 通用函数:快速的元素级数组函数

    通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。

    许多ufunc都是简单的元素级变体,如sqrt和exp:

    In [137]: arr = np.arange(10)
    
    In [138]: arr
    Out[138]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    In [139]: np.sqrt(arr)
    Out[139]: 
    array([ 0.    ,  1.    ,  1.4142,  1.7321,  2.    ,  2.2361,  2.4495,
            2.6458,  2.8284,  3.    ])
    
    In [140]: np.exp(arr)
    Out[140]: 
    array([    1.    ,     2.7183,     7.3891,    20.0855,    54.5982,
             148.4132,   403.4288,  1096.6332,  2980.958 ,  8103.0839])
    

    这些都是一元(unary)ufunc。另外一些(如add或maximum)接受2个数组(因此也叫二元(binary)ufunc),并返回一个结果数组:

    In [141]: x = np.random.randn(8)
    
    In [142]: y = np.random.randn(8)
    
    In [143]: x
    Out[143]: 
    array([-0.0119,  1.0048,  1.3272, -0.9193, -1.5491,  0.0222,  0.7584,
           -0.6605])
    
    In [144]: y
    Out[144]: 
    array([ 0.8626, -0.01  ,  0.05  ,  0.6702,  0.853 , -0.9559, -0.0235,
           -2.3042])
    
    In [145]: np.maximum(x, y)
    Out[145]: 
    array([ 0.8626,  1.0048,  1.3272,  0.6702,  0.853 ,  0.0222,  0.7584,   
           -0.6605])
    

    这里,numpy.maximum计算了x和y中元素级别最大的元素。

    虽然并不常见,但有些ufunc的确可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,它会返回浮点数数组的小数和整数部分:

    In [146]: arr = np.random.randn(7) * 5
    
    In [147]: arr
    Out[147]: array([-3.2623, -6.0915, -6.663 ,  5.3731,  3.6182,  3.45  ,  5.0077])
    
    In [148]: remainder, whole_part = np.modf(arr)
    
    In [149]: remainder
    Out[149]: array([-0.2623, -0.0915, -0.663 ,  0.3731,
    0.6182,  0.45  ,  0.0077])
    
    In [150]: whole_part
    Out[150]: array([-3., -6., -6.,  5.,  3.,  3.,  5.])
    

    Ufuncs可以接受一个out可选参数,这样就能在数组原地进行操作:

    In [151]: arr
    Out[151]: array([-3.2623, -6.0915, -6.663 ,  5.3731,  3.6182,  3.45  ,  5.0077])
    
    In [152]: np.sqrt(arr)
    Out[152]: array([    nan,     nan,     nan,  2.318 ,  1.9022,  1.8574,  2.2378])
    
    In [153]: np.sqrt(arr, arr)
    Out[153]: array([    nan,     nan,     nan,  2.318 ,  1.9022,  1.8574,  2.2378])
    
    In [154]: arr
    Out[154]: array([    nan,     nan,     nan,  2.318 ,  1.9022,  1.8574,  2.2378])
    

    表4-3和表4-4分别列出了一些一元和二元ufunc。

    img img img img img

    有兴趣的了解一下,我上次看过这些函数就直接复制浏览一遍就过了。

    相关文章

      网友评论

        本文标题:【Chapter 4】 NumPy基础:数组和矢量计算

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