Python numpy之索引切片

482 阅读6分钟

image.png

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

前言

我们前面已经对numpy ndarray进行深入学习了 numpy 内部结构。

  • numpy 相比 python list 区别是 numpy 具有 ndarray 数据结构,可以创建N维数组。
  • numpy 数据元素是同种类型的,所有每一个元素数据内存空间大小相同
  • numpy 数组可以直接引用到数据本身,而不用像Python list 存放的是对象的引用,然后再通过引用找到具体的数据对象,。
  • numpy 数组数据元素存储是一段连续的空间,极大的节省了内存消耗,而Python list 引用的数据对象的物理地址不是连续的。

如下图,我们可以看到 numpy 数组与Python list 清晰的比较

image.png

在我们之前学习Python list中,都知道列表可以通过元素位置-索引进行访问。同理,numpy 数组也同样与python list 类似,也是可以进行索引切片操作的。

我们在numpy 视图与副本中知道视图是与原数组共享数据,因此数组中进行索引切片,其实是返回的视图操作。

本期,我们将继续探索,numpy 数组索引切片的操作相关方法的学习,Let's go~

1. 索引概念

  • 什么是索引?

    在 Python 中,如list可以通过下标index访问指定位置的元素值,下标index就是索引。

    同理,在numpy 数组中,可以使用标准的Python 数组[obj] 语法进行索引。

    在进行索引切片操作时,numpy 内部是采取视图的形式进行的,当更改索引的内容时,将对原始数组的数据产生影响。因此,如果索引的值,如果要与原始数组数据相互独立,可以采用copy()副本形式拷贝一份完整数据。

  • 索引的分类

    按照数组中元素数据类型分类,可以四类:

    • 基本索引
    • 整数数组索引
    • 布尔索引
    • 花式索引

    其中,整数数组索引、布尔索引、花式索引是属于高级索引的

2. 基本索引

  • 单元素索引

    我们可以使用类似 python list[index] 形式来进行索引工作。默认index从0开始。

    同样,numpy 数组也可以支持 Python 列表一样从尾部开始进行负索引操作

    例如,我们使用nump.arange()方法创建一个数组,通过单元数索引如arr[7]可以得到数据7

    >>> import numpy as np
    >>> arr1 = np.arange(10)
    >>> arr1
    array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    >>> arr1[7]
    7
    >>> arr1[-2]
    8
    >>> arr1[0]
    0
    >>>
    

    对于多维度的数组,我们可以使用 "," 来进行索引查找

    >>> import numpy as np
    >>> arr2 = np.arange(15)
    >>> arr2.shape = (3,5)
    >>> arr2
    array([[ 0,  1,  2,  3,  4],
           [ 5,  6,  7,  8,  9],
           [10, 11, 12, 13, 14]])
    >>> arr2[2,3]
    13
    >>> arr2[-1,-1]
    14
    >>>
    

    对于多维的数组来说,我们填写的索引比多维数组维度小于时,是会按照row方式输出一个子维数组。

    >>> arr2[0]
    array([0, 1, 2, 3, 4])
    >>>
    
  • 单元素索引返回的是视图 or 副本?

    首先对于一维数组如上述数组arr1来说,我们通过索引如arr1[7] 会得到数值7,那么arr1[7]是属于arr1数组的数据吗?

    别急,我们还是按照之前metdata包含的指标,ndarray.flags.owndata 和 ndarray.base来看看真像是什么?

    >>> print(arr1.flags.owndata)
    True
    >>> print(arr1[7].flags.owndata)
    True
    >>> print(arr1.base)
    None
    >>> print(arr1[7].base)
    None
    

    我们分别查看了 arr1 和 arr1[7] base和 flags.owndata 信息怎么都是一样的,因此arr1[7] 是与 arr1 原始数组相互独立的,是arr1 的副本

    同时我们来id()方法来验证一下arr1 和 arr1[7] 内存地址,果然也不一样的

    >>> id(arr1)
    2182599900176
    >>> id(arr1[7])
    2182320408080
    >>>
    

    是不是同理推理,arr2[1,3] 也是arr2的数据相互独立的,返回的也是arr1副本

    那么,arr2[0] 也是 arr2 的副本吗?

    同样我们获取base、flags.owndata三个参数看看

    >>> arr2[0]
    array([0, 1, 2, 3, 4])
    >>> print(arr2.base)
    None
    >>> print(arr2[0].base)
    [[ 0  1  2  3  4]
     [ 5  6  7  8  9]
     [10 11 12 13 14]]
     >>> print(arr2.flags.owndata)
    True
    >>> print(arr2[0].flags.owndata)
    False
    

    arr2[0].base 来源是 arr2 的,那么arr2[0] 与 arr2 是共享数据的,那么arr2[0]返回的是arr2的视图。

    同时对于多维数组来说,我们 arr2[2,3] 等价于 arr2[2][3],都是返回数值13。

    但是这样arr2[2][3] 较于 arr2[2,3] 效率上慢了许多。

    arr2[2] 是 arr2 的一个视图,返回一个一维数组,arr2[2]基础上会创建一个新的临时数组arr2[2][3],最后得到一个单元素数值

3. 切片

  • 什么是切片?

    我们之前学习,Python 基本数据类型如列表、字符串,都使用过切片操作。因此,numpy 数组中也沿用了Python 内部的切片概念。

    numpy 数组与Python 索引一样,所有索引都是从0开始的,返回一个有效范围内的数组。

    基本切片的生成的数组始终都是原始数组的视图

    • 触发切片的条件

      1. obj 是一个由(star:stop:step)组成的slice 对象
      2. 与整数、或者切片对象和整数元组
      3. 可添加省略号对象(Ellipsis)和 维度方向(newaxis)对象
    • 切片的操作方法

      • 形式如i:j:k,i为起始索引,j为终止索引,k为步长
      • 使用slice方法
  • 使用 : 来进行切片

    可以像Python list一样使用:进行切片操作,同样支持正索引和负索引

    • 当i,j,k都为正整数时,进行正索引,如下栗子:

      >>> arr3 = np.arange(10)
      >>> arr3
      array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
      >>> arr3[1:6:2]
      array([1, 3, 5])
      
    • 当i为负数,k也负数走向更小的索引

      >>> arr3[-2:3:-2]
      array([8, 6, 4])
      >>>
      
    • 只设置i,则从i开始切片

      >>> arr3[1:]
      array([1, 2, 3, 4, 5, 6, 7, 8, 9])
      
    • ::这与:和 表示选择沿该轴的所有索引

      >>> arr3[::]
      array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
      >>>
      
      
  • 使用 slice() 索引

    我们也可以slice方法设置索引对象,从而对数组来进行索引切片操作

    >>> arr3[slice(1,6,2)]
    array([1, 3, 5])
    >>>
    

总结

本期,我们对 numpy 模块中基本索引切片相关方法进行学习。对于单元素索引中,对于一维数组的索引返回的对象是原始数组的副本,而对于多维数组来说,当索引少于其维度时,返回的索引对象是原始数组的视图。

对于,切片操作来说,不适用在高级索引。

以上是本期内容,欢迎大佬们点赞评论,下期见~