NumPy简介
NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。
NumPy 的前身 Numeric 最早是由 Jim Hugunin 与其它协作者共同开发,2005 年,Travis Oliphant 在 Numeric 中结合了另一个同性质的程序库 Numarray 的特色,并加入了其它扩展而开发了 NumPy。NumPy 为开放源代码并且由许多协作者共同维护开发。
NumPy 是一个运行速度非常快的数学库,主要用于数组计算,包含:
一个强大的N维数组对象 ndarray
广播功能函数
整合 C/C++/Fortran 代码的工具
线性代数、傅里叶变换、随机数生成等功能
NumPy 通常与 SciPy(Scientific Python)和 Matplotlib(绘图库)一起使用, 这种组合广泛用于替代 MatLab,是一个强大的科学计算环境,有助于我们通过 Python 学习数据科学或者机器学习。
SciPy 是一个开源的 Python 算法库和数学工具包。
SciPy 包含的模块有最优化、线性代数、积分、插值、特殊函数、快速傅里叶变换、信号处理和图像处理、常微分方程求解和其他科学与工程中常用的计算。
Matplotlib 是 Python 编程语言及其数值数学扩展包 NumPy 的可视化操作界面。它为利用通用的图形用户界面工具包,如 Tkinter, wxPython, Qt 或 GTK+ 向应用程序嵌入式绘图提供了应用程序接口(API)。
安装自行百度
NumPy Ndarray对象
NumPy 最重要的一个特点是其 N 维数组对象 ndarray,它是一系列同类型数据的集合,以 0 下标为开始进行集合中元素的索引。
ndarray 对象是用于存放同类型元素的多维数组。
ndarray 中的每个元素在内存中都有相同存储大小的区域。
ndarray 内部由以下内容组成:
一个指向数据(内存或内存映射文件中的一块数据)的指针。
数据类型或 dtype,描述在数组中的固定大小值的格子。
一个表示数组形状(shape)的元组,表示各维度大小的元组。
一个跨度元组(stride),其中的整数指的是为了前进到当前维度下一个元素需要"跨过"的字节数。
ndarray 的内部结构:
创建一个 ndarray 只需调用 NumPy 的 array 函数即可:
numpy.rarray(object, dtype=None, copy=True, order=None, subok=False, ndmin=0)
# object:数组或嵌套的数列
# dtype 数组元素的数据类型,可选
# copy 对象是否需要复制,可选
# order 创建数组的样式,C为行方向,F为列方向,A为任意方向(默认)
# subok 默认返回一个与基本类型一致的数组
# ndmin 指定生成的数组的最小维度
c = numpy.array([[1, 2], [3, 4]], order='C', ndmin=3, dtype=complex)
print(c)
[[[1.+0.j 2.+0.j]
[3.+0.j 4.+0.j]]]
# ndarray对象由计算机内存中的连续一维部分组成,并结合索引模式,将每个元素映射到内存块中的一个位置。
NumPy数据对象
名称 描述
bool_ 布尔型数据类型(True 或者 False)
int_ 默认的整数类型(类似于 C 语言中的 long,int32 或 int64)
intc 与 C 的 int 类型一样,一般是 int32 或 int 64
intp 用于索引的整数类型(类似于 C 的 ssize_t,一般情况下仍然是 int32 或 int64)
int8 字节(-128 to 127)
int16 整数(-32768 to 32767)
int32 整数(-2147483648 to 2147483647)
int64 整数(-9223372036854775808 to 9223372036854775807)
uint8 无符号整数(0 to 255)
uint16 无符号整数(0 to 65535)
uint32 无符号整数(0 to 4294967295)
uint64 无符号整数(0 to 18446744073709551615)
float_ float64 类型的简写
float16 半精度浮点数,包括:1 个符号位,5 个指数位,10 个尾数位
float32 单精度浮点数,包括:1 个符号位,8 个指数位,23 个尾数位
float64 双精度浮点数,包括:1 个符号位,11 个指数位,52 个尾数位
complex_ complex128 类型的简写,即 128 位复数
complex64 复数,表示双 32 位浮点数(实数部分和虚数部分)
complex128 复数,表示双 64 位浮点数(实数部分和虚数部分)
数据类型对象 (dtype)
数据类型对象是用来描述与数组对应的内存区域如何使用,这依赖如下几个方面:
数据的类型(整数,浮点数或者 Python 对象)
数据的大小(例如, 整数使用多少个字节存储)
数据的字节顺序(小端法或大端法)
在结构化类型的情况下,字段的名称、每个字段的数据类型和每个字段所取的内存块的部分
如果数据类型是子数组,它的形状和数据类型
字节顺序是通过对数据类型预先设定"<“或”>“来决定的。”<“意味着小端法(最小值存储在最小的地址,即低位组放在最前面)。”>"意味着大端法(最重要的字节存储在最小的地址,即高位组放在最前面)。
dtype 对象是使用以下语法构造的:
import numpy as np
# numpy.dtype(object, align, copy)
# object - 要转换为的数据类型对象
# align - 如果为 true,填充字段使其类似 C 的结构体。
# copy - 复制 dtype 对象 ,如果为 false,则是对内置数据类型对象的引用
# int8, int16, int32, int64 四种数据类型可以使用字符串 'i1', 'i2','i4','i8' 代替
dt = np.dtype('i4')
print(dt)
int32
# 将数据类型应用于narray对象
# 创建结构化数据类型
dt = np.dtype([('age', np.int8)]) # 这里的np.int8可以用‘i1’来代替
# 将数据类型应用于ndarray对象
a = np.array([(10,), (20,), (30.)], dtype=dt)
print(a)
[(10,) (20,) (30,)]
# 类型字段名可以用于存取实际的age列
print(a['age'])
[10 20 30]
每个内建类型都有一个唯一定义它的字符代码,如下:
字符 对应类型
b 布尔型
i (有符号) 整型
u 无符号整型 integer
f 浮点型
c 复数浮点型
m timedelta(时间间隔)
M datetime(日期时间)
O (Python) 对象
S, a (byte-)字符串
U Unicode
V 原始数据 (void)
student = np.dtype([('name', 'S20'), ('age', 'i1', ('marks', 'f4')])
a = np.array([('R', 20, 50), ('King', 22, 55)], dtype=student)
print(a)
NumPy 数组属性
NumPy数组的维数称为秩(rank)。在NumPy中每个线性的数组称为一个轴(axis),也就是维度(dimensions)。例如二维数组就是两个一维数组的组合,第一个一维数组中每个元素又是一个一维数组。故一维数组就是NumPy的轴,第一个轴相当于是底层数组,第二个轴相当于底层数组里面的数组。而轴的数量就是秩,也称为数组的维数。
声明axis=0,表示沿着第0轴进行操作,即对每列进行操作;axis=1,表示沿着第一轴进行操作,即对每行进行操作。
常用的ndarray对象属性:
属性 说明
ndarray.ndim 秩,即轴的数量或维度的数量
ndarray.shape 数组的维度,对于矩阵,n 行 m 列
ndarray.size 数组元素的总个数,相当于 .shape 中 n*m 的值
ndarray.dtype ndarray 对象的元素类型
ndarray.itemsize ndarray 对象中每个元素的大小,以字节为单位
ndarray.flags ndarray 对象的内存信息
ndarray.real ndarray元素的实部
ndarray.imag ndarray 元素的虚部
ndarray.data 包含实际数组元素的缓冲区,由于一般通过数组的索引获取元素,所以通常不需要使用这个属性。
a = np.arange(24)
print(a)
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
print(a.ndim)
1
b= a.reshape(2, 4, 3) #现在有3个维度,依此为2, 4, 3
print(b)
[[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
[[12 13 14]
[15 16 17]
[18 19 20]
[21 22 23]]]
print(b.ndim)
3
———————————————————————————————————————————————
# dnarray.shape
# 表示数组的维度,返回一个元组,记录的就是每一维的大小。同时,其也可以用于调整数组的大小。
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a.shape)
(2, 3)
a.shape = (3,2) # 也可以使用b = a.reshape(3, 2)来调整数组大小
print(a.shape)
(3, 2)
print(a)
[[1 2]
[3 4]
[5 6]]
———————————————————————————————————————————————
# ndarray.itemsize
# 以字节的形式返回数组中每个元素的大小。如float64的itemsize为8(其占64bit,每个字节8bit,故字节数=64bit/8bit=8)
a = np.array([1, 2, 3], 'i1') # 相当于np.int8
print(a.itemsize)
1
b = np.array([[1,2], [3, 4]], 'f8') # 相当于np.float64
print(b.itemsize)
8
———————————————————————————————————————————————
# ndarray.flags
# 返回ndarray对象的内存信息
a = np.arrya([1, 2, 3])
print(a.flags)
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
包含的属性如下:
属性 描述
C_CONTIGUOUS (C) 数据是在一个单一的C风格的连续段中
F_CONTIGUOUS (F) 数据是在一个单一的Fortran风格的连续段中
OWNDATA (O) 数组拥有它所使用的内存或从另一个对象中借用它
WRITEABLE (W) 数据区域可以被写入,将该值设置为 False,则数据为只读
ALIGNED (A) 数据和所有元素都适当地对齐到硬件上
UPDATEIFCOPY (U) 这个数组是其它数组的一个副本,当这个数组被释放时,原数组的内容将被更新
初始化一个数组
数组可以使用底层的ndarray构造器来创建
或者通过以下方式进行创建:
np.empty(shape, dype=float, order='C')
# shape表示数组的形状
# dtype表示数组的数据类型
# order取值为‘C’或‘F’,表示行优先,或者列优先,即其在计算机中的存储顺序
a = np.empty([3, 2'], dtype=int, order='F')
print(a) # 数组未初始化,故数组元素是随机的
[[3346295607879824230 3203306633247944825]
[3700596020924673381 2827541920861024032]
[8386863780984922156 844425100601158]]
———————————————————————————————————————————————
np.zeros(shape, dype=float, order='C')
a = np.zeros((2, 2), dtype=[('x', 'i1'), ('y', 'f8')])
print(a)
[[(0, 0.) (0, 0.)]
[(0, 0.) (0, 0.)]]
———————————————————————————————————————————————
np.ones(...)
# 与np.zeros相似,元素用1来填充
———————————————————————————————————————————————
# 从已有数组中创建数组
# np.asarray(a, dtype=None, order=None)
a = [[1, 2], [3, 4]]
b = np.asarray(a)
print(b)
[[1 2]
[3 4]]
———————————————————————————————————————————————
# numpy.frombuffer(buffer, dtype=float, count=-1, offset = 0)
# buffer 可以是任意对象,以流的形式读入
# count 读取数据量,-1表示全部读取
# offset 读取的起始位置,默认为0,即第一个位置
s = b'Hello World'
a = np.frombuffer(s, dtype='S1', offset=5)
print(a)
[b' ' b'W' b'o' b'r' b'l' b'd']
———————————————————————————————————————————————
# numpy.fromiter(iterable, dtype, count=-1)
# 表示从迭代器中建立ndarray对象,返回一维数组。
ls = range(5)
# 迭代器:是访问集合元素的一种方式。迭代器只能往前不会后退
it = iter(ls) # 可以使用next(it)读出下一个元素
x = np.fromiter(it, dtype=int, count=3)
print(x)
[0 1 2]
———————————————————————————————————————————————
# np.arrange(start, stop, step, dtype)
# 定义开始和结束的值,以及步长增量
x = np.arange(0, 10, 2)
print(x) # 注意输出没有10,因为range(0, 2)输出的结果为0, 1.可以理解为范围为[start, end)
[0 2 4 6 8]
———————————————————————————————————————————————
# np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
#num表示要生成的等步长的样本数量,endpoint=True表示数列中包含stop值。
# retstep:为Ture时会在生成的数列中显示间距
a = np.linspace(0, 4.5, 9, endpotint=False, retstep=True)
(array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. ]), 0.5)
b = np.linspace(0, 7, 8).reshape([2, 2, 2]) # 注意这里不能设置retstep为True,否则系统会认为reshape操作对象是一个元组(tuple)
[[[0. 1.]
[2. 3.]]
[[4. 5.]
[6. 7.]]]
———————————————————————————————————————————————
# np.logspace(start, stop, num=50, endporint=True, base=10.0, type=None)
# 用于创建一个等比数列
# base:对数log的底数
a = np.logspace(1.0, 2.0, num=10) # 10^1~10^2生成10个
print(a)
[ 10. 12.91549665 16.68100537 21.5443469 27.82559402
35.93813664 46.41588834 59.94842503 77.42636827 100. ]
NumPy切片和索引
ndarray对象的内容可以英国索引或者切片来访问和修改,于list切片操作一样。
a = np.narray(10)
s = slice(2, 7, 2)
print(a)
[2 4 6]
———————————————————————————————————————————————
# 同时可以使用冒号分隔切片参数start_index:stop_index:step来进行操作:
a = np.arange(10)
b = a[2:7:2]
print(b)
[2 4 6]
# 冒号 : 的解释:如果只放置一个参数,如 [2],将返回与该索引相对应的单个元素。如果为 [2:],表示从该索引开始以后的所有项都将被提取。如果使用了两个参数,如 [2:7],那么则提取两个索引(不包括停止索引)之间的项。这同样适用于多维数组。
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a[1:])
[[4 5 6]
[7 8 9]]
# 切片还可以适用...来选择元组的长度和数组的维度相同。如果在行位置适用省略号,则其将返回行中元素的ndarray
print(a[..., 1]) # 可以理解为选择每一行中的index为1的,组成ndarray返回
[2 5 8]
print(a[1: , ...]) # 可以理解为返回每列中index为1的作为第一个,index为2的作为第二个,返回其组成的ndarray
[[4 5 6]
[7 8 9]]
———————————————————————————————————————————————
# NumPy高级索引:整数数组索引、布尔索引、花式索引
# 整数数组索引
# 获取数组中(0,0),(1,1)位置的元素
x = np.array([[0, 1], [2., 3], [4, 5.]])
print(x[[0, 1], [0, 1]]) # x表示是在x中进行取值,[[0, 1], [0, 1]]是在一个二维数组中将其行索引和列索引分别存储在两个一维数组中
[0. 3.]
# 输出x中四个角上的元组
row_index = np.array([[0, 0], [len(x[..., 0]) - 1, len(x[..., 0]) - 1]])
print(row_index)
col_index = np.array([[0, len(x[0, ...]) - 1], [0, len(x[0, ...]) - 1]])
print(col_index)
print(x[row_index, col_index])
[[0. 1.]
[4. 5.]]
———————————————————————————————————————————————
# 布尔索引
# 布尔索引通过布尔运算(如:比较运算符)来获取符合指定条件的元素的数组。
a = np.linspace(0, 15, endpoint=True, num=16, dtype=float).reshape(2, 2, 4)
print(a)
[[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]]
[[ 8. 9. 10. 11.]
[12. 13. 14. 15.]]]
print(a[a >= 10])
[10. 11. 12. 13. 14. 15.]
# 过滤NaN元素
a = np.array([np.nan, 1, np.nan, 2, 3, np.nan]) # 这里必须定义的是一个array,而不能是list定义的列表
print(a[~np.isnan(a)])
[1. 2. 3.]
# 过滤掉非复数元素
a = np.array([1, 2+6j, 5, 3.5+5j])
print (a[np.iscomplex(a)])
[2. +6.j 3.5+5.j]
———————————————————————————————————————————————
# 花式索引
#花式索引指的是利用整数数组进行索引。花式索引根据索引数组的值作为目标数组的某个轴的下标来取值。对于使用一维整型数组作为索引,如果目标是一维数组,那么索引的结果就是对应位置的元素;如果目标是二维数组,那么就是对应下标的行。花式索引跟切片不一样,它总是将数据复制到新数组中。
a = np.arange(32).reshape((8, 4))
print(a[[2, 5, 1, 7]]) # a[[x]]表示读取二维数组a下面x行的元素,可以表示为a[[2, 5, 1, 7]]==[a[2]+a[5]+a[1]+a[7]],对其进行整合就得到表示方法
[[ 8 9 10 11]
[20 21 22 23]
[ 4 5 6 7]
[28 29 30 31]]
# 同时也可以倒序输出
print(a[[-2, -5, -1, -7]]) # 输出的是倒数多上行的数据
[[24 25 26 27]
[12 13 14 15]
[28 29 30 31]
[ 4 5 6 7]]
NumPy广播(Boardcast)
是NumPy对不同形状(shape)的数组进行计算的方式,对数组的算数运算通常在相应的元素上进行。
ls = range(10, 101,10)
it = iter(ls)
a = np.fromiter(it, dtype=int)
print(a)
[ 10 20 30 40 50 60 70 80 90 100]
b = np.arange(1, 11, 1)
print(b)
[1 2 3 4 5 6 7 8 9 10]
c = a * b
print(c)
[ 10 40 90 160 250 360 490 640 810 1000]
———————————————————————————————————————————————
# 当运算中的 2 个数组的形状不同时,numpy 将自动触发广播机制。
a = np.array(a.reshape((5, 2)))
print(a)
[[ 10 20]
[ 30 40]
[ 50 60]
[ 70 80]
[ 90 100]]
it = iter(range(1, 10))
next(it)
b = np.fromiter(it, count=2, dtype='i1')
print(b)
[2 3]
c = a + b
print (c)
[[ 12 23]
[ 32 43]
[ 52 63]
[ 72 83]
[ 92 103]]
# 相当于c = a + np.tile(b,(np.shape(a)[0], 1)),np.tile可以理解为将数组b的行列拓展为现在的多少倍
———————————————————————————————————————————————
# NumPy迭代数组
a = np.arange(0, 11, 2).reshape((3, 2))
print(a)
[[ 0 2]
[ 4 6]
[ 8 10]]
# 进行顺序输出
for x in np.nditer(a): # 输出的值for x in np.nditer(a.T)
print(x, end=",")
0,2,4,6,8,10,
# 这里切换维度进行输出
for x in np.nditer(a.T.copy(order='C')): # a 和 a.T 的遍历顺序是一样的,也就是他们在内存中的存储顺序也是一样的,但是 a.T.copy(order='C') 的遍历结果是不同的,那是因为它和前两种的存储方式是不一样的,默认是按行访问。即这里用.copy()去重新生成了一个实例,实例的存储顺序和原来的不一样了,而np.nditer读取时只是根据该数组存储的位置,按照默认的风格进行读取
print(x, end=",")
0,4,8,2,6,10,
# 因此,我们也可以通过显显式设置,来强制nditer对象以某种顺序
for x in np.nditer(a, order='F'):
print(x, end=',')
0,4,8,2,6,10,
———————————————————————————————————————————————
# 修改数组中元素的值
# nditer对象有另一个可选参数op_flags。 默认情况下,nditer 将视待迭代遍历的数组为只读对象(read-only),为了在遍历数组的同时,实现对数组元素值得修改,必须指定 read-write 或者 write-only 的模式。
for x in np.nditer(a, op_flags=['readwrite']):
x[...] = 2 * x # x[...]
print(a)
[[ 0 4]
[ 8 12]
[16 20]]
———————————————————————————————————————————————
# 使用外部循环
# nditer类的构造器拥有flags参数,它可以接受下列值:
# 参数 描述
# c_index 可以跟踪 C 顺序的索引
# f_index 可以跟踪 Fortran 顺序的索引
# multi-index 每次迭代可以跟踪一种索引类型
# external_loop 给出的值是具有多个值的一维数组,而不是零维数组
for x in np.nditer(a, flags=['c_index'], order='F'):
print (x, end=", " )
0, 8, 16, 4, 12, 20,
for x in np.nditer(a, flags=['f_index'], order='F'):
print (x, end=", " )
0, 8, 16, 4, 12, 20,
for x in np.nditer(a, flags=['external_loop'], order='F'):
print (x, end=", " )
[ 0 8 16], [ 4 12 20],
———————————————————————————————————————————————
# 广播迭代
# 如果两个数组是可广播的,nditer 组合对象能够同时迭代它们。 假设数组 a 的维度为 3X4,数组 b 的维度为 1X4 ,则可以使用迭代器(数组 b 被广播到 a 的大小)。
a = np.arange(0,60,5).reshape(3, 4)
b = np.array([1, 2, 3, 4], dtype = int)
for x,y in np.nditer([a,b]):
print ("%d:%d" % (x,y), end=", " )
0:1, 5:2, 10:3, 15:4, 20:1, 25:2, 30:3, 35:4, 40:1, 45:2, 50:3, 55:4,
NumPy数组操作
# Numpy 中包含了一些函数用于处理数组,大概可分为以下几类:
a = np.arange(0, 16, 1)
———————————————————————————————————————————————
# 修改数组形状
# reshape 不改变数据的条件下修改形状
b = a.reshape(4, 4)
# flat 数组元素迭代器
for ele in a.flat(4, 4):
print(ele)
# 对数组元素依此输出
# flatten 返回一份数组拷贝,对拷贝所做的修改不会影响原始数组
print (a.flatten(order = 'F')) # order:'C' -- 按行,'F' -- 按列,'A' -- 原顺序,'K' -- 元素在内存中的出现顺序。
[ 0 20 40 5 25 45 10 30 50 15 35 55]
# ravel 返回展开数组
# numpy.ravel() 展平的数组元素,顺序通常是"C风格",返回的是数组视图(view,有点类似 C/C++引用reference的意味),修改会影响原始数组。
print (a.ravel(order = 'F'))
[ 0 20 40 5 25 45 10 30 50 15 35 55]
———————————————————————————————————————————————
# 翻转数组
# transpose 对换数组的维度
print (np.transpose(a, axes=0))
# ndarray.T 和 self.transpose() 相同
# rollaxis 向后滚动指定的轴
# numpy.rollaxis(arr, axis, start)
# arr:数组
# axis:要向后滚动的轴,其它轴的相对位置不会改变
# start:默认为零,表示完整的滚动。会滚动到特定位置。
b = a.reshape(2, 2, 3)
print(np.where(b==5))
(array([0]), array([0]), array([1]))
c = np.rollaxis(b, 0, 2)
print(np.where(c==5)
# swapaxes 对换数组的两个轴
# numpy.swapaxes(arr, axis1, axis2)
# arr:输入的数组
# axis1:对应第一个轴的整数
# axis2:对应第二个轴的整数
print (np.swapaxes(b, 2, 0)) # 从(2, 2, 3)-->(3, 2, 2)
[[[ 0 30]
[15 45]]
[[ 5 35]
[20 50]]
[[10 40]
[25 55]]]
———————————————————————————————————————————————
# 修改数组维度
# broadcast 产生模仿广播的对象
# broadcast_to 将数组广播到新形状
# expand_dims 扩展数组的形状
# squeeze 从数组的形状中删除一维条目
———————————————————————————————————————————————
# 连接数组
# concatenate 连接沿现有轴的数组序列
# stack 沿着新的轴加入一系列数组。
# hstack 水平堆叠序列中的数组(列方向)
# vstack 竖直堆叠序列中的数组(行方向)
———————————————————————————————————————————————
# 分割数组
# split 将一个数组分割为多个子数组
# hsplit 将一个数组水平分割为多个子数组(按列)
# vsplit 将一个数组垂直分割为多个子数组(按行)
———————————————————————————————————————————————
# 数组元素的添加与删除
# resize 返回指定形状的新数组
# append 将值添加到数组末尾
# insert 沿指定轴将值插入到指定下标之前
# Wdelete 删掉某个轴的子数组,并返回删除后的新数组
# unique 查找数组内的唯一元素