NumPy广播机制

NumPy 中的广播机制(Broadcast)旨在解决不同形状数组之间的算术运算问题。我们知道,如果进行运算的两个数组形状完全相同,它们直接可以做相应的运算

1
2
3
4
5
import numpy as np
a = np.array([0.1,0.2,0.3,0.4])
b = np.array([10,20,30,40])
c = a * b
print(c)

但如果两个形状不同的数组呢?它们之间就不能做算术运算了吗?当然不是!为了保持数组形状相同,NumPy 设计了一种广播机制,这种机制的核心是对形状较小的数组,在横向或纵向上进行一定次数的重复,使其与形状较大的数组拥有相同的维度。

当进行运算的两个数组形状不同,Numpy 会自动触发广播机制

1
2
3
4
5
6
7
8
import numpy as np
a = np.array([[ 0, 0, 0],
[10,10,10],
[20,20,20],
[30,30,30]])
#b数组与a数组形状不同
b = np.array([1,2,3])
print(a + b)

NumPy遍历数组

NumPy 提供了一个 nditer 迭代器对象,它可以配合 for 循环完成对数组元素的遍历。

使用 arange() 函数创建一个 3*4 数组,并使用 nditer 生成迭代器对象。

1
2
3
4
5
6
import numpy as np
a = np.arange(0,60,5)
a = a.reshape(3,4)
#使用nditer迭代器,并使用for进行遍历
for x in np.nditer(a):
print(x)
遍历顺序

在内存中,Numpy 数组提供了两种存储数据的方式,分别是 C-order(行优先顺序)与 Fortrant-order(列优先顺序)。nditer 迭代器选择了一种与数组内存布局一致的顺序,之所以这样做,是为了提升数据的访问效率。

在默认情况下,当我们遍历数组中元素的时候,不需要考虑数组的存储顺序,这一点可以通过遍历上述数组的转置数组来验证。

1
2
3
4
5
6
7
8
import numpy as np
a = np.arange(0,60,5)
a = a.reshape(3,4)
#a的转置数组
b = a.T
print (b)
for x in np.nditer(b):
print(x,end=",")

从示例的输出结果可以看出,a 和 a.T 的遍历顺序是一样的,也就是说,它们在内存中的存储顺序是一样的。

下面以 C 样式访问转置数组的副本

1
2
3
4
5
import numpy as np
a = np.arange(0,60,5).reshape(3,4)
#copy方法生成数组副本
for x in np.nditer(a.T.copy(order='C')):
print (x, end=", " )

通过示例可知 a.T.copy(order = ‘C’) 的遍历结果与前面的数组遍历结果不一样。究其原因,就是因为它们在内存中的存储方式不一样。

指定遍历顺序

您可以通过 nditer 对象的order参数来指定数组的遍历的顺序。

1
2
3
4
5
6
7
8
9
10
import numpy as np
a = np.arange(0,60,5)
a = a.reshape(3,4)
print(a)

for x in np.nditer(a, order = 'C'):
print (x,end=",")

for x in np.nditer(a, order = 'F'):
print (x,end=",")
修改数组元素值

nditer 对象提供了一个可选参数op_flags,它表示能否在遍历数组时对元素进行修改。它提供了三种模式

  1. read-only

只读模式,在这种模式下,遍历时不能修改数组中的元素。

  1. read-write
    读写模式,遍历时可以修改元素值。

  2. write-only
    只写模式,在遍历时可以修改元素值。

    1
    2
    3
    4
    5
    6
    7
    import numpy as np
    a = np.arange(0,60,5)
    a = a.reshape(3,4)
    print ("原数组是:",a)
    for x in np.nditer(a, op_flags=['readwrite']):
    x[...]=2*x
    print ('修改后的数组是:',a)
外部循环使用

nditer 对象的构造函数有一个“flags”参数,它可以接受以下参数值:

c_index
可以跟踪 C 顺序的索引。
f_index
可以跟踪 Fortran 顺序的索引。
multi_index
每次迭代都会跟踪一种索引类型。
external_loop
返回的遍历结果是具有多个值的一维数组。

1
2
3
4
5
6
7
import numpy as np
a = np.arange(0,60,5)
a = a.reshape(3,4)
print("原数组",a)
#修改后数组
for x in np.nditer(a, flags = ['external_loop'], order = 'F'):
print(x)
迭代多个数组

如果两个数组都能够被广播,那么 nditer 对象就可以同时对它们迭代。

假设数组 a 的维度是 34,另一个数组 b 的维度是 14 (即维度较小的数组 b 可以被广播到数组 a 中)

1
2
3
4
5
6
7
8
9
import numpy as np
a = np.arange(0,60,5)
a = a.reshape(3,4)
print (a)
b = np.array([1, 2, 3, 4], dtype = int)
print (b)
#广播迭代
for x,y in np.nditer([a,b]):
print ("%d:%d" % (x,y),end=",")

NumPy相关数组操作

NumPy 中包含了一些处理数组的常用方法,大致可分为以下几类:

数组变维操作
数组转置操作
修改数组维度操作
连接与分割数组操作

数组变维操作

reshape
在不改变数组元素的条件下,修改数组的形状。

flat
返回是一个迭代器,可以用 for 循环遍历其中的每一个元素。

flatten
以一维数组的形式返回一份数组的副本,对副本的操作不会影响到原数组。

ravel
返回一个连续的扁平数组(即展开的一维数组),与 flatten不同,它返回的是数组视图(修改视图会影响原数组)。

  1. numpy.ndarray.flat
    numpy.ndarray.flat 返回一个数组迭代器

    1
    2
    3
    4
    5
    6
    7
    import numpy as np
    a = np.arange(9).reshape(3,3)
    for row in a:
    print (row)
    #使用flat属性:
    for ele in a.flat:
    print (ele,end=",")
  2. numpy.ndarray.flatten()

numpy.ndarray.flatten 返回一份数组副本,对副本修改不会影响原始数组

语法格式:ndarray.flatten(order=’C’)

1
2
3
4
5
6
7
import numpy as np
a = np.arange(8).reshape(2,4)
print (a)
#默认按行C风格展开的数组
print (a.flatten())
#以F风格顺序展开的数组
print (a.flatten(order = 'F'))
  1. numpy.ravel()

numpy.ravel() 将多维数组中的元素以一维数组的形式展开,该方法返回数组的视图(view),如果修改,则会影响原始数组。

numpy.ravel(a, order=’C’)

1
2
3
4
5
6
7
8
9
10
import numpy as np
a = np.arange(8).reshape(2,4)
print ('原数组:')
print (a)

print ('调用 ravel 函数后:')
print (a.ravel())

print ('F 风格顺序调用 ravel 函数之后:')
print (a.ravel(order = 'F'))
数组转置操作

transpose
将数组的维度值进行对换,比如二维数组维度(2,4)使用该方法后为(4,2)。

ndarray.T
与 transpose 方法相同。

rollaxis
沿着指定的轴向后滚动至规定的位置。

swapaxes
对数组的轴进行对换。

  1. numpy.transpose()

numpy.transpose() 用于对换多维数组的维度,比如二维数组使用此方法可以实现矩阵转置

语法格式:numpy.transpose(arr, axes)

arr:要操作的数组

axes:可选参数,元组或者整数列表,将会按照该参数进行转置

1
2
3
4
import numpy as np
a = np.arange(12).reshape(3,4)
print (a)
print (np.transpose(a))
  1. numpy.rollaxis()

该方法表示沿着指定的轴,向后滚动至一个特定位置

格式如下:numpy.rollaxis(arr, axis, start)

arr:要传入的数组;
axis:沿着哪条轴向后滚动,其它轴的相对位置不会改变;
start:默认以 0 轴开始,可以根据数组维度调整它的值。

  1. numpy.swapaxes()

该方法用于交换数组的两个轴,

语法格式:numpy.swapaxes(arr, axis1, axis2)

1
2
3
4
5
6
import numpy as np
# 创建了三维的 ndarray
a = np.arange(27).reshape(3,3,3)
print (a)
#对换0轴与2轴
print(np.swapaxes(a,2,0))
修改数组维度操作

修改数组维度的操作,主要有以下方法:

broadcast
生成一个模拟广播的对象。

broadcast_to
将数组广播为新的形状。

expand_dims
扩展数组的形状。

squeeze
从数组的形状中删除一维项。

  1. numpy.broadcast()

返回值是数组被广播后的对象,该函数以两个数组作为输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
a = np.array([[1], [2], [3]])
b = np.array([4, 5, 6])
# 对b广播a
d = np.broadcast(a,b)
#d它拥有 iterator 属性
r,c = d.iters
print (next(r), next(c))
print (next(r), next(c))
# 使用broadcast将a与b相加
e = np.broadcast(a,b)
f=np.empty(e.shape)
f.flat=[x+y for (x,y) in e]
print(f)
print(a+b)
  1. numpy.broadcast_to()

该函数将数组广播到新形状中,它在原始数组的基础上返回一个只读视图。 如果新形状不符合 NumPy 的广播规则,则会抛出 ValueError 异常。函数的语法格式如下:numpy.broadcast_to(array, shape, subok)

1
2
3
4
5
import numpy as np
a = np.arange(4).reshape(1,4)
print("原数组",a)
print ('调用 broadcast_to 函数之后:')
print (np.broadcast_to(a,(4,4)))
  1. numpy.expand_dims()

在指定位置插入新的轴,从而扩展数组的维度,语法格式如下:numpy.expand_dims(arr, axis)

参数说明:arr:输入数组axis:新轴插入的位置

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
x = np.array(([1,2],[3,4]))
print ('数组 x:')
print (x)
# 在 0 轴处插入新的轴
y = np.expand_dims(x, axis = 0)
print ('数组 y:')
print (y)
print ('\n')
print ('数组 x 和 y 的形状:')
print (x.shape, y.shape)
  1. numpy.squeeze()
    删除数组中维度为 1 的项,例如,一个数组的 shape 是 (5,1),经此函数后,shape 变为 (5,) 。其函数语法格式如下:

numpy.squeeze(arr, axis)

参数说明:arr:输入数的组;axis:取值为整数或整数元组,用于指定需要删除的维度所在轴,指定的维度值必须为 1 ,否则将会报错,若为 None,则删除数组维度中所有为 1 的项。

下面是带有 axis 参数的实例:

1
2
3
4
5
6
7
>>> x = np.array([[[0], [1], [2]]])
>>> x.shape
(1, 3, 1)
>>> np.squeeze(x).shape
(3,)
>>> np.squeeze(x, axis=(2,)).shape
(1, 3)
1
2
3
4
5
6
7
import numpy as np
a = np.arange(9).reshape(1,3,3)
print (a)
b = np.squeeze(a)
print (b)
print ('数组 a 和 b 的形状:')
print (x.shape, y.shape)
连接与分割数组操作

连接与分割数组是数组的两种操作方式

1) 连接数组操作 numpy.concatenate()

沿指定轴连接相同形状的两个或多个数组
格式如下:numpy.concatenate((a1, a2, …), axis)

a1, a2, …:表示一系列相同类型的数组;
axis:沿着该参数指定的轴连接数组,默认为 0。

创建两个 a 、b 数组,并沿指定轴将它们连接起来。注意两个数组的形状要保持一致。

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
#创建数组a
a = np.array([[10,20],[30,40]])
print (a)
#创建数组b
b = np.array([[50,60],[70,80]])
print (b)
#沿轴 0 连接两个数组
print (np.concatenate((a,b)))
#沿轴 1 连接两个数组
print (np.concatenate((a,b),axis = 1))

数组连接操作至少需要两个维度相同的数组,才允许对它们进行垂直或者水平方向上的操作。在垂直方向堆叠数组

1
2
3
4
5
6
import numpy as np
a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
#垂直堆叠
c = np.vstack((a,b))
print (c)
2) 分割数组操作

numpy.split() 沿指定的轴将数组分割为多个子数组

语法格式:numpy.split(ary, indices_or_sections, axis)

ary:被分割的数组
indices_or_sections:若是一个整数,代表用该整数平均切分,若是一个数组,则代表沿轴切分的位置(左开右闭);
axis:默认为0,表示横向切分;为1时表示纵向切分。

1
2
3
4
5
6
7
8
9
10
import numpy as np
a = np.arange(6)
#原数组
print (a)
#将数组分为二个形状大小相等的子数组
b = np.split(a,2)
print (b)
#将数组在一维数组中标明要位置分割
b = np.split(a,[3,4])
print (b)

最后看一下 hsplit() 的使用方法

1
2
3
4
5
6
import numpy as np
#arr1数组
arr1 = np.floor(10 * np.random.random((2, 6)))
print(arr1)
#拆分后数组
print(np.hsplit(arr1, 3))