本节主要介绍numpy中在数组上的一些常规操作,在数组级别上包括数组迭代,数组拼接、数组分割,在元素级别包括元素迭代、元素增加、元素删除等。
本节的内容比较重要,同时也比较基础,是养成良好的编程习惯的重要的一个环节,因为每一个方法都可以通过最笨拙的索引方法去实现,但是这对于代码的可读性和程序的运行速度都是有影响的。
1. 迭代操作
迭代操作是最体现代码水平的,因为总是可以通过索引实现,这里介绍几种迭代的形式。
1.1 for i in a 进行数组的第一维度迭代
例如:
import numpy as np
a = np.arange(16)
a.shape=(4,2,2)
print('a 数据为:',a)
b = np.array([1,2,3,4])
print('b 数组为:',b)
for i in a:
print(i)
事实上,
for i in a:
print(i)
可以改写为,
for i in range(a.shape[0]):
print(a[i,:,:])
他们的结果是一样的。这种方式只能按照数组的第一维度进行迭代,返回的是数组第一维度的值,可能是数组也可能是元素(元素实际上是0维数组)。
1.2 使用numpy提供的迭代器nditer进行迭代。
nditer和python标准的迭代器iter一样,但是它既可以用来迭代数组也可以用来迭代元素,有可选的参数来控制,同时还能调整迭代的顺序,是nnumpy中比较实用的工具。
1) 迭代元素
此时按照默认的存储顺序返回元素:
import numpy as np
a = np.arange(16)
a.shape=(4,4)
print('a 数据为:',a)
b = np.array([1,2,3,4])
print('b 数组为:',b)
for i in np.nditer(a):
print(i)
输出:
a 数据为: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
b 数组为: [1 2 3 4]
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
而此时,代码块:
for i in np.nditer(a):
print(i)
相当于:
for i in range(a.shape[0]):
for j in range(a.shape[1]):
print(a[i,j])
至于那种写法比较好,一目了然。
上面是以行优先(第一维度)迭代数据,想要以列优先迭代数据,事实上以’F’顺序迭代就可以:
for i in np.nditer(a, order='F'):
print(i)
同样等价于:
for i in range(a.shape[0]):
for j in range(a.shape[1]):
print(a[j,i])
默认创建数组的顺序为’c’,默认迭代元素顺序也是’C‘,通过显示的使用order='F'
可以改变迭代顺序。
2) 数组迭代
默认迭代返回的是单个元素,使用参数flags = ['external_loop']
将返回值变为数组,order='C'
时,返回值压缩为一维数组,order='F'
,将每一列打包一个数组返回。
import numpy as np
a = np.arange(16)
a.shape=(4,2,2)
print('a 数据为:',a)
# b = np.array([1,2,3,4])
# print('b 数组为:',b)
for i in np.nditer(a, flags = ['external_loop'], order='C'):
print('c顺序')
print(i)
for i in np.nditer(a, flags = ['external_loop'], order='F'):
print('F顺序')
print(i)
输出:
a 数据为: [[[ 0 1]
[ 2 3]]
[[ 4 5]
[ 6 7]]
[[ 8 9]
[10 11]]
[[12 13]
[14 15]]]
c顺序
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
F顺序
[ 0 4 8 12]
F顺序
[ 2 6 10 14]
F顺序
[ 1 5 9 13]
F顺序
[ 3 7 11 15]
可以看出,当参数为order='F'
每次也是按行迭代,只是迭代了4次。说明nditer是按照内存储存的顺序来迭代的。
1.3 广播迭代
nditer也可以对多个数组同时迭代,当这些数组的维度大小不一样的时候,遵循numpy的广播机制。
import numpy as np
a = np.arange(16)
a.shape=(4,4)
print('a 数据为:',a)
b = np.array([1,2,3,4])
print('b 数组为:',b)
for i,j in np.nditer([a,b]):
print('iter value is %d:%d'%(i,j))
输出:
thonFiles\PythonTools\visualstudio_py_launcher.py' 'e:\home\Python\machineLearning' '14039' '34806ad9-833a-4524-8cd6-18ca4aa74f14' 'RedirectOutput,RedirectOutput' 'e:\home\Python\machineLearning\test.py'
a 数据为: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
b 数组为: [1 2 3 4]
iter value is 0:1
iter value is 1:2
iter value is 2:3
iter value is 3:4
iter value is 4:1
iter value is 5:2
iter value is 6:3
iter value is 7:4
iter value is 8:1
iter value is 9:2
iter value is 10:3
iter value is 11:4
iter value is 12:1
iter value is 13:2
iter value is 14:3
iter value is 15:4
1.4 展开为一维数组迭代
迭代后结果为一维数组的迭代器有两个:flatten和ravel,同样两者都可以接受参数order=F,order:’C’ — 按行,’F’ — 按列,’A’ — 原顺序,’k’ — 元素在内存中的出现顺序
import numpy as np
a = np.arange(10).reshape(2,5)
print('数组a为:\n',a)
b = a.flatten()
# 经过flatten迭代器迭代后的结果
print('flat 迭代后的结果:\n', b)
# 与其他迭代器一样,接收参数order,order='F'
c = a.flatten(order='F')
print('order=F迭代结果\n',c)
d = a.ravel()
# 经过ravel迭代
print('ravel 迭代后的结果:\n',d)
输出结果:
数组a为:
[[0 1 2 3 4]
[5 6 7 8 9]]
flat 迭代后的结果:
[0 1 2 3 4 5 6 7 8 9]
order=F迭代结果
[0 5 1 6 2 7 3 8 4 9]
ravel 迭代后的结果:
[0 1 2 3 4 5 6 7 8 9]
可以看出,两者在功能上是完全一样的,但是两个还是有一个致命的区别:flatten返回的是拷贝,也就是从重新开辟了一块儿内存,与原数组无关。ravel返回的是原始数组的一个视图,结果与原数组是共内存的,修改结果会改变原数组的值,因而在使用上是需要注意的。
2. 数组拼接
在numpy的数组拼接中,常用的以下函数:
函数 | 参数 | 功能 |
numpy.concatenate((a1, a2, …), axis) | a1, a2, …:相同类型的数组序列,axis:沿着它连接数组的轴,默认为 0 | 沿着现存的轴连接数据序列,连接后新数组的维度不变 |
numpy.stack(arrays, axis) | arrays:相同形状的数组序列,axis:返回数组中的轴,输入数组沿着它来堆叠 | 沿着新轴连接数组序列,新数组的维度增加1 |
2.1 numpy.concatenate: 数组的连接是指元素上的连接。 此函数用于沿指定轴连接相同形状的两个或多个数组。连接的数组维度必须一样,连接轴的维度大小必须一样。
import numpy as np
a = np.arange(16)
a.shape=(4,4)
print('a 数据为:',a)
b = np.array([[1,2,3,4]])
print('b 数组为:',b)
c = np.concatenate((a,b))
print(c)
输出:
a 数据为: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
b 数组为: [[1 2 3 4]]
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[ 1 2 3 4]]
很显然,这是第一维度(行)拼接。下面是第二维度(列)拼接。
import numpy as np
a = np.arange(16)
a.shape=(4,4)
print('a 数据为:',a)
b = np.array([[1],[2],[3],[4]])
print('b 数组为:',b)
c = np.concatenate((a,b),axis=1)
print(c)
输出:
a 数据为: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
b 数组为: [[1]
[2]
[3]
[4]]
[[ 0 1 2 3 1]
[ 4 5 6 7 2]
[ 8 9 10 11 3]
[12 13 14 15 4]]
注意这两个例子中b数组的维度,拼接轴的维度大小一定要一样。
2.2 numpy.stack: 此函数沿新轴连接数组序列. 新数组的维度比原数组的维度大1。被拼接的数组的维度以及每个维度大小必须一样。
import numpy as np
a = np.arange(4)
a.shape=(2,2)
print('a 数据为:',a)
b = np.array([[5,6],[7,8]])
print('b 数组为:',b)
c = np.stack((a,b),axis=0)
print(c)
输出:
a 数据为: [[0 1]
[2 3]]
b 数组为: [[5 6]
[7 8]]
[[[0 1]
[2 3]]
[[5 6]
[7 8]]]
原数组是2维,新数组是3维。
将c = np.stack((a,b),axis=0)
改为:c = np.stack((a,b),axis=1)
输出:
a 数据为: [[0 1]
[2 3]]
b 数组为: [[5 6]
[7 8]]
[[[0 1]
[5 6]]
[[2 3]
[7 8]]]
stack有两个扩充变体:
hstack:水平堆叠序列中的数组(列方向) 与函数np.concatenate((a,b),axis=1)等价
vstack :竖直堆叠序列中的数组(行方向) 与函数np.concatenate((a,b),axis=0)等价
注意这两个函数与stack的不同
3. 数组分割
数组分割常用到以下函数:
函数 | 参数 | 功能 |
numpy.split(ary, indices_or_sections, axis) | ary:被分割的输入数组,indices_or_sections:可以是整数,表明要从输入数组创建的,等大小的子数组的数量。 如果此参数是一维数组,则其元素表明要创建新子数组的点,axis:分割轴,默认为 0 | 该函数沿特定的轴将数组分割为子数组 |
import numpy as np
a = np.arange(24)
a.shape=(6,4)
print('a 数据为:',a)
# b = np.array([[5,6],[7,8]])
# print('b 数组为:',b)
c = np.split(a,3)
print(c)
输出:
a 数据为: [[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]
[array([[0, 1, 2, 3],
[4, 5, 6, 7]]), array([[ 8, 9, 10, 11],
[12, 13, 14, 15]]), array([[16, 17, 18, 19],
[20, 21, 22, 23]])]
分割后元素的维度不变。上面是数组按轴等分分割,也可以标明分割位置:
c = np.split(a,[1,3,4])
输出:
a 数据为:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]
[array([[0, 1, 2, 3]]), array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]]), array([[12, 13, 14, 15]]), array([[16, 17, 18, 19],
[20, 21, 22, 23]])]
注意分割[1,3,4]是分割位置,故产生了4个数组。
同样,他有两个变体:
numpy.hsplit是split()函数的特例,其中轴为 1 表示水平分割,无论输入数组的维度是什么。
numpy.vsplit是split()函数的特例,其中轴为 0 表示竖直分割,无论输入数组的维度是什么