基础
NumPy提供的最主要的对象就是同数据类型的多维数组。他实际上可以被理解成一张表(元素通常是数组),所有元素具有相同的类型,这些元素可以使用由正整数构建的元组(tuple)来索引。在NumPy中,各个维度被称为轴(axes)。轴的总数被称为秩(rank)。
举例来说,三维空间的一个点的坐标是[1,2,3],它就是一个rank是1的一个数组。因为它只要䘝axis,而这个axis的长度是3.
再来看一个例子:
[[1.,0.,0.],
[0.,1.,2.]]
该数组的rank是2(两个维度),第一个维度的长度是2,第二个维度的长度是3。
NumPy的数组类叫做ndarray。同时也有个别名array。这里需要注意一下,numpy.array和Python标准库的array.array完全不是一个东西,后者仅仅支持一维数组且提供的功能较少。ndarray对象比较重要的属性有:
ndarray.ndim
数组的axes的数目。
ndarray.shape
数组的形状。如一个n * m的矩阵,它的shape就是(n,m)。而且len(ndarray.shape)==ndarray.ndim
ndarray.size
数组中所有元素的个数。它实际上是ndarray.shape中所有值的乘积。
ndarray.dtype
用于描述数组中元素类型的对象。我们可以使用Python标准类型来创建或指定dtype。不过NumPy也提供了一些它自己的类型,比如:numpy.int32,numpy.int16,numpy.float64等。
ndarray.itemsize
数组中单个元素以字节计的大小。比如,若类型是float64,则itemsize就是8(64/8)。它限ndarray.dtype.itemsize是等价的。
ndarray.data
存储数组中实际的数据。通常我们不会直接用这个属性,因为当我们需要访问数据时几乎总是使用数组提供的索引功能。
一个例子
import numpy as np
a = np.arange(15).reshape(3,5)
a
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
a.shape
(3, 5)
a.ndim
2
a.dtype.name,a.itemsize
('int32', 4)
a.size
15
type(a)
numpy.ndarray
b = np.array([6,7,8])
b
array([6, 7, 8])
type(b)
numpy.ndarray
创建数组
有好几种方法可以创建数组。
比如:通过array,我们可以从普通的Python列表或者元组直接创建。元素的类型根据源类型推断。
a = np.array([2,3,4])
a
array([2, 3, 4])
a.dtype
dtype('int32')
b = np.array([1.2,3.5,5.1])
b.dtype
dtype('float64')
一个常见的错误是,把数组内容当做参数直接传给array,而不是作为一个列表或元组:
a = np.array(1,2,3,4) #错误
a = np.array([1,2,3,4]) #正确
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-16-f55a2f3e0f54> in <module>()
----> 1 a = np.array(1,2,3,4) #错误
2 a = np.array([1,2,3,4]) #正确
ValueError: only 2 non-keyword arguments accepted
array将两层嵌套序列转成二维数组,三层嵌套序列转成三维数组,以此类推:
b = np.array([(1.5,2,3),(4,5,6)])
b
array([[1.5, 2. , 3. ],
[4. , 5. , 6. ]])
数据类型在创建时可以指定:
c = np.array([[1,2],[3,4]],dtype=complex)
c
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j]])
在实际的工作中,我们常常需要固定形状大小的数组,但在定义数组时还没有加载数据。
NumPy为我们提供了一些包含占位符的数组创建函数。这样我们就可以避免使用动态数组,因为动态数组在增长的时候很耗时的。
- zeros函数可以创建所有元素均为0的数组;
- ones函数可以创建所有元素均为1的数组;
- empty函数创建的数组,其元素都是随机的!!!
默认情况下,创建时的dtype都是float64;
np.zeros((3,4)) #他的参数并不是数据,而是数组的形状(shape)
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
np.ones((2,3,4),dtype=np.int16) # 指定数据类型
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]]], dtype=int16)
np.empty((2,3)) # 一定要小心,元素都是随机的
array([[1.5, 2. , 3. ],
[4. , 5. , 6. ]])
NumPy提供了一个类似于Python标准库中range类似的函数np.arange来生成数字序列
np.arange(10,30,5) # start,stop,step
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])
不过因为浮点数的有限精度问题,arange返回的数组长度可能无法预知。因此,最好使用linspace,它可以指定所需要的数组长度,而不需要设定步长:
np.linspace(0,2,9)
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
from numpy import pi
x = np.linspace(0,2*pi,100)
f = np.sin(x)
f
array([ 0.00000000e+00, 6.34239197e-02, 1.26592454e-01, 1.89251244e-01,
2.51147987e-01, 3.12033446e-01, 3.71662456e-01, 4.29794912e-01,
4.86196736e-01, 5.40640817e-01, 5.92907929e-01, 6.42787610e-01,
6.90079011e-01, 7.34591709e-01, 7.76146464e-01, 8.14575952e-01,
8.49725430e-01, 8.81453363e-01, 9.09631995e-01, 9.34147860e-01,
9.54902241e-01, 9.71811568e-01, 9.84807753e-01, 9.93838464e-01,
9.98867339e-01, 9.99874128e-01, 9.96854776e-01, 9.89821442e-01,
9.78802446e-01, 9.63842159e-01, 9.45000819e-01, 9.22354294e-01,
8.95993774e-01, 8.66025404e-01, 8.32569855e-01, 7.95761841e-01,
7.55749574e-01, 7.12694171e-01, 6.66769001e-01, 6.18158986e-01,
5.67059864e-01, 5.13677392e-01, 4.58226522e-01, 4.00930535e-01,
3.42020143e-01, 2.81732557e-01, 2.20310533e-01, 1.58001396e-01,
9.50560433e-02, 3.17279335e-02, -3.17279335e-02, -9.50560433e-02,
-1.58001396e-01, -2.20310533e-01, -2.81732557e-01, -3.42020143e-01,
-4.00930535e-01, -4.58226522e-01, -5.13677392e-01, -5.67059864e-01,
-6.18158986e-01, -6.66769001e-01, -7.12694171e-01, -7.55749574e-01,
-7.95761841e-01, -8.32569855e-01, -8.66025404e-01, -8.95993774e-01,
-9.22354294e-01, -9.45000819e-01, -9.63842159e-01, -9.78802446e-01,
-9.89821442e-01, -9.96854776e-01, -9.99874128e-01, -9.98867339e-01,
-9.93838464e-01, -9.84807753e-01, -9.71811568e-01, -9.54902241e-01,
-9.34147860e-01, -9.09631995e-01, -8.81453363e-01, -8.49725430e-01,
-8.14575952e-01, -7.76146464e-01, -7.34591709e-01, -6.90079011e-01,
-6.42787610e-01, -5.92907929e-01, -5.40640817e-01, -4.86196736e-01,
-4.29794912e-01, -3.71662456e-01, -3.12033446e-01, -2.51147987e-01,
-1.89251244e-01, -1.26592454e-01, -6.34239197e-02, -2.44929360e-16])
作为补充,可以看看他们的文档:array,zeros,zero_like(生成一个和原矩阵相同形状的全是0的矩阵),numpy.random.rand,numpy.random.randn,fromfunction,fromfile
可以在notebook里面直接np.array?阅读,也可以去Numpy的官方文档。
打印数组
打印出来非常类似于嵌套函数
a = np.arange(6) # 一维
print(a)
[0 1 2 3 4 5]
b = np.arange(12).reshape(3,4) # 二维
print(b)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
c = np.arange(24).reshape(2,3,4) # 二维
print(c)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
如果数组太长,NumPy会自动忽略中间部分的数据,只打印首尾:
print(np.arange(10000))
[ 0 1 2 ... 9997 9998 9999]
print(np.arange(10000).reshape(100,100))
[[ 0 1 2 ... 97 98 99]
[ 100 101 102 ... 197 198 199]
[ 200 201 202 ... 297 298 299]
...
[9700 9701 9702 ... 9797 9798 9799]
[9800 9801 9802 ... 9897 9898 9899]
[9900 9901 9902 ... 9997 9998 9999]]
当然,我们可以通过设置set_printoptions选项来关闭该功能:
np.set_printoptions(threshold='nan)
基础操作
在数组上进行算术操作都是元素级别的操作
a = np.array([20,30,40,50])
b = np.arange(4)
c = a - b
c
array([20, 29, 38, 47])
b ** 2
array([0, 1, 4, 9], dtype=int32)
10 * np.sin(a)
array([ 9.12945251, -9.88031624, 7.4511316 , -2.62374854])
a < 45
array([ True, True, True, False])
在线性代数中,乘号 * 一般表达的是矩阵乘法,但在NumPy中它表示元素级别的乘法,矩阵乘法在NumPy中使用dot:
A = np.array([[1,1],
[0,1]])
B = np.array([[2,0],
[3,4]])
A * B #元素级别乘法
array([[2, 0],
[0, 4]])
A.dot(B)
array([[5, 4],
[3, 4]])
np.dot(A,B)
array([[5, 4],
[3, 4]])
上面的操作符都会新建一个数组,而有一些操作符则会直接修改现有数组,比如+=,*=
a = np.ones((2,3),dtype=int)
b = np.random.random((2,3))
a *= 3
a
array([[3, 3, 3],
[3, 3, 3]])
b += a
b
array([[3.96590659, 3.57016481, 3.16824186],
[3.7746882 , 3.05928896, 3.94018039]])
a += b # 类型转换失败,因为b是float,更加精确,而a是int
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-50-294cacd62d6f> in <module>()
----> 1 a += b
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int32') with casting rule 'same_kind'
ndarray类里内置了很多方法来完成一些一元操作符,比如计算所有元素之和:
a = np.random.random((2,3))
a
array([[0.80608285, 0.07392699, 0.49798093],
[0.69115847, 0.82903752, 0.63852812]])
print(a.sum(),a.min(),a.max())
18 3 3
默认情况下,这些方法不关心数组的形状,会将所有元素一起计算,不过可以通过制定axis(轴)来计算沿着该轴的数据:
b = np.arange(12).reshape(3,4)
b
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
b.sum(axis=0)
array([12, 15, 18, 21])
b.min(axis=1)
array([0, 4, 8])
b.cumsum(axis=1)
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]], dtype=int32)
通用函数
numpy提供了一些常用的数学函数,如:sin,cos,exp等,操作是作用于每个单独的元素上,并产生一个新的数组。
B = np.arange(3)
B
array([0, 1, 2])
np.exp(B)
array([1. , 2.71828183, 7.3890561 ])
np.sqrt(B)
array([0. , 1. , 1.41421356])
C = np.array([2.,-1,4.])
np.add(B,C)
array([2., 0., 6.])
索引,切片以及遍历
一维数组非常类似于python的序列,他们可以被索引,被切片,被遍历
a = np.arange(10) ** 3
a
array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729], dtype=int32)
a[2]
8
a[2:5]
array([ 8, 27, 64], dtype=int32)
a[:6:2] = -1000 # 等价于 a[0:6:2] = -1000 从开始到第6个(不含),以2为步长将元素设置为-1000
a
array([-1000, 1, -1000, 27, -1000, 125, 216, 343, 512,
729], dtype=int32)
a[::-1] #数组倒序
array([ 729, 512, 343, 216, 125, -1000, 27, -1000, 1,
-1000], dtype=int32)
for i in a:
print(i ** (1/3.0))
nan
1.0
nan
3.0
nan
5.0
5.999999999999999
6.999999999999999
7.999999999999999
8.999999999999998
C:\Users\ttc\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: RuntimeWarning: invalid value encountered in power
多维数组每一个轴均有一个索引,这些索引使用“,”分隔,以元组的形式表现:
def f(x,y):
return 10 * x + y
b = np.fromfunction(f,(5,4),dtype=int)
b
array([[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
b[2,3]
23
b[0:5,1]
array([ 1, 11, 21, 31, 41])
b[:,1]
array([ 1, 11, 21, 31, 41])
b[1:3,:]
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
当提供的索引不够,那么NumPy会自动补全其他轴的索引:
b[-1] # 等价于b[-1,:]
array([40, 41, 42, 43])
另外,NumPy还有...这种写法,它会自动判断所需要填补的索引:
比如,我们现在有一个拥有五个轴(axis)的数组:
- x[1,2,...]等价于 x[1,2,:,:,:]
- x[...,3]等价于x[:,:,:,:,3]
- x[4,...,5,:] 等价于x[4,:,:,5,:]
c = np.array([[[0,1,2],
[10,12,13]],
[[100,101,102],
[110,112,113]]])
c.shape
(2, 2, 3)
c[1,...]
array([[100, 101, 102],
[110, 112, 113]])
c[...,2]
array([[ 2, 13],
[102, 113]])
遍历从第一个轴开始的:
for row in b:
print(row)
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]
不过,要是想要将数组"打平"访问,可以使用flat属性:
for element in b.flat:
print(element,end=' ')
0 1 2 3 10 11 12 13 20 21 22 23 30 31 32 33 40 41 42 43
形状操作
修改一个数组的形状
a = np.floor(10 * np.random.random((3,4)))
a
array([[4., 9., 4., 0.],
[7., 1., 6., 6.],
[2., 7., 4., 0.]])
a.shape
(3, 4)
在NumPy中有多种方法可以改变一个数组的形状。
请注意,下面三种方法均返回一个修改后的数组,但又不改变原来的数组:
a.ravel() # 返回打平的数组
array([4., 9., 4., 0., 7., 1., 6., 6., 2., 7., 4., 0.])
a.reshape(6,2) # 返回目标形状的数组
array([[4., 9.],
[4., 0.],
[7., 1.],
[6., 6.],
[2., 7.],
[4., 0.]])
a.T # 返回它的转置
array([[4., 7., 2.],
[9., 1., 7.],
[4., 6., 4.],
[0., 6., 0.]])
由ravel()返回的数组,其元素的顺序默认是C语言风格的,即最右侧的索引“变化最快”,因此a[0,0]的下一个元素是a[0,1],如果传给ravel()的数组时从其他数组切片得到的,或者使用不寻常的选项构建的,那就不得不拷贝一次数据了。
ravel()和reshape()函数本身也接受一个可选的参数,可以使用Fortran风格(Fortran-style)的数组u,它最左侧的索引变化的最快,即a[0,0]的下一个元素是a[1,0]。
reshape函数不会改变原数组,而ndarray.resize方法则会改变原数组:
a
array([[4., 9., 4., 0.],
[7., 1., 6., 6.],
[2., 7., 4., 0.]])
a.resize((2,6))
a
array([[4., 9., 4., 0., 7., 1.],
[6., 6., 2., 7., 4., 0.]])
如果在reshape操作时将某一维度设置为-1,那么该维度就会被自动计算,比如:
a.reshape(3,-1) # 只在乎数组第一维是3,第二维让系统自动计算
array([[4., 9., 4., 0.],
[7., 1., 6., 6.],
[2., 7., 4., 0.]])
将不同的数组堆叠(stacking)起来
多个数组可以沿着不同的轴堆叠起来:
a = np.floor(10 * np.random.random((2,2)))
a
array([[4., 3.],
[5., 3.]])
b = np.floor(10 * np.random.random((2,2)))
b
array([[9., 7.],
[9., 3.]])
np.vstack((a,b)) # 垂直堆叠
array([[4., 3.],
[5., 3.],
[9., 7.],
[9., 3.]])
np.hstack((a,b)) # 水平堆叠
array([[4., 3., 9., 7.],
[5., 3., 9., 3.]])
column_stack可以将一维数组作为一列插入一个二维数组:
c = np.array([3,3])
np.column_stack((a,c))
array([[4., 3., 3.],
[5., 3., 3.]])
对于超过二维的情况,不讨论
将一个数组切分成多个
使用hsplit,我们可以将一个数组沿水平方向进行切分。切分时可以指定:
- 目标数组个数 ——将自动计算切分后的数组的形状
- 切分列下表 —— 将在指定的列下标处进行切分
a = np.floor(10 * np.random.random((2,12)))
a
array([[5., 4., 7., 6., 0., 6., 2., 8., 7., 1., 2., 5.],
[3., 9., 3., 0., 4., 1., 3., 1., 0., 9., 2., 1.]])
np.hsplit(a,3) # 将a横向切分成三个数组
[array([[5., 4., 7., 6.],
[3., 9., 3., 0.]]), array([[0., 6., 2., 8.],
[4., 1., 3., 1.]]), array([[7., 1., 2., 5.],
[0., 9., 2., 1.]])]
np.hsplit(a,(3,4)) # 在第三列和第四列进行切分,从1开始计数
[array([[5., 4., 7.],
[3., 9., 3.]]), array([[6.],
[0.]]), array([[0., 6., 2., 8., 7., 1., 2., 5.],
[4., 1., 3., 1., 0., 9., 2., 1.]])]
类似的,还有vsplit,而array_split则允许指定切分的轴
拷贝(copy)与视图(view)
当我们操作数组的时候,其数据有时候会被拷贝到一个新的数组,而有时又不会。这对于初学者而言常常是一个觉得很不清晰的地方。
实际上,一共有三种情况:
根本不拷贝
简单的赋值不会发生任何拷贝行为:
a = np.arange(12)
b = a # 不会新建对象
b is a # a和b是对同一个ndarray对象的两个名字
True
b.shape = 3,4 # 同时会修改a的形状
a.shape
(3, 4)
Python以引用的方式传递可变对象,所以函数调用不会产生拷贝:
def f(x):
print(id(x))
print(id(a)) # id是一个对象(实例)独一无二的标识符
f(a)
87464496
87464496
视图和浅拷贝
不同的数据对象可以共享相同的数据。view方法新建一个数组对象,但仍然使用相同的数据。
c = a.view()
c is a
False
c.base is a # c仅仅是a中数据的一个视图
True
c.flags.owndata # c不拥有自己的数据
False
c.shape = 2,6
a.shape
(3, 4)
c[0,4] = 1234 # a的数据会改变!!!
a
array([[ 0, 1, 2, 3],
[1234, 5, 6, 7],
[ 8, 9, 10, 11]])
c
array([[ 0, 1, 2, 3, 1234, 5],
[ 6, 7, 8, 9, 10, 11]])
对一个数组进行切片会返回它的视图:
s = a[:,1:3]
s[:] = 10
a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
深拷贝
copy方法会对数组及其数据做一次完整的拷贝。
d = a.copy() # 新建一个数组对象,而且把数据也拷贝了一份
d is a
False
d.base is a # d不从a共享任何东西
False
d[0,0] = 9999
a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
基础函数及方法总览
我们这里按照类目列出了一些最常用的NumPy的函数及方法。请参考routines以了解更全的列表
Array Creation
arange,array,copy ...
Conversions
ndarray.astype,atleast_1d ....
Mainpulations
array_split,column_stack ...
Questions
all,any,nonzero,where
Odering
argmax,argmin,argsort,max,min,ptp,searchsorted,sort
Operations
choose,compress,cumprod,cumsum,inner....
Basic Statistics
cov,mean,std,var
Basic Linear Algebra
cross,dot,outer,linalg.svd,vdot
广播机制
Numpy的Universal functions 中要求输入的数组shape是一致的,当数组的shape不想等的时候,则会使用广播机制,调整数组使得shape一样,满足规则,则可以运算,否则就出错
四条规则如下:
- 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分都通过在前面加1补齐
- 输出数组的shape是输入数组shape的各个轴上的最大值
- 如果输入数组的某个轴和输出数组的对应轴的长度相同或者其长度为1时,这个数组能够用来计算,否则出错
-
当输入数组的某个轴的长度为1时,沿着此轴运算时都用此轴上的第一组值
时髦索引(fancy indexing)及索引技巧
NumPy比一般的Python序列支持更多的索引功能。
在支持使用整型数字及切片来进行索引的基础上,数组还可以使用整形数据和布尔型数组来进行索引。
使用索引数组(array if indices)来进行索引
a = np.arange(12) ** 2 # 12个平方数
i = np.array([1,1,3,8,5]) # 一些索引
a[i]
array([ 1, 1, 9, 64, 25], dtype=int32)
j = np.array([[3,4],[9,7]]) # 一个二维的索引数组
a[j]
array([[ 9, 16],
[81, 49]], dtype=int32)
当数组a是多维数组的时候,一个单独的索引数组表达的是数组a的第一个维度。
下面这个例子定义了一个调色盘,然后图片以调色盘的索引来表示,它可以诠释上面那句话的含义:
palette = np.array([[0,0,0],[255,0,0],[0,255,0],[0,0,255],[255,255,255]])
image = np.array([[0,1,2,0],
[0,3,4,0]])
palette[image]
array([[[ 0, 0, 0],
[255, 0, 0],
[ 0, 255, 0],
[ 0, 0, 0]],
[[ 0, 0, 0],
[ 0, 0, 255],
[255, 255, 255],
[ 0, 0, 0]]])
我们也可以让索引有多个维度。不过,所有的索引数组在每一个维度上就必须有同样的形状:
a = np.arange(12).reshape(3,4)
a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
i = np.array([[0,1],
[1,2]]) # 数组a的第一个维度
j = np.array([[2,1],
[3,3]]) # 数组a的第二个维度
a[i,j] # i 和 j必须要有相同的形状 (0,2),(1,1),(1,3),(2,3)
array([[ 2, 5],
[ 7, 11]])
a[i,2]
array([[ 2, 6],
[ 6, 10]])
a[:,j]
array([[[ 2, 1],
[ 3, 3]],
[[ 6, 5],
[ 7, 7]],
[[10, 9],
[11, 11]]])
我们也可以把i和j打包到一个序列中,然后使用这个序列来进行索引:
l = [i,j]
a[l]
array([[ 2, 5],
[ 7, 11]])
但是得千万注意,不要把i和j放在一个数组(ndarray)里,因为这个数组表达的是以第一个维度来对数组a进行索引。
s = np.array([i,j])
a[s]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-135-537bf3252a3e> in <module>()
1 s = np.array([i,j])
----> 2 a[s]
IndexError: index 3 is out of bounds for axis 0 with size 3
a[tuple(s)] # 等价于a[i,j]
array([[ 2, 5],
[ 7, 11]])
另一个常用的使用数组进行索引的方式是:在一个时间依赖的序列中找到最大值:
time=np.linspace(20,145,5)
data = np.sin(np.arange(20)).reshape(5,4)
time
array([ 20. , 51.25, 82.5 , 113.75, 145. ])
data
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]])
ind = data.argmax(axis=0)
ind
array([2, 0, 3, 1], dtype=int64)
time[ind]
array([ 82.5 , 20. , 113.75, 51.25])
data_max = data[ind,range(data.shape[1])]
data_max
array([0.98935825, 0.84147098, 0.99060736, 0.6569866 ])
np.all(data.max(axis=0) == data_max) # 比较所有元素
True
使用索引数组不仅可以访问数组元素,还可以更改数组元素:
a = np.arange(5)
a
array([0, 1, 2, 3, 4])
a[[1,3,4]] = 0
a
array([0, 0, 2, 0, 0])
不过如果同一个索引在索引数组中出现多次的话,那么赋值操作实际上就会被多次执行:
a = np.arange(5)
a[[0,0,2]] = [1,2,3] # 0位置被赋值了两次
a
array([2, 1, 3, 3, 4])
不过要注意的是,python的+=语法结构的行为会出乎你的预料,建议不要使用+=操作:
a[[0,0,2]] +=1 # 0位置出现两次 但是并没有多次执行+1
a
array([3, 1, 4, 3, 4])
使用布尔数组来索引
前面我们使用整型的索引数组来访问数据的时候,我们实际上是提供了想要访问的数据的坐标。
而布尔数组则有所不同,它明确指示了哪些数组元素是想要的,哪些数组元素是不想要的。
一种最自然的布尔数组的想法是,用于索引的布尔数组形状和数据数组的形状完全一致:
a = np.arange(12).reshape(3,4)
b = a > 4
b
array([[False, False, False, False],
[False, True, True, True],
[ True, True, True, True]])
a[b]
array([ 5, 6, 7, 8, 9, 10, 11])
这种性质非常适合赋值:
a[b] = 0
a
array([[0, 1, 2, 3],
[4, 0, 0, 0],
[0, 0, 0, 0]])
第二种使用布尔索引的方式跟整形索引更加相似。
对于数组的每一个维度,使用一个一维数组来指定所需要的数据切片:
a = np.arange(12).reshape(3,4)
a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
b1 = np.array([False,True,True]) # 第一维
b2 = np.array([True,False,True,False]) # 第二维
a[b1,:] # 选择行
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
a[:,b2] # 选择列
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
a[b1,b2] # 选定坐标,(1,0),(2,2)这个实际上很奇怪,一般不会用
array([ 4, 10])
ix_()函数
ix_函数可以用于使用不同的向量来生成它们之间的笛卡尔积。
比如我们现在有a,b,c三个长度不一致的向量,我们想计算所有可能的a+b* c的值:
a = np.array([2,3,4,5])
b = np.array([8,5,4])
c = np.array([5,4,6,8,3])
ax,bx,cx = np.ix_(a,b,c)
result = ax+bx*cx
result
array([[[42, 34, 50, 66, 26],
[27, 22, 32, 42, 17],
[22, 18, 26, 34, 14]],
[[43, 35, 51, 67, 27],
[28, 23, 33, 43, 18],
[23, 19, 27, 35, 15]],
[[44, 36, 52, 68, 28],
[29, 24, 34, 44, 19],
[24, 20, 28, 36, 16]],
[[45, 37, 53, 69, 29],
[30, 25, 35, 45, 20],
[25, 21, 29, 37, 17]]])
result[3,2,4]
17
上面用了广播机制,技巧比较高
基本的线性代数
简单的数组操作
如有必要,请阅读linalg的文档以了解更多
a = np.array([[1.0,2.0],[3.0,4.0]])
print(a)
[[1. 2.]
[3. 4.]]
a.transpose() #转置
array([[1., 3.],
[2., 4.]])
np.linalg.inv(a) #求逆
array([[-2. , 1. ],
[ 1.5, -0.5]])
u = np.eye(2) # 单位阵
u
array([[1., 0.],
[0., 1.]])
j = np.array([[0.0,-1.0],[1.0,0.0]])
np.dot(j,j) # 矩阵乘法
array([[-1., 0.],
[ 0., -1.]])
np.trace(u) # 矩阵的迹
2.0
解线性方程:
x_1 + 2x_2 = 5
3x_1 + 4x_2 = 7
y = np.array([[5.],[7.]])
np.linalg.solve(a,y)
array([[-3.],
[ 4.]])
np.linalg.eig(j) # 求特征值和特征向量
(array([0.+1.j, 0.-1.j]),
array([[0.70710678+0.j , 0.70710678-0.j ],
[0. -0.70710678j, 0. +0.70710678j]]))
技巧与小贴士
这里给一些短小但有用的贴士。
“自动”变形
在改变一个数组的形状的时候,我们可以省略一个维度的值,它会被自动计算出来:
a = np.arange(30)
a.shape = 2,-1,3 # 这里的-1指的是在满足其他维度的情况下,这个维度自动帮我算出来
a.shape
(2, 5, 3)
a
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]]])
向量堆叠
我们如何将等长的行向量组建成一个二维的数组呢?
我们可以使用column_stack,dstack,hstack,vstack。实际上只要记住stack这个词就够了。
x = np.arange(0,10,2)
y = np.arange(5)
m = np.vstack([x,y])
m
array([[0, 2, 4, 6, 8],
[0, 1, 2, 3, 4]])
xy = np.hstack([x,y])
xy
array([0, 2, 4, 6, 8, 0, 1, 2, 3, 4])
超过二维时,函数的逻辑有些怪,不需要关心,不怎么用
直方图
NumPy的histogram函数作用在一个数组上的时候,会返回一堆向量:
- 每个桶的技术
- 同的划分
请注意,matplotlib也有一个函数(hist)可以创建直方图,它和NumPy的不一样。
二者主要的区别在于,matplotlib的hist会自动绘制出直方图,而numpy.histogram仅仅生成数据。
import matplotlib.pyplot as plt
mu,sigma = 2,0.5
v = np.random.normal(mu,sigma,10000) # 使用正态分布随机生成一万个点
# matplotlib版本
plt.hist(v,bins=50,density=1) # 数据都规范化了
plt.show()
# NumPy版本
# 先生成histogram数据,然后再绘制
(n,bins)= np.histogram(v,bins=50,density=True) # bins是区间的上下界
plt.plot(.5*(bins[1:]+bins[:-1]),n)
plt.show()
进一步学习
如果完全消化了本课的内容,进一步学习建议:
- 阅读《利用Python进行数据分析》
- 阅读NumPy手册