机器学习系列(四)——Numpy中的矩阵运算与索引
效率对比
给定向量a=[0,1,2,3,...,9],让其中每一个元素乘2得到[0,2,4,6,...,18]。
n=100000
L=[i for i in range(L)]
2*L
out:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
可以发现python原生list不支持这种整体操作。当然可以像下面这样来达成目的:
A=[]
for e in L:
A.append(2*e)
A
out:[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
但是,这样做效率其实是很低的,低于生成表达式效率
#时间效率测试
%%time
A=[]
for e in L:
A.append(2*e)
out:CPU times: user 167 ms, sys: 8 ms, total: 175 ms
Wall time: 174 ms
%%time
A=[2*e for e in L]
out:CPU times: user 66.3 ms, sys: 20.2 ms, total: 86.5 ms
Wall time: 85.3 ms
如果使用numpy.array方式,效率将更强:
%%time
A=np.array(2*e for e in L)
out:CPU times: user 4.28 ms, sys: 7.84 ms, total: 12.1 ms
Wall time: 12 ms
可以看到速度大大提高。而且numpy支持直接的整体操作速度又要更快:
L=np.array([1,2,3,4])
2*L
out:array([2, 4, 6, 8])
numpy将数组看成向量或矩阵,而且加入了非常好的优化,运行速度非常非常快,和python原生list不在一个数量级。
Universasl Function
x=np.arange(6).reshape((2,3))
x
array([[0, 1, 2],
[3, 4, 5]])
××××××××××××××××××××××××××××××××××××
x*2#运算是对其中每个元素的操作
array([[ 0, 2, 4],
[ 6, 8, 10]])
x+1#每个元素加1
array([[1, 2, 3],
[4, 5, 6]])
x/2#每个元素除以2
array([[0. , 0.5, 1. ],
[1.5, 2. , 2.5]])
x//2#除以2取整
array([[0, 0, 1],
[1, 2, 2]])
x**2#二次方
array([[ 0, 1, 4],
[ 9, 16, 25]])
x%2#每个元素取余
array([[0, 1, 0],
[1, 0, 1]])
1/x#取倒数
np.cos(x)
np.arctan(x)
np.exp(x)
np.power(3,x)#等价于3**x
np.log(x+0.001)#默认以e为底,log2,log10
以上是对一个矩阵的操作,numpy还支持矩阵与矩阵之间的运算:
A=np.arange(4).reshape(2,2)
B=np.full((2,2),3)
A*B#相乘是对应元素相乘,类似有相加是对应元素相加
out:array([[0, 3],
[6, 9]])
如果要进行矩阵乘法,用dot方法:
A.dot(B)
out:array([[ 3, 3],
[15, 15]])
v.dot(A)
array([4, 7])
在向量与矩阵做乘法时,系统会自动判断向量处理为列向量还是行向量,A.dot(v)一样能运行。
A的转置:A.T
A的逆矩阵:np.linalg.inv(A)
非方阵可以求广义逆矩阵:np.linalg.pinv(B)
矩阵与向量运算
向量与矩阵相加默认是向量与矩阵每一行相加,相乘是与每行做乘法。
A
out:array([[0, 1],
[2, 3]])
v=np.array([1,2])
v+A
out:array([[1, 3],
[3, 5]])
v*A
out:array([[0, 2],
[2, 6]])
上述方式往往不太直观,可以用vstack方法对v处理后再相加,效果一样:
v1=np.vstack([v]*A.shape[0])
out:array([[1, 2],
[1, 2]])
v1+A
out:array([[1, 3],
[3, 5]])
对于堆叠,有一个tile方法可以更方便进行,如对v在行上堆叠两次,列上堆叠一次:
np.tile(v,(2,1))
out:array([[1, 2],
[1, 2]])
聚合运算
聚合运算是一组值处理为一个值的过程,如求和。
L=np.random.random(10)
sum(L)#python原生方法
out:5.379448046210108
np.sum(L)#numpy方法
out:5.379448046210108
两种求和差别在效率,np.sum效率远远超高sum。可用%time检测。
相应有:
np.max(L)#这种方式更全,建议使用
np.min(L)
#也可以这样调用
L.max()
L.min()
L.sum()
如果是二维矩阵,返回的仍然是一个值,如果要求每行的和或者每行的最大值,则需要指定axis:
A=np.arange(9).reshape(3,3)
out:array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
np.sum(A)
out:36
np.sum(A,axis=0)#axis=0,按行运算即沿着行进行运算,求得的是列和
out:array([ 9, 12, 15])
np.sum(A,axis=1)
out:array([ 3, 12, 21])#axis=1,按列运算即沿着列进行运算,求得的是对应的行和
下面这些聚合运算同样可以指定axis:
返回矩阵中所有元素乘积:np.prod(A)
返回均值:np.mean(A)
返回中位数:np.median(A)
返回方差:np.var(A)
返回标准差:np.std(A)
返回百分位分位点:np.percentile(L,q=50)
50%分位数
for p in [25,50,75]:
print(np.percentile(A,q=p))
25%分位点:2.0
50%:4.0
75%:6.0
索引——arg运算
我们可以轻易求得向量中的最小值,但有时我们希望得到这个最小值的索引。
x=np.array([2,1,7,4,6,5,9,12,3])
np.max(x)
out:12
np.argmax(x)
out:7
排序中使用索引
x=np.arange(16)
np.random.shuffle(x)#对x乱序排列,会改变x本身
x
out:array([10, 13, 0, 14, 12, 15, 8, 9, 1, 7, 2, 5, 11, 6, 4, 3])
np.sort(x)#不改变x本身
out:array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
如果希望直接改变x本身,则x.sort()
即可。
如果对二维矩阵进行排序,默认是对每一行排序,默认axis为1:
m=np.random.randint(10,size=(4,4))
m
out:array([[2, 2, 1, 2],
[7, 6, 0, 5],
[5, 3, 9, 0],
[3, 7, 1, 0]])
np.sort(m)
out:array([[1, 2, 2, 2],
[0, 5, 6, 7],
[0, 3, 5, 9],
[0, 1, 3, 7]])
np.sort(m,axis=0)
out:array([[2, 2, 0, 0],
[3, 3, 1, 0],
[5, 6, 1, 2],
[7, 7, 9, 5]])
接着讨论对于向量x,排序后返回排序索引:
np.argsort(x)
out:array([ 2, 8, 10, 15, 14, 11, 13, 9, 6, 7, 0, 12, 4, 1, 3, 5])
·快速排序中有个partition,小于标定点的放左边,大于的放右边,也有np.argpartition方法求索引,只是用的比较少。
np.partition(x,9)
out:array([ 7, 3, 0, 4, 6, 5, 8, 2, 1, 9, 13, 15, 11, 12, 14, 10])
Fancy Indexing
指定索引下标后开始索引
x=np.arange(16)
out:array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
[x[3],x[5],x[8]]
out:[3, 5, 8]
ind=[3,5,8]
x[ind]
out:array([3, 5, 8])
如果索引指定为二维,则会返回按索引排列的二维矩阵:
ind=np.array([[0,2],[1,3]])
x[ind]
out:array([[0, 2],
[1, 3]])
同样在二维矩阵中也可以有一些神奇的索引:
m=x.reshape(4,-1)#不改变原数组
m
out:array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
现在对一些数据感兴趣,但是他们没有规律:
row=np.array([0,1,2])#(0,1)(1,2)(2,3)
col=np.array([1,2,3])
m[row,col]
out:array([ 1, 6, 11])
m[0,col]#第0行的列
out:array([1, 2, 3])
col=[True,False,True,True]#对0,2,3行感兴趣
m[1:3,col]#对中间两列的col感兴趣
out:array([[ 4, 6, 7],
[ 8, 10, 11]])
numpy.array比较得bool值
x
out:array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
x<3
out:array([ True, True, True, False, False, False, False, False, False,
False, False, False, False, False, False, False])
x==3
out:array([False, False, False, True, False, False, False, False,
False,False, False, False, False, False, False, False])
x!=3
out:array([ True, True, True, False, True, True, True, True, True,
True, True, True, True, True, True, True])
可以结合加减乘除获得更复杂表达式,也可以在二维矩阵使用:
2*x==24-4*x
out:array([False, False, False, False, True, False, False, False,
False,False, False, False, False, False, False, False])
假如x是年龄样本,求问有多少个年龄小于3的孩子:
np.sum(x<=3)#这种方式将true当作1,false当作0
out:4
数非零的元素个数:np.count_nonzero(x<=3)
将true当作1,false当作0
查看是否有为0的元素:np.any(x==0)
查看是否有小于0的元素:np.any(x<0)
相应有:np.all(x>=0)
必须全部>=0才会返回True
所有以上方法对二维矩阵也适用。
np.sum(m%2==0) #求m中偶数的个数,默认全局,可以指定axis求每行或每列
out:8
np.sum(m%2==0,axis=0)#按行,即看每列有多少偶数
out:array([4, 0, 4, 0])
np.all(m>0,axis=1)
out:array([False, True, True, True])#第0行不满足每个元素都大于0
对以上比较也可以进行组合,如求x中>3,<10的个数:
np.sum((x>3)&(x<10))#这里必须使用位运算符
out:6
求年龄为偶数或者大于10的个数
np.sum((x%2==0)|(x>10))
out:11
求不等于0的
np.sum(~(x==0))#等价于np.sum(x!=0)
out:15
下面看索引的用法,返回x中小于5的数组成的子数组:
x[x<5]
out:array([0, 1, 2, 3, 4])
返回年龄是偶数的:
x[x%2==0]
out:array([ 0, 2, 4, 6, 8, 10, 12, 14])
返回矩阵m中最后一个元素能被3整除的行:
m[m[:,3]%3==0,:]
out:array([[ 0, 1, 2, 3],
[12, 13, 14, 15]])
这些方法在实际处理数据时有非常大的用处,比如boston房价数剧集,取出室内面积大于50的行,去除距离中心小于100的行等。
另外有一个pandas库可对数据进行预处理。以后有机会会更新在笔者简书中。