基础数据类型:ndarray数组

ndarray数组是Numpy的基础数据结构式,可以灵活、高效的处理多个元素的操作。本节主要从如下五部分展开介绍:

  • 为什么引入ndarray数组
  • 如何创建ndarray数组
  • ndarray数组的基本运算
  • ndarray数组的切片和索引
  • ndarray数组的统计运算

为什么引入ndarray数组

Python中的list列表也可以非常灵活的处理多个元素的操作,但效率却非常低。与之比较,ndarray数组具有如下特点:

  • ndarray数组中所有元素的数据类型相同、数据地址连续,批量操作数组元素时速度更快。而list列表中元素的数据类型可能不同,需要通过寻址方式找到下一个元素。
  • ndarray数组支持广播机制,矩阵运算时不需要写for循环。
  • Numpy底层使用c语言编写,内置并行计算功能,运行速度高于Python代码。

下面通过几个实际例子体会一下,在完成同一个任务时,使用ndarray数组和list列表的差异。

案例1:实现a+1的计算

  1. # Python原生的list
  2. # 假设有两个list
  3. a = [1, 2, 3, 4, 5]
  4. b = [2, 3, 4, 5, 6]
  5. # 完成如下计算
  6. # 对a的每个元素 + 1
  7. # a = a + 1 不能这么写,会报错
  8. # a[:] = a[:] + 1 也不能这么写,也会报错
  9. for i in range(5):
  10. a[i] = a[i] + 1
  11. a
  1. [2, 3, 4, 5, 6]
  1. # 使用ndarray
  2. import numpy as np
  3. a = np.array([1, 2, 3, 4, 5])
  4. a = a + 1
  5. a
  1. array([2, 3, 4, 5, 6])

案例2:实现c=a+b的计算

  1. # 计算 a和b中对应位置元素的和,是否可以这么写?
  2. a = [1, 2, 3, 4, 5]
  3. b = [2, 3, 4, 5, 6]
  4. c = a + b
  5. # 检查输出发现,不是想要的结果
  6. c
  1. [1, 2, 3, 4, 5, 2, 3, 4, 5, 6]
  1. # 使用for循环,完成两个list对应位置元素相加
  2. c = []
  3. for i in range(5):
  4. c.append(a[i] + b[i])
  5. c
  1. [3, 5, 7, 9, 11]
  1. # 使用numpy中的ndarray完成两个ndarray相加
  2. import numpy as np
  3. a = np.array([1, 2, 3, 4, 5])
  4. b = np.array([2, 3, 4, 5, 6])
  5. c = a + b
  6. c
  1. array([ 3, 5, 7, 9, 11])

通过上面的两个案例可以看出,在不写for循环的情况下,ndarray数组就可以非常方便的完成数学计算。在编写矢量或者矩阵的程序时,可以像编写普通数值一样,使得代码极其简洁。

另外,ndarray数组还提供了广播机制,它会按一定规则自动对数组的维度进行扩展以完成计算。如下面例子所示,1维数组和2维数组进行相加操作,ndarray数组会自动扩展1维数组的维度,然后再对每个位置的元素分别相加。

  1. # 自动广播机制,1维数组和2维数组相加
  2. # 二维数组维度 2x5
  3. # array([[ 1, 2, 3, 4, 5],
  4. # [ 6, 7, 8, 9, 10]])
  5. d = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
  6. # c是一维数组,维度5
  7. # array([ 4, 6, 8, 10, 12])
  8. c = np.array([ 4, 6, 8, 10, 12])
  9. e = d + c
  10. e
  1. array([[ 5, 8, 11, 14, 17],
  2. [10, 13, 16, 19, 22]])

创建ndarray数组

创建ndarray数组最简单的方式就是使用array函数,它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的numpy数组。下面通过实例体会下arrayarangezerosones四个主要函数的用法。

  • array:创建将嵌套序列(比如由一组等长列表组成的列表),并转换为一个多维数组。
  1. # 导入numpy
  2. import numpy as np
  3. # 从list创建array
  4. a = [1,2,3,4,5,6] # 创建简单的列表
  5. b = np.array(a) # 将列表转换为数组
  6. b
  1. array([1, 2, 3, 4, 5, 6])
  • arange:创建元素从0到10依次递增2的数组。
  1. # 通过np.arange创建
  2. # 通过指定start, stop (不包括stop),interval来产生一个1维的ndarray
  3. a = np.arange(0, 20, 2)
  4. a
  1. array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
  • zeros:创建指定长度或者形状的全0数组。
  1. # 创建全0的ndarray
  2. a = np.zeros([3,3])
  3. a
  1. array([[0., 0., 0.],
  2. [0., 0., 0.],
  3. [0., 0., 0.]])
  • ones:创建指定长度或者形状的全1数组。
  1. # 创建全1的ndarray
  2. a = np.ones([3,3])
  3. a
  1. array([[1., 1., 1.],
  2. [1., 1., 1.],
  3. [1., 1., 1.]])

查看ndarray数组的属性

ndarray的属性包括shapedtypesizendim等,通过如下代码可以查看darray数组的属性。

  • shape:数组的形状 ndarray.shape,1维数组(N, ),二维数组(M, N),三维数组(M, N, K)。
  • dtype:数组的数据类型。
  • size:数组中包含的元素个数 ndarray.size,其大小等于各个维度的长度的乘积。
  • ndim:数组的维度大小,ndarray.ndim, 其大小等于ndarray.shape所包含元素的个数。
  1. a = np.ones([3, 3])
  2. print('a, dtype: {}, shape: {}, size: {}, ndim: {}'.format(a.dtype, a.shape, a.size, a.ndim))
  1. a, dtype: float64, shape: (3, 3), size: 9, ndim: 2
  1. import numpy as np
  2. b = np.random.rand(10, 10)
  3. b.shape
  1. (10, 10)
  1. b.size
  1. 100
  1. b.ndim
  1. 2
  1. b.dtype
  1. dtype('float64')

改变ndarray数组的数据类型和形状

创建ndarray之后,可以对其数据类型或形状进行修改,代码如下所示。

  1. # 转化数据类型
  2. b = a.astype(np.int64)
  3. print('b, dtype: {}, shape: {}'.format(b.dtype, b.shape))
  4. # 改变形状
  5. c = a.reshape([1, 9])
  6. print('c, dtype: {}, shape: {}'.format(c.dtype, c.shape))
  1. b, dtype: int64, shape: (3, 3)
  2. c, dtype: float64, shape: (1, 9)

ndarray数组的基本运算

ndarray数组可以像普通的数值型变量一样进行加减乘除操作,主要包含如下两种运算:

  • 标量和ndarray数组之间的运算
  • 两个ndarray数组之间的运算

标量和ndarray数组之间的运算

标量和ndarray数组之间的运算主要包括除法、乘法、加法和减法运算,具体代码如下所示。

  1. # 标量除以数组,用标量除以数组的每一个元素
  2. arr = np.array([[1., 2., 3.], [4., 5., 6.]])
  3. 1. / arr
  1. array([[1. , 0.5 , 0.33333333],
  2. [0.25 , 0.2 , 0.16666667]])
  1. # 标量乘以数组,用标量乘以数组的每一个元素
  2. arr = np.array([[1., 2., 3.], [4., 5., 6.]])
  3. 2.0 * arr
  1. array([[ 2., 4., 6.],
  2. [ 8., 10., 12.]])
  1. # 标量加上数组,用标量加上数组的每一个元素
  2. arr = np.array([[1., 2., 3.], [4., 5., 6.]])
  3. 2.0 + arr
  1. array([[3., 4., 5.],
  2. [6., 7., 8.]])
  1. # 标量减去数组,用标量减去数组的每一个元素
  2. arr = np.array([[1., 2., 3.], [4., 5., 6.]])
  3. 2.0 - arr
  1. array([[ 1., 0., -1.],
  2. [-2., -3., -4.]])

两个ndarray数组之间的运算

两个ndarray数组之间的运算主要包括减法、加法、乘法、除法和开根号运算,具体代码如下所示。

  1. # 数组 减去 数组, 用对应位置的元素相减
  2. arr1 = np.array([[1., 2., 3.], [4., 5., 6.]])
  3. arr2 = np.array([[11., 12., 13.], [21., 22., 23.]])
  4. arr1 - arr2
  1. array([[-10., -10., -10.],
  2. [-17., -17., -17.]])
  1. # 数组 加上 数组, 用对应位置的元素相加
  2. arr1 = np.array([[1., 2., 3.], [4., 5., 6.]])
  3. arr2 = np.array([[11., 12., 13.], [21., 22., 23.]])
  4. arr1 + arr2
  1. array([[12., 14., 16.],
  2. [25., 27., 29.]])
  1. # 数组 乘以 数组,用对应位置的元素相乘
  2. arr1 * arr2
  1. array([[ 11., 24., 39.],
  2. [ 84., 110., 138.]])
  1. # 数组 除以 数组,用对应位置的元素相除
  2. arr1 / arr2
  1. array([[0.09090909, 0.16666667, 0.23076923],
  2. [0.19047619, 0.22727273, 0.26086957]])
  1. # 数组开根号,将每个位置的元素都开根号
  2. arr ** 0.5
  1. array([[1. , 1.41421356, 1.73205081],
  2. [2. , 2.23606798, 2.44948974]])

ndarray数组的索引和切片

在编写模型过程中,通常需要访问或者修改ndarray数组某个位置的元素,则需要使用ndarray数组的索引。有些情况下可能需要访问或者修改一些区域的元素,则需要使用ndarray数组的切片。

ndarray数组的索引和切片的使用方式与Python中的list类似。通过[ -n , n-1 ]的下标进行索引,通过内置的slice函数,设置其start,stopstep参数进行切片,从原数组中切割出一个新数组。

ndarray数组的索引是一个内容丰富的主题,因为选取数据子集或的单个元素的方式有很多。下面从一维数组和多维数组两个维度介绍索引和切片的方法。

一维ndarray数组的索引和切片

从表面上看,一维数组跟Python列表的功能类似,它们重要区别在于:数组切片产生的新数组,还是指向原来的内存区域,数据不会被复制,视图上的任何修改都会直接反映到源数组上。将一个标量值赋值给一个切片时,该值会自动传播到整个选区。

  1. # 1维数组索引和切片
  2. a = np.arange(30)
  3. a[10]
  1. 10
  1. a = np.arange(30)
  2. b = a[4:7]
  3. b
  1. array([4, 5, 6])
  1. #将一个标量值赋值给一个切片时,该值会自动传播到整个选区。
  2. a = np.arange(30)
  3. a[4:7] = 10
  4. a
  1. array([ 0, 1, 2, 3, 10, 10, 10, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
  2. 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
  1. # 数组切片产生的新数组,还是指向原来的内存区域,数据不会被复制。
  2. # 视图上的任何修改都会直接反映到源数组上。
  3. a = np.arange(30)
  4. arr_slice = a[4:7]
  5. arr_slice[0] = 100
  6. a, arr_slice
  1. (array([ 0, 1, 2, 3, 100, 5, 6, 7, 8, 9, 10, 11, 12,
  2. 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
  3. 26, 27, 28, 29]), array([100, 5, 6]))
  1. # 通过copy给新数组创建不同的内存空间
  2. a = np.arange(30)
  3. arr_slice = a[4:7]
  4. arr_slice = np.copy(arr_slice)
  5. arr_slice[0] = 100
  6. a, arr_slice
  1. (array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
  2. 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
  3. array([100, 5, 6]))

多维ndarray数组的索引和切片

多维ndarray数组的索引和切片具有如下特点:

  • 在多维数组中,各索引位置上的元素不再是标量而是多维数组。
  • 以逗号隔开的索引列表来选取单个元素。
  • 在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray。

多维ndarray数组的索引和切片代码如下所示。

  1. # 多维数组索引和切片
  2. a = np.arange(30)
  3. arr3d = a.reshape(5, 3, 2)
  4. arr3d
  1. array([[[ 0, 1],
  2. [ 2, 3],
  3. [ 4, 5]],
  4.  
  5. [[ 6, 7],
  6. [ 8, 9],
  7. [10, 11]],
  8.  
  9. [[12, 13],
  10. [14, 15],
  11. [16, 17]],
  12.  
  13. [[18, 19],
  14. [20, 21],
  15. [22, 23]],
  16.  
  17. [[24, 25],
  18. [26, 27],
  19. [28, 29]]])
  1. # 只有一个索引指标时,会在第0维上索引,后面的维度保持不变
  2. arr3d[0]
  1. array([[0, 1],
  2. [2, 3],
  3. [4, 5]])
  1. # 两个索引指标
  2. arr3d[0][1]
  1. array([2, 3])
  1. # 两个索引指标
  2. arr3d[0, 1]
  1. array([2, 3])
  1. # 使用python中的for语法对数组切片
  2. a = np.arange(24)
  3. a
  1. array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
  2. 17, 18, 19, 20, 21, 22, 23])
  1. a = a.reshape([6, 4])
  2. a
  1. array([[ 0, 1, 2, 3],
  2. [ 4, 5, 6, 7],
  3. [ 8, 9, 10, 11],
  4. [12, 13, 14, 15],
  5. [16, 17, 18, 19],
  6. [20, 21, 22, 23]])
  1. # 使用for语句生成list
  2. [k for k in range(0, 6, 2)]
  1. [0, 2, 4]
  1. # 结合上面列出的for语句的用法
  2. # 使用for语句对数组进行切片
  3. # 下面的代码会生成多个切片构成的list
  4. # k in range(0, 6, 2) 决定了k的取值可以是0, 2, 4
  5. # 产生的list的包含三个切片
  6. # 第一个元素是a[0 : 0+2],
  7. # 第二个元素是a[2 : 2+2],
  8. # 第三个元素是a[4 : 4+2]
  9. slices = [a[k:k+2] for k in range(0, 6, 2)]
  10. slices
  1. [array([[0, 1, 2, 3],
  2. [4, 5, 6, 7]]), array([[ 8, 9, 10, 11],
  3. [12, 13, 14, 15]]), array([[16, 17, 18, 19],
  4. [20, 21, 22, 23]])]
  1. slices[0]
  1. array([[0, 1, 2, 3],
  2. [4, 5, 6, 7]])

ndarray数组的统计方法

可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。主要包括如下统计方法:

  • mean:计算算术平均数,零长度数组的mean为NaN。
  • stdvar:计算标准差和方差,自由度可调(默认为n)。
  • sum :对数组中全部或某轴向的元素求和,零长度数组的sum为0。
  • maxmin:计算最大值和最小值。
  • argminargmax:分别为最大和最小元素的索引。
  • cumsum:计算所有元素的累加。
  • cumprod:计算所有元素的累积。

说明:

sum、mean以及标准差std等聚合计算既可以当做数组的实例方法调用,也可以当做Numpy函数使用。


  1. # 计算均值,使用arr.mean() 或 np.mean(arr),二者是等价的
  2. arr = np.array([[1,2,3], [4,5,6], [7,8,9]])
  3. arr.mean(), np.mean(arr)
  1. (5.0, 5.0)
  1. # 求和
  2. arr.sum(), np.sum(arr)
  1. (45, 45)
  1. # 求最大值
  2. arr.max(), np.max(arr)
  1. (9, 9)
  1. # 求最小值
  2. arr.min(), np.min(arr)
  1. (1, 1)
  1. # 指定计算的维度
  2. # 沿着第1维求平均,也就是将[1, 2, 3]取平均等于2,[4, 5, 6]取平均等于5,[7, 8, 9]取平均等于8
  3. arr.mean(axis = 1)
  1. array([2., 5., 8.])
  1. # 沿着第0维求和,也就是将[1, 4, 7]求和等于12,[2, 5, 8]求和等于15,[3, 6, 9]求和等于18
  2. arr.sum(axis=0)
  1. array([12, 15, 18])
  1. # 沿着第0维求最大值,也就是将[1, 4, 7]求最大值等于7,[2, 5, 8]求最大值等于8,[3, 6, 9]求最大值等于9
  2. arr.max(axis=0)
  1. array([7, 8, 9])
  1. # 沿着第1维求最小值,也就是将[1, 2, 3]求最小值等于1,[4, 5, 6]求最小值等于4,[7, 8, 9]求最小值等于7
  2. arr.min(axis=1)
  1. array([1, 4, 7])
  1. # 计算标准差
  2. arr.std()
  1. 2.581988897471611
  1. # 计算方差
  2. arr.var()
  1. 6.666666666666667
  1. # 找出最大元素的索引
  2. arr.argmax(), arr.argmax(axis=0), arr.argmax(axis=1)
  1. (8, array([2, 2, 2]), array([2, 2, 2]))
  1. # 找出最小元素的索引
  2. arr.argmin(), arr.argmin(axis=0), arr.argmin(axis=1)
  1. (0, array([0, 0, 0]), array([0, 0, 0]))