当我们在处理数据的时候,我们经常会使用到一维数组、二维数组或者多维数组来放置我们的数据,那么我们有哪些可供选择的数据结构呢?我们就先来了解一下为啥要使用numpy。
(1)python自带的list是一个选择,简单灵活,快速上手;而且列表元素类型是任意的,通用性好。但在处理多维数组时,底层运算复杂,耗时较长;相对Numpy来说它的存储效率和输入输出性能远远不及。
(2)python的标注库array模块,是一种高效的数组存储类型,它和list相似,但是所有的数组成员必须是同一种类型,即创建数组就确定了数组的类型。虽然这个限定降低了array的灵活性,但运行效率非常高;还有一个缺点就是array没有将数据看成是向量或者矩阵,也没有配备相应的向量或者矩阵的运算;
(3)Numpy.array专门针对array的缺点进行了改进,不仅支持多维矩阵,而且提供了相应的向量或者矩阵的运算。非常适合机器学习的数值计算。
首先将三种类型的数组创建进行对比:
#python自带的list
%timeit L=[i**2 for i in range(1000)]
>>417 µs ± 14.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
#python的array
import array
%timeit arr=array.array('i',[i for i in range(1000)])
>>91.4 µs ± 1.88 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
#numpy的array
import numpy as np
%timeit narr = np.arange(1000)
>>1.99 µs ± 342 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
从每轮的运行速度来看显然Numpy的array毫无悬念的胜出,当然这种比较实验应该要取多次的均值进行对比,这次比较是三种类型的一维数组创建所花费的时间对比,我们接下来进行以下运算的时间对比。
简单的数组运算进行对比:
#python自带的list
L1=[i**2 for i in range(1000)]
L2=[i**2 for i in range(1000)]
%timeit L = L1+L2
>>4.98 µs ± 40.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
import array
arr1=array.array('i',[i for i in range(1000)])
arr2=array.array('i',[i for i in range(1000)])
%timeit arr = arr1 + arr2
>>381 ns ± 6.62 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
import numpy as np
narr1 = np.arange(1000)
narr2 = np.arange(1000)
%timeit narr = narr1 + narr2
>>1.58 µs ± 183 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
这次呢array模块胜出,由于list仅支持两个数组之间的加法运算,所以只进行了加法运算的时间对比。当然已经没有继续对比下去的必要了,因为三者之间都是各有优缺点的,而在我们机器学习中会大量使用多维矩阵以及向量和矩阵等之间的运算。我们言归正传,回到numpy.array的使用中来。
np.array类中常用的只有两个参数,Object(传入值)、dtype(传入类型),以下所有方法都可以传入这两个参数,达到想要的效果。
import numpy as np
#1.传入的Object参数为列表,得到一维数组
np.array([2,3,4])
>>array([2, 3, 4])
#2.传入的Object参数为嵌套元组或嵌套列表的列表,得到二维数组
np.array([(1.5,2,3), (4,5,6)])
>>array([[ 1.5, 2. , 3. ],
[ 4. , 5. , 6. ]])
#3.可指定数组的数据类型,即指定dtype的值(int、float...)
np.array( [ [1,2], [3,4] ], dtype=complex )
>>array([[ 1.+0.j, 2.+0.j],
[ 3.+0.j, 4.+0.j]])
#4.调用array模块的zeros函数生成全为0的数组,传入元组更改shape
np.zeros(shape=(3,4))
>>array([[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.]])
#5.调用array模块的ones函数生成全为1的数组
np.ones( (2,3,4))
>>array([[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]],
[[ 1, 1, 1, 1],
[ 1, 1, 1, 1],
[ 1, 1, 1, 1]]])
#6.调用array模块的empty函数生成全为空的数组
np.empty( (2,2) )
>>array([[ 3.73603959e-262, 6.02658058e-154],
[ 5.30498948e-313, 3.14673309e-307]])
#7.调用array模块的full函数生成全为你指定值的数组
np.full(shape=(3,5),fill_value=666.0)
>>array([[666., 666., 666., 666., 666.],
[666., 666., 666., 666., 666.],
[666., 666., 666., 666., 666.]])
#8.调用array模块的arange函数自动生成指定范围的数值
'''调用reshape函数可更改数组形状(创建数组时皆可用)'''
np.arange(15).reshape(3, 5)
>>array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
np.arange( 10, 30, 5 )#可以指定相邻数之间的距离
>>array([10, 15, 20, 25])
np.arange( 0, 2, 0.3 )#可以指定相邻数之间的距离为小数,但有时无法预测获得的元素数量
array([ 0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
#9.调用array模块的linspace函数自动生成指定范围的数值,可以指定该范围内等值切分的数目
np.linspace( 0, 2, 9 )
>>array([ 0. , 0.25, 0.5, 0.75, 1., 1.25, 1.5, 1.75, 2.])
#10.调用array模块的random函数
#10.1使用random函数中的randint,生成整数类型的数组
np.random.randint(0,10)#随机生成0-10内的一个数
>7
np.random.randint(0,10,5)#随机生成0-10内的5个数
>array([3, 0, 2, 8, 3])
np.random.randint(0,10,size=(3,4))
>array([[5, 9, 3, 8],
[6, 4, 9, 2],
[8, 1, 8, 2]])
#当然也可以指定随机种子,每次生成一样的随机数
#10.1使用random函数中的normal,生成指定均值、方差的数值类型数组
np.random.normal(0,1)#返回一个满足均值为0,方差为1的一个数
>-0.3594293107178017
np.random.normal(0,1,5)#返回一个均值为0,方差为1的5个数
>array([-0.39801589, 0.9165711 , -0.1142665 , 0.129576 , 1.63568466])
np.random.normal(0,2,size=(3,4))#返回一个均值为0,方差为2的3*4的矩阵
>array([[ 0.04922377, 2.6061568 , 2.07202205, 4.58360629],
[-2.14488929, -3.60351148, -1.27428809, -3.08561304],
[-1.16596335, -1.9297661 , -2.51929358, -1.68002885]])
基本上以上的操作囊括了numpy数组的创建方式,如果想查看更多的创建方式,可以使用help函数查找。
小结:
自己有数据,就用np.array(自己的数据)
自动生成数据,就用np.zeros[ones[full(指定范围、类型)]]、np.arange[linspace(指定范围、类型)]、
np.random.randint[normal()]
我们学习机器学习算法的时候可多采用生成数据+噪音的方式进行实践,处理项目数据时使用数据转换方式。
a = np.arange(10)
b = np.arange(10).reshape(2,5)
#索引,操作同list的
a[5]#一维访问单个元素
>5
b[1]
>array([5, 6, 7, 8, 9])
b[0][3]#多维访问
>3
b[0,3]#多维更推荐方式,比较方便,而且切片不会出错
>3
#切片
a[:5]#得到a数组从索引0到索引4的数组
>array([0, 1, 2, 3, 4])
b[:2,:2]
>array([[0, 1],
[5, 6]])
#切片之后更改值,依然会影响原始矩阵
temp=a[:5]
temp[2]=100 #更改第三个元素的值
a
>array([ 0, 1, 100, 3, 4, 5, 6, 7, 8, 9])
temp
>array([ 0, 1, 100, 3, 4])
#解决方法是
temp=a[:5].copy()
小结:
尽量使用b[0,3]这种方式访问多维数组,可以增加对切片的理解。
#调用concatenate将两个一维向量进行拼接
a = np.arange(1,5)
b = np.arange(10,15)
np.concatenate([a,b])
>array([ 1, 2, 3, 4, 10, 11, 12, 13, 14])
#二维数组拼接
A = np.arange(10).reshape(2,5)
B = np.arange(10,20).reshape(2,5)
np.concatenate([A,B])
>array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]])
np.concatenate([A,B],axis=1) # axis取值0或者1拼接位置不同
>array([[ 0, 1, 2, 3, 4, 10, 11, 12, 13, 14],
[ 5, 6, 7, 8, 9, 15, 16, 17, 18, 19]])
#一维向量与二维数组拼接
a = np.arange(5)
A = np.arange(10).reshape(2,5)
np.concatenate([A,a]) #会报错
>ValueError: all the input arrays must have same number of dimensions, but the array at index 0 has 2 dimension(s) and the array at index 1 has 1 dimension(s)
#解决办法一:将一维向量转为一维数组
np.concatenate([A,a.reshape(1,-1)])
>array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[0, 1, 2, 3, 4]])
#解决办法二:直接调用vstack方法
'''必须有相同的列数'''
np.vstack([A,a])
>array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[0, 1, 2, 3, 4]])
np.vstack([A,a]).shape
>(3, 5)
#还有一种hstack方法
'''必须有相同的行数'''
A = np.arange(10).reshape(2,5)
B = np.full((2,2),100)
np.hstack([A,B])
>array([[ 0, 1, 2, 3, 4, 100, 100],
[ 5, 6, 7, 8, 9, 100, 100]])
np.hstack([A,B]).shape
>(2, 7)
小结:
相信大家已经发现了,数组之间的合并难点就是跨维度的合并,需要对一维向量进行变型,并且要掌握vstack和hstack的区别,可以这样理解,v是vertical垂直的意思,vstack在垂直方向上叠加,即增加行数;h是horizen水平的意思,hstack在水平方向上叠加,即增加列数。
#一维向量分割成三部分(两部分,传入的列表只填写一个数)
x = np.arange(10)
>array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
x1,x2,x3 = np.split(x,[3,7])
x1
>array([0, 1, 2])
x2
>array([3, 4, 5, 6])
x3
>array([7, 8, 9])
#二维数组分割
A = np.arange(16).reshape(4,4)#默认垂直方向切割,即分割行
A1,A2 = np.split(A,[2])
A1
>array([[0, 1, 2, 3],
[4, 5, 6, 7]])
A2
>array([[ 8, 9, 10, 11],
[12, 13, 14, 15]])
A1,A2 = np.split(A,[2],axis=1)#以水平方向切割,即分割列
A1
>array([[ 0, 1],
[ 4, 5],
[ 8, 9],
[12, 13]])
#分割除了使用np.split()方法,还有vsplit和hsplit方法
upper,lower = np.vsplit(A,[2])
upper
>array([[0, 1, 2, 3],
[4, 5, 6, 7]])
X,y = np.hsplit(A,[-1])#机器学习数据预处理可能会用
X
>array([[ 0, 1, 2],
[ 4, 5, 6],
[ 8, 9, 10],
[12, 13, 14]])
y
>array([[ 3],
[ 7],
[11],
[15]])
小结:
分割操作和合并操作类似,
合并使用np.concatenate和np.vstack、np.hstack;
分割使用np.split()、np.vsplit、np.hsplit.
然后就是要注意传入的参数
首先是单个矩阵的简单运算
X = np.arange(20).reshape(4,5)
X * 2
>array([[ 0, 2, 4, 6, 8],
[10, 12, 14, 16, 18],
[20, 22, 24, 26, 28],
[30, 32, 34, 36, 38]])
X / 2
>array([[0. , 0.5, 1. , 1.5, 2. ],
[2.5, 3. , 3.5, 4. , 4.5],
[5. , 5.5, 6. , 6.5, 7. ],
[7.5, 8. , 8.5, 9. , 9.5]])
X // 2
>array([[0, 0, 1, 1, 2],
[2, 3, 3, 4, 4],
[5, 5, 6, 6, 7],
[7, 8, 8, 9, 9]], dtype=int32)
X ** 2
>array([[ 0, 1, 4, 9, 16],
[ 25, 36, 49, 64, 81],
[100, 121, 144, 169, 196],
[225, 256, 289, 324, 361]], dtype=int32)
np.sin(X)
>array([[ 0. , 0.84147098, 0.90929743, 0.14112001, -0.7568025 ],
[-0.95892427, -0.2794155 , 0.6569866 , 0.98935825, 0.41211849],
[-0.54402111, -0.99999021, -0.53657292, 0.42016704, 0.99060736],
[ 0.65028784, -0.28790332, -0.96139749, -0.75098725, 0.14987721]])
np.tan(X)
>array([[ 0.00000000e+00, 1.55740772e+00, -2.18503986e+00,
-1.42546543e-01, 1.15782128e+00],
[-3.38051501e+00, -2.91006191e-01, 8.71447983e-01,
-6.79971146e+00, -4.52315659e-01],
[ 6.48360827e-01, -2.25950846e+02, -6.35859929e-01,
4.63021133e-01, 7.24460662e+00],
[-8.55993401e-01, 3.00632242e-01, 3.49391565e+00,
-1.13731371e+00, 1.51589471e-01]])
np.exp(X)
>array([[1.00000000e+00, 2.71828183e+00, 7.38905610e+00, 2.00855369e+01,
5.45981500e+01],
[1.48413159e+02, 4.03428793e+02, 1.09663316e+03, 2.98095799e+03,
8.10308393e+03],
[2.20264658e+04, 5.98741417e+04, 1.62754791e+05, 4.42413392e+05,
1.20260428e+06],
[3.26901737e+06, 8.88611052e+06, 2.41549528e+07, 6.56599691e+07,
1.78482301e+08]])
np.power(3,X)
>array([[ 1, 3, 9, 27, 81],
[ 243, 729, 2187, 6561, 19683],
[ 59049, 177147, 531441, 1594323, 4782969],
[ 14348907, 43046721, 129140163, 387420489, 1162261467]],
dtype=int32)
np.log(X)
>array([[ -inf, 0. , 0.69314718, 1.09861229, 1.38629436],
[1.60943791, 1.79175947, 1.94591015, 2.07944154, 2.19722458],
[2.30258509, 2.39789527, 2.48490665, 2.56494936, 2.63905733],
[2.7080502 , 2.77258872, 2.83321334, 2.89037176, 2.94443898]])
接下来是矩阵之间的运算
A = np.arange(4).reshape(2,2)
>array([[0, 1],
[2, 3]])
B = np.full((2,2),10)
>array([[10, 10],
[10, 10]])
A+B
>array([[10, 11],
[12, 13]])
A-B
>array([[-10, -9],
[ -8, -7]])
A*B
>array([[ 0, 10],
[20, 30]])
A/B
>array([[0. , 0.1],
[0.2, 0.3]])
#显然两个相同类型的矩阵之间的运算是对应位置的每个元素之间的运算
#矩阵相乘(即点乘)
A.dot(B)
>array([[10, 10],
[50, 50]])
#转置
A.T
>array([[0, 2],
[1, 3]])
显然矩阵之间进行点乘时,需要满足矩阵乘法规则,不然报错。
还有向量与矩阵的运算如下:
v = np.array([1,2])
v+A
>array([[1, 3],
[3, 5]])
#可以用其他形式达到一样的效果
np.vstack([v]*2) + A
>array([[1, 3],
[3, 5]])
np.tile(v,(2,1)) + A
>array([[1, 3],
[3, 5]])
v*A
>array([[0, 2],
[2, 6]])
v.dot(A)
>array([4, 7])
A.dot(v)
>array([2, 8])
最后还有矩阵的逆:
A
>array([[0, 1],
[2, 3]])
inv = np.linalg.inv(A)
>array([[-1.5, 0.5],
[ 1. , 0. ]])
A.dot(inv)
>array([[1., 0.],
[0., 1.]])
inv.dot(A)
>array([[1., 0.],
[0., 1.]])
X = np.arange(16).reshape(2,8)
>array([[ 0, 1, 2, 3, 4, 5, 6, 7],
[ 8, 9, 10, 11, 12, 13, 14, 15]])
X.dot(v)
>ValueError: shapes (2,8) and (2,) not aligned: 8 (dim 1) != 2 (dim 0)
#报错,维度不符合点乘规则
pinvX = np.linalg.pinv(X)
>array([[-1.35416667e-01, 5.20833333e-02],
[-1.01190476e-01, 4.16666667e-02],
[-6.69642857e-02, 3.12500000e-02],
[-3.27380952e-02, 2.08333333e-02],
[ 1.48809524e-03, 1.04166667e-02],
[ 3.57142857e-02, -1.04083409e-17],
[ 6.99404762e-02, -1.04166667e-02],
[ 1.04166667e-01, -2.08333333e-02]])
X.dot(pinvX)
>array([[ 1.00000000e+00, -2.49800181e-16],
[ 0.00000000e+00, 1.00000000e+00]])
小结:
单个矩阵与数值进行运算相当于矩阵中的每个元素进行运算,并且初中学过的基础数学函数都可以numpy.函数 的方式调用;
矩阵之间的乘法是调用 A.dot(B) 的形式进行的,还需要记住 A.T是矩阵的转置
矩阵的秩np.linalg.inv(A);
L = np.random.random(100)
sum(L)
>54.621329241060636
np.sum(L)
>54.62132924106065
# sum和numpy.sum的区别
big_array = np.random.random(1000000)
%timeit sum(big_array)
>175 ms ± 43.7 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit np.sum(big_array)
>1.4 ms ± 15.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
#其他聚合操作
np.min(big_array)
> 4.6084052063299907e-07
np.max(big_array)
>0.9999971435351365
#二维数组
X = np.arange(16).reshape(4,-1)
X
>array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
np.sum(X)
>10
np.sum(X,axis=1)#沿着列进行求和
>array([ 6, 22, 38, 54])
np.sum(X,axis=0)#沿着行进行求和
>array([24, 28, 32, 36])
np.prod(X)#X中每个元素的累积
>0
np.prod(X+1)
>2004189184
np.mean(X)#均值
>7.5
np.median(X)#中位数
>7.5
np.percentile(big_array,q=50)#百分之50的位置的值--中位数;但可以根据占比找分位点的值
>0.4991106444476129
np.var(big_array)#方差
>0.08326051007476702
np.std(big_array)#标准差
>0.2885489734425805
小结:
聚合操作就是引用numpy.函数()形式进行的,sum、max、min、mean、var、std函数较常用。
并且每个函数都是可以使用axis=0/1参数灵活的使用的。
X = np.arange(0,33,2)
>array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32])
index=[3,5,8]
X[index]
>array([6, 10, 16])
#索引矩阵还可以是二维的
index=np.array([[0,2],[1,3]])
X[index]
>array([[0, 4],
[2, 6]])
A = np.arange(16).reshape(4,-1)
>array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
row = np.array([0,1,2])
col = np.array([1,2,3])
A[row,col]
>array([ 1, 6, 11])
A[:2,col]
>array([[1, 2, 3],
[5, 6, 7]])
#还可以传入布尔矩阵
col = [True,False,True,True]
A[:,col]
>array([[ 0, 2, 3],
[ 4, 6, 7],
[ 8, 10, 11],
[12, 14, 15]])
#还可以进行矩阵比较,返回布尔类型的数组
A>3
>array([[False, False, False, False],
[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True]])
A!=3
>array([[ True, True, True, False],
[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True]])
2*A == 24 - 4*A
>array([[False, False, False, False],
[ True, False, False, False],
[False, False, False, False],
[False, False, False, False]])
#将聚合运算和矩阵比较相结合
np.sum(A<=3)
>4
np.count_nonzero(A<=3)
>4
#判断数组中所以元素是否都满足某一个条件
np.any(A==0)
>True
np.all(A>=0)
>True
#结合聚合运算的时候依然可以使用axis参数
np.sum(A%2 == 0 , axis=0)
>array([4, 0, 4, 0])
np.sum(A%2 == 0 , axis=1)
>array([2, 2, 2, 2])
np.all(A>=0,axis=1)
>array([ True, True, True, True])
#注意不是两个&&,而是位运算符&
np.sum((A>4)&(A<10))
>5
小结:
矩阵接受一个索引矩阵,然后返回所有索引对应值的矩阵;索引矩阵可以是整形数值的向量,也可以是矩阵,还可以布尔类型的向量或者矩阵;
矩阵可以和数值进行比较,并返回每个元素比较后的结果矩阵(布尔类型);
矩阵的数值比较还可以与聚合运算结合起来,可以方便快捷的找到矩阵中需要的值,非常灵活。
总结:
numpy的基础操作已经全部讲完了,虽然numpy具有非常强大的矩阵操作和各种灵活多变的运算,但是依然掩盖不了它只能接收单一的数值类型硬伤。所以下一篇介绍另外一种存储结构。