NumPy基础
NumPy,是Numerial Python的简称,它是python数值计算中最重要的基础包。
包含了以下内容:
- ndarray,是一种高效多维数组。
- 对所有数据进行快速的矩阵计算,而无须编写循环程序。
- 对硬盘中数组数据进行读写的工具,并对内存映射文件进行操作。
- 线性代数,随机数生成以及傅里叶变换功能
- 用于连接NumPy到C、C++和FORTRAN语言类库的C语言API
NumPy提供了一个非常易用的C语言API,将数据传递给底层语言编写的外部类库,再由外部类库将计算结果按照NumPy数组的方式返回,变得非常简单。这个特征使得Pyhton可以对存量C/C++/Fortran代码库进行封装,并为这些代码提供动态、易用的接口。
NumPy本身并不提供建模和科学函数,理解NumPy的数组可以帮助更高效地使用基于数组的工具。比如pandas。
NumPy的数组相对于列表,速度会更快,并且内存占用会更少。
1 NumPy ndarray:多维数组对象
NumPy的核心特征之一就是N-维数组对象——ndarray。ndarray是Python中快速、灵活的大型数据收集容器。数组允许使用类似于标量的操作语法在整块数据上进行数学计算。
data=np.random.randn(2,3)
data
#array([[ 0.41468797, 0.80393093, 0.13026413],
# [ 0.53841874, -0.13085222, -1.68962615]])
data*10
#array([[ 4.14687973, 8.03930932, 1.30264126],
# [ 5.3841874 , -1.30852216, -16.8962615 ]])
1.1生成ndarray
生成数组最简单的方式就是使用array函数。array函数接收任意的序列型对象,生成一个新的包含传递数据的NumPy数组,例如
data1=[6,7.5,8,0,1]
arr1=np.array(data1)
arr1
# array([6. , 7.5, 8. , 0. , 1. ])
data2=[[1,2,3,4],[5,6,7,8]]
arr2=np.array(data2)
arr2
# array([[1, 2, 3, 4],
# [5, 6, 7, 8]])
对于array生成的ndarray,可以用ndim方法来检查维度,会返回一个数字,用shape方法来检查各位维度的大小,会返回一个元组。dtype方法会返回存储数据的数据类型。
arr2.ndim
#2
arr2.shape
#(2,4)
arr2.dtype
#dtype('int32')
arr1.dtype
#dtype('float64')
除了np.array,还有一些函数可以创建出一些独特的数组,类似于,ones可以一次性创造全1数组,zeros创造全0数组,empty创建一个没有初始化数值的数组。创建高维数组时,需要为这些函数的shape参量传递一个元组。
虽然看似empty和zeros一样都是生成全0数组,但时empty创建全0数组并不安全,有时候会返回未初始化的垃圾数值。
arange是range的数组版,返回一个ndarray:
np.arange(20)
# array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
# 17, 18, 19])
值得注意的是,除了以上提到的函数,zeros_like、ones_like等加上后缀的函数,可以用来创建一个和所给数组形状一样的全0或1数组。eye则是生成N*N的特征矩阵,对角线都为1,其余为0。
1.2 ndarray的数据类型
数据类型是用来申明内存块信息的。dtype使得NumPy可以方便的与其他系统数据进行灵活的交互。具体在这里暂时不做介绍。
通过dtype的关键字,可以在array函数生成时主动地确定数据类型,astype方法也可以显式转换数组的数据类型。
1.3 NumPy数组算术
数组之所以重要就是它允许进行批量操作而且不用任何的for循环,这样的一种特征叫做向量化。 常见的数组运算方法有*** - / ** >**。
1.4基础索引与切片
arr = np.arange(10)
arr
#array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5]
#5
arr[5:8]
#array([5, 6, 7])
arr[5:8]=[12,11,10]
arr
#array([ 0, 1, 2, 3, 4, 12, 11, 10, 8, 9])
arr_slice=arr[5:8]
arr_slice
#array([12, 11, 10])
arr_slice[1]=44
arr
#array([ 0, 1, 2, 3, 4, 12, 44, 10, 8, 9])
正如上述代码所见,改变arr_slice,变化也会体现在原数组上。直接等于的话就是数组切片的试图,而如果想要获得原数组切片的拷贝的话,可以调用copy这个方法。
arr2d=np.array([[1,2,3],[4,5,6],[7,8,9]])
arr2d[2]
#array([7, 8, 9])
arr2d[0][2]
#3
arr2d[0,2]
#3
1.4.1数组的切片索引
和Python列表的一维对象类似,数组可以通过类似的语法进行切片。唯一特殊一点的情况就是,ndarray数组会有多维,就会出现一些特殊的情况。
arr2d
#array([[1, 2, 3],
# [4, 5, 6],
# [7, 8, 9]])
arr2d[:2]
#array([[1, 2, 3],
# [4, 5, 6]])
arr2d[:2,1:2]
#array([[2],
# [5]])
同样的对于这种多维的数组,可以将切片和索引结合起来。
对于切片表达式赋值时,整个切片都会重新赋值。
1.5布尔索引
即在索引数组时可以传入布尔值数组,但是值得注意的是,布尔值选择数据的方法就算在布尔值数组和数组长度不一致时也不会报错,所以要注意有没有错误。
同样的,要取反的时候,就用!=或者在表达式前加上~。
要多选时,需要使用布尔运算符&(and)和|(or)。
1.6神奇索引
为选出一个符合特定顺序的子集,可以简单地通过传递一个包含指明所需顺序的列表或数组来完成。如果是负的索引,将从尾部进行选择。
arr=np.empty((8,4))
for i in range(8):
arr[i]=i
arr
#array([[0., 0., 0., 0.],
# [1., 1., 1., 1.],
# [2., 2., 2., 2.],
# [3., 3., 3., 3.],
# [4., 4., 4., 4.],
# [5., 5., 5., 5.],
# [6., 6., 6., 6.],
# [7., 7., 7., 7.]])
arr[[4,6,0]]
#array([[4., 4., 4., 4.],
# [6., 6., 6., 6.],
# [0., 0., 0., 0.]])
arr=np.arange(32).reshape(8,4)
arr
#array([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19],
# [20, 21, 22, 23],
# [24, 25, 26, 27],
# [28, 29, 30, 31]])
arr[[1,5,7,2],[0,3,1,2]]
#array([ 4, 23, 29, 10])
1.7数组的转置和换轴
转置操作即为线性代数中的转置,很容易理解。在数组中可以通过transpose或者T属性实现。
在此基础上,可以方便的实现内积的操作,在内积时常常会使用np.dot。
arr=np.random.randn(5,3)
arr
#array([[ 0.33189443, 0.2602446 , 0.18628936],
# [-0.47716025, 1.56646311, -0.45902414],
# [-0.28233402, 1.14226371, 1.02847283],
# [ 2.12918906, 2.46841164, -2.49623598],
# [ 0.79750251, -0.54452314, -0.50174922]])
np.dot(arr.T,arr)
#array([[ 5.58700463, 3.83787639, -5.72462099],
# [ 3.83787639, 10.21586179, -5.38430027],
# [-5.72462099, -5.38430027, 7.78610959]])
对于更高维度的数组,transpose方法可以接受包含轴编号的元组,用于置换轴。
ndarray还有一个swapaxes方法,该方法接受一堆轴编号作为参数,并对轴进行调整用于重组数据。
2通用函数:快速的逐元素数组函数
通用函数又叫做ufunc,是一种在ndarray数据中进行逐元素操作的函数。某些简单函数接受一个或者多个标量数值,并产生一个或多个标量结果,而通用函数就是这些简单函数的向量化封装。
有很多简单ufunc是简单的逐元素转换,比如sqrt或者exp函数。np.sqrt(arr)
、np.exp(arr)
np.maximum(x,y)
用于计算x和y中各元素中的最大值。
除了以上这种返回单个数组的函数,还有一些能返回多个数组,比如np.modf(arr)
可以用来返回浮点值数组的小数部分和整数部分。
arr=np.random.randn(7)*5
arr
#array([-6.96942889, 12.23263409, 5.32081984, 1.74423571, 2.77531764,
# -5.50555805, -7.164958 ])
remainder,whole_part=np.modf(arr)
remainder
#array([-0.96942889, 0.23263409, 0.32081984, 0.74423571, 0.77531764,
# -0.50555805, -0.164958 ])
whole_part
#array([-6., 12., 5., 1., 2., -5., -7.])
通用函数还可以接受一个参数out,允许对数组按位置操作。
np.sqrt(arr,arr)
#array([ nan, 1.87016531, 1.51877919, 1.14921502, 1.29070851,
# nan, nan])
以下为常用的一元通用函数
abs、fabs | 逐元素地计算整数,浮点数或者复数地绝对值 |
---|---|
sqrt | 计算每个元素地平方根(于arr**0.5)相等 |
square | 计算每个元素的平方 |
exp | 计算每个元素的自然指数值 e x e^x ex |
log、log10、log2、log1p | 分别计算:自然对数,对数以10为底,以2为底,log(1+x) |
sign | 计算每个元素的符号值,1(正)、0(0)、-1(负) |
ceil | 计算每个元素的最高整数,即向上取整 |
floor | 计算每个元素的最小整数值,即向下取整 |
rint | 将元素保留到整数位,并保持dtype |
modf | 分别将数组的每个元素的小数和整数部分按数组形式返回 |
isnan | 返回数组中的元素是否为一个NaN,输出形式为布尔值数组 |
isfinite、isinf | 返回数组中的元素是否有限、是否无限。 |
cos、cosh、sin、sinh,tan····· | 三角和反三角函数 |
logicial_not | 对数组元素按位取反 |
以下为二元通用函数
add | 将数组的对应元素相加 |
---|---|
substract | 在第二个数组中,将第一个数组中包含的元素去除 |
mutiply | 将数组按对应元素相乘 |
divide、floor_divide | 除或整除 |
power | 将第二组元素作为第一组元素的幂次方 |
maximum、minimum、fmax、fmin | 逐个元素计算最大/最小值,fmax,fmin忽略NaN |
mod | 按元素求模(即求除法的余数) |
copysign | 将第一个数组的符号值改成第二组的符号值 |
greater,greater_equal,less、less_equal、equal、not_equal、logical_and、logical_or、logical_xor | 逐个元素进行比较,返回布尔值数组。与数学操作符(>,≥,<,≤,==,≠)效果一致,进行逐个元素的逻辑操作(&, |
3使用数组进行面向数组编程
使用NumPy数组可以使你利用简单的数组表达式完成多种数据操作任务,而无须写些大量循环。这种利用数组表达式来代替显示循环的方法,称为向量化。通常来说向量化的数组操作会比纯数组操作快上一到两个数量级。
3.1将条件逻辑作为数组操作
numpy.where
函数是三元表达式x if condition else y
xarr=np.array([1.1,1.2,1.3,1.4,1.5])
yarr=np.array([2.1,2.2,2.3,2.4,2.5])
cond=np.array([True,False,True,True,False])
result=[(x if c else y)for x,y,c in zip(xarr,yarr,cond)]
result
#[1.1, 2.2, 1.3, 1.4, 2.5]
这样的话,会出现多个问题,最明显的就是,在数组很大的时候,速度会很慢,其次,当数组是多维的时候,就无法奏效了。而使用np.where
就和方便了
result=np.where(cond,xarr,yarr)
result
#array([1.1, 2.2, 1.3, 1.4, 2.5])
3.2数学和统计方法
许多关于计算整个数组的统计值或者关于轴向数据的数学函数,可以作为数组类型的方法被调用。例如sum、mean和std函数。
arr=np.random.randn(5,4)
arr.mean()
np.mean(arr)
arr.sum()
像这样的mean、sum的函数可以接受一个可选参数axis,这个参数可以用于计算给定州向上的统计值。
cumsum就是用来计算累计和的,cumprod就是累乘结果。
sum | 沿着轴向计算所有元素的类和 |
---|---|
mean | 数学平均 |
std,var | 标准差和方差 |
min,max | 最大值,最小值 |
argmax,argmin | 最大或最小值所在位置 |
cumsum | 从第0个开始累加 |
cumprod | 从第0个开始累乘 |
3.3布尔值数组的方法
布尔值会被强制为1(True)和0(False),sum可以用来计算布尔值数组中的True个数。
arr=np.random.randn(100)
(arr>0).sum()
#58
还有两个非常有用的方法,any用来检查数组中是否至少有一个True,all用于检查是否每个值都为True。返回一个布尔值
3.4排序
和python内建的列表一样,NumPy数组也可以使用sort方法按位置排序:
arr=np.random.randn(7)
arr
#array([ 0.68978135, -0.09067604, -0.55088789, -0.2874031 , 2.09792107,
# 0.4953943 , -0.92822354])
arr.sort()
arr
#array([-0.92822354, -0.55088789, -0.2874031 , -0.09067604, 0.4953943 ,
# 0.68978135, 2.09792107])
多维数组的情况下,可以传递一个axis值,使其沿着轴向对每一个一维数据进行排序:
large_arr=np.random.randn(1000)
large_arr.sort()
large_arr[int(0.05*len(large_arr))]
#-1.690393689203918
3.5唯一值与其他集合逻辑
NumPy包含一些针对一维ndarray的基础集合操作。常用的方法是np.unique,返回的是数组中唯一值排序后形成的数组。
names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
np.unique(names)
#array(['Bob', 'Joe', 'Will'], dtype='<U4')
纯python的情况下就是。
sorted(set(names))
#['Bob','Joe','Will']
另一个函数,np.in1d,可以用来检查一个数组的值是否在另一个数组中,并返回一个布尔值。
values=np.array([6,0,0,3,5,6,2,5,7])
np.in1d(values,[2,3,6])
#array([ True, False, False, True, False, True, True, False, False])
unique | 计算x的唯一值并排序 |
---|---|
intersect1d(x,y) | 计算x和y的交集,并排序 |
union1d(x,y) | 计算并集,并排序 |
in1d(x,y) | 计算x的元素是否在y中,返回布尔值数组 |
setdiff1d(x,y) | 差集,在x中但不在y中的元素 |
setxor1d(x,y) | 异或集,在x或y中,但不属于x,y的交集的元素 |
4使用数组进行文件输入输出
NumPy可以在硬盘中将数据以文本或二进制文件的形式存入硬盘或者由硬盘载入,我在这里只讨论NumPy的内建二进制格式,因为大多数情况下会使用pandas或者其他工具来载入文本或表格型数据。
np.save
和np.load
是高效存取硬盘数据的两大工具函数,数组在默认情况下是以未压缩的格式进行存储,后缀名是.npy。
arr=np.arange(10)
arr2=np.arange(20)
np.save('some_array',arr)
np.load('some_array.npy')
np.savez('array_archive.npz',a=arr,b=arr2)
arch=np.load('array_archive.npz')
arch['a']
#array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
当载入.npz文件时,就可以获得一个字典型对象,并可以通过这个对象很方便地载入数组。
5线性代数
线性代数,比如矩阵乘法、分解、行列式等方阵数学,是所有数组类库的重要组成部分。但是和Matlab等其他语言相比,NumPy的线性代数中所不同的是*是矩阵的逐元素乘积,而不是矩阵的点乘积。因此NumPy的数组方法和numpy命名空间中都有个函数dot,用于矩阵乘。
x=np.array([[1,2,3],[4,5,6]])
y=np.array([[6,23],[-1,7],[8,9]])
x.dot(y)
#array([[ 28, 64],
# [ 67, 181]])
x.dot(y)
等价于np.dot(x,y)
一个二维数组和一个长度合适的一维数组之间的矩阵乘积,其结果是一个一维数组。
np.dot(x,np.ones(3))
#array([ 6., 15.])
x@np.ones(3)
#array([ 6., 15.])
特殊符号@也作为中缀操作符,用于点乘矩阵操作。
numpy.linalg拥有一个矩阵分解的标准函数集,以及其他常用函数,例如求逆和行列式求解。这些函数都是通过在MATLAB和R语言等其他语言使用的相同的行业标准线性代数库来实现的,例如BLAS、LAPACK或英特尔专有的MKL
from numpy.linalg import inv,qr
X=np.random.randn(5,5)
mat=X.T.dot(X)
inv(mat)
mat.dot(inv(mat))
q,r=qr(mat)
r
diag | 将一个方阵的对角元素作为一维数组返回,或者将一维数组转换成一个方阵,并且在非对角线上有零点 |
---|---|
dot | 矩阵点乘 |
trace | 计算对角元素和 |
det | 计算方阵的行列式 |
eig | 计算方阵的特征值和特征向量 |
inv | 计算方阵的逆矩阵 |
pinv | 计算矩阵的Moore-Penrose伪逆 |
qr | 计算QR分解 |
svd | 计算奇异值分解 |
solve | 求解x的线性系统Ax=b |
lstsq | 计算Ax=b的最小二乘解 |
6伪随机数的生成
numpy.ramdom模块填补了Python内建的random模块的不足,可以高效地生成多种概率分布下地完整样本值数组。例如,normal可以用来获得一个4*4的正态分布样本组:
samples=np.random.normal(size=(4,4))
samples
#array([[ 1.56176748, 0.48538943, -0.7808379 , 1.06432323],
# [ 2.11994955, -0.09619966, 1.51316118, 2.92043215],
# [ 0.95791275, 0.03980304, -0.67315202, 1.15299308],
# [-0.27907097, 0.4123277 , 1.50907855, -0.9758201 ]])
python的内建random模块,一次只能生成一个值。所以numpy的random模块在生成大型样本的时候比纯python快了一个数量级。
虽然叫做随机数,但实际上还是伪随机,这里不作过多的解释,感兴趣的可以自行搜索了解一下。我们可以通过np.random.seed来更改NumPy的随机数种子。
以下为random模块中的部分函数
seed | 向随机数生成器传递随机状态种子 |
---|---|
permutation | 返回一个序列的随机排列,或者返回一个乱序的整数范围序列 |
shuffle | 随机排列一个序列 |
rand | 从均匀分布中抽取样本 |
randint | 根据给定的由低到高的范围抽取随机整数 |
randn | 从均值0方差1的正态分布中抽取样本(Matlab型接口) |
binomial | 从二项分布中抽取样本 |
normal | 从正态分布中抽取样本 |
beta | 从beta分布中抽取样本 |
chisquare | 从卡方分布中抽取样本 |
gamma | 从伽马分布中抽取样本 |
uniform | 从均匀分布[0,1)中抽取样本 |
7随机漫步
nsteps=1000
draws=np.random.randint(0,2,size=nsteps)
steps=np.where(draws>0,1,-1)
walk=steps.cumsum()
print((np.abs(walk)>=10).argmax())