Numpy数组的操作


Numpy数组操作

数组广播机制:

数组与数的计算:

Python列表中,想要对列表中所有的元素都加一个数,要么采用map函数,要么循环整个列表进行操作。但是NumPy中的数组可以直接在数组上进行操作。示例代码如下:

import numpy as np
a1 = np.random.random((3,4))
print(a1)
# 如果想要在a1数组上所有元素都乘以10,那么可以通过以下来实现
a2 = a1*10
print(a2)
# 也可以使用round让所有的元素只保留2位小数
a3 = a2.round(2)

以上例子是相乘,其实相加、相减、相除也都是类似的。

数组与数组的计算:

  1. 结构相同的数组之间的运算:

    a1 = np.arange(0,24).reshape((3,8))
    a2 = np.random.randint(1,10,size=(3,8))
    a3 = a1 + a2 #相减/相除/相乘都是可以的
    print(a1)
    print(a2)
    print(a3)
    
  2. 与行数相同并且只有1列的数组之间的运算:

    a4 = np.random.randint(0,5,size=(3,1))
    print(a1)
    print('='*30)
    print(a4)
    Out:
    [[1 2 4 2 2]
     [3 2 2 3 0]
     [1 0 4 2 3]]
    ==============================
    [[0]
     [0]
     [2]]
    
    a1+a4
    Out:
    array([[1, 2, 4, 2, 2],
           [3, 2, 2, 3, 0],
           [3, 2, 6, 4, 5]])
    
  3. 与列数相同并且只有1行的数组之间的运算:

    a5 = np.random.randint(0,5,size=(1,5))
    print(a1)
    print("="*30)
    print(a5)
    Out:
    [[1 2 4 2 2]
     [3 2 2 3 0]
     [1 0 4 2 3]]
    ==============================
    [[2 3 3 4 2]]
    
a1+a5
Out:
array([[3, 5, 7, 6, 4],
       [5, 5, 5, 7, 2],
       [3, 3, 7, 6, 5]])

广播原则:

如果两个数组的后缘维度(trailing dimension,即从末尾开始算起的维度)的轴长度相符或其中一方的长度为1,则认为他们是广播兼容的。广播会在缺失和(或)长度为1的维度上进行。。看以下案例分析:

  1. shape(3,8,2)的数组能和(8,3)的数组进行运算吗?
    分析:不能,因为按照广播原则,从后面往前面数,(3,8,2)(8,3)中的23不相等,所以不能进行运算。
  2. shape(3,8,2)的数组能和(8,1)的数组进行运算吗?
    分析:能,因为按照广播原则,从后面往前面数,(3,8,2)(8,1)中的21虽然不相等,但是因为有一方的长度为1,所以能参与运算。
  3. shape(3,1,8)的数组能和(8,1)的数组进行运算吗?
    分析:能,因为按照广播原则,从后面往前面数,(3,1,4)(8,1)中的41虽然不相等且18不相等,但是因为这两项中有一方的长度为1,所以能参与运算。

总结:

1、数组和数字直接进行运算是没有问题的。

2、两个shape相同的数组是可以进行运算的。

3、如果两个shape不同的数组,想要进行运算,那么需要看他们是否满足广播原则。

4、如果两个数组的后缘维度(trailing dimension,即从末尾开始算起的维度)的轴长度相符或其中一方的长度为1,则认为他们是广播兼容的。广播会在缺失和(或)长度为1的维度上进行。

数组形状的操作:

可以通过一些函数,非常方便的操作数组的形状。

reshape和resize方法:

两个方法都是用来修改数组形状的,但是有一些不同。

  1. reshape是将数组转换成指定的形状,然后返回转换后的结果,对于原数组的形状是不会发生改变的。调用方式:

    a1 = np.random.randint(0,10,size=(3,4))
    a2 = a1.reshape((2,6)) #将修改后的结果返回,不会影响原数组本身
    
  2. resize是将数组转换成指定的形状,会直接修改数组本身。并不会返回任何值。调用方式:

    a1 = np.random.randint(0,10,size=(3,4))
    a1.resize((2,6)) #a1本身发生了改变
    

flatten和ravel方法:

两个方法都是将多维数组转换为一维数组,但是有以下不同:

  1. flatten是将数组转换为一维数组后,然后将这个拷贝返回回去,所以后续对这个返回值进行修改不会影响之前的数组。
  2. ravel是将数组转换为一维数组后,将这个视图(可以理解为引用)返回回去,所以后续对这个返回值进行修改会影响之前的数组。
    比如以下代码:
x = np.array([[1, 2], [3, 4]])
x.flatten()[1] = 100 #此时的x[0]的位置元素还是1
x.ravel()[1] = 100 #此时x[0]的位置元素是100

总结:

1、 reshape和resize都是重新定义形状的。但是reshape不会修改数组本身,而是将修改后的结果返回回去,而resize是直接修改数组本书的。

2、flatten和ravel都是用来将数组边成一维数组的,并且他们都不会对原数组造成修改,但是flatten返回的是一个拷贝,所以对flatten的返回值的修改不会影响到原来数组,而ravel返回的是一个View,那么对返回值的修改会影响到原来数组的值。

不同数组的组合:

如果有多个数组想要组合在一起,也可以通过其中的一些函数来实现。

  1. vstack:将数组按垂直方向进行叠加。数组的列数必须相同才能叠加。示例代码如下:

    a1 = np.random.randint(0,10,size=(3,5))
    a2 = np.random.randint(0,10,size=(1,5))
    a3 = np.vstack([a1,a2])
    
  2. hstack:将数组按水平方向进行叠加。数组的行必须相同才能叠加。示例代码如下:

    a1 = np.random.randint(0,10,size=(3,2))
    a2 = np.random.randint(0,10,size=(3,1))
    a3 = np.hstack([a1,a2])
    
  3. concatenate([],axis):将两个数组进行叠加,但是具体是按水平方向还是按垂直方向。则要看axis的参数,如果axis=0,那么代表的是往垂直方向(行)叠加,如果axis=1,那么代表的是往水平方向(列)上叠加,如果axis=None,那么会将两个数组组合成一个一维数组。需要注意的是,如果往水平方向上叠加,那么行必须相同,如果是往垂直方向叠加,那么列必须相同。示例代码如下:

    a = np.array([[1, 2], [3, 4]])
    b = np.array([[5, 6]])
    np.concatenate([a, b], axis=0)
    # 结果:
    array([[1, 2],
        [3, 4],
        [5, 6]])
    
    np.concatenate([a, b], axis=1)
    # 结果:
    array([[1, 2, 5],
        [3, 4, 6]])
    
    np.concatenate((a, b), axis=None)
    # 结果:
    array([1, 2, 3, 4, 5, 6])
    

总结:

1、hstack代表在水平方向叠加,如果想要叠加成功,那么他们的行必须一致。

2、vstack代表在垂直方向叠加,如果想要叠加成功,那么他们的列必须一致。

3、concatenate可以手动的指定axis参数具体在哪个方向叠加,如果axis=0,代表在水平方向叠加,如果axis=1,代表在垂直方向叠加,如果axis=None,那么会先进行叠加,再转换成1维数组。

数组的切割:

通过hsplitvsplit以及array_split可以将一个数组进行切割。

  1. hsplit:按照水平方向进行切割。用于指定分割成几列,可以使用数字来代表分成几部分,也可以使用数组来代表分割的地方。示例代码如下:

    a1 = np.arange(16.0).reshape(4, 4)
    np.hsplit(a1,2) #分割成两部分
    >>> [array([[ 0.,  1.],
         [ 4.,  5.],
         [ 8.,  9.],
         [12., 13.]]), array([[ 2.,  3.],
         [ 6.,  7.],
         [10., 11.],
         [14., 15.]])]
    
    np.hsplit(a1,[1,2]) #代表在下标为1的地方切一刀,下标为2的地方切一刀,分成三部分
    >>> [array([[ 0.],
         [ 4.],
         [ 8.],
         [12.]]), array([[ 1.],
         [ 5.],
         [ 9.],
         [13.]]), array([[ 2.,  3.],
         [ 6.,  7.],
         [10., 11.],
         [14., 15.]])]
    
  2. vsplit:按照垂直方向进行切割。用于指定分割成几行,可以使用数字来代表分成几部分,也可以使用数组来代表分割的地方。示例代码如下:

    np.vsplit(x,2) #代表按照行总共分成2个数组
    >>> [array([[0., 1., 2., 3.],
         [4., 5., 6., 7.]]), array([[ 8.,  9., 10., 11.],
         [12., 13., 14., 15.]])]
    
    np.vsplit(x,(1,2)) #代表按照行进行划分,在下标为1的地方和下标为2的地方分割
    >>> [array([[0., 1., 2., 3.]]),
        array([[4., 5., 6., 7.]]),
        array([[ 8.,  9., 10., 11.],
               [12., 13., 14., 15.]])]
    
  3. split/array_split(array,indicate_or_seciont,axis):用于指定切割方式,在切割的时候需要指定是按照行还是按照列,axis=1代表按照列,axis=0代表按照行。示例代码如下:

    np.array_split(x,2,axis=0) #按照垂直方向切割成2部分
    >>> [array([[0., 1., 2., 3.],
         [4., 5., 6., 7.]]), array([[ 8.,  9., 10., 11.],
         [12., 13., 14., 15.]])]
    

    总结:

    1、hsplit代表在水平方向切割,按列进行切割的。他的切割方式有两种,第一种就是直接指定平均切割成多少列,第二种就是指定切割的下标值。

    2、 vsplit代表在垂直方向切割,按行进行切割。他的切割方式跟hsplit是一样的。

    3、split/array_split是手动的指定axis参数,axis=0,代表按行进行切割,axis=1,代表按列进行切割。


数组(矩阵)转置和轴对换:

numpy中的数组其实就是线性代数中的矩阵。矩阵是可以进行转置的。ndarray有一个T属性,可以返回这个数组的转置的结果。示例代码如下:

a1 = np.arange(0,24).reshape((4,6))
a2 = a1.T
print(a2)

另外还有一个方法叫做transpose,这个方法返回的是一个View,也即修改返回值,会影响到原来数组。示例代码如下:

a1 = np.arange(0,24).reshape((4,6))
a2 = a1.transpose()

为什么要进行矩阵转置呢,有时候在做一些计算的时候需要用到。比如做矩阵的内积的时候。就必须将矩阵进行转置后再乘以之前的矩阵:

a1 = np.arange(0,24).reshape((4,6))
a2 = a1.T
print(a1.dot(a2))

总结:

1、可以通过ndarray.T的形式进行转置。

2、也可以通过ndarray.transpose()进行转置,这个方法返回的是一个View,所以对返回值上进行修改,会影响到原来的数组。