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 则表示对应位置不进行索引)
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。
print(np.cout_nonzero(x <= 3)) # 4
还可以通过 np.any
函数看数组中是否有为零的元素。传入 np.any
的数组,只要有一个是非零元素,则返回 True,否则返回 False。对于传入的是 bool 数组,对应的 True 就是 1,False 就是 0。
print(np.any(x == 0)) # True
使用 np.any
来判断数组中是否年龄都小于 0 呢?
- 只要有任何一个样本的年龄小于 0 时,则
np.any
就会返回 True; - 当样本中的年龄都大于等于 0时,则
np.any
就返回 False。
print(np.any(x < 0)) # False
与 np.any
相对应的还有 np.all
,只有当传入的 bool 数组中的元素全部都为 True,np.all
才会返回 True,其余情况返回 False。
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.any
和 np.all
也可以指定运算的维度。
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 > 3
和 x < 10
返回的是两个形状相同的布尔数组,这里希望两个布尔数组按照相应的索引位置进行与的运算,相当于把两个布尔数组中的每个元素看成是一个位。如果使用条件运算符会抛出异常:
print(np.sum((x > 3) && (x < 10)))
'''
print(np.sum((x > 3) && (x < 10)))
^
SyntaxError: invalid syntax
'''
类似的位运算符还有 |
, ~
。统计孩子年龄是偶数或者年龄大于 10 的个数。
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