当前位置: 代码迷 >> 综合 >> 【Task02】Numpy学习打卡
  详细解决方案

【Task02】Numpy学习打卡

热度:30   发布时间:2024-03-07 17:16:25.0

六、副本与视图

前言

在学习本章之前,我们先回顾一下Python中引用和对象的概念:

Python 中,一切皆对象。每个对象由:标识(identity)、类型(type)、value(值)
组成。

  • 标识用于唯一标识对象,通常对应于对象在计算机内存中的地址。使用内置函数 id(obj) 可返回对象 obj 的标识。
  • 类型用于表示对象存储的“数据”的类型。类型可以限制对象的取值范围以及可执行的 操作。可以使用 type(obj)获得对象的所属类型。
  • 值表示对象所存储的数据的信息。使用 print(obj)可以直接打印出值。

举例说明:

【例六、0-1】python原生的对象和引用

#自定义输出函数printInfo
>>> def printInfo(obj):
>>>     print("id:%s type:%s"%(id(obj),type(obj)))
>>> a = 2
>>> b = "datawhale"
>>> print("a的信息",end='')
>>> printInfo(a)
a的信息id:4503882768 type:<class 'int'>
>>> print("b的信息",end='')
>>> printInfo(b)
b的信息id:4571412400 type:<class 'str'>

在这里插入图片描述
如上图所示,右边分别是两个不同类型的对象,变量a和b分别引用了这两个对象。在python中,变量也成为:对象的引用。变量存储的就是对象的地址。变量通过地址引用了“对象”。

【例六、0-2】python改变变量的引用

>>> data = [x for x in range(5)]
>>> data2 = [x for x in range(5,10)]
>>> print(data,data2)
[0, 1, 2, 3, 4] [5, 6, 7, 8, 9]
>>> a = data
>>> print(a,id(a),data,id(data))
[0, 1, 2, 3, 4] 4570869056 [0, 1, 2, 3, 4] 4570869056
>>> a[0] = 100
>>> print(a,id(a),data,id(data))
[100, 1, 2, 3, 4] 4570869056 [100, 1, 2, 3, 4] 4570869056
>>> a = data2
>>> print(a,id(a),data,id(data),data2,id(data2))
[5, 6, 7, 8, 9] 4654437248 [100, 1, 2, 3, 4] 4570869056 [5, 6, 7, 8, 9] 4654437248

在这里插入图片描述
如上图,我们可以看到这样的过程,开始时,我们可以通过变量a修改data的数据,变量a的引用从data转向data2后,a的id和数据都发生了改变,与data2保持一致。

1.numpy中的引用

类比前言中python的引用例子,我们也可以看一下对numpy的数据类型ndarray进行引用会发生什么:

【六、例1】引用并修改ndarray变量

>>> data = np.arange(5)
>>> a = data
>>> print(a,id(a),data,id(data))
>>> print(a is data)
[0 1 2 3 4] 4659785120 [0 1 2 3 4] 4659785120
True
>>> a[0] = 100
>>> print(a,id(a),data,id(data))
>>> print(a is data)
True

a is data也可以用来判断2个变量所引用的对象id是否相同,id也叫做同一性运算符。

我们通过上面的例子可以看出,目前为主,numpy和python原生没有太大区别。

2.视图

视图是数据的一个别称或引用,通过该别称或引用亦便可访问、操作原有数据,但原有数据不会产生拷贝。如果我们对视图进行修改,它会影响到原始数据,物理内存在同一位置。

视图一般发生在:

  • numpy 的切片操作返回原数据的视图。
  • 调用 ndarray 的 view() 函数产生一个视图。

【六、例2-1】使用numpy切片操作返回视图

#自定义输出函数printInfo
>>> def printInfo(obj):
>>>     print(obj,end=" ")
>>>     print("id:%s type:%s"%(id(obj),type(obj)))>>> data = np.arange(5)
>>> print("data:",end='')
>>> printInfo(data)
data:[0 1 2 3 4] id:4656357504 type:<class 'numpy.ndarray'>
>>> a = data[:2]
>>> print("a:",end=' ')
>>> printInfo(a)
a: [0 1] id:4661896176 type:<class 'numpy.ndarray'>
>>> b = data[:-1]
>>> print("b:",end=' ')
>>> printInfo(b)
b: [0 1 2 3] id:4661897776 type:<class 'numpy.ndarray'>
>>> a[0] = 100
>>> b[-1] = 200
>>> print(data)
[100   1   2 200   4]>>> a.shape = (2,1)
>>> print(a)
>>> print(data)[[100][  1]]
[100   1   2 200   4]

a和b均由ndarray数据类型切片而来,可以看到,虽然data、a、b三个变量所引用的id不同,但是改变a和b的行为均在data上体现,但是改变a的shape并不会对data的shape产生影响。

【六、例2-2】调用 ndarray 的 view() 函数产生视图

#自定义输出函数printInfo
>>> def printInfo(obj):
>>>     print(obj,end=" ")
>>>     print("id:%s type:%s"%(id(obj),type(obj)))>>> data = np.arange(5)
>>> a = data.view()
>>> print("data:",end='')
>>> printInfo(data)
data:[0 1 2 3 4] id:4679609280 type:<class 'numpy.ndarray'>
>>> print("a:",end=' ')
>>> printInfo(a)
a: [0 1 2 3 4] id:4661896976 type:<class 'numpy.ndarray'>
>>> a[0] = 100
>>> print(data)
[100   1   2   3   4]>>> a.shape = (5,1)
>>> print(a)
>>> print(data)
[[100][  1][  2][  3][  4]]
[100   1   2   3   4]

通过view()方法生成视图,变量a和变量data的id仍不相同,但是改变a仍然可以改变data,另外,用这种视图生成方式改变a的shape,data的shape仍然不会改变。

那么可以把视图理解成半相关,部分相关(数据),部分无关(shape等)。

3、副本

副本是一个数据的完整的拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置。

副本一般发生在:

  • Python 序列的切片操作,调用deepCopy()函数。
  • 调用 ndarray 的 copy() 函数产生一个副本。

【例六、3-1】Python 序列的切片生成副本

>>> a = [x for x in range(5)]
>>> b = a[:-1]
>>> print(a is b)
False
>>> print(a,b)
[0, 1, 2, 3, 4] [0, 1, 2, 3]
>>> b[0] = 100
>>> print(a,b)
[0, 1, 2, 3, 4] [100, 1, 2, 3]

可以看到通过python List的切片操作,生成的b和a的id不同,改变b中的数据也并不会改变a的数据。

【例六、3-2】调用 ndarray 的 copy() 函数生成副本

>>> a = np.arange(5)
>>> b = a.copy()
>>> print(a is b)
False
>>> print(a,b)
[0 1 2 3 4] [0 1 2 3 4]
>>> b[0] = 100
>>> print(a,b)
[0 1 2 3 4] [100   1   2   3   4]

numpy.ndarray.copy() 函数创建一个副本。 对副本数据进行修改,不会影响到原始数据,它们物理内存不在同一位置。

七、索引与切片

前言

ndarray对象的内容可以通过索引或切片来访问和修改,与 Python 中 list 的切片操作一样。

1.整数索引

【七、例1】通过整数索引访问数组

>>> a = np.random.randint(0,10,5)
>>> print(a,a[2])
[2 7 3 6 6] 3
>>> b = np.random.randint(0,10,(5,5))
>>> print(b,b[3][1],b[3,1])
[[3 7 0 7 9][1 6 9 3 1][2 7 5 0 7][6 2 6 1 0][5 5 6 8 1]] 2 2
>>> c = np.random.randint(0,10,(3,3,3))
>>> print(c,c[0][0][0],c[0,0,0])
[[[6 1 5][9 5 9][2 3 5]][[1 1 5][2 1 5][1 5 4]][[3 5 9][8 9 6][7 3 3]]] 6 6

这里用了np.random.randint()方法生成了随机ndarray数组,然后利用整数索引进行访问。

注:[x][y][z] 与 [x,y,z]的效果相同

在这里插入图片描述
顾名思义,我们可以把切片理解成一系列切蛋糕的行为,第一个参数是第一刀,第二个参数是最后一刀后面的位置,第三个参数是刀与刀之间的距离,那么整数索引在二维数组就相当于与横纵都仅切1刀,且刀宽为1。

2.切片索引

我们在上一章知道,python切片获得原来数据的副本(深拷贝),而ndarray数据类型切片获得原数据的视图。

ndarray 数组可以基于 0 - n 的下标进行索引,切片对象可以通过内置的 slice 函数,并设置 start, stop 及 step 参数进行,从原数组中切割出一个新数组,也可以通过冒号分隔切片参数 start:stop:step 来进行切片操作。

【七、例2-1】单维度切片

#一维数组
>>> a = np.random.randint(0,10,5)
>>> print(a)
>>> print(a[:])
>>> print(a[:-2])
>>> print(a[1:])
>>> print(a[::2])
[8 6 4 8 6]
[8 6 4 8 6]
[8 6 4]
[6 4 8 6]
[8 4 6]#二维数组
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[1 7 5 8 3][1 7 8 7 3][2 1 7 3 7][3 3 4 1 4][6 4 3 8 6]]
#打印倒数前2行之前的数组(不包括倒数第二行)
>>> print(a[:-2])
[[1 7 5 8 3][1 7 8 7 3][2 1 7 3 7]]
#步长为2打印整个数组
>>> print(a[::2])
[[1 7 5 8 3][2 1 7 3 7][6 4 3 8 6]]
#打印倒数前2列之前的数组(不包括倒数第二列)
>>> print(a[...,:-2])
[[1 7 5][1 7 8][2 1 7][3 3 4][6 4 3]]
#打印倒数前2行之前的数组(不包括倒数第二行)
>>> print(a[:-2,...])
[[1 7 5 8 3][1 7 8 7 3][2 1 7 3 7]]

二维数组单维度切片示意图如下:

上面的案例仅以一维、二维数组为例进行单维度切片(在二维数组中要么是行,要么是列),上升到多维数组后,行列就要换成第几维去描述。

我们注意到a[:-2]其实是和a[:-2,...]是等价的,那么就此引出dots:

NumPy 允许使用...表示足够多的冒号来构建完整的索引列表。

那么在上述案例中,:::其实就和...是等价。对于其他维度的数组,...可以起到一个补全的效果,我们接下来会在多维度切片中进行演示:

【七、例2-2】多维度切片

#二维数组
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[7 7 9 8 0][7 9 9 9 9][2 1 4 7 7][4 9 7 3 8][2 1 8 2 8]]
#第2行与第3、4列交汇的数据
>>> print(a[1,2:4])
[9 9]
#第2列与第3、4行交汇的数据
>>> print(a[2:4,1])
[1 9]
#第3、4行与第3、4列交汇的数据
>>> print(a[2:4,2:4])
[[4 7][7 3]]
#从第一行开始以2为步长的行与从第一行开始以2为步长的列交汇的数据
>>> print(a[::2,::2])
[[7 9 0][2 4 7][2 8 8]]
#去掉最后一列
>>> print(a[...,:-1])
[[7 7 9 8][7 9 9 9][2 1 4 7][4 9 7 3][2 1 8 2]]

二维数组多维度切片示意图:
在这里插入图片描述
复杂版:
在这里插入图片描述

在这里比较好奇,如果是一行一列,返回的结果是怎样的呢?

>>> print(a[0,0])
7

是一个整数,切片切多了,忘了[0,0]其实就是整数索引 —_-!

3.整数数组索引

先简单看一个案例:

【七、例3-1】单维度整数数组索引

>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[0 3 2 8 0][2 6 8 7 9][4 0 5 0 5][8 5 9 0 0][1 9 8 8 5]]
>>> s = [0,2,4]
#取a的第1,3,5行
>>> print(a[s])
[[0 3 2 8 0][4 0 5 0 5][1 9 8 8 5]]

整数数组解决的是不连续切片且间隔是随机非等差的问题,上面的案例是操作行,我们也可以同时操作行和列:

【七、例3-2】多维度整数数组索引

>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[6 2 9 7 2][8 5 4 5 9][1 4 3 3 9][9 8 0 8 8][7 1 6 5 2]]
>>> s = [0,2,3]
>>> p = [1,2,4]
>>> q = [1,2]
>>> print(a[s,p])
[2 3 8]
>>> print(a[s,q])
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (3,) (2,)

最后一句代码出现问题,原因是传入的两个index数组的数量必须相等,怎样理解,就是将整数索引重复n遍,那么两个index数组当然要相等。

二维数组多维度整数数组切片示意图:
在这里插入图片描述

4.布尔索引

与其称为布尔索引,不如理解成条件索引:

【七、例4-1】控制最小值及类型的布尔索引

#控制最小值
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[5 9 6 6 1][0 0 4 0 0][0 8 2 5 7][8 0 2 0 0][3 4 0 1 6]]
>>> b = a > 5
>>> print(b)
[[False  True  True  True False][False False False False False][False  True False False  True][ True False False False False][False False False False  True]]
>>> print(a[b])
[9 6 6 8 7 8 6]#控制类型,找到不是nan的数据
>>> c = np.array([np.nan,9,8,7,np.nan,6,5])
>>> d = np.logical_not(np.isnan(c))
>>> print(d)
[False  True  True  True False  True  True]
>>> print(c[d])
[9. 8. 7. 6. 5.]

我们其实可以看出,最终带入的就是一个布尔数组b,然后利用a[b]去生成满足条件的数组。

【七、例4-2】布尔索引在matplotlib上的应用

未完待续

八、数组迭代

前言

除了for循环,Numpy 还提供另外一种更为优雅的遍历方法。

  • apply_along_axis(func1d, axis, arr) Apply a function to 1-D slices along the given axis.

第一个参数是要执行的函数,第二个参数是遍历的维度,第三个参数是要遍历的数据

1.采用系统函数迭代

>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[6 8 7 0 5][3 4 4 3 7][6 0 4 2 4][3 9 8 4 8][8 9 3 5 7]]
>>> b = np.apply_along_axis(np.sum, 0, x)
>>> print(b)
[25 12 28 12 28]
>>> b = np.apply_along_axis(np.sum, 1, x)
>>> print(b)
[28 19 16 24 18]

这里解释一下axis = 0的含义,以二维数组距离,当axis = 0时,是指遍历的方向是行,也就是说列是固定的,比如第一列6+3+6+3+8=25,就是结果数组的第一个数,以此类推。axis = 1的含义是,遍历的方向是列,示意图如下:

在这里插入图片描述

2.采用自定义函数迭代

也就是更改第一个参数,如下:

>>> def xiao_func(x):
>>>     return (x[0] + x[3])
>>> b = np.apply_along_axis(xiao_func, 0, a)
>>> print(b)
[14  8 10  8 12]