NumPy
的核心是多维数组类numpy.ndarray
,矩阵类numpy.matrix
是多位数组类的派生类。以多位数组类为数据组织结构,Numpy
提供了众多的数学、科学和工函数,此外,NumPy
还提供了以下多个子模块
子模块名 | 功能 |
---|---|
numpy.random |
随机抽样子模块 |
numpy.linalg |
线性代数子模块 |
numpy.fft |
傅里叶变换子模块 |
numpy.ctypeslib |
C-Types外部函数接口子模块 |
numpy.emath |
具有自动域的数学函数子模块 |
numpy.testing |
测试支持子模块 |
numpy.matlib |
矩阵库子模块 |
numpy.dual |
Scipy加速支持子模块 |
numpy.distutils |
打包模块 |
pip install numpy
import numpy as np
append()
、insert()
、pop()
、remove()
Numpy支持的数据类型主要有整形(integer)、浮点型(float)、布尔型(bool)和复数型(complex),每一种数据类型根据占用内存的字节数又分为多个不同的类型,具体的数据类型如图所示。Numpy也支持字符串类型和自定义类型的数据,但绝大多数函数和方法不适用于非数值型数组
np.int8
、np.int16
、np.int32
、np.int64
np.uint8
、np.uint16
、np.uint32
、np.uint64
np.float16
、np.float32
、np.float64
np.bool
np.complex64
、np.complex128
创建数组时,如果不指定数据类型,Numpy会根据输入的参数自动选择合适的数据类型。通常在指定数据类型的时候,可以省略类型后面的数字。如果省略数字,整型和无符号整形默认是32位,浮点型默认是64位,复数型默认是128位
import numpy as np
a = np.array([0, 1, 2])
print(a.dtype)
b = np.array([0, 1, 2.0])
print(b.dtype)
c = np.array([0, 1, 2], dtype=np.uint8)
print(c.dtype)
属性 | 说明 |
---|---|
ndarray |
数组的数据类型 |
ndarray.shape |
数组的结构,也可以理解为数组的形状 |
ndarray.size |
数组的元素个数 |
ndarray.itemsize |
每个元素占用内存的大小,以字节为单位 |
ndarray.ndim |
数组的维度数,也叫秩 |
ndarray.flags |
数组的内存信息 |
ndarray.real |
数组实部 |
ndarray.imag |
数组虚部 |
ndarray.data |
数组在内存中实际存储区域的起始地址 |
import numpy as np
a = np.arange(6)
print(a)
print(a.shape)
print(a.dtype)
# 改变数组结构
b = a.reshape((2, 3))
print(b)
# 改变数据类型
c = b.astype(np.float)
print(c.dtype)
维,就是维度。通常说数组是几维的,就是指维度数,如三维数组的维度数就是3。维度数还有一个专用名字,叫秩,也就是数组属性ndim
。一维数组,类比于一维空间,只有一个轴,那就是0轴。二维数组,类比于二维空间,有两个轴,习惯表示成行和列,行的方向是0轴,列的方向是1轴。三维数组,类比于三维空间,有三个轴,习惯表示成层、行和列,层的方向是0轴,行的方向是1轴,列的方向是2轴
import numpy as np
# 3层2行3列的结构
a = np.arange(18).reshape((3, 2, 3))
print(a)
# 全部元素之和
print(a.sum())
# 0轴方向求和:3层合并成1层,返回二维数组
print(a.sum(axis=0))
# 1轴方向求和:2行合并成1行,返回二维数组
print(a.sum(axis=1))
# 2轴方向求和:3列合并成1列,返回二维数组
print(a.sum(axis=2))
# 分层求和方法1
print(a.sum(axis=1).sum(axis=1))
# 分层求和方法2
print(a.sum(axis=2).sum(axis=1))
要求:数值型数组的各个元素加1
import numpy as np
# 列表实现
a = [0, 1, 2, 3, 4]
for i in range(len(a)):
a[i] += 1
print(a)
# numpy实现
b = np.array([0, 1, 2, 3, 4])
b += 1
print(b)
要求:两个等长的数值型数组的对应元素相加
import numpy as np
# 列表实现
a = [0, 1, 2, 3, 4]
b = [5, 6, 7, 8, 9]
print([i+j for i, j in zip(a, b)])
# numpy实现
a = np.array([0, 1, 2, 3, 4])
b = np.array([5, 6, 7, 8, 9])
print(a+b)
np.array(object, dtype=None, copy=True, order=None, subok=False, ndmin=0)
import numpy as np
# 如果不指定数据类型,就会根据object参数自动选择合适的数据类型
a = np.array([[1, 2, 3], [4, 5, 6]])
# 也可以在创建数组时,指定元素的数据类型
b = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.uint8)
这里的特殊数值指的是0、1、空值。特殊数值法适合构造全0、全1、空数组,或由0、1组成的类似单位矩阵(主对角线为1,其余为0)的数组。
np.zeros(shape, dtype=float, order='C')
np.ones(shape, dtype=float, order='C')
np.empty(shape, dtype=float, order='C')
np.eye(N, M=None, k=0, dtype=float, order='C')
固定参数shape表示生成的数组结构,默认参数dtype用于指定数据类型(默认浮点型)。order参数指定的是数组在内存中的存储顺序,'C'
表示C语言使用的行为优先方式,'F'
表示Fortran语言使用的列优先方式
import numpy as np
print(np.zeros(6))
print(np.zeros((2, 3)))
print(np.ones((2, 3), dtype=int))
print(np.empty((2, 3)))
print(np.eye(3, dtype=np.uint8))
如果需要一个3行4列,初始值都是255的无符号整型数组
fill()
import numpy as np
a = np.empty((3, 4), dtype=np.uint8)
a.fill(255)
print(a)
np.random.random(size=None)
np.random.randint(low, high=None, size=None)
np.random.normal(loc=0.0, scale=1.0, size=None)
random()
函数用于生成[0,1)
区间内的随机浮点型整数,randint()
函数用于生成[low,high)
区间内的随机整形数组。参数size是一个元组,用于指定生成数组的结构
import numpy as np
print(np.random.random(3))
print(np.random.random((2, 3)))
print(np.random.randint(5))
print(np.random.randint(1, 5, size=(2, 3)))
normal()
函数用于生成以loc为均值、以scale为标准差的正态分布数组。
import numpy as np
# 导入绘图模块
import matplotlib.pyplot as plt
# 生成正态分布数据
tall = np.random.normal(170, 4, 1000)
# 从156厘米到190厘米,每2厘米一个分段
bins = np.arange(156, 190, 2)
# 绘制柱状图
plt.hist(tall, bins)
# 显示图形
plt.show()
import numpy as np
np.arange(start, stop, step, dtype=None)
np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
arange()
函数和Python的range()
函数用法相同,并且还可以接收浮点型参数
import numpy as np
print(np.arange(5))
print(np.arange(5, 11))
print(np.arange(5, 11, 2))
print(np.arange(5.5, 11, 1.5))
print(np.arange(3, 15).reshape(3, 4))
linspace()
函数需要3个参数:一个起点、一个终点、一个返回元素的个数。linspace()
函数返回的元素包括起点和终点,可以通过endpoint
参数选择是否包含终点
import numpy as np
# 返回0到5之间的5个等距数值,包括0和5
print(np.linspace(0, 5, 5))
# 返回5个等距数值,包括0但不包括5
print(np.linspace(0, 5, 5, endpoint=False))
repeat()
函数用来重复数组元素。但如果被重复的数组是一个多维数组,且repeat()
函数指定了axis
参数,情况就会变得有些复杂
import numpy as np
a = np.arange(5)
print(a)
# 重复一维数组元素3次
print(np.repeat(a, 3))
a = np.arange(6).reshape((2, 3))
print(a)
# 重复二维数组元素3次,不指定轴
print(np.repeat(a, 3))
# 重复二维数组元素3次,指定0轴
print(np.repeat(a, 3, axis=0))
# 重复二维数组元素3次,指定1轴
print(np.repeat(a, 3, axis=1))
tile()
将整个数组而非数组元素水平和垂直重复指定的次数
import numpy as np
a = np.arange(5)
print(a)
# 重复一维数组3次
print(np.tile(a, 3))
# 重复一维数组3行2列
print(np.tile(a, (3, 2)))
a = np.arange(6).reshape((2, 3))
print(a)
# 重复二维数组3次
print(np.tile(a, 3))
# 重复二维数组2行3列
print(np.tile(a, (2, 3)))
用数组表示经纬度网格方式一:用两个一维数组表示。使用定长分隔函数linspace()
,将经度从-180°
到180°
分为间隔为10°
的37个点,将纬度从90°
到-90
分为间隔为10°
的19个点,得到两个一维数组
import numpy as np
# 精度为10°,共计37个经度点
lon = np.linspace(-180, 180, 37)
# 精度为10°,共计19个纬度点
lat = np.linspace(90, -90, 19)
用数组表示经纬度网格方式二:用两个二维数组分别表示经度网格和纬度网格。经度网格中每一列的元素都是相同的(同一个经度),纬度网格中每一行的元素都是相同的(同一个纬度)。生成二维经纬度网格的常用函数是np.meshgrid()
,该函数以一维经度数组lon和一维纬度数组lat为参数,返回二维的经度数组和纬度数组
import numpy as np
# 精度为10°,共计37个经度点
lon = np.linspace(-180, 180, 37)
# 精度为10°,共计19个纬度点
lat = np.linspace(90, -90, 19)
lons, lats = np.meshgrid(lon, lat)
print(lons.shape)
print(lats.shape)
print(lons[:,0])
print(lons[0])
构造经纬度网格,除了使用np.meshgrid()
函数外,还有一个更强大的方法,这个方法可以直接生成纬度网格和经度网格而无需借助于一维数组(请注意,纬度在前,经度在后)
import numpy as np
# 使用实数指定网格经度为5°
lats, lons = np.mgrid[90:-91:-5, -180:181:5]
print(lons.shape, lats.shape)
# 也可以用虚数指定分隔点数
lats, lons = np.mgrid[90:-90:37j, -180:180:73j]
print(lons.shape, lats.shape)
结果显示:数组会将所有元素的数据类型都转为'
<
表示字节顺序,意为小端在前(低位字节存储在最小地址中);32表示数组元素占用32字节,数组元素占用的字节数由所有元素中最长的那个元素决定
import numpy as np
print(np.array(['Anne', 1.70, 55]).dtype)
怎样在数组中保留用以生成数组的列表中的元素类型呢?这就需要用到自定义数据类型了。自定义数据类型类似于C语言的结构体,其代码如下
import numpy as np
mytype = np.dtype([('name', 'S32'), ('tall', np.float), ('bw', np.int)])
print(np.array([('Anne', 1.70, 55)], dtype=mytype))
索引是定位一维或多维数组中的单个或多个元素的行为模式。切片是返回一维或多维数组中的单个或多个相邻元素的视图,目的是引用或赋值。NumPy
数组对象的内容可以通过索引或切片来访问和修改。
import numpy as np
a = np.arange(9)
# 最后一个元素
print(a[-1])
# 返回第2到第5个元素
print(a[2:5])
# 返回第0到底7个元素,步长为3
print(a[:7:3])
# 返回逆序的数组
print(a[::-1])
对于多维数组操作,NumPy
数组比Python
的列表更加灵活、强大。假设有一栋楼,共2层,每层的房间都是3行4列,那我们可以用一个三维数组来保存每个房间的居住人数(也可以是房间面积等其他数值信息)
import numpy as np
# 2层3行4列
a = np.arange(24).reshape(2, 3, 4)
print(a)
# 虽然可以这样索引
print(a[1][2][3])
# 但这样才是规范的用法
print(a[1, 2, 3])
# 1层的所有房间,等价于a[0]或a[0,...]
print(a[0,:,:])
# 所有楼层所有排的第1到第3列
print(a[:, :,1:3])
# 2层每一行的最后一个房间
print(a[1:,:, -1])
从上面的代码中可以看出,对多维数组索引或切片得到的结果的维度不是确定的。另外还有一点需要特别提醒:切片返回的数组不是原始数组的副本,而是指向与原始数组相同的内存区域。数组切片不会复制内部数组数据,只是产生了原始数据的一个新视图
import numpy as np
a = np.arange(12).reshape(3, 4)
print(a)
# 数组b是数组a的切片
b = a[1:, 2:]
print(b)
# 改变数组b的值,也会同时影响数组a
b[:, :] = 99
print(b)
print(a)
上面的代码中,数组b是数组a的切片,当改变数b的元素时,数组a也同时发生了改变。这就证明了切片返回的数组不是一个独立数组,而是指向原始数组相同的内存区域
NumPy
之所以拥有极高的运算速度,除了并行、广播和矢量化等技术因素外,其数组存储顺序和数组视图相互独立也是一个很重要的原因。改变数组结构的操作通常不会改变所操作的数组本身的存储顺序,只是生成了一个新的视图。np.resize()
函数是个例外,它不返回新的视图,而是真正改变了数组的存储顺序
函数 | 功能 |
---|---|
ndarray.reshape() |
按照指定的结构(形状)返回数组的新视图,不改变原数组 |
ndarray.ravel() |
返回多维数组一维化的视图,不改变原数组 |
ndarray.transpose() |
返回行变列的视图,不改变原数组 |
ndarray.resize() |
按照指定的结构(形状)改变原数组,无返回值 |
numpy.roolaxis() |
翻滚轴,返回新的视图,不改变原数组 |
import numpy as np
a = np.arange(12)
# reshape()函数返回数组a的一个新视图,但不会改变数组a
b = a.reshape((3, 4))
print(a.shape)
print(b is a)
print(b.base is a)
# resize()函数没有返回值,但真正改变了数组a的结构
a.resize([4, 3])
# 返回多维数组一维化的视图,但不会改变原数组
print(a.ravel())
# 返回行变列的视图,但不会改变原始图
print(a.transpose())
# 返回行变列的视图,等价于transpose()函数
print(a.T)
# 翻滚轴,1轴变0轴
print(np.rollaxis(a, 1, 0))
翻滚轴函数有一个很容易理解的应用,就是用它来实现图像的通道分离。下面的代码生成了一个宽为800像素、高为600像素的彩色随机噪声图,使用翻滚轴函数可以将其分离成RGB三个颜色通道。
from PIL import Image
import numpy as np
img = np.random.randint(0, 256, (600, 800, 3), dtype=np.uint8)
print(img.shape)
# 将图像数据分离成RGB三个颜色通道
r, g, b = np.rollaxis(img, 2, 0)
print(r.shape, g.shape, b.shape)
Image.fromarray(img).show()
NumPy
数组一旦创建不能再改变其元素数量。如果要动态改变数组元素数量,只能通过合并或拆分的方法生成新的数组。NumPy
仍然保留了append()
函数,只不过这个函数不再是数组的函数,而是升级到最外层的NumPy
命名空间了,并且该函数的功能不再是追加元素,而是合并数组
import numpy as np
print(np.append([[1, 2, 3]], [[4, 5, 6]]))
print(np.append([[1, 2, 3]], [[4, 5, 6]], axis=0))
print(np.append([[1, 2, 3]], [[4, 5, 6]], axis=1))
hstack()
水平合并函数,vstack()
垂直合并函数,dstack()
深度合并函数
import numpy as np
a = np.arange(4).reshape(2, 2)
b = np.arange(4, 8).reshape(2, 2)
# 水平合并
print(np.hstack((a, b)))
# 垂直合并
print(np.vstack((a, b)))
# 深度合并
print(np.dstack((a, b)))
stack()
函数使用axis轴参数指定合并的规则
import numpy as np
a = np.arange(60).reshape(3, 4, 5)
b = np.arange(60).reshape(3, 4, 5)
print(a.shape, b.shape)
print(np.stack((a, b), axis=0).shape)
print(np.stack((a, b), axis=1).shape)
print(np.stack((a, b), axis=2).shape)
print(np.stack((a, b), axis=3).shape)
import numpy as np
a = np.arange(16).reshape(2, 4, 2)
# 水平方向拆分成2部分
print(np.hsplit(a, 2))
# 垂直方向拆分成2部分
print(np.vsplit(a, 2))
# 深度方向拆分成2部分
print(np.dsplit(a, 2))
改变数组结构返回的是原数组的一个新视图,而不是原数组的副本。浅复制(view)和深复制(copy)则是创建原数组的副本,但二者之间也有细微差别:浅复制(view)是共享内存,深复制(copy)是独享内存
import numpy as np
a = np.arange(6).reshape((2, 3))
b = a.view()
print(b is a)
print(b.base is a)
print(b.flags.owndata)
c = a.copy()
print(c is a)
print(c.base is a)
print(c.flags.owndata)
NumPy
数组有两个排序函数,一个是sort()
,另一个是argsort()
。sort()
函数返回输入数组的排序副本,argsort()
函数返回的是数组值从小到大的索引号,这两个函数的参数完全一致
numpy.sort(arr, axis=-1, kind='quicksort', order=None)
numpy.argsort(arr, axis=-1, kind='quicksort', order=None)
# arr: 要排序的数组
# axis: 轴,指定排序的轴,默认值-1,表示没有指定排序轴,返回结果将沿着最后的轴排序
# kind: 排序方法,默认为"quickstart"(快速排序),其他选项还有"mergesort"(归并排序)和"heapsort"(堆排序)
# order: 指定用于排序的字段,前提是数组包含该字段
import numpy as np
a = np.random.random((2, 3))
print(a)
# 返回行内从小到大排序的索引号(列排序),相当于axis=1(最后的轴)
print(np.argsort(a))
# 返回行内从小到大排序的一个新数组(列排序)
print(np.sort(a))
# 返回列内从小到大排序的一个新数组(行排序)
print(np.sort(a, axis=0))
这里约定查找是返回数组中符合条件的元素的索引号,或返回和数组具有相同结构的布尔型数组,元素符合条件在布尔型数组中对应True,否则对应False。
最大值和最小值查找(下面的代码演示了返回数组中最大值和最小值的索引号,如果是多维数组,这个索引号是数组转成一维之后的索引号)
import numpy as np
a = np.random.random((2, 3))
print(a)
# 最大值的索引号
print(np.argmax(a))
# 最小值的索引号
print(np.argmin(a))
非零元素查找(下面代码演示了返回数组中非零元素的索引号)
import numpy as np
a = np.random.randint(0, 2, (2, 3))
print(a)
print(np.nonzero(a))
使用逻辑表达式查找(下面的代码演示了使用逻辑表达式查找符合条件的元素,返回结果是一个和原数组结构相同的布尔型数组,元素符合条件在布尔型数组中对应True,否则对应False)
import numpy as np
a = np.arange(10).reshape((2, 5))
print(a)
print((a>3)&(a<8))
使用where条件查找(np.where()
函数返回数组中满足给定条件的元素和索引号,其结构为元组,元组的第k个元素对应符合条件的元素在数组k轴上的索引号。这句话可以简单理解为,一维数组返回一个元素的元组,二维数组返回两个元素的元组,以此类推。np.where()
函数还可以用于替换符合条件的元素
import numpy as np
a = np.arange(10)
print(a)
print(np.where(a<5))
a = a.reshape((2, -1))
print(a)
print(np.where(a<5))
# 满足条件的元素不变,其他元素乘10
print(np.where(a<5, a, 10*a))
筛选是返回符合条件的元素。筛选条件有三种表示方式,一是使用np.where()
函数返回的Python元组,二是使用逻辑表达式返回的布尔型数组,三是使用整形数组
import numpy as np
a = np.random.random((3, 4))
print(a)
# 返回大于0.5的元素(使用np.where()函数返回的Python元组)
print(a[np.where(a>0.5)])
# 返回大于0.3且小于0.7的元素(使用逻辑表达式返回的布尔型数组)
print(a[(a>0.3)&(a<0.7)])
# 返回整形数组指定的项(使用整型数组)
print(a[np.array([2, 1])])
a = a.ravel()
# 返回整型数组指定的项(使用整型数组)
print(a[np.array([3, 5, 7, 11])])
# 返回整型数组指定的项(使用整型数组)
print(a[np.array([[3, 5], [7, 11]])])
一般而言,灰度图像每个像素的值域范围是[0, 255]
。假如用于表现不同灰度的字符集是['','.','-','+','=','*','#','@']
,从''
到'@'
表示从白到黑的8个灰度等级。我们需要将每个像素的灰度值分段转换成相应的字符。例如,灰度值小于32的像素用@
表示,大于或等于32且小于64的像素用#
表示,依次类推直至大于或等于224的像素用''
表示
import numpy as np
img = np.random.randint(0, 256, (5, 10), dtype=np.uint8)
print(img)
# 将256级灰度值转为8级灰度值
img = (img/32).astype(np.uint8)
print(img)
# 灰度级字符集
chs = np.array([' ', '.', '-', '+', '+', '*', '#', '@'])
# 用整形数组筛选数组元素
print(chs[img])
数据文件格式 | 存储数据格式 | 读写函数 |
---|---|---|
CSV |
通用格式 | loadtxt() 、savetxt() |
NPY/NPZ |
单个数组/多个数组 | load() 、save() 、savez() |
import numpy as np
a = np.random.random((15, 5))
# 将数组a保存成CSV格式的数据文件
np.savetxt('demo.csv', a, delimiter=',')
# 打开CSV格式的数据文件
data = np.loadtxt('demo.csv', delimiter=',')
print(data.shape, data.dtype)
import numpy as np
# 存储单个数组文件名
single_arr_fn = 'single_arr.npy'
# 存储多个数组文件名
multi_arr_fn = 'multi_arr.npz'
lon = np.linspace(10, 90, 9)
lat = np.linspace(20, 60, 5)
# 用save()函数把经度数组保存成.npy文件
np.save(single_arr_fn, lon)
# 接着用load()函数读出来
lon = np.load(single_arr_fn)
# 保存两个数组到一个文件
np.savez(multi_arr_fn, longitude=lon, latitude=lat)
# 用load()函数把这个.npz文件读成一个结构
data = np.load(multi_arr_fn)
# 查看所有的数组名
print(data.files)
# 使用data[数组名]就可以取得想要的数据
print(data['longitude'])
常量 | 含义 |
---|---|
np.pi |
圆周率 |
np.e |
自然常数 |
np.euler_gamma |
欧拉常数 |
np.nan |
非数字 |
np.inf |
无穷大 |
两个np.nan
不相等,但两个np.inf
是相等的,判断一个数组元素是否是np.nan
或np.inf
,需要使用np.isnan()
和np.isinf()
这两个相应的函数,而不是使用两个等号的逻辑表达式
import numpy as np
a = np.array([1, 2 ,np.nan, np.inf])
print(a.dtype)
a[0] = np.nan
a[1] = np.inf
print(a)
# 两个np.nan不相等
print(a[0] == a[2])
# 两个np.inf
print(a[1] == a[3])
# 判断一个数组元素是否是np.nan
print(np.isnan(a[0]))
# 判断一个数组元素是否是np.inf
print(np.isinf(a[1]))
import numpy as np
a = np.array([9, 3, np.nan, 5, 3])
a = np.repeat(a, 2)[:-1]
a[1::2] += (a[2::2]-a[1::2])/2
print(a)
对于NumPy
函数的使用,可能会有这样的困惑:实现同样的功能,一个函数却有两种写法;有时以为某个函数可以有两种写法,但用起来却会出错,归纳起来,这些困惑有以下三种类型
都是求和、求极值,下面两种写法有什么区别么?
import numpy as np
a = np.random.random(10)
print(a.max(), np.max(a))
print(a.sum(), np.sum(a))
同样是赋值,为什么深复制copy()
两种写法都行,而浅复制view()
则只有数组的方法?
import numpy as np
a = np.random.random(5)
print(a.copy())
print(np.copy(a))
print(a.view())
# 会报错
print(np.view(a))
为什么where()
不能作为数组ndarray
的函数,必须作为NumPy
的函数
import numpy as np
a = np.random.random(10)
print(np.where(a>0.5))
# 会报错
print(a.where(a>0.5))
以上这些差异取决于函数在不同的命名空间是否有映射。数组的大部分函数在顶层命名空间有映射,因此可以有两种写法。但数组的一小部分函数没有映射到顶层命名空间,所以只能有一种写法。而顶层命名空间的大部分函数,也都只有一种写法。
类别 | NumPy函数 | Math模块函数 | 功能 |
---|---|---|---|
数学常数 | np.e |
math.e |
自然常数 |
数学常数 | np.pi |
math.pi |
圆周率 |
舍入函数 | np.ceil() |
math.ceil() |
进尾取整 |
舍入函数 | np.floor() |
math.floor() |
去尾取整 |
舍入函数 | np.around() |
四舍五入到指定精度 | |
舍入函数 | np.rint() |
四舍五入到最近整数 | |
快速转换函数 | np.deg2rad() 、np.radians() |
np.radians() |
度转弧度 |
快速转换函数 | np.rad2deg() 、np.degrees() |
math.degrees() |
弧度转度 |
幂指数对数函数 | np.hypot() |
math.hypot() |
计算直角三角形的斜边 |
幂指数对数函数 | np.square() |
平方 | |
幂指数对数函数 | np.sqrt() |
math.sqrt() |
开平方 |
幂指数对数函数 | np.power() |
math.pow() |
幂 |
幂指数对数函数 | np.exp() |
math.exp() |
指数 |
幂指数对数函数 | np.log() 、np.log10() 、np.log2() |
math.log() 、math.log10() 、math.log2() |
对数 |
三角函数 | np.sin()/arcsin() |
math.sin()/asin() |
正弦/反正弦 |
三角函数 | np.cos()/arccos() |
math.cos()/acos() |
余弦/反余弦 |
三角函数 | np.tan()/arctan() |
math.tan()/atan() |
正切/反正切 |
import numpy as np
import math
# 两个模块的自然常数相等
print(math.e == np.e)
# 两个模块的圆周率相等
print(math.pi == np.pi)
print(np.ceil(5.3), np.ceil(-5.3))
print(np.floor(5.8), np.floor(-5.8))
print(np.around(5.87, 1))
print(np.rint(5.87))
print(np.degrees(np.pi/2))
print(np.radians(180))
# 求平面上任意两点的距离
print(np.hypot(3,4))
print(np.power(3, 1/2))
print(np.log2(1024))
print(np.exp(1))
# 正弦、余弦函数的周期是2π
print(np.sin(np.radians(30)))
print(np.sin(np.radians(150)))
# 反正弦、反余弦函数的周期则是π
print(np.degrees(np.arcsin(0.5)))
上述代码中使用的函数都是单一的数值,实际上,这些函数也都可以用到NumPy
数组上。例如,平面直角坐标系中有1000万个点,他们的x坐标和y坐标都分布在[0,1)
区间,哪一个点距离点(0.5,0.5)
最近呢?使用Numpy
数组计算的代码如下
import numpy as np
p = np.random.random((10000000,2))
# 分离每一个点的x和y坐标
x, y = np.hsplit(p, 2)
# 计算每一个点距离点(0.5,0.5)的距离
d = np.hypot(x-0.5, y-0.5)
# 返回最短距离的点的索引号
i = np.argmin(d)
print('距离点(0.5,0.5)最近的点的坐标是(%f,%f),距离为%f' % (*p[i], d[i]))
类别 | 函数 | 功能 |
---|---|---|
查找特殊值 | np.max/min(a,axis=None) |
返回最大值/最小值 |
查找特殊值 | np.nanmax/nanmin(a, axis=None) |
忽略nan返回最大值/最小值 |
查找特殊值 | np.argmax/argmin(a, axis=None) |
返回最大值和最小值的索引号 |
查找特殊值 | np.nanargmax/nanargmin(a, axis=None) |
忽略nan返回最大值和最小值索引号 |
查找特殊值 | np.median(a, axis=None) |
返回中位数 |
查找特殊值 | np.nanmedian(a, axis=None) |
忽略nan返回中位数 |
求和差积 | np.ptp(a, axis=None) |
返回元素最大值与最小值的差 |
求和差积 | np.sum(a, axis=None) |
按指定轴求和 |
求和差积 | np.nansum(a, axis=None) |
忽略nan按指定轴求和 |
求和差积 | np.cumsum(a, axis=None) |
按指定周求累计和 |
求和差积 | np.nancumsum(a, axis=None) |
忽略nan按指定轴求累计和 |
求和差积 | np.diff(a, axis=None) |
按指定轴返回相邻元素的差 |
求和差积 | np.prod(a, axis=None) |
按指定轴求积 |
求和差积 | np.nanprod(a, axis=None) |
忽略nan按指定轴返回算数平均值 |
均值和方差 | np.mean(a, axis=None) |
按指定轴返回算数平均值 |
均值和方差 | np.nanmean(a, axis=None) |
忽略nan按指定轴返回算数平均值 |
均值和方差 | np.average() |
返回所有元素的加权平均值 |
均值和方差 | np.var(a) |
返回数组方差 |
均值和方差 | np.nanvar(a) |
忽略nan返回数组方差 |
均值和方差 | np.std() |
返回数组标准差 |
均值和方差 | np.nanstd() |
忽略nan返回数组标准差 |
相关系数 | np.corrcoef(a, b) |
返回a和b的皮尔逊关系数 |
下面以求最大值和最小值为例,演示忽略np.nan
的必要性
import numpy as np
import math
a = np.random.random(10)
print(np.max(a), np.min(a))
a[1::2] = np.nan # 将索引1,3,5,7,9的元素设置为nan
print(a)
print(np.max(a), np.min(a)) # 此时,min()和max()失效了
print(np.nanmax(a), np.nanmin(a)) # 必须使用nanmax()和nanmin()
方差和标准差
import numpy as np
import math
a = np.random.randint(0,50,(3,4))
print(np.sum(np.square(a-a.mean()))/a.size) # 用方差定义求方差
print(np.var(a)) # 直接用方差函数求方差,结果相同
print(np.sqrt(a.var())) # 对方差开方,即是标准差
print(a.std()) # 直接用标准差函数求标准差,结果相同
pa和pb是两只股票连续30个交易日的股价数组。每日股价收益定义为当日股价与前一个交易日股价之差再除以最后一个交易日的股价
import numpy as np
import math
# 综合运用统计函数,分析两只股票的关联关系和收益率
pa = np.array([
79.66, 81.29, 80.37, 79.31, 79.84, 78.53, 78.29, 78.51, 77.99, 79.82,
80.41, 79.27, 80.26, 81.61, 81.39, 80.29, 80.18, 78.38, 75.06, 76.15,
75.66, 73.90, 72.14, 74.27, 75.27, 76.15, 75.40, 76.51, 77.57, 77.06
])
pb = np.array([
30.93, 31.61, 31.62, 31.77, 32.01, 31.52, 30.09, 30.54, 30.78, 30.84,
30.80, 30.38, 30.88, 31.38, 31.05, 29.90, 29.96, 29.59, 28.71, 28.95,
29.19, 28.71, 27.93, 28.35, 28.92, 29.17, 29.02, 29.43, 29.12, 29.11
])
print(np.corrcoef(pa, pb)) # 两只股票的相关系数为0.867,关联比较密切
pa_re = np.diff(pa)/pa[:-1] # 股价收益率
pb_re = np.diff(pb)/pb[:-1] # 股价与前一个交易日股价差除以最后一个交易日的股价
import matplotlib.pyplot as plt
plt.plot(pa_re)
plt.plot(pb_re)
plt.show()
数据插值是数据处理过程中经常用到的技术,常用的插值有一维插值、二维插值、高阶插值等,常见的算法有线性插值、B样条插值、临近插值等。不过,Numpy只提供了一个简单地一维线性插值函数np.interp()
,其他更加复杂的插值功能放到了Scipy
中。
下面用一个实例来演示NumPy一维线性插值函数的使用方法。假定_x
和_y
是原始的样本点x坐标和y坐标构成的数组,总数只有11个点。如果想在_x
的值域范围内插值更多的点,如增加到33个点,就需要在_x
的值域范围内生成33个点的x坐标构成的数组x,再利用插值函数np.interp()
得到对应的33个点的y坐标构成的数组y。
import numpy as np
import matplotlib.pyplot as plt
_x = np.linspace(0, 2*np.pi, 11)
_y = np.sin(_x)
x = np.linspace(0, 2*np.pi, 33)
y = np.interp(x, _x, _y)
plt.plot(x, y, 'o')
plt.plot(_x, _y, 'o')
plt.show()
拟合又称回归,是指已知某函数的若干离散函数值,通过调整该函数中特定若干待定系数,使得该函数与已知离散函数值的误差达到最小。
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['FangSong'] # 指定字体以保证汉字正常显示
plt.rcParams['axes.unicode_minus'] = False # 正确显示连字符
_x = np.linspace(-1, 1, 201)
_y = ((_x**2-1)**3 + 0.5)*np.sin(2*_x) + np.random.random(201)/10 - 0.1
plt.plot(_x, _y, ls='', marker='o', label="原始数据")
for k in range(4, 8):
g = np.poly1d(np.polyfit(_x, _y, k)) # g是k次多项式
loss = np.sum(np.square(g(_x)-_y)) # g(x)和f(x)的误差
plt.plot(_x, g(_x), label="%d次多项式,误差:%0.3f"%(k,loss))
plt.legend()
plt.show()
np.frompyfunc()
定义广播函数使用np.frompyfunc()
将数值函数转换成数组函数需要提供三个参数:数值函数、输入函数的个数和返回值的个数。另外,np.frompyfunc()
返回的广播函数,其返回值是object类型,最终需要根据实际情况显式地转换数据类型
import numpy as np
def func_demo(x, y):
if x == 0 or y == 0 or x == y:
return 0
elif x&(x-1) == 0 and y&(y-1) == 0: # x和y都是2的整数次幂
return max(x, y)
elif x&(x-1) == 0: # 仅有x等于2的整数次幂
return x
elif y&(y-1) == 0: # 仅有y等于2的整数次幂
return y
else:
return max(x, y)
uf = np.frompyfunc(func_demo, 2, 1)
a = np.random.randint(0, 256, (2,5), dtype=np.uint8)
b = np.random.randint(0, 256, (2,5), dtype=np.uint8)
c = uf(a, b)
print(c.dtype) # 此时c的数据类型为object
c = c.astype(np.uint8) # 改变类型
print(c)
np.vectorize()
定义广播函数np.frompyfunc()
适用于多个返回值的函数。如果返回值只有一个,使用np.vectorize()
定义广播函数更方便,并且还可以通过otypes
参数指定返回数组的元素类型
import numpy as np
def func_demo(x, y):
if x == 0 or y == 0 or x == y:
return 0
elif x&(x-1) == 0 and y&(y-1) == 0: # x和y都是2的整数次幂
return max(x, y)
elif x&(x-1) == 0: # 仅有x等于2的整数次幂
return x
elif y&(y-1) == 0: # 仅有y等于2的整数次幂
return y
else:
return max(x, y)
uf = np.vectorize(func_demo, otypes=[np.uint8])
c = uf(a, b)
print(c) # 此时c的数据类型为uint8
自定义广播函数并不是真正的广播函数,其运行效率和循环遍历几乎没有差别,因此除非确实必要,否则不应该滥用自定义广播函数。
numpy.ma
子模块通过引入掩码数组提供了一种解决数据缺失或无效问题的安全、便捷的方法。numpy.ma
子模块的主体是MaskedArray
类,他是numpy.ndarray
的派生类,可以把numpy.ma
子模块当做ndarray
来用,且无须考虑数组的无效值是否会给操作带来无法预制的意外
import numpy as np
import numpy.ma as ma
掩码数组子模块的ma.array()
函数和Numpy
的np.array()
函数类似,可以直接将列表生成掩码数组,默认mask参数为False,生成的数组类型是MaskedArray
类。数组掩码梳理后,无论是查找最大值、最小值,还是计算均值、方差,都不用再担心数据是否无效的问题了
import numpy as np
import numpy.ma as ma
a = ma.array([0, 1, 2, 3], mask=[0, 0, 1, 0]) # 指定第3个元素无效
print(a)
print(type(a))
print(a.min(), a.max(), a.mean(), a.var())
ma.asarray()
函数可以将普通的NumPy数组转成掩码数组。新生成的掩码数组不会对原数组中的np.nan
或np.inf
做掩码处理,但是会相应调整填充值(fill_value)
import numpy as np
import numpy.ma as ma
a = np.arange(5)
print(ma.asarray(a))
a = np.array([1, np.nan, 2, np.inf, 3]) # 包含特殊值的数组
print(ma.asarray(a))
ma.asarray()
函数不会对原数组中的np.nan
或np.inf
做掩码处理,ma.masked_invalid()
函数则可以实现这个功能
import numpy as np
import numpy.ma as ma
a = np.array([1, np.nan, 2, np.inf, 3])
print(ma.masked_invalid(a))
有时需要将数组中的某个给定值设置为无效(掩码),ma.masked_equal()
函数可以实现这个功能
import numpy as np
import numpy.ma as ma
a = np.arange(3).repeat(2)
print(ma.masked_equal(a, 1)) # 对数组元素1做掩码
有时需要将数组中符合条件的某些特定值设置为无效(掩码),掩码数组子模块提供了若干函数实现条件掩码。这些可能的筛选条件包括大于、大于等于、小于、小于等于、区间内、区间外等6中。
import numpy as np
import numpy.ma as ma
a = np.arange(8)
print(ma.masked_greater(a, 4)) # 掩码大于4的元素
print(ma.masked_greater_equal(a, 4)) # 掩码大于等于4的元素
print( ma.masked_less(a, 4)) # 掩码小于4的元素
print(ma.masked_less_equal(a, 4)) # 掩码小于等于4的元素
print(ma.masked_inside(a, 2, 5)) # 掩码 [2,5]之间的元素
print(ma.masked_outside(a, 2, 5)) # 掩码 [2,5]之外的元素
a和b是两个结构相同的数组,如果用a>5
的条件对数组b掩码,上面那些函数就失效了。这种情况可以使用ma.masked_where()
函数,该函数也可以对数组自身掩码
import numpy as np
import numpy.ma as ma
a = np.arange(8)
b = np.random.random(8)
print( ma.masked_where(a>5, b)) # 用a>5的条件掩码数组b
因为掩码数组MaskedArray
类是numpy.ndarray
的派生类,所以那些用在普通NumPy数组上的索引和切片操作也依然有效
import numpy as np
import numpy.ma as ma
a = np.array([1, np.nan, 2, np.inf, 3])
a = ma.masked_invalid(a)
print(a[0], a[1], a[-1])
print(a[1:-1])
掩码数组内置方法的使用和普通数组没有区别。使用NumPy命名空间的函数则要慎重,如果掩码数组子模块有对应函数,应优先使用数组子模块的对应函数。例如,对掩码数组求正弦,如果使用np.sin()
函数,会发出警告信息;如果使用ma.sin()
函数,则无任何问题
import numpy as np
import numpy.ma as ma
a = np.array([1, np.nan, 2, np.inf, 3])
a = ma.masked_invalid(a)
print(a.min(), a.max(), a.mean(), a.var())
#print(np.sin(a)) # 虽然可以执行,但会弹出警告
print(ma.sin(a)) # 这才是正确的用法
任何情况下,我们都可以通过掩码数组的data属性来获得掩码数组的数据视图,其类型就是np.ndarray
数组。另外,还可以使用掩码数组的__array__()
函数或ma.getdata()
函数来获取掩码数组的数据视图。上述三种方法获得数据视图的操作,本质上都是操作掩码的数组本身。如果需要数据视图副本,需使用copy()
函数
import numpy as np
import numpy.ma as ma
a = ma.array([1, np.nan, 2, np.inf, 3])
print(a)
x = a.data
y = a.__array__()
z = ma.getdata(a)
w = np.copy(a.__array__()) # 复制数据视图
print(x)
print(y)
print(z)
print(w)
a[-1] = 9
print(x)
print(y)
print(z)
print(w)
通过掩码数组的mask属性可以查看当前数组的掩码情况,其代码如下。通常,数组的掩码是一个布尔型数组,或是一个布尔值
import numpy as np
import numpy.ma as ma
a = ma.masked_invalid(np.array([1, np.nan, 2, np.inf, 3]))
print(a.mask)
如果要对数组切片掩码或对数组的某个元素掩码,直接令该切片或该元素等于ma.masked
常量即可
import numpy as np
import numpy.ma as ma
a = ma.masked_invalid(np.array([1, np.nan, 2, np.inf, 3]))
print(a.mask)
print(a)
a[:2] = ma.masked
print(a)
如果要撤销对数组切片或数组中的某个元素的掩码,只需要对该切片或该元素做赋值操作即可
import numpy as np
import numpy.ma as ma
a = ma.masked_invalid(np.array([1, np.nan, 2, np.inf, 3]))
a[1] = 1.5
a[2:4] = 5
print(a)
np.matrix
是数组np.ndarray
的派生类。这意味着矩阵本质上是一个数组,拥有数组的所有属性和方法;同时,矩阵又有一些不同于数组的特性和方法np.mat()
函数用于创建矩阵,它可以接受列表、数组甚至是字符串等形式的参数,还可以使用dtype参数指定数据类型,其代码如下:
import numpy as np
import numpy.matlib as mat
print(np.mat([[1,2,3],[4,5,6]], dtype=np.int)) # 使用列表创建矩阵
print(np.mat(np.arange(6).reshape((2,3)))) # 使用数组创建矩阵
print(np.mat('1 4 7; 2 5 8; 3 6 9')) # 使用Matlab风格的字符串创建矩阵
此外,和生成特殊值数组类似,numpy.matlib
子模块也提供了多个函数用于生成特殊值矩阵和随机数矩阵
print(mat.zeros((2,3))) # 全0矩阵
print(mat.ones((2,3))) # 全1矩阵
print(mat.eye(3)) # 单位矩阵
print(mat.empty((2,3))) # 空矩阵
print(mat.rand((2,3))) # [0,1)区间随机数矩阵
print(mat.randn((2,3))) # 均值0方差1的高斯(正态)分布矩阵
矩阵有几个特有的属性,如转置矩阵、逆矩阵、共轭矩阵、共轭转置矩阵等。
import numpy as np
m = np.mat(np.arange(6).reshape((2,3)))
print(m)
print(m.T) # 返回自身的转置矩阵
print(m.H) # 返回自身的共轭转置矩阵
print(m.I) # 返回自身的逆矩阵
print(m.A) # 返回自身数据的视图(ndarray类)
矩阵运算和数组运算大致相同,只有乘法运算有较大差别。两个数组相乘就是对应元素相乘,条件是两个数组的结构相同。事实上,及时两个数组的结构不同,只要满足特定条件,也能做乘法运算
import numpy as np
a = np.random.randint(0,10,(2,3))
print(a)
b = np.random.randint(0,10,3)
print(b)
print(a*b) # shape不同的两个数组也可以相乘
print(b*a)
除了对应元素相乘,数组还可以使用np.dot()
函数相乘
a = np.random.randint(0,10,(2,3))
b = np.random.randint(0,10,3)
c = np.random.randint(0,10,(3,2))
print(np.dot(a,b))
print(np.dot(a,c))
属于数组而言,使用星号相乘和使用np.dot()
函数相乘是完全不同的两种乘法:对于矩阵来说,不管是使用星号相乘还是使用np.dot()
函数相乘,结果都是np.dot()
函数相乘的结果,因为矩阵没有对应元素相乘这个概念。np.dot()
函数实现的乘法就是矩阵乘法
不是所有的矩阵都能相乘,矩阵乘法不满足交换律。概括来说,就是矩阵A的各行逐一去乘矩阵B的各列。例如,矩阵A的第1行和矩阵B的第1列,它们的元素个数一定相等,对应元素相乘后求和的值作为结果矩阵第1行第1列的值。又如,矩阵A的第3行和矩阵B的第3列,对应元素相乘后求和的值作为结果矩阵第3行第3列的值。以此类推,最终得到矩阵A乘矩阵B的结果矩阵
np.random.random()
是最常用的随机数生成函数,该函数生成的随机数随机均匀分布于[0, 1)
区间。如果不提供参数,np.random.random()
函数返回一个浮点型随机数。np.random.random()
函数还可以接受一个整型或元组参数,用于指定返回的浮点型随机数数组的结构(shape)。也有很多人习惯使用np.random.rand()
函数生成随机数,其功能和np.random.random()
函数一样,知识np.random.rand()
函数不接受元组参数,必须要写成两个整型参数
import numpy as np
print(np.random.random())
print(np.random.random(2))
print(np.random.random((2,3)))
np.random.randint()
是另一个常用的随机数生成函数,该函数生成的随机整数均匀分布于[low, high)
区间。如果省略low参数,则默认low的值等于0。np.random.randint()
函数还有一个默认参数size
,用于指定返回的整型随机数数组的结构(shape)
print(np.random.randint(10))
print(np.random.randint(10, size=5))
print(np.random.randint(10, size=(2,5)))
print(np.random.randint(10, 100, size=(2,5)))
随机抽样是从指定的有序列表中随机抽取指定数量的元素。随机抽样的应用比较广泛,如产品抽检、抽签顺序等。NumPy的随机抽样函数是np.random.choice()
,其原型如下
np.random.choice(a, size=None, replace=True, p=None)
参数a表示待抽样的全体样本,它值接受整数或一维的数组(列表)。参数a如果是整数,相当于将数组np.arange(a)
作为全体样本。参数size用于指定返回抽样结果数组的结构(shape)。参数replace用于指定是否允许多次抽取同一个样本,默认为允许。参数p是和全体样本集合等长的权重数组,用于指定对应样本被抽中的概率。
import numpy as np
print(np.random.choice(1,5)) # 抽签样本只有1个元素0,抽取5次
print(np.random.choice(['a','b','c'], size=(3,5), p=[0.5,0.25,0.25])) # 指定权重
print(np.random.choice(np.arange(100), size=(2,5), replace=False)) # 不允许重复
使用np.random.randn()
函数是最简单的生成标准正态分布随机数的方法。np.random.randn()
函数用于生成均值为0、标准差为1的正态分布(标准正态分布)的随机数、该函数可以接受一个或两个整型参数,用来指定返回的符合标准正态分布的随机数数组的结构(shape)
import numpy as np
print(np.random.randn()) # 标准正态分布,均值为0,标准差为1
print(np.random.randn(5))
print(np.random.randn(2,5))
如果需要生成非标准正态分布随机数,则应该使用np.random.normal()
函数。np.random.nomal()
函数默认生成均值为0、标准差为1的正态分布随机数。参数loc用于指定均值,参数scale用于指定标准差,参数size用于指定返回的符合正态分布的随机数数组的结构(shape)。从下面的代码可以看出,和使用默认标准差相比,指定标准差为0.2时,数据分布更加靠近均值
print(np.random.normal()) # 默认均值为0,标准差为1
print(np.random.normal(loc=2, size=5)) # 参数loc指定均值为2
print(np.random.normal(loc=2, scale=0.2, size=(2,5))) # 参数loc指定均值为2,参数scale指定标准差为0.2
计算机程序或编程语言中的随机数都是伪随机数。因为计算机硬件是确定的,代码是固定的,算法是准确的,通过这些确定的、固定的、准确的东西不会产生真正的随机数,除非引入这个封闭系统以外的因素。计算机系统的随机算法一般使用线性同余或平方取中的算法,通过一个种子(通常用时钟代替)产生。这意味着,如果知道了种子和已经产生的随机数,就可能获得接下来随机数序列的信息,这就是伪随机数的可预测性
NumPy随机数函数内部使用了一个伪随机数生成器,这个生成器每次实例化时都需要一个种子(整数)完成初始化。如果两次初始化的种子相同,则每次初始化后产生的随机数序列就完全一致。np.random.seed()
函数可以指定伪随机数生成器的初始化种子
import numpy as np
np.random.seed(12345) # 使用'12345'随机种子初始化伪随机数生成器
print(np.random.random(5))
print(np.random.random((2,3)))
np.random.seed(12345) # 再次使用'12345'随机种子初始化伪随机数生成器
print(np.random.random(5)) # 和上面完全一致
print(np.random.random((2,3))) # 和上面完全一致
从上述代码汇总可以看出,只要指定相同的种子,接下来的随机序列就完全一致。这意味着,只有从外部引入真正的随机因子(如天空云朵的形状、邻居家无线网络信号的强度等)作为种子,才可以得到真正的随机数
此外,NumPy还提供了随机数生成器,可以直接操作这个生成器来生成随机数
r = np.random.RandomState(12345) # 使用随机数生成器也同样
print(r.random(5)) # 和上面完全一致
print(r.random((2,3))) # 和上面完全一致