科学计算基础软件包Numpy学习 03

操作数组

数组的索引和切片,合并与拆分,复制和排序,查找和筛选,以及改变数组结构等,这些是数组操作的基本技术,其中最抽象的是查找和筛选,但这也是数组操作中最重要,最精髓的一部分。在数组操作中用好查找和筛选才能避免使用循环,这是数组操作的最高境界。

索引和切片

索引是定位一维或多维数组中的单个或多个元素的行为模式。切片是返回一维或多维数组中的单个或多个相邻元素的视图,目的是引用或赋值。Numpy数组对象的内容可以通过索引或切片来访问和修改。对于一维数组的索引和切片,Numpy数组使用起来和Python的列表一样灵活

>>> import numpy as np
>>> 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])

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

>>> import numpy as np
>>> 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] #所有楼层的第0行第0列
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] #所有楼层所有排的第1列到第3列
array([[[ 1,  2],
        [ 5,  6],
        [ 9, 10]],

       [[13, 14],
        [17, 18],
        [21, 22]]])
>>> a[1,:,-1]  #2层每一行的最后一个房间
array([15, 19, 23])

从上面的代码中可以看出,对多维数组索引或切片得到的结果的维度不是确定的。另外还有一点需要特别提醒:切片返回的数组不是原始数据的副本,而是指向与原始数组相同的内存区域。数组切片不会复制内部数组数据,只是产生了原始数据的一个新视图。

>>> import numpy as np
>>> 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]])

上面的代码中,数组b是数组a的切片,当改变数组b的元素时,数组a也同时发生了改变,这就证明了切片返回的数组不是一个独立数组,而是指向与原始数组相同的内存区域。

改变数组结构

ndarray自带多个改变数组结构的方法,在大部分情况下学会ndarray.reshape()函数即可。之前文章中已经多次用到该函数,在某些情况下,翻滚轴函数numpy.rollaxis()才是最佳的选择。以下是改变数组结构的几个常用函数:

  • ndarray.reshape():按照指定的结构返回数组的新视图,不改变原数组。
  • ndarray.ravel(): 返回多维数组一维化的视图,不改变原数组。
  • ndarray.transpose():返回行变列的视图,不改变原数组。
  • ndarray.resize():按照指定的结构改变原数组,无返回值。
  • ndarray.rollaxis():翻滚轴,返回新的视图,不改变原数组。

下面演示这几种改变数组结构的函数用法:

>>> import numpy as np
>>> 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 #返回行变列的视图,等价于a.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]])

合并

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

>>> import numpy as np
>>> 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]])

不过,append()函数换不够好用,推荐使用stack()函数及其hstack()水平合并函数,vstack()垂直合并函数和dstack()深度合并函数,下面演示这三个函数的用法:

>>> import numpy as np
>>> 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]]])

拆分

因为数组切片非常简单,所以数组拆分应用较少,拆分是合并的逆过程,最常用的函数是split(),其代码如下:

>>> import numpy as np
>>> a = np.arange(16).reshape(2,4,2)
>>> np.hsplit(a,2) #水平方向拆分成2部分
[array([[[ 0,  1],
        [ 2,  3]],

       [[ 8,  9],
        [10, 11]]]), array([[[ 4,  5],
        [ 6,  7]],

       [[12, 13],
        [14, 15]]])]
>>> np.vsplit(a,2) #垂直方向拆分成2部分
[array([[[0, 1],
        [2, 3],
        [4, 5],
        [6, 7]]]), array([[[ 8,  9],
        [10, 11],
        [12, 13],
        [14, 15]]])]
>>> np.dsplit(a,2) #深度方向拆分成2部分
[array([[[ 0],
        [ 2],
        [ 4],
        [ 6]],

       [[ 8],
        [10],
        [12],
        [14]]]), array([[[ 1],
        [ 3],
        [ 5],
        [ 7]],

       [[ 9],
        [11],
        [13],
        [15]]])]

复制

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

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

排序

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

numpy.sort(arr, axis=-1, kind='quicksort', order=None)
numpy.argsort(arr, axis=-1, kind='quicksort', order=None)

第一个参数arr,是要排序的数组;第二个参数axis,也就是轴,指定排序的轴,默认值为-1,表示没有指定排序轴,返回结果将沿着最后的轴排序;第3个参数kind,表示排序方法,默认为‘quicksort’(快速排序),其他选项有’mergesort’(归并排序)和‘heapsort’(堆排序);第4个参数order,指定用于排序的字段,前提是数组包含该字段。

>>> import numpy as np
>>> a = np.random.random((2,3))
>>> a
array([[0.81201676, 0.39729347, 0.34520116],
       [0.69121878, 0.53239521, 0.90317282]])
>>> np.argsort(a) #返回行内从小到大排序的索引号(列排序),相当于axis=1(最后的轴)
array([[2, 1, 0],
       [1, 0, 2]], dtype=int64)
>>> np.sort(a) #返回行内从小到大排序的一个新数组(列排序)
array([[0.34520116, 0.39729347, 0.81201676],
       [0.53239521, 0.69121878, 0.90317282]])
>>> np.sort(a,axis=0) #返回列内从小到大排序的一个新数组(行排序)
array([[0.69121878, 0.39729347, 0.34520116],
       [0.81201676, 0.53239521, 0.90317282]])

查找

这里约定查找是返回数组中符合条件的元素的索引号,或返回和数组具有相同结构的布尔型数组,元素符合条件在布尔型数组中对应True,否则对应False,查找分为最大值和最小值查找,非零元素查找,使用逻辑表达式查找和使用where条件查找这4种方式。
== 最大值和最小值==
下面代码演示了返回数组中最大值和最小值的索引号,如果是多维数组,这个索引号是数组转成一维之后的索引号。

>>> import numpy as np
>>> a = np.random.random((2,3))
>>> a
array([[0.51527117, 0.65600334, 0.04528759],
       [0.80279109, 0.77222158, 0.05716852]])
>>> np.argmax(a)
3
>>> np.argmin(a)
2

非零元素查找
下面的代码演示了返回数组中非零元素的索引号,返回的结果是一个元组。

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

使用逻辑表达式查找

下面的代码演示了使用逻辑表达式查找符合条件的元素,返回结果是一个和原数组结构相同的布尔型数组,元素符合条件在布尔型数组中对应True,否则对应False.

>>> import numpy as np
>>> a = np.arange(10).reshape((2,5))
>>> a
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
>>> (a>3)&(a<8)
array([[False, False, False, False,  True],
       [ True,  True,  True, False, False]])

使用where条件查找
np.where()函数返回数组中满足给定条件的元素的索引号,其结构为元组,元组的第K个元素对应符合条件的元素在数组k轴上的索引号。这句话可以简单理解为,一维数组返回一个元素的元组,二维数组返回两个元素的元组,依次类推。np.where()函数还可以用于替换符合条件的元素。

>>> import numpy as np
>>> 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]])

除去睡眠,人的一生只有一万六千多天。而人与人之间的差距就在于你究竟是活了一万六千多天还是只活了一天却重复了一万六千多次

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值