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

【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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容