我的知识记录

digSelf

机器学习基础:对NumPy数组进行数据处理操作

2021-10-31
机器学习基础:对NumPy数组进行数据处理操作

在将目标数据加载到NumPy数组中后,就需要进行一系列的数组处理操作,包括:对NumPy数组的访问、切片、过滤、计算数据的统计学指标、数据间的聚合和组合以及对计算后的结果保存和再加载的操作,本篇博文就对上述常用操作进行了阐述...

访问NumPy数组和对Numpy数组切片

NumPy数组进行访问

对于NumPy数组中的数据的索引方式与Python中的list的索引访问是一致的,均可以通过数组的下标来进行访问。对于NumPy中的多维数组,它等价于Python中的list中的list

arr = np.array([1, 2, 3, 4, 5])
print(arr[0])
print(arr[4])

arr = np.array([[6, 3], [0, 2]])
# Subarray
print(repr(arr[0]))

NumPy数组切片

NumPy数组也支持切片,可以使用:运算符来进行切片。在NumPy数组切片时,也可以使用负数指定索引方向是从后往前的方向进行索引的。

下面的示例是对一维数组进行切片的代码示例:

arr = np.array([1, 2, 3, 4, 5])
print(repr(arr[:]))
print(repr(arr[1:]))
print(repr(arr[2:4]))
print(repr(arr[:-1]))
print(repr(arr[-2:]))

需要注意的是,在切片时,它遵循的也是左闭右开的表示方式,即:[m, n),不包括索引为n的元素。

对于多维数组的切片,使用逗号,将各个维度分开即可。

arr = np.array([[1, 2, 3],
                [4, 5, 6],
                [7, 8, 9]])
print(repr(arr[:]))
print(repr(arr[1:]))
print(repr(arr[:, -1]))
print(repr(arr[:, 1:]))
print(repr(arr[0:1, 1:]))
print(repr(arr[0, 1:]))

返回最大值或最小值元素的索引下标

有的时候,我们经常需要返回数组中最大值或者最小值元素所在的索引下标,可以使用np.argmaxnp.argmin分别获取最大值和最小值元素所在的索引下标值。

arr = np.array([[-2, -1, -3],
                [4, 5, -6],
                [-3, 9, 1]])
print(np.argmin(arr[0]))
print(np.argmax(arr[2]))
print(np.argmin(arr))

对于多维数组的argmax或者argmin,它返回的索引下标相当于将给定的多维数组flatten为一维数组后,在一维数组中的索引下标值。

np.argminnp.argmax还有一个重要的参数axis,这个关键字参数可以指定是对于哪一维度进行的操作。

arr = np.array([[-2, -1, -3],
                [4, 5, -6],
                [-3, 9, 1]])
print(repr(np.argmin(arr, axis=0)))
print(repr(np.argmin(arr, axis=1)))
print(repr(np.argmax(arr, axis=-1)))

arr = np.arange(24)
reshaped = arr.reshape((2, 4, 3))
np.random.shuffle(reshaped)
print(repr(reshaped))
print(repr(np.argmin(reshaped, axis=-1)))

axis=0时,相当于对列做argmax/argmin的操作,返回行下标;当axis=1时,相当于对行做argmax/argmin操作,返回列下标值;当axis=-1相当于对数组的最后一个维度做argmax/agmin,在上例中,由于arr是二维数组,所以等价于是对行做argmax/argmin操作,返回列下标值。简而言之:axis指定的是返回的是最大值或者最小值所在位置的行下标值还是列下标值

过滤NumPy数组中的数据

过滤数据

有的时候,我们只关心数据中的符合条件的那一部分,而不是数据的整体。因此我们需要对数据进行过滤。比如说:当我们需要统计某一类别的图书的售价时,我们只关心数据中每个图书的售价部分,而不是所有的数据。

过滤数据的关键是通过基础关系运算符,如:==, >等等。在NumPy的数组中逐个元素的做基础关系运算。

arr = np.array([[0, 2, 3],
                [1, 3, -6],
                [-3, -2, 1]])
print(repr(arr == 3))
print(repr(arr > 0))
print(repr(arr != 1))
# Negated from the previous step
print(repr(~(arr != 1)))

Numpy之所以操作如此方便,就在于它对于常用的运算符进行了操作符重载,方便我们的使用。

上面示例中的关系运算的结果是一个boolean数组,它标志着数组所在位置中的元素是否符合所进行的关系运算。需要注意的是:np.nan不能进行任何的关系运算,它提供了一个专有的内置函数来判断一个值是否是nan,这个函数是np.isnan

arr = np.array([[0, 2, np.nan],
                [1, np.nan, -6],
                [np.nan, -2, 1]])
print(repr(np.isnan(arr)))

NumPy中进行过滤

np.where函数接受一个boolean数组,返回boolean数组中值为True的数组下表所有所构成的数组的元组。一般情况下,boolean数组的True标志的是我们所关心的值。

print(repr(np.where([True, False, True])))

上述代码示例返回的是一维数组的元组,这个元组的元素的个数等于数据的维度个数。上述元组中的每一个数组表示的是在该维度下,True元素所在的位置的索引下标。

arr = np.array([0, 3, 5, 3, 1])
print(repr(np.where(arr == 3)))
arr = np.array([[0, 2, 3],[1, 0, 0], [-3, 0, 0]])
x_ind, y_ind = np.where(arr != 0)
print(repr(x_ind)) # x indices of non-zero elements
print(repr(y_ind)) # y indices of non-zero elements
print(repr(arr[x_ind, y_ind]))

在使用np.where函数时,它只接受1个参数或者3个参数。当使用3个参数时,第一个实参仍然是一个boolean数组,而第二个参数和第三个参数分别指的是boolean数组中的True所在的位置的元素的替换值以及False所在的位置的元素的替换值。说的有点绕,可以看下面的代码示例:

np_filter = np.array([[True, False], [False, True]])
positives = np.array([[1, 2], [3, 4]])
negatives = np.array([[-2, -5], [-1, -8]])
print(repr(np.where(np_filter, positives, negatives)))
np_filter = positives > 2
print(repr(np.where(np_filter, positives, negatives)))
np_filter = negatives > 0
print(repr(np.where(np_filter, positives, negatives)))

输出的结果为:

array([[ 1, -5],       
       [-1,  4]])
array([[-2, -5],       
       [ 3,  4]])
array([[-2, -5],       
       [-1, -8]])

需要注意的是:np.where的第二个参数和第三个参数要和第一个参数的形状是相同的。但是当我们想要使用一个常量替换值时,可以使用broadcasting技术,我们可以使用这个常量值本身作为参数而不是由它构成的数组。其他情况下,第二个和第三个参数的形状要与第一个参数的形状相同

np_filter = np.array([[True, False], [False, True]])
positives = np.array([[1, 2], [3, 4]])
print(repr(np.where(np_filter, positives, -1)))

轴向过滤(Axis-wise filtering)

当我们的过滤操作是基于行数据或者列数据时,可以使用np.anynp.all函数。它们所接受的参数是相同的,都是一个boolean数组,返回的是一个布尔值或者一个布尔数组。

arr = np.array([[-2, -1, -3],                
                [4, 5, -6],                
                [3, 9, 1]])
print(repr(arr > 0))
print(np.any(arr > 0))
print(np.all(arr > 0))

np.any函数等价于逻辑或操作,只要有一个符合条件,则值为真,否则为假。np.all函数等价于逻辑与操作,必须所有的都要符合条件,否则值为假。

当只传入一个参数,它对于整个传入数组做操作,所以这两个函数返回的都是一个布尔值。当不仅仅传入一个参数,指定axis关键字参数时,返回的就会是一个布尔数组。当axis=0时,相当于是对数组中逐列做操作;当axis=1时,相当于是对数组中逐行做某种操作;当axis=-1时,以数组中的最后一个维度为单位逐一的做某种动作。简而言之:当指定axis时,相当于对另一维度逐一验证是否满足某一条件

arr = np.array([[-2, -1, -3],                
                [4, 5, -6],                
                [3, 9, 1]])
print(repr(arr > 0))
print(repr(np.any(arr > 0, axis=0)))
print(repr(np.any(arr > 0, axis=1)))
print(repr(np.all(arr > 0, axis=1)))

可以将np.any, np.allnp.where组合使用,过滤出一行或者一列数据。示例代码如下:

arr = np.array([[-2, -1, -3], [4, 5, -6], [3, 9, 1]])
has_positive = np.any(arr > 0, axis=1)
print(has_positive)print(repr(arr[np.where(has_positive)]))

NumPy数组中的统计学指标

获取数组中的最大最小值

NumPy的数组对象中,内置了minmax函数,可以返回当前数组对象中所存储的数据的最小或者最大值。它们也都有axis关键词参数,它的值与上面两节提到的用法相同。

arr = np.array([[0, 72, 3], [1, 3, -60],[-3, -2, 4]])
print(arr.min())
print(arr.max())
print(repr(arr.min(axis=0)))
print(repr(arr.max(axis=-1)))

统计学指标

np.mean, np.median, np.var分别用来计算均值、中位数以及方差。

arr = np.array([[0, 72, 3], [1, 3, -60], [-3, -2, 4]])
print(np.mean(arr))
print(np.var(arr))
print(np.median(arr))
print(repr(np.median(arr, axis=-1)))

可以在NumPy中的统计学指标查看其余的统计学指标

NumPy中的数据聚合技术

对数组求和

对于一维数组可以使用np.sum函数求累加和。对于多维数组,可以通过axis关键词参数指定对哪一个维度求累加和,其用法与之前两节所提到的用法一致;如果没有指定axis最是对整个数组求累加和

arr = np.array([[0, 72, 3], [1, 3, -60], [-3, -2, 4]])
print(np.sum(arr))
print(repr(np.sum(arr, axis=0)))
print(repr(np.sum(arr, axis=1)))

如果想要求某个数组的前缀和,可以使用np.cumsum进行计算。np.cumsum也有两个参数,第一个参数待求和的数组,第二个参数是axis参数。如果axis参数没有指定,则相当于将多维数组平坦化(flatten)后,求前缀和。

arr = np.array([[0, 72, 3], [1, 3, -60], [-3, -2, 4]])
print(repr(np.cumsum(arr)))
print(repr(np.cumsum(arr, axis=0)))
print(repr(np.cumsum(arr, axis=1)))

结果为:

array([ 0, 72, 75, 76, 79, 19, 16, 14, 18])
array([[  0,  72,   3], 
       [  1,  75, -57], 
       [ -2,  73, -53]])
array([[  0,  72,  75], 
       [  1,   4, -56], 
       [ -3,  -5,  -1]])

级联技术(Concatenation)

NumPy中,对于不同数据集中的数据聚合在一起相当于是多个数组组合并成一个数组。有多种实现方式,如np.concatenate,np.hstacknp.vstack等。其中hstack其实就是水平拼接(按列顺序堆叠);而vstack其实就是垂直拼接(按行顺序堆叠)。

本文中重点讲解的是np.concatenate,该函数有两个参数需要注意,第一个参数是要拼接的数组的列表,第二个参数是axis关键词参数,用来指定拼接的方式,默认是axis=0,也就是按行顺序拼接(垂直拼接)

arr1 = np.array([[0, 72, 3], [1, 3, -60], [-3, -2, 4]])
arr2 = np.array([[-15, 6, 1], [8, 9, -4], [5, -21, 18]])
print(repr(np.concatenate([arr1, arr2])))
print(repr(np.concatenate([arr1, arr2], axis=1)))
print(repr(np.concatenate([arr2, arr1], axis=1)))

拼接结果如下所示:

array([[  0,  72,   3],
       [  1,   3, -60],
       [ -3,  -2,   4], 
       [-15,   6,   1],
       [  8,   9,  -4],
       [  5, -21,  18]])

array([[  0,  72,   3, -15,   6,   1],
       [  1,   3, -60,   8,   9,  -4],  
       [ -3,  -2,   4,   5, -21,  18]])

array([[-15,   6,   1,   0,  72,   3], 
       [  8,   9,  -4,   1,   3, -60], 
       [  5, -21,  18,  -3,  -2,   4]])

将计算结果保存

当我们进行了一系列的操作后,肯定要把运算的结果保存下来,否则每次都需要从头处理,耗时耗力。NumPy提供了一键序列化的功能。

使用np.save保存数据,保存的文件格式后缀为.npy,如果未指定,NumPy会自动添加一个.npy的后缀;而np.load可以加在.npy格式的文件,但是需要注意的是,如果在调用的时候,给定的文件路径未带有.npy后缀,NumPy不会自动补上这个后缀。

保存数据的示例代码:

arr = np.array([1, 2, 3]) # Saves to 'arr.npy'
np.save('arr.npy', arr) # Also saves to 'arr.npy'np.save('arr', arr)

加在数据的示例代码:

arr = np.array([1, 2, 3])
np.save('arr.npy', arr)
load_arr = np.load('arr.npy')
print(repr(load_arr))

# Will result in FileNotFoundError
load_arr = np.load('arr')
  • 0