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

举报
天元浪子 发表于 2022/05/08 00:05:06 2022/05/08
【摘要】 文章目录 1. 索引和切片2. 改变结构3. 合并与拆分4. 复制5. 排序6. 查找和筛选6.1 查找6.2 筛选 7. 数组I/O 1. 索引和切片 NumPy数组对象的内容可以通...

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

提示:

  1. 对多维数组切片或索引得到的结果,维度不是确定的;
  2. 切片返回的数组不是原始数据的副本,而是指向与原始数组相同的内存区域。数组切片不会复制内部数组数据,只是产生了原始数据的一个新视图。
>>> 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((34)) # 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', '<i4')])
>>> np.sort(a, order='age') # 如果指定年龄排序,结果则是李张王赵
array([(b'li', 17), (b'zhang', 21), (b'wang', 25), (b'zhao', 27)],
      dtype=[('name', 'S10'), ('age', '<i4')])

  
 
  • 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='<U1')

  
 
  • 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

文章来源: xufive.blog.csdn.net,作者:天元浪子,版权归原作者所有,如需转载,请联系作者。

原文链接:xufive.blog.csdn.net/article/details/124593501

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。