一、数组的索引和切片
(一)数组的索引
首先,导入 NumPy 库。
import numpy as np
一维数组的索引与 Python 列表的索引用法相同。
m = np.array([23,79,16,5,32]) print("m =",m) print('m[1] =',m[1],'\t','m[-3] =',m[-3])
m = [23 79 16 5 32] m[1] = 79 m[-3] = 16
对于多维数组,它的每一个维度都有一个索引,各个维度的索引之间用逗号分隔。
n = np.array([[1,2,3,],[11,22,33],[111,222,333],[1111,2222,3333]]) print("n =",n) print("n[1,2] =",n[1,2],'\t',"n[-1,-2] =",n[-1,-2]) print("n[1][2] =",n[1][2],'\t',"n[-1][-2] =",n[-1][-2]) # 虽然与上面等价,但更推荐上面的写法
输出:
n = [[ 1 2 3] [ 11 22 33] [ 111 222 333] [1111 2222 3333]] n[1,2] = 33 n[-1,-2] = 2222 n[1][2] = 33 n[-1][-2] = 2222
(二)数组的切片
数组切片的目的是为了获取子数组。一维数组的切片与 Python 列表的切片用法相同。
# 一维数组的切片与Python列表的切片用法相同 print("m =",m) print("m[::-1] =",m[::-1]) # 逆序 *** print("m[1:-1:2] =",m[1:-1:2]) # 从第2个开始、隔一个取一个、一直取到倒数第二个
m[::-1]
表示逆序输出,m[1:-1:2]
表示从第2个开始、隔一个取一个、一直取到倒数第二个。
输出:
m = [23 79 16 5 32] m[::-1] = [32 5 16 79 23] m[1:-1:2] = [79 5]
二维数组允许在每个维度上使用切片,相互间用逗号分隔。
# 二维数组允许在每个维度上使用切片,相互间用逗号分隔 print("n =",n) print("n[1:,2:4] =",n[1:,2:4]) # 行:第2行到最后一行,列:第3、4列 print("n[::,-3::2] =",n[::,-3::2]) # 行:所有行,列:第1列开始、每隔1列 print("n[1,:]用于取第2行:",n[1,:]) # 单个冒号:出现在列的位置上,表示所有列 print("n[:,-1]用于取最后一列:",n[:,-1]) # 单个冒号:出现在行的位置上,表示所有行
注意此处二维数组的冒号与前面一维数组***处冒号的区别:前者冒号是分隔符 ,后者表示所有行/列。
输出:
n = [[ 1 2 3] [ 11 22 33] [ 111 222 333] [1111 2222 3333]] n[1:,2:4] = [[ 33] [ 333] [3333]] n[::,-3::2] = [[ 1 3] [ 11 33] [ 111 333] [1111 3333]] n[1,:]用于取第2行: [11 22 33] n[:,-1]用于取最后一列: [ 3 33 333 3333]
注意:
数组切片返回的是数组数据的视图而非副本,但 Python 列表的切片返回的却是副本,因此通过数组切片赋值将会修改原数组。
# 通过数组切片赋值将会修改原数组 p = n[:,:] print("获取n的全部元素后产生的数组p=",p) # 通过切片赋值,会改变原来的数组 p[1:2,:] = 555 print("切片赋值后数组p=",p) print("切片赋值后数组n=",n)
输出:
获取n的全部元素后产生的数组p= [[ 1 2 3] [ 11 22 33] [ 111 222 333] [1111 2222 3333]] 切片赋值后数组p= [[ 1 2 3] [ 555 555 555] [ 111 222 333] [1111 2222 3333]] 切片赋值后数组n= [[ 1 2 3] [ 555 555 555] [ 111 222 333] [1111 2222 3333]]
数组的切片返回的是原始数组的视图,不会产生新的数据,如果需要的并非视图而是要复制数据,则可以通过 copy()
方法实现。要想实现数组复制,需要掉用数组对象的copy()
方法或np.copy(对象)
。
q = m.copy() # 等价于q = np.copy(m) print(q) # 复制后的数组 q[-1] = 100 print(q) # 修改了m的副本q的最后一个元素 print(m) # m并没有改变
输出:
[23 79 16 5 32] [ 23 79 16 5 100] [23 79 16 5 32]
二、数组的变换
(一)数组转置
r = n.T print("转置后的数组r=",r)
输出:
转置后的数组r= [[ 1 555 111 1111] [ 2 555 222 2222] [ 3 555 333 3333]]
(二)数组重塑
对于定义好的数组,可以通过reshape()
方法改变其数据维度。
参数名称格式: np.reshape(a, newshape, order='C')
参数名称 | 说明 |
a | 需要处理的数据 |
newshape | 新维度——整数或整数元组 |
print("变形前数组r的形状:",r.shape) s = r.reshape((2,6)) # 注意参数是一个元组 print("变形后数组r=",r) print("变形后新数组s=",s)
输出:
变形前数组r的形状: (3, 4) 变形后数组r= [[ 1 555 111 1111] [ 2 555 222 2222] [ 3 555 333 3333]] 变形后新数组s= [[ 1 555 111 1111 2 555] [ 222 2222 3 555 333 3333]]
说明reshape()
方法不改变原数组的形状,而是会创建一个新数组。
注意:
数组变形方法包括:reshape()
方法,shape
属性和resize()
方法,后两个会直接修改原数组对象。reshape()
方法不改变原数组的形状,而是会创建一个新数组。
设置数组对象的shape
属性或调用其resize()
方法都会直接修改原数组对象。
s.shape = (4,3) print(s) print("*************") s.resize((3,4)) print(s)
输出:
[[ 1 555 111] [1111 2 555] [ 222 2222 3] [ 555 333 3333]] ************* [[ 1 555 111 1111] [ 2 555 222 2222] [ 3 555 333 3333]]
(三)数组合并
hstack
函数:实现横向合并
vstack
函数:实现纵向组合是利用 vstack 将数组纵向合并;
concatenate
函数:可以实现数组的横向或纵向合并,参数 axis=1 时进行横向合并, axis=0 时进行纵向合并。
(四)数组分割
与数组合并相反, hsplit
函数、 vsplit
函数和split
函数分别实现数组的横向、纵向和指定方向的分割。
三、数组的运算
(一)数组和标量间的运算
数组之所以很强大是因为不需要通过循环就可以完成批量计算。
a = [1,2,3] b = [] for i in a: b.append(i*i) print('b数组:',b) wy = np.array([1,2,3]) c = wy*2 print('c数组:',c)
输出:
b数组: [1, 4, 9] c数组: [2 4 6]
(二)ufunc函数
ufunc 函数全称为通用函数,是一种能够对数组中的所有元素进行操作的函数,对数组实施向量化操作(逐元素进行相同的操作)。对一个数组进行重复运算时,使用 ufunc 函数比使用 math 库中的函数效率要高很多,方便程序书写(替代了循环)。
1、常用的 ufunc 函数运算
常用的 ufunc 函数运算有四则运算、比较运算和逻辑运算。
(1)四则运算:
加( + )、减( - )、乘( * )、除( / )、幂( ** )。数组间的四则运算表示对每个数组中的元素分别进行四则运算,所以形状必须相同。
(2)比较运算:
< 、 > 、 == 、 >= 、 <= 、 != 。比较运算返回的结果是一个布尔数组,每个元素为每个数组对应元素的比较结果。
(3)逻辑运算:
np.any 函数表示逻辑 “or”, np.all 函数表示逻辑 “and”,运算结果返回布尔值。
数组的四则运算。
s = np.arange(2,10,2) print("s=",s) t = np.arange(1,5) print("t=",t) print("s+t =",s+t) # 逐元素相加 print("s/t =",s/t) # 逐元素相除 print("2**s=",2**s) # 逐元素求以2为底数的幂 print("|s-3t| =",np.abs(s-3*t)) # 逐元素求绝对值 print("s+2 =",s+2) # 最后三条语句都已经用到了广播机制
输出:
s= [2 4 6 8] t= [1 2 3 4] s+t = [ 3 6 9 12] s/t = [ 2. 2. 2. 2.] 2**s= [ 4 16 64 256] |s-3t| = [1 2 3 4] s+2 = [ 4 6 8 10]
数组的比较运算。
x = np.array([1,3,6]) y = np.array([2,3,4]) print('比较结果 (<) :',x<y) print('比较结果 (>) :',x>y) print('比较结果 (==) :',x==y) print('比较结果 (>=) :',x>=y) print('比较结果 (!=) :',x!=y)
输出:
比较结果 (<) : [ True False False] 比较结果 (>) : [False False True] 比较结果 (==) : [False True False] 比较结果 (>=) : [False True True] 比较结果 (!=) : [ True False True]
2、ufunc 函数的广播机制
广播( broadcasting )是指不同形状的数组之间执行算术运算的方式。需要遵循 4 个原则:
(1)让所有输入数组都向其中 shape 最长的数组看齐, shape 中不足的部分都通过在左边加 1 补齐。
(2)如果两个数组的形状在任何一个维度上都不匹配,那么数组的形状会沿着维度为 1 的维度进行扩展,以匹配另一个数组的形状。
(3)输出数组的 shape 是输入数组 shape 的各个轴上的最大值。
(4)如果两个数组的形状在任何一个维度上都不匹配,并且没有任何一个维度等于 1 ,则引发异常。
# 通用函数的广播机制:适用于形状不同但相容的数组间运算 a = np.array([[ 0, 0, 0], [10,10,10], [20,20,20], [30,30,30]]) b = np.array([1,2,3]) print(a + b) print("*************") print(a+2) # 标量2看成shape是(1,1),然后应用广播机制
输出:
[[ 1 2 3] [11 12 13] [21 22 23] [31 32 33]] ************* [[ 2 2 2] [12 12 12] [22 22 22] [32 32 32]]
(三)条件逻辑运算
在 NumPy 中可以利用基本的逻辑运算就可以实现数组的条件运算。
arr1 = np.array([1,3,5,7]) arr2 = np.array([2,4,6,8]) cond = np.array([True,False,True,False]) result = [(x if c else y)for x,y,c in zip(arr1,arr2,cond)] result
输出:
[1, 4, 5, 8]
这种方法对大规模数组处理效率不高,也无法用于多维数组。 NumPy 提供的 where 方法可以克服这些问题。
np.where() 函数的用法
where()的用法:np.where(condition, x, y),满足条件 (condition) ,输出 x ,不满足则输出 y 。
用法1:当满足 con 条件时,用数组 x 的值填充原数组元素,否则就用数组 y 的值填充原数组元素,此时 where 函数有三个参数。
days = np.arange(1,8) # 一周7天 con = days<6 # 判断是否工作日 x,y = 100,60 # 工作日每天100元,非工作日每天60元 np.where(con,x,y) # 一周的工资情况
输出:
array([100, 100, 100, 100, 100, 60, 60])
np.where([[True,False], [True,True]], [[1,2], [3,4]], [[9,8], [7,6]])
输出:
array([[1, 8], [3, 4]])
条件为 [[True,False], [True,False]]
,分别对应最后输出结果的四个值,运算时第一个值从 [1,9] 中选,因为条件为 True
,所以是选 1 。第二个值从 [2,8] 中选,因为条件为 False
,所以选 8 ,后面以此类推。
用法2:常用于找到满足条件的元素的下标(例如寻找x数组中3的倍数的元素的下标),此时where()
函数只有一个参数。
x = np.arange(15).reshape((3,5)) print(x) print() rows,cols = np.where(x%3==0) print(rows) print(cols) print("数组x中3的倍数的元素下标:") for row,col in zip(rows,cols): print("("+str(row)+","+str(col)+")",end=" ")
输出:
[[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14]] [0 0 1 1 2] [0 3 1 4 2] 数组x中3的倍数的元素下标: (0,0) (0,3) (1,1) (1,4) (2,2)
where()
中若只有条件 (condition),没有 x 和 y ,则输出满足条件元素的坐标。这里的坐标以 tuple 的形式给出,通常原数组有多少维,输出的 tuple 中就包含几个数组,分别对应符合条件元素的各维坐标。
w = np.array([2,5,6,3,10]) np.where(w>4)
输出:
(array([1, 2, 4], dtype=int64),)
四、布尔数组与花式索引
x = np.arange(5) x1 = x<=2 x1 # 不使用print函数输出,可以直接看到数组元素的类型
输出:
array([ True, True, True, False, False], dtype=bool)
布尔数组用于索引,可以筛选满足条件的元素,等价于np.extract()
函数。
x = np.arange(15).reshape((3,5)) print(x) y = x[x%3==0] # 等价于y = np.extract(x%3==0,x) print("数组x中3的倍数构成的新数组y=",y)
注意布尔数组用于索引,筛选的结果是一维数组。
输出:
[[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14]] 数组x中3的倍数构成的新数组y= [ 0 3 6 9 12]
花式索引(fancy indexing),允许用一个索引数组作为另一个数组的索引以获取后者的子集。
# 设定随机数种子,这样每次运行的数据都相同 np.random.seed(666) z = np.random.randint(1,100,12).reshape((3,4)) print("二维随机整数数组z=:",z)
输出:
二维随机整数数组z=: [[ 3 46 31 63] [71 74 31 37] [62 92 95 52]]
索引数组的第1维表示行,第2维表示列。
注意:花式索引的结果子集的形状与索引数组的形状一致。
idx = [2,[1,3]] # 2表示要获取第3行,[1,3]表示要获取第2、4列 print("索引数组idx=",idx) print("用idx做索引检索数组z得到的子集z[idx]=",z[idx])
输出:
索引数组idx= [2, [1, 3]] 用idx做索引检索数组z得到的子集z[idx]= [92 52]
五、应用统计与排序函数
(一)常用统计函数
NumPy 中提供了很多用于统计分析的函数,常见的有 sum 、mean 、std 、var 、min 和 max 等。几乎所有的统计函数在针对二维数组的时候需要注意轴的概念。axis=0 时表示沿着纵轴进行计算,axis=1 时沿横轴进行计算。
np.random.seed(666) z = np.random.randint(1,100,12).reshape((3,4)) print("二维随机整数数组z =",z)
输出:
二维随机整数数组z = [[ 3 46 31 63] [71 74 31 37] [62 92 95 52]]
计算元素的和。
print("z的全部元素之和:",z.sum()) # z.sum()等价于np.sum(z) print("z的列元素之和:",z.sum(axis=0)) # z.sum(axis=0)等价于np.sum(z,axis=0) print("z的行元素之和:",z.sum(axis=1)) # z.sum(axis=1)等价于np.sum(z,axis=1)
输出:
z的全部元素之和: 657 z的列元素之和: [136 212 157 152] z的行元素之和: [143 213 301]
计算元素的均值。
print("z的全部元素均值:",np.mean(z)) print("z的列元素均值:",z.mean(axis=0)) print("z的行元素均值:",z.mean(axis=1))
输出:
z的全部元素均值: 54.75 z的列元素均值: [ 45.33333333 70.66666667 52.33333333 50.66666667] z的行元素均值: [ 35.75 53.25 75.25]
找出数组的最大值和它们各自所在的索引。
print("z的最大值:",z.max()) print("z的最大值所在的索引:",z.argmax()) print("z的每行最大值:",z.max(axis=1)) print("z的每行最大值所在的索引:",z.argmax(axis=1))
输出:
z的最大值: 95 z的最大值所在的索引: 10 z的每行最大值: [63 74 95] z的每行最大值所在的索引: [3 1 2]
统计满足条件的元素个数。
print("z大于90的元素个数:",np.sum((z>90))) # 统计大于90的元素个数 print("z介于60到80之间的元素个数:",np.sum((z>=60) & (z<=80))) # 统计介于60到80之间的元素个数
输出:
z大于90的元素个数: 2 z介于60到80之间的元素个数: 4
(二)数组排序
sort 函数对数据直接进行排序,调用改变原始数组,无返回值。数组排序,默认按升序。
格式:numpy.sort(a, axis, kind, order)
参数 | 使用说明 |
a | 要排序的数组 |
kind | 排序算法,默认为“quicksort” |
order | 排序的字段名,可指定字段排序,默认为 None |
axis | 使 得 sort 函 数 可 以 沿 着 指 定 轴 对 数 据 集 进 行 排序。 axis=1 为沿横轴排序; axis=0 为沿纵轴排序;axis=None, 将数组平坦化之后进行排序。 |
np.argsort 函数和 np.lexsort 函数根据一个或多个键值对数据集进行排序。
np.argsort()
:返回的是数组值从小到大的索引值;
np.lexsort()
:返回值是按照最后一个传入数据排序的结果。
使用 argsort 和 lexsort 函数,可以在给定一个或多个键时,得到一个由整数构成的索引数组,索引值表示数据在新的序列中的位置。
#数组排序,默认按升序 print("排序前数组z =",z) #默认按行排序,相当于axis=1 print("按行排序的结果:",np.sort(z)) print("按行排序结果的原索引:",np.argsort(z)) #按列排序 print("按列排序的结果:",np.sort(z,axis=0)) print("按列排序结果的原索引:",np.argsort(z,axis=0))
输出:
排序前数组z = [[ 3 46 31 63] [71 74 31 37] [62 92 95 52]] 按行排序的结果: [[ 3 31 46 63] [31 37 71 74] [52 62 92 95]] 按行排序结果的原索引: [[0 2 1 3] [2 3 0 1] [3 0 1 2]] 按列排序的结果: [[ 3 46 31 37] [62 74 31 52] [71 92 95 63]] 按列排序结果的原索引: [[0 0 0 1] [2 1 1 2] [1 2 2 0]]
使用 lexsort 排序。
a = np.array([7,2,1,4]) b = np.array([5,2,6,7]) c = np.array([5,2,4,6]) d = np.lexsort((a,b,c)) print('排序后:',list(zip(a[d],b[d],c[d])))
输出:
排序后: [(2, 2, 2), (1, 6, 4), (7, 5, 5), (4, 7, 6)]
#二维数组拉成一维后再排序,默认按行拉伸 r = z.flatten() print("z按行拉成的一维数组r =",r) print("拉伸后的数组r的排序结果:",np.sort(r)) #逆序 print("通过切片实现降序排列:",np.sort(r)[::-1]) #注意:不能写成np.sort(-r),没有这种操作! print("通过argsort函数实现降序排列:",r[np.argsort(-r)]) #注意此用法很常见:argsort()返回的索引数组用于花式索引
输出:
z按行拉成的一维数组r = [ 3 46 31 63 71 74 31 37 62 92 95 52] 拉伸后的数组r的排序结果: [ 3 31 31 37 46 52 62 63 71 74 92 95] 通过切片实现降序排列: [95 92 74 71 63 62 52 46 37 31 31 3] 通过argsort函数实现降序排列: [95 92 74 71 63 62 52 46 37 31 31 3]
注意np.sort(r)
与r.sort()
排序的区别:前者会产生新数组存放排序结果,而原数组不变 ;后者会直接改变原数组的元素顺序。
print("排序前的数组r=",r) print("用np.sort(r)排序后的结果:",np.sort(r)) print("排序后的数组r=",r) print("用r.sort()排序后的结果:",r.sort()) print("排序后的数组r=",r)
输出:
排序前的数组r= [ 3 31 31 37 46 52 62 63 71 74 92 95] 用np.sort(r)排序后的结果: [ 3 31 31 37 46 52 62 63 71 74 92 95] 排序后的数组r= [ 3 31 31 37 46 52 62 63 71 74 92 95] 用r.sort()排序后的结果: None 排序后的数组r= [ 3 31 31 37 46 52 62 63 71 74 92 95]