numpy(五)——数据索引与查找

numpy(五)——数据索引与查找

import numpy as np

numpy中的索引方式大致可以分为基本切片索引,花式索引和布尔掩码索引三种。三者之间又可以相互组合,以准确选取需要的数据。现介绍如下

  • 基本切片索引

arr_slice  = np.arange(20).reshape(4,5)
arr_slice
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])

选择第一行

line0 = arr_slice[0]
line0
array([0, 1, 2, 3, 4])

选择前两行

line0_1 = arr_slice[0:2]
line0_1
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

选择所有行

line_all = arr_slice[:]
line_all
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])

如果要选取某一列或者某几列,则情况较为复杂,一般情况下这一这样做:

col0 = [x[0] for x in arr_slice]
col0
[0, 5, 10, 15]

这是很好的Python列表推导式,但不是numpy的风格,numpy应当更加高效。方法就是,在切片的中括号中加入和数组维度相等的切片表达式,以此选取任意的数组子集

以numpy方式选取第一行

col0_numpy = arr_slice[:,0]
col0_numpy
array([ 0,  5, 10, 15])

选取前两列

col0_1 = arr_slice[:,0:2]
col0_1
array([[ 0,  1],
       [ 5,  6],
       [10, 11],
       [15, 16]])

选取前两行和前两列

arr_sub = arr_slice[0:2,0:2]
arr_sub
array([[0, 1],
       [5, 6]])

只要稍具Python语言的基础,便可轻易学会这种索引方式。所需要注意的地方只有一点:Python切片产生的是一个独立于原列表的副本,而numpy中的切片索引虽然也返回一个新对象,但这个新对象是原数组的视图:

修改line0和col0_1,然后查看原数组

line0[2] = 999
arr_slice
array([[  0,   1, 999,   3,   4],
       [  5,   6,   7,   8,   9],
       [ 10,  11,  12,  13,  14],
       [ 15,  16,  17,  18,  19]])
col0_1[1] = np.array([999,999])
arr_slice
array([[  0,   1, 999,   3,   4],
       [999, 999,   7,   8,   9],
       [ 10,  11,  12,  13,  14],
       [ 15,  16,  17,  18,  19]])
  • 花式索引

花式索引的本质就是用一个索引列表代替切片表达式,并且使用这种方式索引,可以自由选取你所需要的特定行

arr_fancy = np.arange(20).reshape(4,5)
arr_fancy
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])

选取第一行

line_fancy_0 = arr_fancy[[0]] #请注意,若是这里直接传入0而不是传入[0]那么就将
                              #变为切片索引而非花式索引。其中的区别稍候会将
line_fancy_0
array([[0, 1, 2, 3, 4]])

选取第一行和第三行

line_fancy_0_2 = arr_fancy[[0,2]]
line_fancy_0_2
array([[ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14]])

返回数组的数据顺序也可以根据输入的列表的顺序而改变

line_fancy_0_2_list = arr_fancy[[2,0]]
line_fancy_0_2_list
array([[10, 11, 12, 13, 14],
       [ 0,  1,  2,  3,  4]])

那么如何用花式索引选取特定的列呢?你也许会以为以下例子会选取原数组的第一行第二行以及第一列第二列,因此结果应该是0,1,5,6这四个数的:

arr_sub_fancy = arr_fancy[[0,1],[0,1]]
arr_sub_fancy
array([0, 6])

但我们发现,结果并不是我们所料想的那样,其实**返回的结果是原数组中坐标为(0,0)和(1,1)的点。**这也是在花式索引中所需要特别注意的。那么究竟如何选取特定的列,比如第一列和第三列呢?我们的实现方法是这样的:

arr_sub_fancy_13 = arr_fancy[:,[0,2]]
arr_sub_fancy_13
array([[ 0,  2],
       [ 5,  7],
       [10, 12],
       [15, 17]])

也可以沿着这种思路进行更为复杂的选取,比如选取第一行第三行和第一列第三列(交集)。而总体的思路就是分两次选取,先选取所需要的行,而不管列,然后再针对列进行特定的选取

arr_complex = arr_fancy[[0,2]][:,[0,2]]
arr_complex
array([[ 0,  2],
       [10, 12]])

关于花式索引选取子数组的的更多介绍,参看《利用Python进行数据分析 第二版》P104以及《Python数据科学手册》P70

关于花式索引,另外特别需要注意的一点就是:“结果的形状与索引数组的形状一致,而不是与被索引数组的形状一致”——《Python数据科学手册》P69

arr_test = np.arange(15)
arr_test
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
ind = np.array([[1,3],
       [4,5]])
arr_result = arr_test[ind]
arr_result
array([[1, 3],
       [4, 5]])

不过需要注意的是,这种情况下的索引数组ind,必须是ndarray对象,而不能是普通的Python列表

ind_list = [
    [1,3],
    [4,5]
]
arr_result_list = arr_test[ind_list]
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-6-15fab3ec0e0c> in <module>
      3     [4,5]
      4 ]
----> 5 arr_result_list = arr_test[ind_list]


IndexError: too many indices for array

也许你会说,上文中line_fancy_0_2的例子中,传入的明明是一个一维的Python列表,为什么返回的是二维的ndarray数组呢?这样子结果的形状与索引数组的形状岂不是不一致了吗?其实,二者还是一致的,line_fancy_0_2数组本质上就是一个一维数组,只不过其中的每一个元素又是一个数组罢了,这才会形成最终的2x5的效果。那么如果用一个真正的二维数组去索引arr_fancy数组又会如何呢?

ind_2d = np.array([
    [0,3],
    [2,1]
])
arr_result_2d = arr_fancy[ind_2d]
arr_result_2d
array([[[ 0,  1,  2,  3,  4],
        [15, 16, 17, 18, 19]],

       [[10, 11, 12, 13, 14],
        [ 5,  6,  7,  8,  9]]])
arr_result_2d.shape
(2, 2, 5)

可以清楚地发现,此时索引数组的形状为2*2,但又因为被索引数组是一个二维数组,第0,3,2,1个元素皆为5有个元素的一维数组,因此,最终的输出形状为2x2x5

关于花式索引第三点需要注意的就是:花式索引得到的结果为原数组的的副本而非视图

line_fancy_0_2[0] = 111 #这是numpy数组的向量化操作
line_fancy_0_2
array([[111, 111, 111, 111, 111],
       [ 10,  11,  12,  13,  14]])
arr_fancy #原数组arr_fancy并未改变
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
  • 布尔掩码索引

names = np.array(["Alice","Bob","Cindy","David","Eward"])
data = np.random.randn(5,4) #正态分布随机值
data
array([[-0.06258778,  0.52872147, -0.04599932,  0.74988058],
       [ 0.97329726, -0.17697473, -0.39675704, -0.04186056],
       [ 0.03632327, -0.48029852,  0.91197692, -0.63599128],
       [ 1.0569272 ,  0.07264866,  0.81851043, -0.89669881],
       [ 0.74546042, -1.32091392, -0.4786972 , -0.89497835]])
ind = names=="Cindy"
ind
array([False, False,  True, False, False], dtype=bool)
data_Cindy = data[ind]
data_Cindy
array([[ 0.03632327, -0.48029852,  0.91197692, -0.63599128]])
data_Eward = data[names=="Eward"]
data_Eward
array([[ 0.74546042, -1.32091392, -0.4786972 , -0.89497835]])

当然,你也可以在布尔索引中使用与或非进行更加复杂的布尔索引。需要注意的地方在于,Python中的关键字and or和not对于布尔索引并没有作用,应该使用& | ~进行替代。更深入的讨论,参见《Python数据科学手册》P67,P68

data_Alice_Bob = data[(names=="Alice") | (names=="Bob")]
data_Alice_Bob
array([[-0.06258778,  0.52872147, -0.04599932,  0.74988058],
       [ 0.97329726, -0.17697473, -0.39675704, -0.04186056]])
data_not_Alice = data[~(names=="Alice")]
data_not_Alice
array([[ 0.97329726, -0.17697473, -0.39675704, -0.04186056],
       [ 0.03632327, -0.48029852,  0.91197692, -0.63599128],
       [ 1.0569272 ,  0.07264866,  0.81851043, -0.89669881],
       [ 0.74546042, -1.32091392, -0.4786972 , -0.89497835]])

当然,默认布尔索引都是沿着第0条轴,也就是沿着上下方向进行映射匹配,从而选出行的。(在pandas的DataFrame结构中,布尔索引也可以沿着第1条轴进行映射匹配,从而选出列)。
我们再来看另一种情况:

data_greater_0 = data > 0
data_greater_0
array([[False,  True, False,  True],
       [ True, False, False, False],
       [ True, False,  True, False],
       [ True,  True,  True, False],
       [ True, False, False, False]], dtype=bool)
data_0 = data[data_greater_0]
data_0
array([ 0.52872147,  0.74988058,  0.97329726,  0.03632327,  0.91197692,
        1.0569272 ,  0.07264866,  0.81851043,  0.74546042])

可以看见,data_greater_0将所有data中大于0的数据标记为了True值,并且用data_greater_0q索引data数组,结果就是选取了所有data中大于0的数据,并且以一个一维数组的形式返回。这时候就不再是按照第0条轴的方向进行映射匹配,从而选出一整行的数据了,因为此时索引数组的形状和原数组一致,所以是逐元素的选取,而不是逐行的选取。

另外需要注意的就是:布尔索引得到的返回结果也是原数组的副本而非视图。只有纯切片数组才会返回视图,并且当三种方式混合使用时,返回的也是副本而非视图。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值