快速的元素级数组函数

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

  1. In [1]: import numpy as np
  2. In [2]: arr = np.arange(10)
  3. In [3]: arr
  4. Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  5. In [4]: np.sqrt(arr)
  6. Out[4]:
  7. array([0. , 1. , 1.41421356, 1.73205081, 2. ,
  8. 2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ])
  9. In [5]: np.exp(arr)
  10. Out[5]:
  11. array([1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
  12. 5.45981500e+01, 1.48413159e+02, 4.03428793e+02, 1.09663316e+03,
  13. 2.98095799e+03, 8.10308393e+03])

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

  1. In [6]: x = np.random.randn(8)
  2. In [7]: y = np.random.randn(8)
  3. In [8]: x
  4. Out[8]:
  5. array([ 0.10716392, 0.49217214, -1.14228221, 0.31049503, -1.11340346,
  6. -1.21463633, 0.11805104, -0.30401207])
  7. In [9]: y
  8. Out[9]:
  9. array([ 0.70061311, -0.48270951, -1.62563645, -0.02473194, 0.08231935,
  10. -1.8701379 , 0.86725936, 0.49598605])
  11. In [10]: np.maximum(x, y)
  12. Out[10]:
  13. array([ 0.70061311, 0.49217214, -1.14228221, 0.31049503, 0.08231935,
  14. -1.21463633, 0.86725936, 0.49598605])

这里,numpy.maximum计算了x和y中元素级别最大的元素
虽然并不常见, 但有些ufunc的确可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,它会返回浮点数数组的小数和整数部分

  1. In [11]: arr = np.random.randn(7) * 5
  2. In [12]: arr
  3. Out[12]:
  4. array([ 1.96753342, 5.72566158, 5.33468918, -9.30095939, -5.27622152,
  5. -4.50344508, -3.2361097 ])
  6. In [13]: remainder, whole_part = np.modf(arr)
  7. In [14]: remainder
  8. Out[14]:
  9. array([ 0.96753342, 0.72566158, 0.33468918, -0.30095939, -0.27622152,
  10. -0.50344508, -0.2361097 ])
  11. In [15]: whole_part
  12. Out[15]: array([ 1., 5., 5., -9., -5., -4., -3.])

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

  1. In [16]: arr
  2. Out[16]:
  3. array([ 1.96753342, 5.72566158, 5.33468918, -9.30095939, -5.27622152,
  4. -4.50344508, -3.2361097 ])
  5. In [17]: np.sqrt(arr)
  6. E:\Environment\anaconda\Scripts\ipython:1: RuntimeWarning: invalid value encountered in sqrt
  7. Out[17]:
  8. array([1.40268793, 2.39283547, 2.30969461, nan, nan,
  9. nan, nan])
  10. In [18]: np.sqrt(arr, arr)
  11. E:\Environment\anaconda\Scripts\ipython:1: RuntimeWarning: invalid value encountered in sqrt
  12. Out[18]:
  13. array([1.40268793, 2.39283547, 2.30969461, nan, nan,
  14. nan, nan])
  15. In [19]: arr
  16. Out[19]:
  17. array([1.40268793, 2.39283547, 2.30969461, nan, nan,
  18. nan, nan])
  1. 一元ufunc

image.png

  1. 二元ufunc

image.png

利用数组进行数据处理

作为简单的例子,假设我们想要在一组值(网格型)上计算函数 sqrt(x^2+y^2) 。 np.meshgrid函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对)

  1. In [20]: points = np.arange(-5, 5, 0.01) # 1000 equally spaced points
  2. In [21]: xs, ys = np.meshgrid(points, points)
  3. In [22]: ys
  4. Out[22]:
  5. array([[-5. , -5. , -5. , ..., -5. , -5. , -5. ],
  6. [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
  7. [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
  8. ...,
  9. [ 4.97, 4.97, 4.97, ..., 4.97, 4.97, 4.97],
  10. [ 4.98, 4.98, 4.98, ..., 4.98, 4.98, 4.98],
  11. [ 4.99, 4.99, 4.99, ..., 4.99, 4.99, 4.99]])

现在,对该函数的求值运算就好办了,把这两个数组当做两个浮点数那样编写表达式即可

  1. In [23]: z = np.sqrt(xs ** 2 + ys ** 2)
  2. In [24]: z
  3. Out[24]:
  4. array([[7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 7.05693985,
  5. 7.06400028],
  6. [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
  7. 7.05692568],
  8. [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
  9. 7.04985815],
  10. ...,
  11. [7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 7.03571603,
  12. 7.04279774],
  13. [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
  14. 7.04985815],
  15. [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
  16. 7.05692568]])

我用matplotlib创建了这个二维数组的可视化

  1. In [27]: plt.imshow(z, cmap=plt.cm.gray); plt.colorbar()
  2. Out[27]: <matplotlib.colorbar.Colorbar at 0x1afa7b44548>
  3. In [28]: plt.title('Image plot of $\sqrt{x^2 + y^2}$ for a grid values')
  4. Out[28]: Text(0.5, 1.0, 'Image plot of $\\sqrt{x^2 + y^2}$ for a grid values')

image.png

使用where函数将条件逻辑表述为数组运算

numpy.where函数是三元表达式x if condition else y的矢量化版本。假设我们有一个布尔数组和两个值数组

  1. In [29]: xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
  2. In [30]: yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
  3. In [31]: cond = np.array([True, False, True, True, False])

假设我们想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。 列表推导式的写法应该如下所示

  1. In [32]: result = [(x if c else y) for x,y,c in zip(xarr, yarr, cond)]
  2. In [33]: result
  3. Out[33]: [1.1, 2.2, 1.3, 1.4, 2.5]

这有几个问题。第一,它对大数组的处理速度不是很快(因为所有工作都是由纯Python完成的)。第二,无法用于多维数组。若使用np.where,则可以将该功能写得非常简洁

  1. In [2]: xarr = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
  2. In [3]: yarr = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
  3. In [4]: cond = np.array([True, False, True, True, True])
  4. In [5]: result = np.where(cond, xarr, yarr)
  5. In [6]: result
  6. Out[6]: array([1.1, 2.2, 1.3, 1.4, 1.5])

np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where通常用于根据另一个数组而产生一个新的数组。假设有一个由随机数据组成的矩阵,你希望将所有正值替换为2,将所有负值替换为-2。若利用np.where,则会非常简单

  1. In [8]: arr = np.random.randn(4, 4)
  2. In [9]: arr
  3. Out[9]:
  4. array([[ 0.94949802, 0.41144197, -1.91940976, 1.67166374],
  5. [ 1.32145048, 1.71122163, 0.15182974, -0.65659749],
  6. [-0.2575805 , -0.10543429, 1.24716095, 0.44008596],
  7. [ 0.01099706, 1.46653594, -0.41596372, 1.1810662 ]])
  8. In [11]: arr > 0
  9. Out[11]:
  10. array([[ True, True, False, True],
  11. [ True, True, True, False],
  12. [False, False, True, True],
  13. [ True, True, False, True]])
  14. In [12]: np.where(arr>0, 2, -2)
  15. Out[12]:
  16. array([[ 2, 2, -2, 2],
  17. [ 2, 2, 2, -2],
  18. [-2, -2, 2, 2],
  19. [ 2, 2, -2, 2]])

使用np.where,可以将标量和数组结合起来,例如,用常数2替换arr中所有正的值

  1. In [13]: np.where(arr > 0, 2, arr)
  2. Out[13]:
  3. array([[ 2. , 2. , -1.91940976, 2. ],
  4. [ 2. , 2. , 2. , -0.65659749],
  5. [-0.2575805 , -0.10543429, 2. , 2. ],
  6. [ 2. , 2. , -0.41596372, 2. ]])

数学和统计方法

可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级NumPy函数使用

  1. In [14]: arr = np.random.randn(5, 4)
  2. In [15]: arr
  3. Out[15]:
  4. array([[-0.18450665, 1.09160022, -0.45164509, -1.29567266],
  5. [-0.36163517, 0.37244986, 0.98034514, 0.82316027],
  6. [ 1.13255244, -0.75213107, 0.85103452, -0.15437491],
  7. [ 0.18715067, 1.75264532, 0.38247457, 1.18892021],
  8. [-0.34493636, 1.16156775, 1.92229378, -0.15901044]])
  9. In [16]: arr.mean()
  10. Out[16]: 0.40711411999618424
  11. In [17]: np.mean(arr)
  12. Out[17]: 0.40711411999618424
  13. In [18]: arr.sum()
  14. Out[18]: 8.142282399923685

mean和sum这类的函数可以接受一个axis选项参数,用于沿着该轴计算的统计值,最终结果是一个少一维的数组

  1. In [19]: arr.mean(axis=0)
  2. Out[19]: array([0.08572498, 0.72522642, 0.73690058, 0.08060449])
  3. In [20]: arr.mean(axis=1)
  4. Out[20]: array([-0.21005604, 0.45358002, 0.26927025, 0.87779769, 0.64497868])

这里, arr.mean(1) 是“计算行的平均值”,arr.mean(0) 是“计算每列的和”
其他如cumsum和cumprod之类的方法则不聚合, 而是产生一个由中间结果组成的数组

  1. In [21]: arr = np.array([0,1,2,3,4,5,6,7])
  2. In [22]: arr.cumsum()
  3. Out[22]: array([ 0, 1, 3, 6, 10, 15, 21, 28], dtype=int32)

在多维数组中,累加函数(如cumsum)返回的是同样大小的数组,但是会根据每个低维的切片沿着标记轴计算部分聚类

  1. In [23]: arr = np.array([[0,1,2],[3,4,5], [6,7,8]])
  2. In [24]: arr
  3. Out[24]:
  4. array([[0, 1, 2],
  5. [3, 4, 5],
  6. [6, 7, 8]])
  7. In [25]: arr.cumsum(axis=0)
  8. Out[25]:
  9. array([[ 0, 1, 2],
  10. [ 3, 5, 7],
  11. [ 9, 12, 15]], dtype=int32)
  12. In [26]: arr.cumsum(axis=1)
  13. Out[26]:
  14. array([[ 0, 1, 3],
  15. [ 3, 7, 12],
  16. [ 6, 13, 21]], dtype=int32)
  17. In [27]: arr.cumprod(axis=1)
  18. Out[27]:
  19. array([[ 0, 0, 0],
  20. [ 3, 12, 60],
  21. [ 6, 42, 336]], dtype=int32)

下表列出了全部的基本数组统计方法
image.png

排序

跟Python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序

  1. In [28]: arr = np.random.randn(6)
  2. In [29]: arr
  3. Out[29]:
  4. array([ 0.70636751, -0.73598582, 1.16913476, -1.37940337, -0.51591008,
  5. -0.42780537])
  6. In [30]: arr.sort()
  7. In [31]: arr
  8. Out[31]:
  9. array([-1.37940337, -0.73598582, -0.51591008, -0.42780537, 0.70636751,
  10. 1.16913476])

多维数组可以在任何一个轴向上进行排序, 只需将轴编号传给sort即可

  1. In [32]: arr = np.random.randn(5, 3)
  2. In [33]: arr
  3. Out[33]:
  4. array([[-1.53935156, 1.50062807, -0.21328874],
  5. [ 0.92822483, 1.77466032, 0.43758902],
  6. [-0.9286603 , 0.63807763, -1.77895267],
  7. [ 1.24869571, 0.17529268, 0.46659006],
  8. [ 0.88561678, 1.36707667, 0.40294837]])
  9. In [34]: arr.sort(axis=1)
  10. In [35]: arr
  11. Out[35]:
  12. array([[-1.53935156, -0.21328874, 1.50062807],
  13. [ 0.43758902, 0.92822483, 1.77466032],
  14. [-1.77895267, -0.9286603 , 0.63807763],
  15. [ 0.17529268, 0.46659006, 1.24869571],
  16. [ 0.40294837, 0.88561678, 1.36707667]])

顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值

  1. In [37]: large_arr = np.random.randn(1000)
  2. In [38]: large_arr.sort()
  3. In [39]: large_arr[int(0.05 * len(large_arr))] # 选择第%5的元素值
  4. Out[39]: -1.6077327462775528

唯一化以及其它的集合逻辑

NumPy提供了一些针对一维ndarray的基本集合运算。最常用的可能要数np.unique了,它用于找出数组中的唯一值并返回已排序的结果

  1. In [41]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Joe', 'Joe'])
  2. In [42]: np.unique(names)
  3. Out[42]: array(['Bob', 'Joe', 'Will'], dtype='<U4')
  4. In [43]: ints = np.array([3,3,3,2,2,1,1,4,4])
  5. In [44]: np.unique(ints)
  6. Out[44]: array([1, 2, 3, 4])

拿跟np.unique等价的纯Python代码来对比一下

  1. In [45]: sorted(set(names))
  2. Out[45]: ['Bob', 'Joe', 'Will']

另一个函数np.in1d用于测试一个数组中的值在另一个数组中的成员资格, 返回一个布尔型数组

  1. In [46]: values = np.array([6,0,0,3,2,5,6])
  2. In [47]: np.in1d(values, [2,3,6])
  3. Out[47]: array([ True, False, False, True, True, False, True])

NumPy中的集合函数如下表
image.png