本教程需要提前掌握Python基础语法以及线性代数相关基础知识 教程默认代码运行之前执行了import numpy as np
概述
本节内容主要介绍数据的相关概念
1、ndarray数组概念、创建、基本操作以及数组的相关属性
2、数组的广播概念
3、数组的切片和索引
4、数组的坐标轴和维度问题
5、数组中元素的数据类型和类型指定方法
6、数组的视图和副本的相关概念和区别
1.1 多维数据结构ndarray
NumPy是以多维数组为基础数据结构进行操作的软件库。因此,NumPy没有使用Python的列表,而是使用NumPy自己实现的名为ndarray的这一独特的数据结构进行更为高效的运算。
1.1.1 ndarrary概念
ndarray(The N-dimensional array)是有多个具有相同类型和尺寸的元素所组成的(通常具有固定的尺寸)多维的数组。
- 只能存储具有相同数据类型的元素
- 每个维度中的元素数量必须是固定的
- 基于C语言实现,并经过了大量优化的矩阵运算,可以实现高性能的数据处理
如果使用ndarray代替Python的列表,就可以很方便地使用其中所提供的用于操作多维数组的属性和方法
1.1.2 创建数组
np.array
(object, dtype=None, *, copy=True, order='K', subok=False, ndmin=0, like=None)
将array_like(类数组对象)转化为数组对象- object:array_like(类数组对象)
- 数组,公开数组接口的任何对象,__array_方法返回数组的对象,任何嵌套的序列。
- dtype:(可选)表示数组元素的类型
- float、int、complex等(可以通过numpy.sctypes查看)
- copy:(可选)默认为True,表示复制该对象
- order:(可选)指定阵列的内存布局,参数值有{‘K’, ‘A’, ‘C’, ‘F’},为可选
- K(默认),按照元素在内存中出现的顺序创建数组
- C:表示使用行主序方式存储数据
- F:表示使用列主序方式存储数据
- subok:(可选)表示为True返回一个与基类关联的子类,否则返回的数组将被强制为基类数组(默认,为False)
- ndmin:(可选)表示返回数组的最小维数。默认为0
np.arange([start,] stop[, step,], dtype=None, *, like=None)
类似range函数,生成一个数组。
上面提供了创建一维数组和多维数组的相关方法,在例子中
np.pi
为数学概念中的圆周率π。1.1.3 基本操作符
在ndarray中,有坐标轴概念axis
- axis=1 表示列方向
- axis=0表示行方向
1.1.4 数组属性
本节对ndarray所包含的属性(attributes)进行介绍。使用(实例变量名).(属性)形式的语句就可以获取ndarray实例中所包含的属性的值。
属性 | 说明 | ㅤ | 备注 |
T | 返回经过转置的矩阵 | ㅤ | ㅤ |
data | 用于表示数组中的数据从哪里开始的Python缓冲区对象 | ㅤ | ㅤ |
dtype | ndarray中元素的类型 | ㅤ | ㅤ |
flags | 关于ndarray中的数据在内存中的保存方式(内存布局)的信息 | ㅤ | ㅤ |
flat | 将ndarray转换为一维数组的迭代器 | ㅤ | ㅤ |
imag | ndarray中的虚数部分(imaginary part) | ㅤ | ㅤ |
real | ndarray中的实数部分(real part) | ㅤ | ㅤ |
size | ndarray中所包含元素的数量 | ㅤ | ㅤ |
itemsize | 保存在内存中的每个元素所需的以字节为单位的内存容量 | ㅤ | ㅤ |
nbytes | ndarray中所有元素所占内存空间的总字节数 | ㅤ | ㅤ |
ndim | ndarray中所包含的维数 | ㅤ | ㅤ |
shape | 使用元组表示的ndarray的形状 | ㅤ | (行数,列数) |
strides | 使用元组表示的在各个维度方向上要移动到下一个接邻的元素时所需移动的字节数 | ㅤ | ㅤ |
ctypes | 用于操作ctypes模块的迭代器 | ㅤ | ㅤ |
base | ndarray的基类对象(用于表示引用的是哪里的内存数据) | ㅤ | ㅤ |
通过属性访问对象的信息并不会导致数组(ndarray)的内容发生变化。例如,使用
.T
属性显示转置矩阵并不会导致原有数据发生变化。1.1.5 步长
步长是指在访问数组时跨过的字节数。在ndarray中,可以使用
strides
属性获取各个维度方向上的步长信息。步长是一个元组,其中的每个元素表示在相应维度方向上移动到下一个相邻元素所需的字节数。了解数组的步长, 对高性能读取数组中的数据提供了参考之一.在上面的例子中,
a
数组的步长为(20, 4)
,表示在行方向上每次移动20个字节,在列方向上每次移动4个字节。1.1.6 内存布局
为了提升使用numpy进行矩阵运算的性能,需要知道ndarray中的元素在内存中具体是如何存储的。
NumPy数组的构成
使用ndarray类生成的实例在内存中是以一维数组的形式进行存储的。
- 其中作为登记信息的一部分,用于描述数据的类型、数组的形状(shape)等,以一维数组的形式保存的,用于指定读取元素数据方式的数据称为元数据。
- 而在这些元数据后面,是以数据形式保存的数组元素的值。其中,数组的排列方式大致可以分为两类。
NumPy数组在内存上的排列方式
- 行主序(row-major):C语言中所使用的数据排列方式
- 在Numpy参数order中对应为C
- 列主序(column-major):FORTRAN和MATLAB等语言所使用的排列方式。
- 在Numpy参数order中对应为F
在上面介绍的数组属性中包括
.flag
,包括C_CONTIGUOUS : True F_CONTIGUOUS : False
其中,C_CONTIGUOUS表示是否可以使用行主序进行读取;F_CONTIGUOUS表示是否可以使用列主序方式进行读取。
因为ndarray是为矩阵运算服务的,ndarray中所有数据类型相同,字节数形同,解释方式也相同,所以可以紧密的排列在一起。这种内存布局方式就为我们从两个方向上带来了优势:缓存命中率和向量化计算。
1.2 关于坐标轴和维度
1.2.1 ndarray的维度
对于NumPy的多维数组ndarray,可以使用
.shape
属性获取其结构信息。而ndim属性可以表示多维数组具有几个维度的结构,也就是说,相当于shape的元素数量,等于len(arr.shape)
右上面可以看出,ndarray对数组维度的描述是从高维度到低维度来描述的。
1.2.2 关于坐标轴(axis)
axis正如其名,是相当于坐标轴一样的东西。指定坐标轴的方法是将axis对应为shape的索引。
让我再来看下三维数组的情况:
对以上内容做个总结,一维数组的axis只有0,二维数组的axis有0和1,而三维数组的axis有0,1,2.
1.2.3 作为函数参数的axis
理解1.2.1和1.2.2节内容对本节所讲的是非常重要的。在NumPy中,使用axis参数的函数众多。其中,ndarray.sum函数可以指定axis对元素进行求和计算。本质上是按照指定的axis坐标轴进行降维计算。
有时候你会遇到axis为-1,-2和-3的情况,分别表示最后一维,倒数第二维,和倒数第三维,和axis为2,1,0一一对应。
1.3 广播
1.3.1 广播(broadcasting)概念
广播是指当进行元素级操作时,NumPy会自动处理不同形状的数组之间的运算。在广播操作中,较小的数组会被较大的数组"拉伸"到相同的形状,使得它们的形状能够匹配。
广播的一个常见应用是在数组之间进行加法、减法、乘法等元素级操作。在进行这些操作时,NumPy会自动对数组进行广播,使得它们具有兼容的形状。
1.3.2 广播的运行机制
1、在作为广播对象的数组中,如果维数(ndim)不同,在其shape的开头加入1以对形状进行调整。
在上面例子中,
np.array([3, 4])
的shape
开头处会被加入1.也就是说,在其形状别转换为(1, 2)
后在进行计算。2、能用于运算处理的数组是每个维度的元素数量与最大数量相等或刚好为1的数组。
接下来,考虑一下符合使用广播功能所要求的条件。NumPy的广播规则中要求,只有当每个维度的元素数量与最大数量相等或者刚好为1时,才能允许广播
也就是说,如果是在具有游侠形状的数组之间进行运算,就允许使用广播功能。
此外,如果结合规则1,对于如下维数的不同的情况,只要在开头加入1就能进行计算了
3、结果中所输出的数组的形状,会根据每个维度中元素数量的最大值进行调整。
例如将
(1, 1, 3)
和(4, 2, 1)
进行广播时,输出的形状使用的是每个维度的最大值,因而得到(4, 2, 3)
这样的结果。4、对于元素数量为1的维度所在的轴,使用相同的值进行重复填充。
练习:
注:
对于初学者可能对下面b和c指向的数组区分不开,不理解,下面看下讲解:
1.4 索引和切片
如果需要将数组中特定的值单独提取出来,就需要用到索引和切片。
只要掌握了一维度的切片,多维度的切片不过就是将各个不同的维度的切片组合起来而已。
索引的更高级玩法:
1.4.1 布尔索引(boolean index)
布尔索引就是通过由bool值构成的一维数组和原数组进行匹配,提取原数组中对应为True的元素,构成新的数组。这种作用,其实是把一维数组中布尔值为True的相应行或列给抽取了出来
(注意:一维数组的长度必须和想要切片的维度或轴的长度一致)
。1.5 元素的数据类型
1.5.1 需要dtype的理由
元素的数据类型是通过dtype参数来指定的。如1.1.1节所讲解过的,NumPy的内部是使用C语言来实现对大量的数据进行高速处理的。Python语言本身并不是那么高速的编程语言,因此,矩阵运算和数据处理等操作都是通过C语言实现的。
通过正确地为NumPy数组指定数据类型,可以实现在Python中进行更为高效的数据处理,提高代码的执行效率。
1.5.2 数据类型
首先,总结一下可以在NumPy中使用的数据类型。将NumPy的数据类型按照数学意义进行分类,可以分为带符号整数(负0正整数)int、浮点数float、复数complex、无符号整数(非负整数)uint、布尔值bool等。此外还可以在位一级上对每个元素所需要保留的内存空间大小进行指定。
数据类型 | 概要 |
带符号整数int | int8、int16、int32、int64 |
无符号整数unit | uint8、uint16、uint32、uint64 |
浮点数float | float16、float32、float64、float128 |
布尔值bool | True、False(数据长度为8位) |
可以使用arr.dtype获取ndarray的dtype信息,在没有指定的情况下,float默认位数为64,int默认位数为32
- int默认为32位,float默认64位,创建时指定其他位数,返回时dtype参数会带有说明。
- 在dtype参数中,指定带位数的数据类型时,需要加引号,否则会报错。
- 如果位数太大,无法用int类型表示时会产生溢出错误,可以用float创建
- 可以使用
arr.dtype = “int64”
形式对已有数组的数据类型进行转化,但原则上是不推荐在生成了数组之后再进行数据类型的转化。
其实,改变数据类型只是改变了数据的读取方式而已,如果将数据类型恢复为原有的类型,就可以再次得到原有的数组。
1.5.3 类型转换
1.6 副本与视图的区别
1.6.1 副本和视图
在对NumPy数组中的元素进行复制时,可以使用的选项包括副本(copy)和视图(view)两类。加深对这两个选项的理解,在编写代码时就可以更加注重内存的使用效率和代码的执行速度,减少无意中改变数组中的数据等错误的发生。
- 副本:使用与原有数组不同的内存空间,但是数据内容是相同的
- nd.copy()
- 视图:与原有数组引用的是同一个内存地址
- nd.view()
由于视图方式是对同一个内存地址进行引用,因此对视图数组中元素所做的更改也会反应在原有数组的数据中。
从内存的使用效率来说,所处理的数组越大,尽量使用视图方式是比较好的,可以节省内存空间。然而,使用视图也可能导致原有数组中的数据发生变化。因此,在需要保证原有数组中数据不会发生变化的情况下,使用副本会比较好。
1.6.2 不同操作方式的区别
从上面两种加法的例子可以看出,代码写成
a = a + 1
,结果中就会生成a的副本,每个元素加1后得到的新的数组被带入变量a中。而a += 1
则是在原有的数组上对每个元素加1,不会生成新的数组。对于上面的运算规则,四则运算符+、-、*、/及幂运算**都适用。
如果要在不创建副本的前提下进行运算操作,还可以使用专门用于计算的函数。加法使用add函数,减法使用subtract函数等等。这些函数的参数中通常都有一个名为out的参数,如果将原始数组指定到这个参数中,就能直接对数组中的值进行覆盖,有效提升内存的使用效率。