03-索引与切片,玩转数组之七十二变

何为索引?

牛津英汉辞典,中英翻译领域的权威著作,收录了近百万个单词,比板砖还要厚。朋友们在使用的时候是怎么查阅的呢?辞典收录单词是按照首字母排序的,如果首字母一样,则按照第二个字母排序,以此类推,这就为我们查找单词提供了极大的便利。例如,我们希望查找 python 这个单词,那我们先找到以首字母 P 开头的部分,然后找第二个字母是 Y ,直至最后查阅到 python

显然牛津辞典以 A-Z 共26个字母,建立了多级索引,通过这样的方式,把单词进行有序排列,每一次我们查阅单词都是基于这样的索引在进行逐层循环。所以才说,索引为数据值提供了一个指向存储位置的指针。

在列表里,我们利用元素在列表中的位置作为索引。在数组领域,为了保证索引的兼容性,很多朋友们想到了,既然数组是列表在多维空间的延伸,那么索引为什么不可以呢?确实是这样,数组的索引是列表的索引在多维的拓展,本节我们将具体看一看,如何玩转数组的索引?

1. 数组索引与切片

1.1 一维数组

一维数组相对简单,基本和列表一致。举例如下:

import numpy as np
arr1d = np.arange(1,10,1, dtype=np.float32)
arr1d
Out: array([1., 2., 3., 4., 5., 6., 7., 8., 9.], dtype=float32)
# 索引的起始位置为0,选择索引为6的元素
arr1d[6]
Out: 7.0
# 选择索引为[5:8)的区间元素,注意是左闭右开
arr1d[5:8]
Out: array([6., 7., 8.], dtype=float32)

1.2 二维数组

这里以3×3大小的数组为例:

# 创建二维数组
arr2d=np.arange(9, dtype=np.float32).reshape(3,3)
arr2d
Out: 
    array([[0., 1., 2.],
       	  [3., 4., 5.],
          [6., 7., 8.]], dtype=float32)

显然,二维数组有两个维度的索引,映射到平面空间的话,二维数组的两个轴分别为 axis 0和 axis 1,Numpy 默认先对 axis 0 轴索引,再对 axis 1轴索引。(数组其实可以视作嵌套列表,通常把最外层的索引定义为 axis 0,依次往里递增,该规律对于高维数组也适用)

图片描述

如果仅仅对axis 0轴进行索引:

# 方括号里面,可以理解为对轴的操作。这里方括号里为单个整数,表示对最外层的axis 0进行操作
arr2d[1]
# 结果请参考下图的左侧部分
Out: array([3., 4., 5.], dtype=float32)

同时对axis 0轴和axis 1轴进行索引:

# 方括号里为2个整数,表示依次对axis 0和axis 1操作(axis 0在前);取同时满足axis 0中index=1和axis 1中index=1的元素
arr2d[1, 1]
# 结果请参考下图的右侧部分
Out: 4.0

图片描述

在索引的基础上稍微拓展一下,同时选择多个连续的索引,那就是切片效果了。例如,我们对上一步的索引做一些简单的修改:

# 方括号的里面,分别还是表示对 axis 0和 axis 1操作,只不过变成了切片。
# 该结果表示:同时在 axis 0 轴上满足[0:2),axis 1轴上满足[1:3)的元素组成的数组
# arr2d[0:2, 1:3]的结果不等价于arr2d[[0,1],[1,2]],请注意这个结论!!!
arr2d[0:2, 1:3]
Out: 
    array([[1., 2.],
           [4., 5.]], dtype=float32)

简单说来,二维数组可比作二维空间的坐标系,比较直观,便于理解。朋友们多多练习,细细揣摩即可熟练掌握;

1.3 三维数组

三维数组比二维数组多了一维。三维数组在图片领域比较常见,对于 RGB 三原色模式的图片,就是用 m×n×3 大小的数组来表示一张图片,其中 m 表示图片垂直尺寸, n 表示图片水平尺寸,3 表示三原色。那对于三维的数组,我们应该怎么去进行索引和切片呢?

三维数组索引和切片的格式是在2维数组上的拓展,举例如下:

# 创建三维数组
arr3d = np.arange(1, 19, 1).reshape(3,2,3)
arr3d
Out: 
    array([[[ 1,  2,  3],
            [ 4,  5,  6]],

           [[ 7,  8,  9],
            [10, 11, 12]],

           [[13, 14, 15],
            [16, 17, 18]]])
# 对3维数组,取最外层索引为1、次外层索引为0的位置的元素
arr3d[1,0]
Out: array([7, 8, 9])
# 对3维数组,取最外层索引切片为0:2、次外层索引为1、最内层索引切片为2:3的位置的元素。
arr3d[:2,1,2:3]
Out: 
    array([[ 6],
       	   [12]])

1.4 高维数组

通常在我们的数据分析领域,甚至在AI大数据领域,原始的输入层都是二维的,即每个样本是一维的(由n个指标去定义一个样本),样本集则是二维的;在图片识别领域,通常用到的原始输入层都是三维的,因为图片一般都是三维的数组。所以朋友们熟悉二维和三维的常用索引、切片就足够应付绝大部分实际场景了。高维数组不建议大家深入挖掘。

2. 布尔型索引

当数组碰到实际场景的时候,布尔型的索引就显得更接地气一点了。打个比方来说的话,布尔型索引更像 Excel 中的筛选器,基于条件判定结果的布尔值,来决定哪些数据是我们的目标数据。

我们先来看一个例子:

cities = np.array(["hz", "sh", "hz", "bj", "wh", "sh", "sz"])
arr_rnd = np.random.randn(7,4)
arr_rnd
Out: 
    array([[ 0.52214772,  0.70276312, -2.2606387 ,  0.44816176],
       [ 1.8575996 , -0.07908252, -0.60976332, -1.24109283],
       [ 0.79739726,  0.86862637,  0.91748762,  1.58236216],
       [-2.01706647,  1.02411895, -0.27238117,  0.11644394],
       [-0.5413323 ,  0.41044278, -0.54505957, -0.27226035],
       [ 0.85592045,  1.14458831,  0.36227036, -0.22211316],
       [ 2.40476032,  1.22042702, -1.07018219,  0.95419508]])
# 利用数组的比较运算,生成一个布尔类型的数组
cities == "hz"
Out: array([ True, False, True, False, False, False, False])
# 利用布尔型数组,进行数组索引;观察索引的规律
# 我们可以做这样一个推断:布尔型数组的长度要和被索引的轴的长度一致
arr_rnd[cities == "hz"]
Out:
    array([[ 0.52214772,  0.70276312, -2.2606387 ,  0.44816176],
           [ 0.79739726,  0.86862637,  0.91748762,  1.58236216]])

这里需要注意的是,布尔型索引可以和索引切片联用:

# 利用布尔型数组、切片进行2个维度的索引
arr_rnd[cities == "hz":3]
Out: 
    array([[ 0.52214772,  0.70276312, -2.2606387 ],
           [ 0.79739726,  0.86862637,  0.91748762]])

当然了,布尔型索引使用得恰到好处,具有化腐朽为神奇的功效。例如对于上一步生成的服从标准正态分布的arr_rnd数组,我希望能够把其中的负数都筛选出来,并置为0。其实非常简便:

arr_rnd[arr_rnd<0] = 0
arr_rnd
Out: 
    array([[0.52214772, 0.70276312, 0.        , 0.44816176],
           [1.8575996 , 0.        , 0.        , 0.        ],
           [0.79739726, 0.86862637, 0.91748762, 1.58236216],
           [0.        , 1.02411895, 0.        , 0.11644394],
           [0.        , 0.41044278, 0.        , 0.        ],
           [0.85592045, 1.14458831, 0.36227036, 0.        ],
           [2.40476032, 1.22042702, 0.        , 0.95419508]])

在这里还需要强调一下布尔型数组的布尔算数运算符,有点绕口,但是其实很好理解,就是如何便捷地实现多个布尔型数组之间的“和”、“或”、“非”运算:

# 非运算 ~
~(cities == "hz")
Out: array([False,  True, False,  True,  True,  True,  True])
# 和运算 &
(cities == "hz") & (cities == "sz")
Out: array([False, False, False, False, False, False, False])
# 或运算 |
(cities == "hz") | (cities == "sz")
Out: array([ True, False,  True, False, False, False,  True])

3. 花式索引

简单总结一下,截止到这里,我们一共讲解了如何利用单个整数、切片、布尔列表以及它们之间的组合来进行索引。事实上,它们已经很强大了,强大到足够应付绝大部分场景。我们来看一个案例,我有一个4×6的二维数组,我希望做一个有趣的切片,把二维数组4个角的元素取出来,组成一个2×2的数组,效果如下:

图片描述

利用我们截止目前讲解到知识,也是可以实现的,只不过因为我们学到的都是连续的切片或者独立的单个索引,所以要实现这个效果会稍微繁琐一些。朋友们读到这里,不妨思考一下,你有什么方法来解决这个问题呢?

这里至少我们可以先提供2个思路:

# 新建4×6的二维数组arr_
arr_demo01 = np.arange(24).reshape(4,6)
arr_demo01
Our:
    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]])
# 方法1:分别将4个角的元素索引出来,然后把取出来的4个元素,重新组成一个新2×2的数组
arr_method1 = np.array([[arr_demo01[0,0], arr_demo01[0,-1]], 
                        [arr_demo01[-1,0],arr_demo01[-1,-1]]])
arr_method1
Out:
    array([[ 0,  5],
           [18, 23]])
# 方法2:利用布尔索引,可以同时索引不连续的行。分别对axis 0方向和axis 1方向进行索引。但是需要注意的是,得分2次索引;
arr_method2 = arr_demo01[[True, False, False, True]][:, [True, False, False, False, False, True]]
arr_method2:
Out:
    array([[ 0,  5],
           [18, 23]])

第一种方法更容易理解一些,第二种方法则是分别做两次索引。第一步是对axis 0方向进行布尔索引;第二步是综合运营切片和布尔索引,在上一步生成的结果上,对axis 1方向进行索引。

那有没有更简洁的方法呢?这里就要祭出花式索引了。

花式索引,其实就是利用整数数组来进行索引。基于上面我们生成的arr_demo01,我们来看两个简单的例子。

# 我们传入一个整数数组,对axis 0方向进行索引,并且索引结果的顺序和传入的整数数组一一对应:
arr_demo01[[2,0]]
Out: 
    array([[12, 13, 14, 15, 16, 17],
           [ 0,  1,  2,  3,  4,  5]])

如果我们同时传入两个整数数组呢,那结果可能会和预料中的有些不一样,我们看下面这个例子:

# 如果同时传入2个整数数组,中间用逗号分开。那么这两个数组会以两两配对的形式,对元素进行索引。而并不是一个矩形状的索引区域!
arr_demo01[[0,-1], [0,-1]]
Out: 
    array([ 0, 23])

这里实际索引到的结果是(0, 0), (-1, -1)这两个坐标上的元素,注意不是矩形状的区域哦。那如何实现上面demo的效果呢?这里我们再介绍几种方法,供大家比对、学习。

方法3:把4个角的坐标都传进去就行了,整个思路和方法1很接近,不过写法更简洁一些:

# 方法3:分别传入4个角的坐标,请朋友们注意观察传入的2个整数数组的规律
arr_demo01[[0, 0, -1, -1], [0, -1, 0, -1]]
Out: array([ 0,  5, 18, 23])

arr_demo01[[0,0,-1,-1], [0,-1,0,-1]].reshape(2,2)
Out: 
    array([[ 0,  5],
           [18, 23]])

注意观察能够发现,这种方式得到的数据只是一系列元素组成的一维数组,还需要我们额外通过reshape方法更改数据形状。

# 方法4:利用花式索引和切片混用,整体思路和方法2很相似。也是通过连续2次索引,得到一个矩形状的区域
arr_demo01[[0,-1]] [:,[0,-1]]
Out: 
    array([[ 0,  5],
           [18, 23]])

最后给大家介绍一个索引器,利用np.ix_函数,把传入的两个一维整数数组,转为为一个用于选取元素的区域索引器。

# 方法5:利用函数np.ix_,构建矩形索引器:
arr_demo01[np.ix_([0,-1], [0,-1])]
Out: 
    array([[ 0,  5],
           [18, 23]])

4. 总结

这一章节给朋友们展示了数组的“七十二变”,我们利用索引和切片的方法,可以随心所欲地变化数组的结构、提取其中的元素以及元素集合。

整体来看,索引可以分为4个类型:单个整数的索引、布尔索引、切片索引(和Python列表一致)以及整数数组。稍微复杂一些的,则是其中的组合索引,比如说其它索引与切片索引的组合。相信朋友们细细读懂上文的内容,必定能够掌握数据的索引方法。在日常的学习中,建议把这一章节当做案例一样的工具书,以逻辑理解为主,遇到问题来查阅即可,并不需要死记硬背。

数组的索引与切片是整个数据分析课程的基础内容,在下一章的Pandas内容中,我们还会遇到切片和索引的问题,届时大家可以对照起来看,加深理解。

猜你喜欢

转载自blog.csdn.net/qq_33254766/article/details/108362559
今日推荐