目录
一、ndarray对象
列表的缺点:
NumPy的优点:
使用方法:
多维数组
ndarray对象:形状,shape
元素类型
元素类型强制转换
从数列创建ndarray数组
用from系列方法创建ndarray
结构数组
掩膜数组
数组下标使用技巧
二、ufunc函数
ufunc的算术运算符/比较运算符
ufunc函数测速
ufunc函数:自定义
广播
三、多维数组的下标存取
数组下标使用技巧
四、NumPy文件读写
NumPy文件读写
python标准库中的列表(list)可以当数组用,支持动态内存分配和垃圾收集。
列表元素可以是任何对象,功能强大!
NumPy官方提供丰富的中文资源
# 导入用conda或者pip安装到python默认路径下的包 import numpy as np # 导入名为numpy的包,起个昵称叫np import numpy # 导入名为numpy的包 from numpy import array as ar # 从numpy中导入array,起个昵称叫ar np = __import__("numpy") # 知道模块的名字就可以导入,这里使用的是import的函数形式 __import__(str) 注意双下划线一个使用包进行动态绘图的示例:
# Create the data. from numpy import pi, sin, cos, mgrid dphi, dtheta = pi/250.0, pi/250.0 [phi,theta] = mgrid[0:pi+dphi*1.5:dphi,0:2*pi+dtheta*1.5:dtheta] m0 = 4; m1 = 3; m2 = 2; m3 = 3; m4 = 6; m5 = 2; m6 = 6; m7 = 4; r = sin(m0*phi)**m1 + cos(m2*phi)**m3 + sin(m4*theta)**m5 + cos(m6*theta)**m7 x = r*sin(phi)*cos(theta) y = r*cos(phi) z = r*sin(phi)*sin(theta) # View it. from mayavi import mlab s = mlab.mesh(x, y, z) mlab.show()
多维数组ndarray(n-dimensional array object)是NumPy的核心对象
它存储单一类型的多维数组,注意与列表(list)的区别
ndarray对象的构造函数创建
array函数创建ndarray
import numpy as np # 直接用ndarray对象的构造函数创建 # 它的参数是多维数组的维度 a = np.ndarray([2,3,4]) print(type(a)) print(a.shape) # a的维度通过shape属性获得,它是一个元组 print(a) # 使用array函数创建ndarray # 给np.array()函数传递python序列对象,将序列对象转换为ndarray对象 a=np.array([1,2,3,4]) b=np.array((5,6,7,8)) c=np.array([[1,2,3,4],[4,5,6,7],[7,8,9,10]]) print(type(a)) print('a = ',a) print('b = ',b) print('c = ',c)
import numpy as np a = np.array([1.1, 2.0, 3.5]) print(a,type(a),a.dtype,a.shape) b = np.array([1.1 + 2.5j, 2.0 + 5.1j, 3.5 + 2.7j]) print(b,type(b),b.dtype,b.shape) c = np.array(['hello, world!','hello, numpy array!','我就试试中文']) print(c,type(c),c.dtype,c.shape) # 也可以用zeros, ones, empty和full函数,创建指定大小,值为0/1/空/指定数值的数组 zz=np.zeros((2,3,4)) oo=np.ones((2,3,4)) ee=np.empty((2,3,4)) ff=np.full((2,3,4),999) print('zz = ', zz) print('oo = ', oo) print('ee = ', ee) print('ff = ', ff) # empty只分配内存,不赋值,最快。但是里面的内容是啥就不一定了!使用empty创建的ndarray,一定得初始化再使用。 # 创建形状类型与a相同的数组 za = np.zeros_like(a) oa = np.ones_like(a) ea = np.empty_like(a) fa = np.full_like(a,999) print('za = ', za) print('oa = ', oa) print('ea = ', ea) print('fa = ', fa)
数组对象的形状通过shape属性获得,返回一个描述数组各个轴的长度的元组(tuple),元组的长度等于数组的维数
ndarray类型的对象里面,数据都是一维化之后存储在连续分配的内存中,ndarray的维度仅仅是告诉numpy如何读取而已
改变shape属性,改变数组的形状。
import numpy as np c = np.array([[[1,2,3,4],[5,6,7,8],[9,10,11,12]],\ [[13,14,15,16],[17,18,19,20],[21,22,23,24]]]) print(c.shape) print(c) # 2片,3行,4列(第0轴长度为2,第1轴长度为3,第2轴长度为4) # 改变数组的形状 c.shape = (2,4,3) # 注意这不是转置!!!改变形状之后,数据的顺序是不变的。 print(c.shape) print(c) # 用-1表示这一个维度的长度是自动计算的 c.shape = 3,-1 print(c.shape) print(c)
import numpy as np # 用-1表示这一个维度的长度是自动计算的 c = np.array([[[1,2,3,4],[5,6,7,8],[9,10,11,12]],\ [[13,14,15,16],[17,18,19,20],[21,22,23,24]]]) print(c.shape) print(c) # 2片,3行,4列(第0轴长度为2,第1轴长度为3,第2轴长度为4) # 使用reshape创建指定形状的新数组 d = c.reshape((2,3,4)) print('d.shape = ', d.shape) print('d = ', d) # c的形状不变 print('c.shape = ', c.shape) # 改变d的元素,c的元素仍会改变! # 完全复制建议使用copy.deepcopy() c[0,0] = 2233 print('c = ', c) print('d = ', d) d = c.reshape((3,2,4)) print('d = ', d)
import numpy as np c = np.array([[[1,2,3,4],[5,6,7,8],[9,10,11,12]],\ [[13,14,15,16],[17,18,19,20],[21,22,23,24]]]) print(c.shape) # 看看ndarray c的类型 print(c.dtype) # 创建array的默认数据类型是? a = np.array([1,2,3,4]) print(a.dtype) b = np.array([1.0, 2.0, 3.0, 4.0]) print(b.dtype) c = np.zeros(4) print(c.dtype)
import numpy as np # 创建数组时指定数据类型 ai32 = np.array([1, 2, 3, 4], dtype=np.int32) af = np.array([1, 2, 3, 4], dtype=float) ac = np.array([1, 2, 3, 4], dtype=complex) # 其中np.int32时numpy的数据类型;float和complex是python内置的类型,会自动转换为numpy的数据类型 print(ai32.dtype) print(af.dtype) print(ac.dtype) # 用 set(np.typeDict.values()) 查看numpy支持的类型 set(np.typeDict.values())
import numpy as np # 类型转换 # np.int16将数值转换为C中的int16型,行为与C语言中的对应类型一致 a=np.int16(200) # 下面这句话会导致溢出,看看是什么么结果? print(a*a) # 数据强制转换为numpy对象后,由于还要套着python对象,运算速度慢,不建议单独使用! # 对numpy的对象,使用numpy的内置功能才能提高速度 v1 = 3.14 v2 = np.float64(v1) print('python内置float类型测速:') %timeit v1*v1 print('numpy内置float64类型测速:') %timeit v2*v2 # 数组的类型转换 t1 = np.array([1, 2, 3, 4], dtype=np.float) t2 = np.array([1, 2, 3, 4], dtype=np.complex) # 使用astype方法对数组元素进行类型转换,返回一个新的数组,原数组不变 t3 = t1.astype(np.int32) t4 = t2.astype(np.complex64) print(t1.dtype) print(t2.dtype) print(t3.dtype) print(t4.dtype)
np.arange()
np.linspace()
np.logspace()
import numpy as np # 通过开始值、终值和步长来创建等差数列 np.arange(0, 1, 0.1) # 从0开始,到1结束,步长0.1,注意1不在数组中! # 通过开始值、终值和元素个数创建等差数列 # np.linspace(0, 1, 10) # 从0开始,到1结束,10个元素的等差数列 np.linspace(0, 1, 10, endpoint=False) # 可以通过endpoint参数指定是否包含终值,默认值为True,即包含终值 # 通过开始值、终值和元素个数创建等比数列 # np.logspace(0, 2, 5) # 从0开始,到2结束,5个元素的等比数列 np.logspace(0, 1, 12, base=2, endpoint=False) # 可以通过base更改底数,默认为10 # 可以通过endpoint参数指定是否包含终值,默认值为True
from系列方法,可以从三个“流”式类型中直接创建出ndarray
fromstring():从字符串类型(str)创建
frombugffer():从字节序列类型(bytes)创建
fromfile():从文件类型(file)创建
除此之外,还有fromfunction方法,从函数创建ndarray
import numpy as np # 从字符串创建ndarray s = '1 2 3 4 5' a = np.fromstring(s, dtype=float, sep=' ') print(a) # 改变dtype参数和sep参数(灵活切换各种分隔符) s = '1, 2, 3, 4, 5' a = np.fromstring(s, dtype=np.int8, sep=',') print(a) # 如果字符串里面保存的是字符而不是数字,转换会有问题 # 报警告提示类型不对 s = 'abcdefgh' a = np.fromstring(s, dtype=np.int8) print(a) # 警告提示该功能已经废弃, # 如果把字符串(真的含有英文字符的串)转化为ndarray, # 应该用frombuffer以二进制的形式进行。
import numpy as np # 从字节序列创建ndarray # 可以将字符转换为数组,按照ASCII码转换 # 字符串之前加b,表示用bytes模式(字节序列)保存的字符串 s = b"abcdefgh" a = np.frombuffer(s, dtype=np.int8) print(a) # 字符串的b模式只能支持ASCII码 s = b"中文字符串" a = np.frombuffer(s, dtype=np.int8) print(a) # fromfile可以读取文本文件和二进制文件,这里我们以文本文件为例 with open('data.txt','r') as f: a = np.fromfile(f) print(a) # 用fromfile的默认参数读取,好像不大对劲,哪里错了? with open('data.txt','r') as f: a = np.fromfile(f, dtype=np.int8) print(a) # 好像还是不对劲啊? with open('data.txt','r') as f: a = np.fromfile(f, dtype=np.int8, sep=' ') print(a) # 要设定正确的类型和分隔符,才能正确读取文本文件! # fromfile相当于对文件对象f,使用fromstring或者frombuffer方法。
import numpy as np # 先定义一个从下标计算数值的函数 def func1d(i): return i % 4 +1 # 再用fromfunction创建指定大小的ndarray,其中的每个元素都通过下标来计算 # 注意数组的大小要用元祖表示。只有一个元素的元祖用(x,)这样的写法,如果只写一个数字会报错 a = np.fromfunction(func1d, (10,)) print(a) # 从函数生成二维数组 def func2d(i,j): return (i+1)*(j+1) a = np.fromfunction(func2d, (9,9)) print(a) # 从函数生成三维数组 def func3d(i,j,k): return (i+1)*(j+1)*(k+1) a = np.fromfunction(func3d, (2,5,5)) print(a) # 来看看帮助 help(np.fromfile) # help(np.fromstring) # help(np.frombuffer) # help(np.fromfunction)
import numpy as np # 定义一个结构体,首先创建一个np.dtype对象persontype # 它的参数是一个描述结构类型的各个字段的字典 # 字段有两个键:names和formats,每个键对应的值都是一个列表。names是字段名称,formats是字段类型。 persontype = np.dtype({ 'names': ['name', 'age', 'weight'], 'formats': ['S30', 'i', 'f']}, align=True) # 'S30'表示长度为30个字节的字符串类型。结构数组中的每个元素的长度必须是固定值,因此字符串类型也必须指定长度 # 'i'表示32位整型,相当于np.int32 # 'f'表示32位单精度浮点型,相当于np.float32
import numpy as np # 定义一个结构体,首先创建一个np.dtype对象persontype # 它的参数是一个描述结构类型的各个字段的字典 # 字段有两个键:names和formats,每个键对应的值都是一个列表。names是字段名称,formats是字段类型。 persontype = np.dtype({ 'names': ['name', 'age', 'weight'], 'formats': ['S30', 'i', 'f']}, align=True) # 'S30'表示长度为30个字节的字符串类型。结构数组中的每个元素的长度必须是固定值,因此字符串类型也必须指定长度 # 'i'表示32位整型,相当于np.int32 # 'f'表示32位单精度浮点型,相当于np.float32 # 用np.array()创建结构数组,其中的每个结构体元素用元祖表示 # 注意要设定dtype参数,设定位persontype a = np.array([('Zhang',32,75.5),('Wang',24,65.2)], dtype=persontype) # a = np.array([('张三',32,75.5),('Wang',24,65.2)], dtype=persontype) # 上面这句话会报错,因为NumPy中的字符串还是ASCII字符串,使用的是b模式,不支持多国语言 # 结构数组的存取方式和一般数组一样,通过下标存取。 # 结构数组的元素看上去像是元组,但实际上是预先定义好的结构类型(在这里是persontype类型) print(a) print(a[0]) print(a[0].dtype) # 可以使用字段名作为下标获取对应的字段值 print(a[0]['name'])
import numpy as np # 定义一个结构体,首先创建一个np.dtype对象persontype persontype = np.dtype({ 'names': ['name', 'age', 'weight'], 'formats': ['S30', 'i', 'f']}, align=True) a = np.array([('Zhang',32,75.5),('Wang',24,65.2)], dtype=persontype) # 修改结构元素的字段,结构数组中对应的部分也会被修改 c = a[0] c['name'] = 'Li' print(c) print(a) # 可以直接获得结构数组的字段,返回的是原始数组的“视图”. # 这个字段视图其实也是一个ndarray对象,对这个视图的修改会反映在结构数组上 b = a['age'] print(b) print(type(b)) # 字段视图其实是ndarray对象 b += 5 # 对ndarray进行计算 print(b) print(a) # 结果反映在原始的结构数组上 # 当某个字段类型为数组时,用元组的第三个元素表示数组形状 # 可以用下面的形式创建新的dtype类型,以列表为构造方法的输入 datatype = np.dtype([('data1','i4'),('data2','f8',(2,3)),('data3','f8',(10,10))]) print(datatype)
如果numpy数组中某些数值有缺失,或者我们不希望某些数值参与后面的运算,可以将数组的一部分掩盖起来。这种做法称之为掩膜(mask)。
import numpy as np a = np.random.normal(size=(3,5)) print(a) # 把小于0的都屏蔽掉 b = np.ma.masked_where(a < 0, a) print(b)
下标方式:a[2]
切片方式:
通过切片获取的数组是原数组的一个“视图”,与原数组共享同一存储空间,因此修改结果数组会改变原始数组
import numpy as np a = np.array([1,2,3,4,5,6,7]) a[2] a[3:5] a[:5] a[:-1] a[1:-1:2] a[::-1] a[5:1:-2]
ufunc是universal function的缩写,它是一种对数组的每个元素进行运算的函数
NumPy内置的许多ufunc函数都是用c语言实现的,速度很快
NumPy的数组对象支持加减乘除等操作
因为加减乘除操作在NumPy中使用ufunc实现,实际上是调用了ufunc
算术运算符:加减乘除乘方同余...
比较运算符:大于小于等于不等于...
数组对象支持操作符,极大的方便了程序编写。但是要注意如果算式很复杂、数组很大的时候,会产生过多的中间变量,降低程序运行速度。
可以适当考虑多用原位操作符,例如 x += y,复杂算式多分几行,减少对中间变量的内存分配
import numpy as np x1 = np.array([1,2,3,4]) x2 = np.array([5,6,7,8]) y = x1 + x2 # add print(y) y = x1 - x2 # subtract print(y) y = x1 * x2 # multiply print(y) y = x1 / x2 # divide print(y) y = x1 // x2 # floor divide print(y) y = -x1 # negative print(y) y = x1 ** x2 # power print(y) y = x1 % x2 # remainder print(y) y += x1 # 原位操作符 print(y) y = x1 == x2 # equal print(y) y = x1 != x2 # not equal print(y) y = x1 < x2 # less print(y) y = x1 <= x2 # less_equal print(y) y = x1 > x2 # greater print(y) y = x1 >= x2 # greater_equal print(y)
下面比较四种方式计算正弦函数的速度,看看谁更快
import numpy as np import math import numpy as np import copy x = [i * 0.001 for i in range(1000000)] def sin_math_loop(x): for i, t in enumerate(x): x[i] = math.sin(t) def sin_math_list(x): x = [math.sin(t) for t in x] def sin_numpy(x): np.sin(x, out = x) # 由于np.sin是一个ufunc函数,因此在其内部对数组x的每个元素进行循环,分别计算它们的正弦值。 # np.sin的返回值是一个保存了计算结果的数组,这个数组是新建的。运算之后x的值并没有改变,仍然保持原状。 # 可以通过指定out参数指定保存计算结果的数组。如果希望进行原位计算,可以设定out=x。 def sin_numpy_loop(x): for i, t in enumerate(x): x[i] = np.sin(t) # math.sin配合python的for循环 # 速度比较慢 x1 = copy.deepcopy(x) %time sin_math_loop(x1) # math.sin配合python的列表推导式 # 使用列表推导式可以稍微加快一点速度,但是不多 x2 = copy.deepcopy(x) %time sin_math_list(x2) # np.sin使用ufunc功能对数组直接计算 # 速度最快,比前面两个快10倍以上,这得益于numpy在c语言级别的循环 x3 = np.array(x) %time sin_numpy(x3) # np.sin配合python的for循环 # 最慢。比直接使用numpy函数在numpy数组上计算要满100倍。说明使用ufunction的必要性。 # np.sin为了同时支持对数组和单个数值的计算,它的内部实现比math.sin复杂的多。 x4 = copy.deepcopy(x) %time sin_numpy_loop(x4)
使用frompyfunc(func, nin, nout)
其中func是python函数,nin是func的输入参数个数,nout是func的返回值个数
import numpy as np # 自定义ufunc def myfunc(x): return x**2 + 1 my_ufunc = np.frompyfunc(myfunc, 1, 1) x = np.linspace(0,10,11) %timeit y = my_ufunc(x) %timeit y = myfunc(x) y = my_ufunc(x) print(y)
import numpy as np # 自定义ufunc def triangle_wave(x, c, c0, hc): x = x - int(x) # 三角波的周期为1,因此只取x坐标的小数部分进行计算 if x >= c: r = 0.0 elif x < c0: r = x / c0 * hc else: r = (c-x) / (c-c0) * hc return r x = np.linspace(0, 2, 1000) y1 = np.array([triangle_wave(t, 0.6, 0.4, 1.0) for t in x]) triangle_ufunc1 = np.frompyfunc(triangle_wave, 4, 1) y2 = triangle_ufunc1(x, 0.6, 0.4, 1.0) %C y2.dtype; y2.astype(np.float).dtype triangle_ufunc2 = np.vectorize(triangle_wave, otypes=[np.float]) y3 = triangle_ufunc2(x, 0.6, 0.4, 1.0)
如果ufunc输入参数有多个数组,形状不同,会自动进行广播操作
import numpy as np a = np.arange(0, 60, 10).reshape(-1,1) print(a) print(a.shape) b = np.arange(0, 5) print(b) print(b.shape) # 计算a+b的和,得到一个加法表。看numpy是如何广播的。 c = a + b print(c) print(c.shape) # 根据规则1,b的shape维度差了1,前面增补1维 # 根据规则2,输出数组的长度是输入数组的各个轴的长度的最大值 # 上面的广播过程,相当于对a和b进行了扩展 # 可以用repeat显示的进行扩展 a = a.repeat(5, axis = 1) print(a) print(a.shape) b.shape = (1,5) b = b.repeat(6, axis = 0) print(b) print(b.shape) # 这种广播方式很常用,numpy提供了ogrid对象,用于创建广播运算用的可以配对的数组 x, y = np.ogrid[:5,:5] print(x) print(y) # numpy还提供了mgrid对象,它返回的是广播之后的数组 x, y = np.mgrid[:5,:5] print(x) print(y)
import numpy as np x = np.arange(5,0,-1) a = x[np.array([True, False, True, False, False])] b = x[x>2] c = x[[True, False, True, False, False]] print('x = ', x) print('a = ', a) print('b = ', b) print('c = ', c) # 使用列表作为下标得到的数组不与原数组共享内存空间,对它的更改不反映到原数组上 b[0] = 2233 print('b = ', b) print('x = ', x) print('a = ', a)
import numpy as np from mayavi import mlab x, y, z = np.mgrid[:6,:7,:8] c = np.zeros((6, 7, 8), dtype=np.int) c.fill(1) k = np.random.randint(2,5,size=(6, 7)) idx_i, idx_j, _ = np.ogrid[:6, :7, :8] idx_k = k[:,:, np.newaxis] + np.arange(3) c[idx_i, idx_j, idx_k] = np.random.randint(2,6, size=(6,7,3)) mlab.points3d(x[c>1], y[c>1], z[c>1], c[c>1], mode="cube", scale_factor=0.8, scale_mode="none", transparent=True, vmin=0, vmax=8, colormap="Blues") mlab.points3d(x[c==1], y[c==1], z[c==1], c[c==1], mode="cube", scale_factor=0.8, scale_mode="none", transparent=True, vmin=0, vmax=8, colormap="Vega20", opacity = 0.2) mlab.gcf().scene.background = (1,1,1) mlab.figure() x, y, z = np.mgrid[:6,:7,:3] mlab.points3d(x, y, z, c[idx_i, idx_j, idx_k], mode="cube", scale_factor=0.8, scale_mode="none", transparent=True, vmin=0, vmax=8, colormap="Purples", opacity = 1) mlab.gcf().scene.background = (1,1,1) mlab.show()
NumPy提供了一系列简便方法,可以直接将ndarray对象保存到文件,或者从文件加载ndarray对象。
下面主要介绍这些方法:
save,savez和load
savetxt和loadtxt
tofile
import numpy as np # 随机生成ndarray a = np.random.random((5,5)) b = np.random.normal(size=(5,5)) print(a) print(b) # 使用save,以二进制形式保存单个ndarray对象 # 注意save保存的文件的扩展名为npy # 也可以省略扩展名,在保存的时候自动添加 np.save('a.npy',a) np.save('b.npy',b) # 使用load加载刚刚保存的ndarray对象 # 读文件的时候,扩展名npy必须要写 af = np.load('a.npy') bf = np.load('b.npy') print(af) print(bf)
import numpy as np # 随机生成ndarray a = np.random.random((5,5)) b = np.random.normal(size=(5,5)) print(a) print(b) # 使用savez打包多个ndarray对象 # 打包的扩展名为npz,其实是多个npy组成的压缩包 np.savez('ab.npz',a,b) # 使用load加载压缩包 zf = np.load('ab.npz') # 查看各个数组的名称,发现数组按照顺序被自动命名了 print(zf.files) # 从打包对象中,解包出原始的ndarray print(zf['arr_0']) print(zf['arr_1']) # 使用savez的时候,可以用关键字参数,给每个ndarray对象起名字,这样就不会搞混了 np.savez('ab.npz',a=a,b=b) zf = np.load('ab.npz') print(zf.files) print(zf['a']) print(zf['b']) # 使用savetxt,可以将ndarray对象输出为文本文件 # 但它只支持1维或2维ndarray # 文件的扩展名可以自由设定,savetxt不会给你自动添加。一般设定为txt。 np.savetxt('a.txt', a, fmt='%10.8f', delimiter=' ', header='a0 a1 a2 a3 a4', comments='#') # savetxt函数有很多参数,可以实现复杂的格式化输出 # help(np.savetxt)
import numpy as np # 随机生成ndarray a = np.random.random((5,5)) b = np.random.normal(size=(5,5)) # 使用loadtxt,从文本文件读取ndarray对象 af = np.loadtxt('a.txt') print(af) # loadtxt函数也有很多参数,可以从多种多样的文本文件读取数据,例如csv文件 # help(np.loadtxt) # 每个ndarray对象都有tofile方法,可以快速输出为文本文件或二进制文件 # 但是tofile方法的选项比较少 # 设定sep参数为非空的字符串,tofile会输出为文本文件 a.tofile('a.txt',sep=',',format='%10.8f') # 可以用fromfile读取,要注意设定正确的sep参数才行 # 读取的数组的维度信息并没有被保存,丢失了 af = np.fromfile('a.txt',sep=',') print(af) # 使用tofile保存二进制文件,这是tofile的默认方式 a.tofile('a.bin') # 使用fromfile同样也能读二进制文件,数组的维度信息也丢失了 af = np.fromfile('a.bin') print(af)