【Task02】Numpy学习打卡

本文详细介绍了numpy中数组的副本、视图概念,通过实例展示了切片操作如何产生视图以及如何通过整数、切片、布尔索引访问数组。同时,讲解了数组的迭代方法apply_along_axis,以及如何交换和反转数组的行和列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Task02学习思维导图

在这里插入图片描述

注:为了节约行数,默认import numpy as np已经写在每段代码前,不再重复写入,如果有新的包引入,会在代码头部import。

六、副本与视图

前言

在学习本章之前,我们先回顾一下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是否相同,is也叫做同一性运算符。

我们通过上面的例子可以看出,目前为止,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数组当然要相等。

二维数组多维度整数数组切片示意图:
在这里插入图片描述
假设a是一个ndarray的二维数组,p是一个一维list,那么a[p]的结果是一个ndarray二维数组。

若p也是一个ndarray数组呢?请看下面的例子:

【七、例3-3】索引标志为ndarray的整数数组索引(单维度)

#单维度情况
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[5 0 1 7 4]
 [3 9 2 5 1]
 [3 5 8 3 2]
 [5 4 4 6 5]
 [5 7 7 5 6]]
>>> p = np.array([[0,2],[2,3]])
>>> print(a[p])
[[[5 0 1 7 4]
  [3 5 8 3 2]]

 [[3 5 8 3 2]
  [5 4 4 6 5]]]

你会发现其实索引标志为ndarray时,其实就是多次整数数组索引,然后再将结果整合到一个ndarray中,在这种单维度的情况下:

目标数组二维+索引标志一维 = 结果数组二维
目标数组二维+索引标志二维 = 结果数组三维

显然,结果数组维数 = 目标数组维数 + 索引标志维数 - 1

【七、例3-4】索引标志为ndarray的整数数组索引(多维度)

>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[0 9 8 2 4]
 [7 8 7 2 0]
 [8 7 3 1 5]
 [9 8 4 8 2]
 [0 9 7 9 4]]
>>> p = np.array([[0,2],[2,3]])
>>> q = np.array([[0,4],[0,4]])
>>> print(a[p,q])
[[0 5]
 [8 2]]

其实可以把这种索引标志为ndarray的切片分解成多个子切片行为,即[a[p1,q1]+a[p2+q2]],如下图所示,求解过程可化解为2个普通多维度整数数组索引,如例3-2图所示。
在这里插入图片描述

易知,条件允许的情况下:

  • 目标数组维数 + 索引标志维数 - 2
  • 且多个索引标志维数必须相等,不然会报错

我们再来介绍一下take()函数:

numpy.take(a,indices,axis = None,out = None,mode =‘raise’ )

在这里插入图片描述

其中,当axis不是None时,此函数与使用数组索引数组的功能相同,但是若未指定axis时,会默认把目标数组维度打成一维,数据扁平化:

【七、例3-5】ndarray.take()的使用

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

如上述案例所示,当axis=0时,取的是1-3行;当axis=1时,取的是1-3列;当axis未赋值时,数据被扁平化,取的是扁平化后的前三个数组元素。

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上的应用

>>> import matplotlib.pyplot as plt

#y = sinx函数图像 x在[0,2pi]之间,最小刻度为pi/20
>>> x = np.linspace(0, 2 * np.pi, 50)
>>> y = np.sin(x)
>>> print(len(x))  
>>> plt.plot(x, y)

#mask1
#mask1 = y >= 0
>>> mask1 = np.logical_and(y >= 0, x >= 0)
>>> plt.plot(x[mask1],y[mask1],'ro')


#mask2
>>> mask2 = np.logical_and(y <= 0, x >= 3*np.pi/2 )
>>> plt.plot(x[mask2],y[mask2],'yo')

在这里插入图片描述
解释一下np.logical_and的含义,这个需要同时满足参数条件,比如 np.logical_and(y >= 0, x >= 0)代表x,y需要同时大于0。

可以把mask理解成一个范式,把它放入x,y中会根据范式规则返回需要的数据。

八、数组迭代

前言

除了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]

练习

1.交换数组arr中的列1和列2

#交换1,2列
>>> arr = np.arange(9).reshape(3, 3)
>>> print(arr,id(arr))
[[0 1 2]
 [3 4 5]
 [6 7 8]] 4711093952
>>> a = arr.copy()
>>> arr[...,0] = a[...,1]
>>> arr[...,1] = a[...,0]
>>> print(arr,id(arr))
[[1 0 2]
 [4 3 5]
 [7 6 8]] 4711093952

2.交换数组arr中的行1和行2

#交换1,2行
>>> arr = np.arange(9).reshape(3, 3)
>>> print(arr,id(arr))
[[0 1 2]
 [3 4 5]
 [6 7 8]] 4711349344
>>> a = arr.copy()
>>> arr[0] = a[1]
>>> arr[1] = a[0]
>>> print(arr,id(arr))
[[3 4 5]
 [0 1 2]
 [6 7 8]] 4711349344

3.反转二维数组arr的行

#反转行
>>> arr = np.arange(9).reshape(3, 3)
>>> print(arr,id(arr))
[[0 1 2]
 [3 4 5]
 [6 7 8]] 4713855696
>>> a = arr.copy()
>>> arr[:] = a[::-1]
>>> print(arr,id(arr))
[[6 7 8]
 [3 4 5]
 [0 1 2]] 4713855696

注意,这里如果有arr = a[::-1]是会改变变量arr的引用,但是原id的数据不会产生变化:

在这里插入图片描述

4.反转二维数组arr的列

#反转列
>>> arr = np.arange(9).reshape(3, 3)
>>> print(arr,id(arr))
[[0 1 2]
 [3 4 5]
 [6 7 8]] 4712885584
>>> a = arr.copy()
>>> arr[...,:] = a[...,::-1]
>>> print(arr,id(arr))
[[2 1 0]
 [5 4 3]
 [8 7 6]] 4712885584

为什么我要打印id,因为题目是交换数组arr中的行和列以及反转,如果采用x = arr[:, [1 , 0 , 2]]的方法,确实能得到交换后的数组,但是其实并没有改变arr本身,而是新生成了一个ndarray。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值