NumPy
NumPy是Python数值计算的基石,它提供多种数据结构、算法以及大部分涉及Python数值计算所需的接口
概括如下:
- 快速、高效的多维数组对象ndarray
- 基于元素的数组计算或数组间数学操作函数
- 用于读写硬盘中基于数组的数据集的工具
- 线性代数操作、傅里叶变换以及随机数生成
- 成熟的C语言API,允许Python拓展和本地的C或C++代码访问NumPy的数据结构和计算设施
创建ndarray
import numpy as np
# 创建数组,下面几种效果一样
a = np.array([1, 2, 3, 4, 5])
b = np.asarray([1, 2, 3, 4, 5]) # np.asarray和np.array的区别:如果输入已经是ndarray,则不再复制
c = np.array(range(1, 6))
d = np.arange(1, 6)
e = np.linspace(1, 5, 5) # 开始、结束、数量
# 生成三行四列全为0或1或空的数组
a = np.zeros((3, 4))
b = np.ones((3, 4))
b = np.empty((3, 4))
# 根据a的形状,生成0或1或空的数组
a = np.arange(1, 13).reshape(3, 4)
print(np.zeros_like(a))
print(np.ones_like(a))
print(np.empty_like(a))
# 创建3阶单位矩阵,以下2个函数等效
a = np.eye(3)
a = np.identity(3)
# 根据形状和数据,创建ndarray
# [
# [1,2,3],
# [1,2,3]
# ]
a = np.full((2, 3), [1, 2, 3])
# 根据给定数组的形状和数据,创建ndarray
# [
# [1,1,1],
# [1,1,1]
# ]
b = np.full_like(a, 1)
数据类型
# 数组的类型 <class 'numpy.ndarray'>
a = np.array([1, 2, 3, 4, 5])
type(a)
# 数据的类型 int64
a.dtype
# 指定创建数据的类型
a = np.array([1, 0, 1, 1, 0, 2], dtype='?')
a = np.array([1, 0, 1, 1, 0, 2], dtype=bool)
# 修改数据的类型
a.astype('int8')
a.astype('i1')
a.astype(np.int8)
类型 | 类型代码 | 描述 |
---|---|---|
int8、uint8 | i1、u1 | 有符号、无符号的8位(1个字节)整型 |
int16、uint16 | i2、u2 | 有符号、无符号的16位(2个字节)整型 |
int32、uint32 | i4、u4 | 有符号、无符号的32位(4个字节)整型 |
int64、uint64 | i8、u8 | 有符号、无符号的64位(8个字节)整型 |
float16 | f2 | 半精度浮点数 |
float32 | f4或f | 标准的单精度浮点数。与C的floaat兼容 |
float64 | f8或d | 标准的双精度浮点数。与C的double和python的float对象兼容 |
float128 | f16或g | 扩展精度浮点数 |
complex64、complex128、complex256 | c8、c16、c32 | 分别用两个32位、64位或128位浮点数表示的复数 |
bool | ? | 存储True、False值的布尔类型 |
数组的形状
reshape有个order参数,是指以什么样的顺序读写元素(一般用默认的即可)
- C:默认参数,使用类似C-like语言(行优先)中的索引方式进行读写
- F:使用类似Fortran-like语言(列优先)中的索引方式进行读写
- A:原数组如果是按照C的方式存储数组,则用C的索引对数组进行reshape,否则使用F的索引方式
import numpy as np
# 一维数组,输出(5,),表示数组中有5列(5维行向量)
a = np.array([1, 2, 3, 4, 5])
print(a.shape)
# 二维数组,输出(3,2),表示3行2列
a = np.array([[1, 2], [3, 4], [5, 6]])
print(a.shape)
# 三维数组,输出(2,3,2),表示2块3行2列
a = np.array([
[[1, 2], [3, 4], [5, 6]],
[[7, 8], [9, 10], [11, 12]]
])
print(a.shape)
# 修改数组的形状
# 一维、二维、三维可以动态切换形状的(前提是元素数量能对上),输出(3,4)
a = np.arange(1, 13).reshape(3, 4)
print(a.shape)
# 展开数组
# 不管几维,通通转换成一维,输出(12,)
a = np.arange(1, 13).reshape(2, 3, 2)
print(a.flatten().shape)
# shape这个属性在深度学习中也经常使用,比如我们要获取数据(B,W,H,C)(一个batchsize为B的(W,H,C)数据)的形状(宽高)
input_data.shape[1:3]
数组的计算
- 与值计算,numpy具有广播机制,加减乘除会被计算到所有元素上
- 与数组计算,后缘维度(从末尾开始算起的维度)的轴相等或其中一方的长度为1,则广播兼容。广播会在缺失或长度为1的维度上进行
- 结构完全相同,对应位置一一相加
- 结构完全对不上,直接报错
- 列相同,行=1,ndarray a=(3, 4),ndarray b=(1, 4),则:a每行的元素+对应位置的b元素
- 行相同,列=1,ndarray a=(3, 4),ndarray b=(3, 1),则:a每列的元素+对应位置的b元素
import numpy as np
# 与值计算
a = np.arange(1, 7).reshape(2, 3)
print(a + 1)
# 结构相同
a = np.arange(1, 7)
b = np.arange(11, 17)
print(a + b)
# 结构对不上
a = np.arange(1, 13).reshape(3, 4)
b = np.arange(1, 13).reshape(2, 6)
print(a + b)
# 列相同,行=1
a = np.arange(1, 13).reshape(3, 4)
print(a)
b = np.arange(1, 5).reshape(1, 4)
print(b)
print(a + b)
# 行相同,列=1
a = np.arange(1, 13).reshape(3, 4)
print(a)
b = np.arange(1, 4).reshape(3, 1)
print(b)
print(a + b)
numpy中的转置
import numpy as np
# 以下效果一样
a = np.arange(1, 13).reshape(3, 4) # type:np.ndarray
print(a.transpose()) # 方法
print(a.T) # 属性
print(a.swapaxes(0, 1)) # 交换0轴和1轴
print(a.swapaxes(1, 0)) # 交换1轴和0轴
# 三维数组的转置
a = np.arange(16).reshape((2, 2, 4))
b = a.copy()
c = a.copy()
'''
[[[ 0 1 2 3]
[ 4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]]
'''
print(a)
# 以下几种效果一致,0轴和1轴互换,2轴不变
'''
0(0,0,0) 1(0,0,1) 2(0,0,2) 3(0,0,3)
4(0,1,0) 5(0,1,1) 6(0,1,2) 7(0,1,3)
8(1,0,0) 9(1,0,1) 10(1,0,2) 11(1,0,3)
12(1,1,0) 13(1,1,1) 14(1,1,2) 15(1,1,3)
交换0轴和1轴后,变成:
0(0,0,0) 1(0,0,1) 2(0,0,2) 3(0,0,3)
8(1,0,0) 9(1,0,1) 10(1,0,2) 11(1,0,3)
4(0,1,0) 5(0,1,1) 6(0,1,2) 7(0,1,3)
12(1,1,0) 13(1,1,1) 14(1,1,2) 15(1,1,3)
'''
print(a.transpose((1, 0, 2)))
print(b.swapaxes(0, 1))
print(c.swapaxes(1, 0))
# 0轴不变,1轴和2轴参照二维数组的转置
print(b.swapaxes(1, 2))
索引和切片
大部分场景用到的是一维数组和二维数组,所以下面以一维数组和二维数组举例
切片和range一样,都是含头不含尾的 [0,2)
下面提到的第x行第x列都是以索引的方式计算,即:从0开头
二维数组只要记住逗号前面的是行后面的是列就行了,其他跟一维数组类似
import numpy as np
# 一维数组
a = np.arange(1, 13)
print(a[0]) # 输出第0个元素,结果:1
print(a[4:6]) # 输出第4个和第5个元素,结果:[5 6]
print(a[:]) # 输出全部元素,结果:[ 1 2 3 4 5 6 7 8 9 10 11 12]
print(a[9:]) # 输出第9个元素到末尾,结果:[10 11 12]
print(a[-1]) # 输出最后个元素,结果:12
print(a[-2:]) # 输出最后2个元素,结果:[11 12]
print(a[-2:-1]) # 输出倒数第2个元素(含头不含尾),结果:[11]
print(a[::2]) # 调整步长为2,输出:[ 1 3 5 7 9 11]
print(a[0:13:2]) # 调整步长为2,输出:[ 1 3 5 7 9 11]
# 二维数组
a = np.arange(1, 13).reshape(3, 4)
print(a)
print(a[0][0]) # 输出第0行第0列,结果:1
print(a[0]) # 输出第0行,结果:[1 2 3 4]
print(a[:, 0]) # 输出第0列,结果:[1 5 9]
print(a[[2, 0]]) # 输出第2行和第0行
print(a[[-1, -2]]) # 输出倒数第1行和倒数第2行(-1表示最后一行(列))
print(a[0:3]) # 输出第0行到第2行(含头不含尾)
print(a[:, [3, 1]]) # 输出第3列和第1列
print(a[:, 0:3]) # 输出第0列到第2列(含头不含尾)
print(a[:, 0:3:2]) # 输出第0列到第2列且步长为2(含头不含尾)
# 取指定元素
a = np.arange(32).reshape(8, 4)
print(a[[1, 5, 7, 2], [0, 3, 1, 2]]) # [ 4 23 29 10]
# 取指定区域(先取行再取列)
# [[ 4 7 5 6]
# [20 23 21 22]
# [28 31 29 30]
# [ 8 11 9 10]]
print(a[[1, 5, 7, 2]][:, [0, 3, 1, 2]])
布尔索引
布尔索引不使用and
、or
、not
,而是使用&
、|
、~
import numpy as np
# 正数的个数
arr = np.random.randn(10)
print((arr > 0).sum())
# 有一个True则返回True
print((arr > 0).any())
# 全为True才返回True
print((arr > 0).all())
a = np.arange(12).reshape(3,4)
b = np.arange(0,24,2).reshape(3,4)
# 只有第一个是True,其他全为False
print(a==b)
# 只有第一个是False,其他全为True,下面2种写法等价
print(a != b)
print(~(a == b))
数组的赋值
import numpy as np
a = np.arange(1, 13).reshape(3, 4) # type:np.ndarray
# 正常赋值
a[:, 2:4] = 0
a[2, 2] = 2
a[2][2] = 2
a[0] = 0
# 利用bool索引进行赋值
a[a < 3] = 0 # 小于3的值全部替换为0
a = np.where(a < 3, 0, 1) # 三元运算,小于3替换为0,其他替换为1
# 裁剪,小于等于10的全部替换为10,大于等于11的全部替换为11
a = a.clip(10, 11)
numpy的复制
numpy被设计成适合处理非常大的数组,所以能用视图的地方就不会用复制
- 视图:可以简单理解为浅拷贝
- 修改原数组,视图会受到影响,赋值、切片、view()、reshape()等
- 复制:可以简单理解为深拷贝
- 修改原数组,副本不会受到影响,神奇索引(如:arr[[1,2]])、arr.copy()、布尔索引等
数组数据结构信息区中有numpy数组的形状(shape)以及数据类型(data-type)等信息
而数据存储区则是用于存储数组的数据,numpy数组中的数据可以指向其它数组中的数据,这样多个数组可以共用同一个数据
import numpy as np
a = np.arange(12)
b = a.reshape((3, 4))
c = a.reshape((4, 3))
# a = [ 0 1 2 3 4 5 6 7 8 9 10 11]
# a.base = None
# a.flags.owndata = True
print(a, a.base, a.flags.owndata)
# b = 3行4列数组
# b.base = [ 0 1 2 3 4 5 6 7 8 9 10 11]
# b.flags.owndata = False
print(b, b.base, b.flags.owndata)
# c = 4行3列数组
# c.base = [ 0 1 2 3 4 5 6 7 8 9 10 11]
# c.flags.owndata = False
print(c, c.base, c.flags.owndata)
numpy中的nan和inf
- nan:not a number
- 当类型为float时,读取文件有缺失或者赋值为None,会变成nan
- 两个nan是不相等的
- 对nan的处理,如:删除、替换为0、替换为均值或中值,根据实际场景来
- inf:无穷大
- 当类型为float时,正数除以0会变成inf,负数除以0会变成-inf
import numpy as np
b = np.arange(1, 13).astype(np.float32).reshape(3, 4) # type:np.ndarray
# inf
b[2, 2] = b[2][2] / 0
# nan
b[2, 2] = None
# nan不会被裁剪到
print(b.clip(9, 10))
# 统计非零值(包括False)的个数,利用nan!=nan的特性,可以统计nan的个数
print(np.count_nonzero(b != b))
# nan和任何值计算还是nan
b = b * 2
# 把nan替换为0,下面2个等效
b[np.isnan(b)] = 0
b[b != b] = 1
数组的拼接
import numpy as np
a = np.arange(1, 13).reshape(3, 4) # type:np.ndarray
b = a.copy()
# 竖直拼接
print(np.vstack((a, b)))
# 水平拼接
print(np.hstack((a, b)))
数组的行列交换
import numpy as np
a = np.arange(1, 13).reshape(3, 4) # type:np.ndarray
# 行交换,第一行和第二行交换
a[[1, 2], :] = a[[2, 1], :]
# 列交换,第一列和第二列交换
a[:, [1, 2]] = a[:, [2, 1]]
常用统计函数
import numpy as np
a = np.arange(1, 13).reshape(3, 4) # type:np.ndarray
# 默认返回多维数组的全部的统计结果,如果指定axis则返回当前轴方向上的聚合结果
print(a.sum()) # 求和
print(a.mean()) # 均值
print(np.median(a)) # 中值,奇数:中间的值,偶数:中间2个值的均值
print(a.max()) # 最大值
print(a.min()) # 最小值
print(a.argmax()) # 最大值的位置
print(a.argmin()) # 最小值的位置
print(a.ptp()) # 极值,最大值和最小值的差
print(a.std()) # 标准差(默认分母是n)
print(a.var()) # 方差(默认分母是n)
print(a.cumsum()) # 迭代累加
print(a.cumprod()) # 迭代累乘
指定轴的话,是以指定轴的方向进行聚合,我们可以将同一个轴上的数据看做同一个单位,那聚合的时候,我们只需要在同级别的单位上进行聚合就可以了
import numpy as np
a = np.arange(18).reshape(3, 2, 3) # type:np.ndarray
'''
[[[ 0 1 2]
[ 3 4 5]]
[[ 6 7 8]
[ 9 10 11]]
[[12 13 14]
[15 16 17]]]
'''
print(a)
print("*" * 100)
'''
0轴方向的单位:
[[ 0 1 2]
[ 3 4 5]]
...
结果是一个(2, 3)的数组:
[
[sum(a_000, a_100, a_200), sum(a_001, a_101, a_201), sum(a_002, a_102, a_202)],
[sum(a_010, a_110, a_210), sum(a_011, a_111, a_211), sum(a_012, a_112, a_212)]
]
[[18 21 24]
[27 30 33]]
'''
print(a.sum(axis=0))
print("*" * 100)
'''
1轴方向的单位
[ 0 1 2]
...
结果是一个(3, 3)的数组:
[
[sum(a_000, a_010), sum(a_001, a_011), sum(a_002, a_012)],
[sum(a_100, a_110), sum(a_101, a_111), sum(a_102, a_112)],
[sum(a_200, a_210), sum(a_201, a_211), sum(a_202, a_212)],
]
[[ 3 5 7]
[15 17 19]
[27 29 31]]
'''
print(a.sum(axis=1))
print("*" * 100)
'''
2轴方向的单位
0 1 2 ...
结果是一个(3, 2)的数组:
[
[sum(a_000, a_001, a_002), sum(a_010, a_011, a_012)],
[sum(a_100, a_101, a_102), sum(a_110, a_111, a_112)],
[sum(a_200, a_201, a_202), sum(a_210, a_211, a_212)],
]
[[ 3 12]
[21 30]
[39 48]]
'''
print(a.sum(axis=2))
print("*" * 100)
常用随机函数
- seed:向随机数生成器传递随机状态种子
- permutation:返回一个序列的随机排列,或者返回一个乱序的整数范围序列
- shuffle:随机排列一个序列
- rand:从均匀分布中抽取样本
- randint:根据给定的由低到高的范围抽取随机整数
- randn:从均值0方差1的正态分布中抽取样本(MATLAB型接口)
- binomial:从二项分布中抽取样本
- normal:从正态(高斯)分布中抽取样本
- beta:从beta分布中抽取样本
- chisquare:从卡方分布中抽取样本
- gamma:从伽马分布中抽取样本
- uniform:从均匀[0,1)分布中抽取样本
import numpy as np
# 生成3行4列的均匀分布的随机数(0到1之间)
a = np.random.rand(3, 4)
# 生成3行4列的标准正态分布的随机数(平均数0,标准差1)
b = np.random.randn(3, 4)
# 生成3行4列1到99的整数随机数
c = np.random.randint(1, 100, (3, 4))
# 生成3行4列的均匀分布的随机数(1-99之间)
d = np.random.uniform(1, 100, (3, 4))
# 生成3行4列的标准正态分布的随机数(平均数50,标准差2)
e = np.random.normal(50, 2, (3, 4))
# 随机种子,生成相同的随机数
np.random.seed(3)
f = np.random.randint(1, 100, (3, 4))
排序函数
如何理解NumPy中axis的使用?
- axis=0代表跨行(实际上就是按列)
- axis=1代表跨列(实际上就是按行)
- 没有指定axis,默认axis=-1(按照数组最后一个轴来排序)
- axis=None,以扁平化的方式作为一个向量进行排序
a = np.random.randint(1, 100, (3, 4)) # type:numpy
# 默认为:快排、升序、最后一个轴
print(np.sort(a, axis=-1, kind='quicksort', order=None))
# 轴为None,扁平排序,降序排序(numpy中的排序没有reverse参数)
print(abs(np.sort(-a, axis=None)))
# 0轴排序
print(np.sort(a, axis=0))
# 1轴排序,结果与默认相同,因为二维数组,1就是最后一个轴
print(np.sort(a, axis=1))
奇进偶舍
奇进偶舍,又称为四舍六入五成双规则、银行进位法(Banker's Rounding),是一种计数保留法,是一种数值修约规则
从统计学的角度,“奇进偶舍”比“四舍五入”更为精确:在大量运算时,因为舍入后的结果有的变大,有的变小,更使舍入后的结果误差均值趋于零
而不是像四舍五入那样逢五就进位,导致结果偏向大数,使得误差产生积累进而产生系统误差。“奇进偶舍”使测量结果受到舍入误差的影响降到最低
- 保留位数的后一位如果是4,则舍
- 保留位数的后一位如果是6,则入
- 保留位数的后一位如果是5(且5是最后一位),保留位数是奇数则入,保留位数是偶则舍
- 保留位数的后一位如果是5(5后面还有数),则入
import numpy as np
from decimal import Decimal
# 奇进偶舍
a = np.array([1.345, 1.3451, 1.335])
a = np.round(a, 2)
print(a)
# 四舍五入
print(Decimal('1.345').quantize(Decimal("0.01"), rounding="ROUND_HALF_UP"))
一元通用函数
- abs、fabs:逐元素地计算整数、浮点数或复数的绝对值
- sqrt:计算每个元素的平方根(与arr ** 0.5相等)
- square:计算每个元素的平方(与arr ** 2相等)
- exp:计算每个元素的自然指数值
- log、log10、log2、log1p:分别对应自然对数(e为底)、对数10为底、对数2为底、log(1+x)
- sign:计算每个元素的符号值,1(正数)、0(0)、-1(负数)
- ceil:向上取整
- floor:向下取整
- rint:将元素保留到整数位,并保持dtype
- modf:分别将数组的小数部分和整数部分按数组形式返回
- isnan:返回数组中的元素是否是一个NaN(不是一个数值),形式为布尔值数组
- isfinite,isinf:分别返回数组中的元素是否有限(非inf、非NaN)、是否无限的,形式为布尔值数组
- cos、cosh、sin、sinh、tan、tanh:常规的双曲三角函数
- arccos、arccosh、arcsin、arcsinh、arctan、arctanh:反三角函数
- logical_not:对数组的元素按位取反(与 ~ arr 效果一致)
二元通用函数
- add:跟直接用加号一致
- subtract:跟直接用减号一致
- multiply:跟直接用乘号一致
- divide、floor_divide:除或整除(放弃余数)
- power:将第二个数组的元素作为第一个数组对应元素的幂次方
- maximum、fmax:逐个元素计算最大值,fmax忽略NaN
- minimum、fmin:逐个元素计算最小值,fmin忽略NaN
- mod:按元素的求模计算(即求除法的余数)
- copysign:将第一个数组的符号值改为第二个数组的符号值
- greater、greater_equal、less、less_equal、equal、not_equal:>、>=、<、<=、==、!=
- logical_and、logical_or、logical_xor:&、|、^
数组合并
import numpy as np
arr1 = np.arange(12).reshape(3, 4)
arr2 = np.arange(13, 25).reshape(3, 4)
arr = np.concatenate([arr1, arr2])
'''
沿着0轴方向合并(默认)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[13 14 15 16]
[17 18 19 20]
[21 22 23 24]]
'''
print(arr)
arr = np.concatenate([arr1, arr2], axis=1)
'''
沿着1轴方向合并
[[ 0 1 2 3 13 14 15 16]
[ 4 5 6 7 17 18 19 20]
[ 8 9 10 11 21 22 23 24]]
'''
print(arr)
扩充维度
扩维操作在深度学习中也经常用到,PyTorch中的函数名叫unsqueeze()(取消挤压),TensorFlow与NumPy名称相同,直接使用tf.newaxis就可以了
import numpy as np
arr = np.arange(3)
# [0 1 2]
print(arr)
arr = arr[:, np.newaxis]
'''
扩充维度
[[0]
[1]
[2]]
'''
print(arr)
线性代数相关函数
numpy中的矩阵相乘是矩阵的逐元素相乘,跟线性代数里面的点乘有所区别
如果想用点乘,可以使用如下2种方式(或np.linalg库)
- ndarray1 @ ndarray2
- ndarray1.dot(ndarray2)
np.linalg库相关函数
- diag:将一个方阵的对角(或非对角)元素作为一维数组返回,或者将一维数组转换成一个方阵,并且在非对角线上有零点
- dot:矩阵点乘
- trace:计算对角元素和
- det:计算矩阵的行列式
- eig:计算方阵的特征值和特征向量
- inv:计算方阵的逆矩阵
- pinv:计算矩阵的Moore-Penrose伪逆
- qr:计算QR分解
- svd:计算奇异值分解(SVD)
- solve:求解x的线性系统Ax=b,其中A是方阵
- lstsq:计算Ax=b的最小二乘解
一维集合相关函数
- unique:去重并排序,np.unique(a)
- intersect1d:交集并排序,np.intersect1d(a, b)
- union1d:并集并排序,np.union1d(a, b)
- in1d:计算a中的元素是否包含在b中,返回一个bool数组,np.in1d(a, b)
- setdiff1d:差集并排序(在a中但不在b中的a的元素),np.setdiff1d(a, b)
- setxor1d:异或集并排序(在a或b中,但不属于a、b交集的元素),np.setxor1d(a, b)
其他常用函数
import numpy as np
a = np.arange(1, 13).reshape(3, 4)
print(len(a)) # 3行,输出:3
print(a.size) # 12个元素,输出:12
print(a.ndim) # 二维,输出:2
print(a.itemsize) # 每个元素的字节大小
points = np.arange(3)
'''
返回两个矩阵,第一个元素是X轴的取值,第二个元素是Y轴的取值
[array([[0, 1, 2],
[0, 1, 2],
[0, 1, 2]]), array([[0, 0, 0],
[1, 1, 1],
[2, 2, 2]])]
'''
print(np.meshgrid(points, points))
随机漫步
python实现
import random
position = 0
walk = []
steps = 100
for i in range(steps):
step = 1 if random.randint(0, 1) else -1
position += step
walk.append(position)
print(walk)
numpy实现
- 效率更高:一次生成100个投币结果
- 方便统计:walk.min()、walk.max()、np.abs(walk)>=10等等
import numpy as np
nsteps = 100
draws = np.random.randint(0, 2, size=nsteps)
steps = np.where(draws > 0, 1, -1)
walk = steps.cumsum()
print(walk)
掩码数组
import numpy as np
from numpy import ma
# 创建一个普通的NumPy数组
arr = np.array([1, 2, 3, 4, 5])
# 创建一个MaskedArray,将索引为1和3的元素标记为缺失
mask = np.array([False, True, False, True, False])
masked_arr = ma.array(arr, mask=mask)
print("原始数组:", arr)
print("MaskedArray:", masked_arr)
print("缺失元素的掩码:", masked_arr.mask)