科学计算基础软件Numpy入门讲座(4):操作数组(科学计算库Numpy)

网友投稿 849 2022-05-30

文章目录

1. 索引和切片

2. 改变结构

3. 合并与拆分

4. 复制

5. 排序

6. 查找和筛选

6.1 查找

6.2 筛选

7. 数组I/O

1. 索引和切片

NumPy数组对象的内容可以通过索引或切片来访问和修改。对于一维数组的索引和切片,NumPy数组和Python的列表一样灵活。

a = np.arange(9) >>> a[-1] # 最后一个元素 8 >>> a[2:5] # 返回第2到第5个元素 array([2, 3, 4]) >>> a[:7:3] # 返回第0到第7个元素,步长为3 array([0, 3, 6]) >>> a[::-1] # 返回逆序的数组 array([8, 7, 6, 5, 4, 3, 2, 1, 0])

1

2

3

4

5

6

7

8

9

对于多维数组操作,NumPy数组比 Python的列表更加灵活、强大。假设有一栋2层楼,每层楼内的房间都是3行4列,那我们可以用一个三维数组来保存每个房间的居住人数(当然,也可以是房间面积等其他数值信息)。

>>> a = np.arange(24).reshape(2,3,4) # 2层3行4列 >>> 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]]]) >>> a[1][2][3] # 虽然可以这样 23 >>> a[1,2,3] # 但这才是规范的用法 23 >>> a[:,0,0] # 所有楼层的第1排第1列 array([ 0, 12]) >>> a[0,:,:] # 1楼的所有房间,等价与a[0]或a[0,...] array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> a[:,:,1:3] # 所有楼层所有排的第2到4列 array([[[ 1, 2], [ 5, 6], [ 9, 10]], [[13, 14], [17, 18], [21, 22]]]) >>> a[1,:,-1] # 2层每一排的最后一个房间 array([15, 19, 23])

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

提示:

对多维数组切片或索引得到的结果,维度不是确定的;

切片返回的数组不是原始数据的副本,而是指向与原始数组相同的内存区域。数组切片不会复制内部数组数据,只是产生了原始数据的一个新视图。

>>> a = np.arange(12).reshape(3,4) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> b = a[1:,2:] # 数组b是数组a的切片 >>> b array([[ 6, 7], [10, 11]]) >>> b[:,:] = 99 # 改变数组b的值,也会同时影响数组a >>> b array([[99, 99], [99, 99]]) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 99, 99], [ 8, 9, 99, 99]])

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

2. 改变结构

NumPy数组的存储顺序和数组的视图是相互独立的,因此改变数组的维度是非常便捷的操作,这一类操作不会改变所操作的数组本身的存储顺序, resize() 除外。

reshape() - 按照指定的结构(形状)返回数组的新视图,但不会改变数组

resize() - 按照指定的结构(形状)改变数组,无返回值

ravel() - 返回多维数组一维化的视图,但不会改变原数组

transpose() - 返回行变列的视图,但不会改变原数组

rollaxis() - 翻滚轴,返回新的视图

>>> a = np.arange(12) >>> b = a.reshape((3,4)) # reshape()返回数组a的一个新视图,但不会改变数组a >>>> a.shape (12,) >>> b.shape (3, 4) >>> b is a False >>> b.base is a True a.resize([4,3]) # resize()则真正改变了数组a的结构 >>> a.shape (4, 3) >>> a.ravel() # 返回多维数组一维化的视图,但不会改变原数组 array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) >>> a.transpose() # 返回行变列的视图,但不会改变原数组 array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]]) >>> a.T # 返回行变列的视图,等价于transpose() array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]]) >>> np.rollaxis(a, 1, 0) # 翻滚轴,1轴变0轴 array([[ 0, 3, 6, 9], [ 1, 4, 7, 10], [ 2, 5, 8, 11]])

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

3. 合并与拆分

NumPy数组一旦创建就不能再改变其元素数量了。如果要动态改变数组元素数量,只能通过合并或者拆分的方法,生成新的数组。对于刚刚上手NumPy的程序员来说,最大的困惑就是不能使用append() 方法向数组内添加元素,甚至连 append() 方法都找不到了。其实,NumPy仍然保留了append() 方法,只不过这个方法不再是NumPy数组的方法,而是是升级到最外层的NumPy命名空间,并且该方法的功能不再是追加元素,而是合并数组。

>>> np.append([[1, 2, 3]], [[4, 5, 6]]) array([1, 2, 3, 4, 5, 6]) >>> np.append([[1, 2, 3]], [[4, 5, 6]], axis=0) array([[1, 2, 3], [4, 5, 6]]) >>> np.append([[1, 2, 3]], [[4, 5, 6]], axis=1) array([[1, 2, 3, 4, 5, 6]])

1

2

3

4

5

6

7

8

9

不过,这个append()委实不够好用,我给大家推荐的是stack()方法。

>>> a = np.arange(4).reshape(2,2) >>> b = np.arange(4,8).reshape(2,2) >>> np.hstack((a,b)) # 水平合并 array([[0, 1, 4, 5], [2, 3, 6, 7]]) >>> np.vstack((a,b)) # 垂直合并 array([[0, 1], [2, 3], [4, 5], [6, 7]]) >>> np.dstack((a,b)) # 深度合并 array([[[0, 4], [1, 5]], [[2, 6], [3, 7]]])

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

stack 函数原型为 stack(arrays, axis=0),请注意体会下面例子中的 axis 的用法。

>>> a = np.arange(60).reshape(3,4,5) >>> b = np.arange(60).reshape(3,4,5) >>> a.shape, b.shape >>> np.stack((a,b), axis=0).shape (2, 3, 4, 5) >>> np.stack((a,b), axis=1).shape (3, 2, 4, 5) >>> np.stack((a,b), axis=2).shape (3, 4, 2, 5) >>> np.stack((a,b), axis=3).shape (3, 4, 5, 2)

1

2

3

4

5

6

7

8

9

10

11

因为数组切片非常简单,所以数组拆分应用较少。拆分是合并的逆过程,最常用的方法是split()。

>>> a = np.arange(8).reshape(2,4) >>> np.vsplit(a, 2) # 垂直方向拆分成2部分 [array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]])] >>> np.hsplit(a, 2) # 水平方向拆分成2部分 [array([[0, 1], [4, 5]]), array([[2, 3], [6, 7]])]

1

2

3

4

5

6

7

4. 复制

改变数组结构返回的是原元数据的一个新视图,而不是原元数据的副本。浅复制(view)和深复制(copy)则是创建原数据的副本,但二者之间也有细微差别:浅复制(view)是共享内存,深复制(copy)则是独享。

>>> a = np.arange(6).reshape((2,3)) >>> b = a.view() >>> b is a False >>> b.base is a False >>> b.flags.owndata False >>> c = a.copy() >>> c is a False >>> c.base is a False >>> c.flags.owndata True

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

5. 排序

NumPy 数组排序函数有两个,一个是sort(),一个是argsort()。sort()返回输入数组的排序副本,argsort()返回的是数组值从小到大的索引号。从函数原型看,这两个函数的参数是完全一样的。

numpy.sort(a, axis=-1, kind=‘quicksort’, order=None)

numpy.argsort(a, axis=-1, kind=‘quicksort’, order=None)

a - 要排序的数组

axis - 沿着它排序数组的轴,如果没有,则沿着最后的轴排序

kind - 排序方法,默认为’quicksort’(快速排序),其他选项还有 ‘mergesort’(归并排序)和 ‘heapsort’(堆排序)

order - 如果数组包含字段,则是要排序的字段

>>> a = np.random.random((2,3)) >>> a array([[0.79658569, 0.14507096, 0.63016223], [0.24983103, 0.98368325, 0.71092079]]) >>> np.argsort(a) # 返回行内从小到大排序的索引序号(列排序),相当于axis=1(最后的轴) array([[1, 2, 0], [0, 2, 1]], dtype=int64) >>> np.sort(a) # 返回行内从小到大排序的一个新数组(列排序) array([[0.14507096, 0.63016223, 0.79658569], [0.24983103, 0.71092079, 0.98368325]]) >>> np.sort(a,axis=0) # 返回列内每一行都是从小到大排序(行排序) array([[0.24983103, 0.14507096, 0.63016223], [0.79658569, 0.98368325, 0.71092079]])

1

2

3

4

5

6

7

8

9

10

11

12

13

我们再看看排序字段的使用。先定义一个新的数据类型dt:dt类似于一个字典,有两个键值对,一个是姓名name,一个是年龄age,姓名长度10个字符,年龄是整型。

>>> dt = np.dtype([('name', 'S10'),('age', int)]) >>> a = np.array([("zhang",21),("wang",25),("li", 17), ("zhao",27)], dtype = dt) >>> np.sort(a, order='name') # 如果指定姓名排序,结果是李王张赵 array([(b'li', 17), (b'wang', 25), (b'zhang', 21), (b'zhao', 27)], dtype=[('name', 'S10'), ('age', '>> np.sort(a, order='age') # 如果指定年龄排序,结果则是李张王赵 array([(b'li', 17), (b'zhang', 21), (b'wang', 25), (b'zhao', 27)], dtype=[('name', 'S10'), ('age', '

1

2

3

4

5

6

7

8

6. 查找和筛选

这里,我们约定查找是返回符合条件的元素的索引号,筛选是返回符合条件的元素。查找和筛选,是 NumPy 数组最令人心动的功能,也是相对比较烧脑的操作。

6.1 查找

下面的代码演示了返回数组中最大值和最小值的索引(对于多维数组,这个索引是数组转成一维之后的索引):

>>> a = np.random.random((2,3)) >>> a array([[0.47881615, 0.55682904, 0.29173085], [0.41107703, 0.91467593, 0.88852535]]) >>> np.argmax(a) 4 >>> np.argmin(a) 2

1

2

3

4

5

6

7

8

下面的代码演示了返回数组中非零元素的索引:

>>> a = np.random.randint(0, 2, (2,3)) >>> a array([[0, 0, 0], [0, 1, 1]]) >>> np.nonzero(a) (array([1, 1], dtype=int64), array([1, 2], dtype=int64))

1

2

3

4

5

6

numpy.where() 用于返回数组中满足给定条件的元素的索引,还可以用于替换符合条件的元素:

numpy.where(condition[, x, y])

>>> a = np.arange(10) >>> a array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> np.where(a < 5) (array([0, 1, 2, 3, 4], dtype=int64),) >>> a = a.reshape((2, -1)) >>> a array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) >>> np.where(a < 5) (array([0, 0, 0, 0, 0], dtype=int64), array([0, 1, 2, 3, 4], dtype=int64)) >>> np.where(a < 5, a, 10*a) # 满足条件的元素不变,其他元素乘以10 array([[ 0, 1, 2, 3, 4], [50, 60, 70, 80, 90]])

1

2

3

4

5

6

7

8

9

10

11

12

13

14

6.2 筛选

筛选有3种方式,一是使用np.where()返回的python元组,二是使用逻辑表达式返回的布尔型数组,三是使用整型数组。

>>> a = np.random.random((3,4)) >>> a array([[0.41551063, 0.38984904, 0.01204226, 0.72323978], [0.82425869, 0.64216573, 0.41475495, 0.21351508], [0.30104819, 0.52046164, 0.58286043, 0.66749564]]) >>> a[np.where(a>0.5)] # 返回大于0.5的元素(使用np.where()返回的python元组) array([0.72323978, 0.82425869, 0.64216573, 0.52046164, 0.58286043, 0.66749564]) >>> a[(a>0.3)&(a<0.7)] # 返回大于0.3且小于0.7的元素(使用逻辑表达式返回的布尔型数组) array([0.41551063, 0.38984904, 0.64216573, 0.41475495, 0.30104819, 0.52046164, 0.58286043, 0.66749564]) >>> a[np.array([2,1])] # 返回整形数组指定的项(使用整型数组) array([[0.30104819, 0.52046164, 0.58286043, 0.66749564], [0.82425869, 0.64216573, 0.41475495, 0.21351508]]) >>> a = a.ravel() >>> a[np.array([3,5,7,11])] # 返回整形数组指定的项(使用整型数组) array([0.72323978, 0.64216573, 0.21351508, 0.66749564]) >>> a[np.array([[3,5],[7,11]])] # 返回整形数组指定的项(使用整型数组) array([[0.72323978, 0.64216573], [0.21351508, 0.66749564]])

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

使用np.where()或者直接使用逻辑表达式来筛选数组元素,很容易想象到这样的做的目的,使用整形数组来筛选数组元素的用途是什么呢?看似不起眼的一个功能,却蕴含着无穷的想象空间。下面用一个例子来演示通过整型数组筛选数组元素的神奇魔法。

上图是用字符表现像素灰度的效果图。一般而言,灰度图像每个像素的值域范围是 [0, 255]。假如用于表现不同灰度的字符集是[’ ‘, ‘.’, ‘-’, ‘+’, ‘=’, ‘*’, ‘#’, ‘@’],从 ’ ’ 到 ‘@’ 表示从白到黑的 8 个灰度等级。我们需要将每个像素的灰度值分段转换成相应的字符。例如,灰度值小于32的像素用 ‘@’ 表示,大于或等于32且小于64的像素用 ‘#’ 表示,依次类推直至大于或等于224的像素用’ '表示。

如何实现图像数组从灰度值到对应字符的转换呢?乍一看,好像只有用循环的方式遍历所有像素才能实现。但是,下面的代码却用“整型数组筛选数组元素”的方法完成了这个转换,不但代码简洁,而且代码的执行速度也非常快。

>>> img = np.random.randint(0, 256, (5, 10), dtype=np.uint8) # 生成10x5的灰度图 >>> img array([[145, 95, 60, 14, 66, 150, 221, 43, 184, 66], [229, 138, 76, 90, 179, 217, 2, 20, 154, 191], [165, 120, 77, 117, 42, 108, 156, 5, 208, 50], [164, 196, 227, 111, 82, 84, 19, 208, 124, 16], [146, 50, 107, 26, 34, 229, 137, 104, 93, 223]], dtype=uint8) >>> img = (img/32).astype(np.uint8) # 将256级灰度值转为8级灰度值 >>> img array([[0, 3, 3, 3, 1, 7, 7, 4, 2, 7], [6, 3, 4, 5, 4, 5, 4, 7, 3, 4], [6, 7, 1, 2, 2, 2, 2, 4, 7, 7], [5, 7, 1, 2, 0, 2, 7, 0, 7, 5], [3, 5, 0, 7, 0, 4, 6, 2, 5, 0]], dtype=uint8) >>> chs = np.array([' ', '.', '-', '+', '=', '*', '#', '@']) # 灰度字符集 >>> chs[img] # 用整型数组筛选数组元素(我认为这是NumPy最精彩之处!) array([[' ', '+', '+', '+', '.', '@', '@', '=', '-', '@'], ['#', '+', '=', '*', '=', '*', '=', '@', '+', '='], ['#', '@', '.', '-', '-', '-', '-', '=', '@', '@'], ['*', '@', '.', '-', ' ', '-', '@', ' ', '@', '*'], ['+', '*', ' ', '@', ' ', '=', '#', '-', '*', ' ']], dtype='

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

7. 数组I/O

所谓数组I/O,就是讨论如何分发、交换数据。在机器学习算法模型的例子中,海量的训练数据通常都是从数据文件中读出来的,而数据文件一般是csv格式,NumPy 自带的csv文件读写函数,可以很方便的读写csv格式的数据文件。除了支持通用的csv格式的数据文件, NumPy 为数组对象引入了新的二进制文件格式,用于数据交换。后缀名为.npy 文件用于存储单个数组,后缀名为.npz 文件用于存取多个数组。

下面的代码演示了NumPy读写CSV格式的数据文件的方法。实际操作下面的代码时,请注意结合实际情况替换对应的文件路径和文件名。

>>> a = np.random.random((15,5)) >>> np.savetxt('demo.csv', a, delimiter=',') # 将数组a保存成CSV格式的数据文件 >>> data = np.loadtxt('demo.csv', delimiter=',') # 打开CSV格式的数据文件 >>> data.shape, data.dtype ((15, 5), dtype('float64'))

1

2

3

4

5

NumPy 自定义的数据交换格式也是一个非常好用的数据交换方式,使用它保存 NumPy 数组时不会丢失任何信息,特别是数据类型的信息。实际操作下面的代码时,请注意结合实际情况替换对应的文件路径和文件名。

>>> single_arr_fn = 'single_arr.npy' # 存储单个数组文件名 >>> multi_arr_fn = 'multi_arr.npz' # 存储多个数组文件名 >>> lon = np.linspace(10,90,9) >>> lat = np.linspace(20,60,5) >>> np.save(single_arr_fn, lon) # 用save()函数把经度数组保存成.npy文件 >>> lon = np.load(single_arr_fn) # 接着用load()函数读出来 >>> np.savez(multi_arr_fn, longitude=lon, latitude=lat) #保存两个数组到一个文件 >>> data = np.load(multi_arr_fn) # 用load()函数把这个.npz文件读成一个结构data >>> data.files # 查看所有的数组名 >>> data['longitude'] # 使用data[数组名],就可以取得想要的数据 >>> data['latitude'] # 使用data[数组名],就可以取得想要的数据

1

科学计算基础软件包NumPy入门讲座(4):操作数组(科学计算库Numpy)

2

3

4

5

6

7

8

9

10

11

Numpy 数据结构

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【前端领域高频笔试面试】—— JavaScript高级相关(前端高频面试题)
下一篇:java框架-spring面试题 丨【奔跑吧!JAVA】(java spring框架面试题)
相关文章