概述
Numpy 中数据结构的核心是 n 维数组 n-dimensional array,简称 ndarray,需要注意的是数组中的元素都要属于同一个数据类型。
几个常用的 ndarray 的属性:ndarray.dtype,ndarray.shape,ndarray.ndim
几个常用的生成 ndarray 的方法:np.zeros( ),np.ones( ),np.zeros_like( ),np.ones_like( ),np.empty( ),np.arange( ),np.eye( ),np.identity( )
np.zeros(shape, dtype=float):shape 参数可以是一个自然数,也可以是二维元组,当元组中有三个参数时,后两个参数可以理解为 ndarray 的行数和列数,第一个参数为同样形状的 array 的个数。继续增加参数的个数的工作机制同上,都是对于后续的数组进行一个按照给定参数的量进行复制,np.ones( ) 工作机制类似。
np.zeros((2, 5, 3))
array([[[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.]],
[[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.],
[ 0., 0., 0.]]])
np.zeros_like(),np.ones_like() 的参数为已有的一个 ndarray,会返回一个同形状的元素全 0 或全 1 的 ndarray。
np.empty() 生成一个占位的空数组,由于需要自行设置数组中的每一个值,因此要尽量少用。
np.arange() 的参数为一个整数,工作机制类似于 python 内建函数 range,但返回一个 ndarray。
np.arange(11)
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
np.eye(N, M=none, k=0, dtype=) :N 为矩阵的行,M 为矩阵的列,如果 M 不指定则默认为 N x N 方阵,k 为 1 所在的对角线的位置
不指定列数:
np.eye(3) # 相当于 np.identity(3)
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
指定列数和 1 所在对角线位置:
np.eye(3, 4, 2)
array([[ 0., 0., 1., 0.],
[ 0., 0., 0., 1.],
[ 0., 0., 0., 0.]])
当 k ≥ M 时,为 0 矩阵:
np.eye(3, 4, 4)
array([[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.]])
还可以通过 np.identity() 来创建单位矩阵
np.identity(4)
array([[ 1., 0., 0., 0.],
[ 0., 1., 0., 0.],
[ 0., 0., 1., 0.],
[ 0., 0., 0., 1.]])
ndarray 的索引和切片
一维 ndarray
一维 ndarray 的索引和切片与 list 类似,主要差别是可以通过索引和切片直接修改原 ndarray 中的值。同时, ndarray 的切片只是提供了原 ndarray 的一个局部视图 view 而不是原 ndarray 的一个拷贝,但对于切片的任何修改都会反映到原有的 ndarray 中去。后续大量数组的操作都是采用视图而非拷贝的原因是 Numpy 是为了操作大量数据而构建的,因此从性能的角度出发需要尽量的减少内存占用。
arr = np.arange(10)
arr[3: 8] = 10
arr_slice = arr[5: 8]
arr_slice
array([10, 10, 10])
对切片进行索引赋值操作会直接作用到原来的数组:
arr_slice[1] = 12
arr
array([0, 1, 2, 10, 10, 10, 12, 10, 8, 9])
如果确实需要进行复制,则可以通过显式的使用 arr[ 5: 8].copy() 来完成。
当对一维数组进行 arr.shape 查询时结果是诸如 (3, ) 的形式,这个看起来很怪异的形状在 Numpy 中称为 Rank 1 数组,对应的 arr.ndim 返回 1,在实际使用中应尽量避免这种数据结构,原因请参见这篇 rank 1 数组。
二维和高维 ndarray
二维数组的单个索引值得到的是一个一维数组:
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2]
array([7, 8, 9])
两个索引值则会获取相应位置的值:
arr2d[2, 2] # arr2d[2][2]
9
高维数组的索引机制与二维类似,即会根据提供的索引值逐次找到相应位置的数组或元素。
arr3d = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]])
arr3d.shape
(1, 4, 3)
单个索引值:
arr3d[0] # arr3d[1] will pop out an error
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
两个索引值:
arr3d[0, 1] # shape = (3, )
array([4, 5, 6])
通过索引修改数组中的值:
arr3d[0, 1] = 1
arr3d
array([[[ 1, 2, 3],
[ 1, 1, 1],
[ 7, 8, 9],
[10, 11, 12]]])
也可以通过切片的形式访问数组中的元素:
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2, :2]
array([7, 8])
上面已经简单提到 Rank 1 数组这个对象,在实际应用中经常需要通过切片方式来获得数组的一个行或列中的数值并用于后续计算,而由下图可知由于这些数值可以通过采用不同的切片方式获得,此时为了避免产生 Rank 1 数组,要尽量使用双冒号切片形式进行选取。
Array slicing
布尔值索引
通过布尔值索引是一个非常重要的特征,可以通过这个操作快速选择满足一定特征的元素:
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
data
array([[ 0.91937812, 0.37117872, -0.24042518, 0.30905519],
[-0.49121926, -1.0682015 , -0.0988304 , 1.43795767],
[ 1.11437469, 1.19878961, 0.00452662, 1.15337206],
[-1.1719382 , -0.13256179, -0.61995845, -0.99759886],
[-0.50507224, -2.60465047, -1.4235047 , 0.83859547],
[-1.26945164, -0.07747245, 1.50020684, -0.55767208],
[ 1.9902588 , -1.35701671, 1.28836883, 0.33033936]])
在数组索引中提供单个索引值时是针对行进行索引的,所以这里作为判断条件的 names 的行数必须和 data 的行数相等。
data[names == 'Bob']
array([[ 0.91937812, 0.37117872, -0.24042518, 0.30905519],
[-1.1719382 , -0.13256179, -0.61995845, -0.99759886]])
布尔值索引还可以和切片联合使用:
data[names == 'Bob', 2:]
array([[-0.24042518, 0.30905519],
[-0.61995845, -0.99759886]])
布尔值索引也可以同时用于修改符合某些条件量的值:
data[data < 0] = 0
data
array([[ 0.91937812, 0.37117872, 0. , 0.30905519],
[ 0. , 0. , 0. , 1.43795767],
[ 1.11437469, 1.19878961, 0.00452662, 1.15337206],
[ 0. , 0. , 0. , 0. ],
[ 0. , 0. , 0. , 0.83859547],
[ 0. , 0. , 1.50020684, 0. ],
[ 1.9902588 , 0. , 1.28836883, 0.33033936]])
需要注意的是:
通过布尔值索引进行数值选择时会返回一个符合判断条件的数值的拷贝,而如果在选择的同时给予赋值则在原地进行
逻辑运算符可以采用 & (and) 和 | (or),但不能直接使用 and 和 or
花式索引 Fancy indexing
花式索引在 Numpy 中是指用一个整数型数组作为索引值来对数组进行索引,并且按照指定的顺序以复制的方式返回索引值。
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, 3, 0, 6]]
array([[ 4., 4., 4., 4.],
[ 3., 3., 3., 3.],
[ 0., 0., 0., 0.],
[ 6., 6., 6., 6.]])
当在花式索引中提供两个整数数组时,返回的是被索引数组中以两个数组对应为元素为坐标的元素值:
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])
而如果想返回一个区域的值,则需要按照如下的方式先进行行索引,再对列进行排序:
arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]]
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
等同于 arr[np.ix_([1, 5, 7, 2], [0, 3, 1, 2])],后续 [0, 3, 1, 2] 为对列进行重新排序。
数组的转置和轴的交换
二维数组的转置可以理解为矩阵的转置,并且还可以在转置的基础上做向量的点积运算。
arr = np.arange(15).reshape((3, 5))
arr
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
arr.T # arr.transpose() 的一个特殊应用形式
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
下面这个运算的结果是一个实对称矩阵,每一个对角线元素等于对应位置的行列元素的平方和,并且转置提供的依然是原有数组的一个视图,不改变原有数组的形状。
np.dot(arr.T, arr)
array([[125, 140, 155, 170, 185],
[140, 158, 176, 194, 212],
[155, 176, 197, 218, 239],
[170, 194, 218, 242, 266],
[185, 212, 239, 266, 293]])
当对高维数组施加 transpose( ) 时,其变换过程需要稍微做一些解释:对于一个数组来说,除了维数外,Numpy 还给它分配了轴,轴的序号按照各个轴在数组的 shape 元组中的索引位置来分配。针对下面这个例子来说,轴与形状的关系为 (2[ axis 0 ], 3[ axis 1 ], 4[ axis 2 ])。
arr = np.arange(24).reshape((2, 3, 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]]])
当按照如下所示的轴的顺序施加转置时,即将原来数组的 0 轴 和 1 轴交换,数组中的每一个元素都需要交换前两个坐标,也即转置后的形状将变为 (3, 2, 4)。例如 12 这个元素原来的索引坐标为 [1, 0, 0],在转置后交换其前两个索引坐标 [0, 1, 0]。
arr.transpose((1, 0, 2))
array([[[ 0, 1, 2, 3],
[12, 13, 14, 15]],
[[ 4, 5, 6, 7],
[16, 17, 18, 19]],
[[ 8, 9, 10, 11],
[20, 21, 22, 23]]])
类似的可以实现转置的操作还有一个方法 swapaxes( ),其参数为两个需要交换的轴,所以下面这个操作和上面的 arr.transpose((1, 0, 2))一致。
arr.swapaxes(0, 1)
array([[[ 0, 1, 2, 3],
[12, 13, 14, 15]],
[[ 4, 5, 6, 7],
[16, 17, 18, 19]],
[[ 8, 9, 10, 11],
[20, 21, 22, 23]]])
Universal Functions: 数组的快速元素级运算
Numpy 的强大之处就在于对于数值型变量的向量化 vectorization,避免了数值操作过程中大量的使用显式的 for 循环,universal functions 提供了一系列的元素级的操作,如:
np.square(some_array) 返回一个数组每一个元素的平方
np.maximum(array_A, array_B) 依次比较两个数组相同位置的元素并返回其中的最大值
np.where(condition, array_A, array_B) 相比 maximum 则可以进一步的返回符合某个判断条件的选择,判断逻辑为当条件满足时取 array_A,否则取 array_B。
arr_x = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
arr_y = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])
np.where(cond, arr_x, arr_y) # 非向量化的实现方式为 [(x if c else y) for x, y, c in zip(arr_x, arr_y, cond)]
array([ 1.1, 2.2, 1.3, 1.4, 2.5])
np.where( ) 的参数中,第 2 个和第 3 个参数也可以是标量,常用于根据某个条件修改已有的数组中的值:
arr = np.random.randn(4, 4)
arr
array([[-1.22678789, -0.6000085 , -0.22466607, -0.85761133],
[ 0.75063805, 1.40825566, -0.72430125, -0.12158849],
[-0.83472573, 0.09379071, -0.1910247 , -2.38272863],
[-1.51568709, -0.65348116, -1.22607901, -1.76620207]])
np.where(arr>0, 2, -2)
array([[-2, -2, -2, -2],
[ 2, 2, -2, -2],
[-2, 2, -2, -2],
[-2, -2, -2, -2]])
np.where() 本身也可以做为备选参数以完成更加复杂的判断:
np.where(cond1 & cond2, 0, np.where(cond1, 1, np.where(cond2, 2, 3)))
数学和统计学计算
前面关于转置的部分简单提到了 Numpy 为数组分配了“轴”,这个概念的演示在很多 Numpy 的教程中都是以一些随机数或简单数值的数组来举例,使得理解这个概念的定义和意义变得困难。
事实上设定轴的现实意义在于实际应用中的数据都代表对于某些研究对象的特征的描述:在常用的单张二维数据表中一般单独的一行记录的是对于一个对象的不同特征的描述,而单独的一列则对应于同一个特征在不同对象中的记录。在二维数据表的基础上可以叠加多张数据表,这种组织形式的数据可以在后续的计算中使得数据可以沿某一个方向进行统计(例如计算加和,均值,方差等),由于这些统计的结果会在相应的方向上压缩相应的轴的长度,因此这些统计过程也常被称为缩减操作 Reduction operation。
在 Numpy 中 shape 和 axis 的关系是 shape 元组中的元素的索引位置就是相应的轴的名称,而shape 元组中的数值代表数组在相应轴上的数据的个数。一个数组的轴的数量 number of dimensions 称为维数,也称为秩 rank,这个 rank 可以通过数组中元素的分组形式来获得,也即 [ ] 的嵌套层数,或者通过 array.ndim 获得。注意这个定义和线性代数中的秩的定义是不同的,后者的查询方式为 np.linalg.matrix_rank(arr)。
关于 Numpy 轴的定义及计算可以参考 Lstyle 的这个笔记 Numpy 小记——有关 axis/axes 的理解,讲的非常好。个人认为更为直观的理解轴上的加总计算的方式就是可以想象这些数据沿着轴被串在一起的数字被汇总压缩为一个新的数字,因此在汇总后生成的新的数组的维数就会被降低。在应用中如果想保持原有的维数,可以使用关键字参数 keepdimes = True。
具体来说,在使用 ndarray.sum( ),ndarry.mean( ) 的时候,如果不指定轴,则是对整个数组内的所有数值进行加总和平均,而如果指定相应的轴,则会沿着轴的方向进行。
b = np.array([[[1,2,3,4],[5,6,7,8]],[[2,4,6,8],[3,5,7,9]],[[2,2,8,8],[1,0,5,8]]])
b # shape = (3, 2, 4)
array([[[1, 2, 3, 4],
[5, 6, 7, 8]],
[[2, 4, 6, 8],
[3, 5, 7, 9]],
[[2, 2, 8, 8],
[1, 0, 5, 8]]])
b.mean()
4.75
b.sum()
114
b.sum(axis=0) # shape = (2, 4)
array([[ 5, 8, 17, 20],
[ 9, 11, 19, 25]])
b.sum(axis=1) # shape = (3, 4)
array([[ 6, 8, 10, 12],
[ 5, 9, 13, 17],
[ 3, 2, 13, 16]])
b.sum(axis=2) # shape = (3, 2)
array([[10, 26],
[20, 24],
[20, 14]])
元素的唯一性和集合
对于一维数组,Numpy 还设有 numpy.unique( ) 这个方法,返回一个数组中的元素的集合,即无重复的返回数组中排序过的元素。
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
np.unique(names)
array(['Bob', 'Joe', 'Will'], dtype='
数组文件的读写
读写专门的 Numpy 文件:
写入硬盘:np.save('filename.npy', array_to_be_saved)
文件读入:np.load('filename.npy')
读写 txt 文件:
写入硬盘:np.savetxt('filename.txt', array_to_be_saved)
文件读入:np.loadtxt('filename.txt', delimiter=',')
线性代数相关操作
ndarray 的运算
我们一般很少直接的将 m 行 n 列的二维数组直接称为矩阵是因为这容易将其运算与矩阵运算混淆:在矩阵乘法中,m x n 的矩阵必须和 n x k 的矩阵才能进行。但在 ndarray 中这个要求则更加宽松:对于任意两个同形状的 n 维数组,其加 +,减 -,乘 *,除 / 运算都是基于相同位置的元素进行的,而标量和 ndarray 的运算是标量与每个元素进行相应的运算。
arr = np.array([[1, 2, 3], [4, 5, 6]])
arr * arr # the same as np.multiply(arr, arr)
array([[ 1, 4, 9],
[16, 25, 36]])
前面已经讲到在 Numpy 中,两个同形的数组之间的乘法发生在相同位置的两个元素之间。对应的矩阵相乘则需要借助下文的点积方法 np.dot(a, b)。除点积外,numpy.linalg 模块下集成了很多线性代数的操作,如矩阵求逆,QR 分解,特征值特征向量求解,SVD 分解等。
X = np.random.randn(4, 4)
mat = X.T.dot(X)
mat
array([[ 3.72779983, -1.3869418 , -0.93343812, -2.25143467],
[-1.3869418 , 5.29047179, 0.18681953, -2.91567006],
[-0.93343812, 0.18681953, 0.43223972, 0.40102812],
[-2.25143467, -2.91567006, 0.40102812, 4.82265242]])
np.linalg.inv(mat)
array([[ 21.4056399 , 13.58072833, 25.42913277, 16.08918057],
[ 13.58072833, 8.94242629, 15.78239186, 10.43411384],
[ 25.42913277, 15.78239186, 33.09360224, 18.66126891],
[ 16.08918057, 10.43411384, 18.66126891, 12.47497716]])
mat.dot(np.linalg.inv(mat))
array([[ 1.00000000e+00, -3.55271368e-15, 0.00000000e+00,
1.42108547e-14],
[ -2.84217094e-14, 1.00000000e+00, 7.10542736e-15,
-7.10542736e-15],
[ -8.88178420e-16, 8.88178420e-16, 1.00000000e+00,
-2.66453526e-15],
[ 0.00000000e+00, 7.10542736e-15, 0.00000000e+00,
1.00000000e+00]])
q, r = np.linalg.qr(mat)
r
array([[-4.66480052, 1.31147337, 1.08153305, 3.34017604],
[ 0. , -6.06042658, 0.03694935, 5.06062672],
[ 0. , 0. , -0.28772209, 0.4701318 ],
[ 0. , 0. , 0. , 0.03387203]])
Numpy 中的矩阵乘法
为了更清楚的表示输入输出,后续我会更改一下显示方式,回归原始的 Jupyter Notebook 中的状态。
import numpy as np
In [2]:
a = np.array([[1, 2, 3, 4],[5, 6, 7, 8]])
a
Out[2]:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
In [3]:
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [11, 12, 13]])
b
Out[3]:
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[11, 12, 13]])
Numpy 的矩阵乘法可以通过 np.dot(a, b) 或者 np.matmul(a, b) 来完成,当两个矩阵都是 2d array 时,结果相同的,而对于高维数组必须用 np.matmul(a, b) 来完成。
In [4]:
c = np.matmul(a, b)
c
Out[4]:
array([[ 74, 84, 94],
[166, 192, 218]])
In [5]:
np.dot(a,b)
Out[5]:
array([[ 74, 84, 94],
[166, 192, 218]])
需要注意的是, 对于一维 Numpy 数组来说,默认是按照行向量来存储的。如果需要通过其来构建列向量的时候,仅给予转置 arr.T 得到的还是行向量,需要采用 arr[ :, None] 或 arr.reshape((n, 1)) 来实现:
import numpy as np
In [2]:
a = np.array([1, 2, 3, 4]) # 1 dimension array
a
Out[2]:
array([1, 2, 3, 4])
In [3]:
a.T # Transpose does not change the shape of 1 dimension array
Out[3]:
array([1, 2, 3, 4])
In [4]:
a.reshape(4, 1) # Reshape is the right way to do
Out[4]:
array([[1],
[2],
[3],
[4]])
In [5]:
a[:, None] # or this one
Out[5]:
array([[1],
[2],
[3],
[4]])
最后
以上就是冷艳水池为你收集整理的python 双冒号切片_Numpy 学习笔记的全部内容,希望文章能够帮你解决python 双冒号切片_Numpy 学习笔记所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复