机器学习入门 3-10 Numpy中的比较和Fancy Indexing

2022-11-08 13:24:04 浏览数 (1)

Fancy Indexing

首先创建一个向量。

代码语言:javascript复制
import numpy as np

x = np.arange(16)

我们可以对向量进行和 Python 列表一样的索引和切片操作。

代码语言:javascript复制
x[3]   # 索引第4个元素
x[3:9]   # 索引第4~9个元素(包左不包右)
x[3:9:2] # 在第4~9个元素中,每隔2个元素索引一个值(包左不包右)

如果我们想索引向量中 "第4,6,9 个元素",上面的索引和切片操作显然不能满足我们的需求。比较直观的想法是直接将三个位置的元素索引出来,然后再存储到一个新的向量中。

代码语言:javascript复制
np.array([x[3], x[5], x[8]])

不过这种调用方式显然不够简洁,方便。因此,NumPy 提供了 Fancy Indexing。

代码语言:javascript复制
index = np.array([3, 5, 8])
print(x[index]) # [3 5 8]

创建一个元素值为索引位置的向量 index,直接通过 x[index] 来进行索引。通过结果也可以看出,np.array([x[3], x[5], x[8]) 和这种 Fancy Indexing 的方式是等价。

如果指定 index 为一个二维矩阵。

代码语言:javascript复制
index = np.array([[0, 2],
                  [1, 3]])
print(x[index])
'''
[[0 2]
 [1 3]]
'''

按照 index 为一个一维向量为例,上面的 Fancy Indexing 方式与下面代码执行的结果一样。

代码语言:javascript复制
np.array([[x[0], x[2]],
          [x[1], x[3]]])

Fancy Indexing 不仅能够应用在一维的向量中,而且还适用于二维的矩阵。

代码语言:javascript复制
X = x.reshape(4, -1)
print(X)
'''
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
'''

我们想要索引矩阵中的某几个元素,只需要传入这几个元素所在的行和列。

代码语言:javascript复制
row = np.array([0, 1, 2])
col = np.array([1, 2, 3])
print(X[row, col])
'''
[ 1  6 11]
'''

在二维矩阵中需要确定某个元素的位置,需要明确这个元素的行标和列标。对于 row = np.array([0, 1, 2])col = np.array([1, 2, 3]) 而言,在二维矩阵中索引元素的位置为 [0,1], [1, 2], [2, 3]

我们也可以只对某一行的某些列进行索引,比如下面就是对矩阵第一行中的第二、三、四列的元素进行索引。

代码语言:javascript复制
print(X[0, col])
'''
[1 2 3]
'''

下面是对矩阵中前两行中的第二、三、四列的元素进行索引。

代码语言:javascript复制
print(X[:2, col])
'''
[[1 2 3]
 [5 6 7]]
'''

索引 index 数组不仅局限具体的索引位置,我们还可以使用 bool 值来决定对应位置是否被索引。比如,我们想要索引第二、三行中的第一、三、四列。除了可以使用 col = np.array([0, 2, 3]),还可以使用 bool 数组。(True 表示对应位置进行索引,而 False 则表示对应位置不进行索引)

代码语言:javascript复制
col = np.array([True, False, True, True])
print(X[1:3, col])
'''
[[ 4  6  7]
 [ 8 10 11]]
'''

numpy.array 比较

使用 bool 来进行 Fancy Indexing 比较常见,很多时候我们会对数据进行批量的比较,这种批量比较的返回值就是 bool 数组。

代码语言:javascript复制
print(x < 3)
'''
[ True  True  True False False False False False False False False False
 False False False False]
'''

x < 3 将 x 中的所有元素都和 3 进行比较,返回的是一个和 x 相同形状的 bool 数组。

  • 当 x 中的某个元素小于 3,则在 bool 数组中对应位置返回 True;
  • 当 x 中的某个元素大于等于 3,则在 bool 数组中对应位置返回 False。

类似的,我们可以对所有的比较运算符进行这种操作。

代码语言:javascript复制
x > 3
x <= 3
x >= 3
x == 3
x != 3

对于这种比较运算符,我们可以与加减乘除进行结合实现更加复杂的逻辑。

代码语言:javascript复制
print(2 * x == 24 - 4 * x)
'''
[False False False False  True False False False False False False False
 False False False False]
'''

对于这种比较运算符,我们同样可以应用在二维矩阵上。

代码语言:javascript复制
X = x.reshape(4, -1)
print(X)
'''
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
'''
代码语言:javascript复制
print(X < 6)
'''
[[ True  True  True  True]
 [ True  True False False]
 [False False False False]
 [False False False False]]
'''

此时,在二维矩阵上的比较运算符,返回的是一个与二维矩阵相同形状的 bool 数组。

这种比较有什么意义呢?假设现在将下面的一维向量赋予实际的意义,收集 16 个孩子的年龄。换句话说,收集 16 个样本,每个样本只有 1 个特征。

代码语言:javascript复制
x = np.arange(16)
print(x)
'''
[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15]
'''

如果我们想统计,年龄小于等于 3 岁孩子的个数。

代码语言:javascript复制
print(np.sum(x <= 3)) # 4

NumPy 中有一个 np.cout_nonzero 函数,能够统计传入函数的数组中有多少个非零元素,对于传入的是 bool 数组,对应的 True 就是 1,False 就是 0。

代码语言:javascript复制
print(np.cout_nonzero(x <= 3)) # 4

还可以通过 np.any 函数看数组中是否有为零的元素。传入 np.any 的数组,只要有一个是非零元素,则返回 True,否则返回 False。对于传入的是 bool 数组,对应的 True 就是 1,False 就是 0。

代码语言:javascript复制
print(np.any(x == 0)) # True

使用 np.any 来判断数组中是否年龄都小于 0 呢?

  • 只要有任何一个样本的年龄小于 0 时,则 np.any 就会返回 True;
  • 当样本中的年龄都大于等于 0时,则 np.any 就返回 False。
代码语言:javascript复制
print(np.any(x < 0)) # False

np.any 相对应的还有 np.all,只有当传入的 bool 数组中的元素全部都为 True,np.all 才会返回 True,其余情况返回 False。

代码语言:javascript复制
print(np.all(x >= 0)) # True
print(np.all(x > 0))  # False

同样,上面的方法依然适用于矩阵。

代码语言:javascript复制
X = x.reshape(4, -1)
print(X)
'''
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
'''

如果我们想看看矩阵中的元素有多少个偶数。

代码语言:javascript复制
print(np.sum(X % 2 == 0)) # 8

类似的,对于矩阵这种多维数组,我们可以指定运算的维度。

代码语言:javascript复制
print(np.sum(X % 2 == 0, axis = 1)) # 沿着列的方向,每一行有多少个偶数
'''
[2 2 2 2]
'''

print(np.sum(X % 2 == 0, axis = 0)) # 沿着行的方向,每一列有多少个偶数
'''
[4 0 4 0]
'''

对于矩阵这种多维数组,np.anynp.all 也可以指定运算的维度。

代码语言:javascript复制
print(np.all(X > 0, axis = 1)) # 沿着列的方向,看的是每一行
'''
[False  True  True  True]
'''

第 0 行不满足所有的元素都大于 0,因为第 0 行中有一个元素值等于 0。

对于这些比较操作,我们可以进行组合操作。

代码语言:javascript复制
x = np.arange(16)
print(x)
'''
[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15]
'''

统计孩子年龄大于 3 且小于 10 的个数。

代码语言:javascript复制
print(np.sum((x > 3) & (x < 10))) # 6

需要注意的是,此处使用的是位运算符 &,不是使用的条件运算符 &&(条件运算符连接的是两个条件)。x > 3x < 10 返回的是两个形状相同的布尔数组,这里希望两个布尔数组按照相应的索引位置进行与的运算,相当于把两个布尔数组中的每个元素看成是一个位。如果使用条件运算符会抛出异常:

代码语言:javascript复制
print(np.sum((x > 3) && (x < 10)))
'''
    print(np.sum((x > 3) && (x < 10)))
                          ^
SyntaxError: invalid syntax
'''

类似的位运算符还有 |, ~。统计孩子年龄是偶数或者年龄大于 10 的个数。

代码语言:javascript复制
print(np.sum((x % 2 == 0) | (x > 10))) # 11

统计孩子年龄不等于 0 的个数。

代码语言:javascript复制
print(np.sum(~(x == 0))) # 15

布尔数组能够作为 Fancy Indexing 的索引数组非常方便。找出年龄小于 5 岁的孩子。

代码语言:javascript复制
print(x[x < 5])
'''
[0 1 2 3 4]
'''

找出年龄是偶数的孩子。

代码语言:javascript复制
print(x[x % 2 == 0])
'''
[ 0,  2,  4,  6,  8, 10, 12, 14]
'''

或者对于一个矩阵,抽出行的最后一个元素能够 3 整除的行,列无要求。

代码语言:javascript复制
print(X[X[:,3] % 3 == 0, :])
'''
[[ 0  1  2  3]
 [12 13 14 15]]
'''

Pandas

对于更加高级的表格进行处理,通常使用 Pandas 库。不过,在 sklearn 中封装的机器学习算法往往接收的数据类型是 NumPy 数组。因此,我们使用 sklearn 实现机器学习算法通常会依照下面的流程:

  • 使用 Pandas 库对数据进行一系列的预处理操作;
  • 将预处理后的数据转换成 NumPy 数组;
  • 使用 sklearn 对 NumPy 数组进行机器学习算法。

References:

Python3入门机器学习 经典算法与应用: https://coding.imooc.com/class/chapter/169.html#Anchor

0 人点赞