布尔索引 boolean indexing 定义
布尔索引是一种通过使用布尔值(True或False)数组来选择数组中的元素的方法。布尔数组的长度必须与源数组相同,它指示了哪些元素应该被选择或过滤。
通过使用布尔索引,我们可以根据指定条件来选择数组中的元素,这些条件可以是基于元素值的比较、逻辑运算符的组合或其他布尔表达式。
简单来将, 布尔索引就是让 1个 boolean dtype 的adarray 作为另1个adarry 的 filter, 所以 布尔索引更应该是个filter 而不是 index。
语法:
arr_a[arr_b] 其中 arr_a 和 arr_b 都是ndarray 对象, 而且arr_b.dtype 是 bool
"""
introduce of ndarray boolean index:
1. ndarray[bool_array] # means select the elements where bool_array is True
2. bool_array could be a boolean ndarray or a list of boolean values
3. bool_array should have the same length with the ndarray
4. bool_array could be generated by comparison operators e.g. >, <, ==, !=
5. bool_array could be generated by logical operators e.g. &, |, ~
6. bool_array could be generated by np.logical_and, np.logical_or, np.logical_not
7. bool_array could be generated by np.all, np.any
"""
1维数组的 boolean indexing
举个例子:
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
logger.info(f"arr: {arr}") # [0 1 2 3 4 5 6 7 8 9]
arr_filter = [True, False, True, False, True, False, True, False, True, False]
logger.info(f"arr[arr_filter]: {arr[arr_filter]}") # [0 2 4 6 8]
arr_filter = [True, False, True, False, True, False, True, False, True, False, True]
# logger.info(f"arr[arr_filter]: {arr[arr_filter]}") # error, because the length of arr_filter is not the same as arr
上面例子中, 首先创建了1个1维数组 arr, dtype = int32
然后构建了另1个 1维数组arr_filter , dtype = bool ,
然后利用 arr[arr_filter] 就可以返回另 一个 adarray 对象, 新的对象只包含了 符合 arr_filter 的元素。
在这个例子中, 就得到了1个偶数的子数组 [0, 2, 4, 6, 8]
值得注意的是, arr_filter 的shape 必须与 arr 一致, 才能另到True/false 和 arr的元素一一对应, 否则会出错
更优雅地编写arr_filter
上的arr_filter 是暴力定义的, 显然不符合实际场景
其实如果有1个公式的, 例如上面例子是求偶数, 是可以有其他方法来定义的.
- 直接利用源arr 名字套用公式
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
logger.info(f"arr: {arr}") # [0 1 2 3 4 5 6 7 8 9]
arr_filter = arr % 2 == 0
logger.info(f"arr_filter: {arr_filter}") # [ True False True False True False True False True False]
是不是很方便?
- 加上括号
arr_filter = (arr % 2 == 0)
logger.info(f"arr_filter: {arr_filter}") # [ True False True False True False True False True False]
注意 return的不是1个tuple, 而是1个ndarray
- 可以用logic 运算符 & | 注意不能是&& ||
arr_filter = (arr % 2 == 0) & (arr > 5)
logger.info(f"arr_filter: {arr_filter}") # [False False False False False False True True True True]
- 不能用随便用[ ] 中括号
(arr % 2 ==0) 返回的是1个1 维 的ndarray
而[ arr % 2 = 0) 返回的是2维 ndarray
arr_filter_2_dimension = [arr % 2 == 0]
logger.info(f"arr_filter_2_dimension: {arr_filter_2_dimension}") # [array([ True, False, True, False, True, False, True, False, True, False])]
使用lambda
如果是1般的python list , 我们可以lambda 去做filter
例如:
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered_list = list(filter(lambda x: x % 2 == 0, my_list))
print(filtered_list) # [2, 4, 6, 8, 10]
还有更简洁的写法:
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered_list = [x for x in my_list if x % 2 == 0]
print(filtered_list)
而这种简洁写法也适用于 ndarray
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
arr_filter = [True if x % 2 == 0 else False for x in arr]
logger.info(f"arr_filter: {arr_filter}") # [True, False, True, False, True, False, True, False, True, False]
logger.info(f"arr[arr_filter]: {arr[arr_filter]}") # [0 2 4 6 8]
上面写法还可以进一步简洁
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
arr_filter = [x for x in arr if x % 2 == 0]
logger.info(f"arr_filter: {arr_filter}") # [0, 2, 4, 6, 8]
使用np.vectorize()
np.vectorize 可以把1个普通的函数 转化为1个可以 用于ndarrary的函数
如:
arr = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# np.vectorize(lambda x: x % 2 == 0) returns a function that can be applied to each element of the array
arr_filter = np.vectorize(lambda x: x % 2 == 0)(arr)
logger.info(f"arr_filter: {arr_filter}") # [False False False False False False True True True True]
浅复制与深复制
正常的切片操作 是浅复制
例如:
arr_a = np.arange(1, 11) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr_a.shape = (2, 5)
logger.info(f"arr_a: {arr_a}") # [[ 1 2 3 4 5] [ 6 7 8 9 10]]
arr_a0 = arr_a[0]
arr_a0[0] = 100
# arr_a0 is a reference to arr_a[0]
assert arr_a0[0] == arr_a[0][0] == 100
arr_a0 是 arr_a 中第1个n-1 维元素, 当arr_a0 的元素被修改, 则代表arr_a 本身被修改
用[:] 方法切片也是一样的
logger.info("deep copy2 ================================")
arr_a = np.arange(1, 11) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr_a.shape = (2, 5)
logger.info(f"arr_a: {arr_a}") # [[ 1 2 3 4 5] [ 6 7 8 9 10]]
arr_a0 = arr_a[:, 2:] # [[3 4 5] [8 9 10]]
# arr_a0 is a reference to arr_a[0]
arr_a0[0][0] = 100
assert arr_a0[0][0] == arr_a[0][2] == 100
但是, boolean 索引 build 出来的新adarray 是深复制出来的。 占用另一块内存, 与源数组不相互影响
arr_a = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
arr_filter = arr_a % 2 == 0
logger.info(f"arr_filter: {arr_filter}") # [ True False True False True False True False True False]
arr_a0 = arr_a[arr_filter]
arr_a0[0] = 100
assert arr_a[0] ==0
2维数组的 boolean indexing
2 维数组和多维数组的 boolean indexing 也不难理解, 我们只要构建 filter 的 ndarray对象,并令其shape 与源adarray保持一致
值得注意是, 无论源数组是多少维
filter 之后的新数组都是1维的, 只包含了符合条件的1为数组, 为何不能保持原来的维数? 因为元素数量减少了, 无法保持原来的形状
例子:
def boolean_index_2_dimension():
logger.info("boolean_index_2_dimension ================================")
arr_a = np.arange(1, 11) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr_a.shape = (2, 5)
logger.info(f"arr_a: {arr_a}") # [[ 1 2 3 4 5] [ 6 7 8 9 10]]
arr_filter = arr_a % 2 == 0 # [[False True False True False] [ True False True False True]]
logger.info(f"arr_filter: {arr_filter}")
logger.info(f"arr_a[arr_filter]: {arr_a[arr_filter]}") # [2 4 6 8 10] why not [[2 4] [6 8 10]] because the shape of arr_filter is (2, 5)
def boolean_index_3_dimension():
logger.info("boolean_index_2_dimension ================================")
arr_a = np.arange(1, 28) # 1 to 27
arr_a.shape = (3, 3, 3)
logger.info(f"arr_a: {arr_a}") # [[[ 1 2 3] [ 4 5 6] [ 7 8 9]] [[10 11 12] [13 14 15] [16 17 18]] [[19 20 21] [22 23 24] [25 26 27]]]
arr_filter = arr_a % 2 == 0 # [[[False True False] [ True False True] [False True False]] [[ True False True] [False True False] [ True False True]] [[False True False] [ True False True] [False True False]]]
logger.info(f"arr_filter: {arr_filter}")
logger.info(f"arr_a[arr_filter]: {arr_a[arr_filter]}") # [ 2 4 6 8 10 12 14 16 18 20 22 24 26] why not [[2 4 6] [8 10 12] [14 16 18] [20 22 24 26]] because the shape of arr_filter is (3, 3, 3)

458

被折叠的 条评论
为什么被折叠?



