Numpy简明教程

NumPy基础

提纲

  • 概述
  • 创建数组
  • 数组的运算
  • 数组的索引
  • 数组的变形操作
  • numpy随机数模块

一、NumPy概述

1.1 NumPy简介

智能算法经常用到数组和矩阵(向量)运算。
虽然Python内置数据结构中的列表可以当作数组来使用,但是一个列表可以存储多种类型的元素,导致数值运算效率较低。
虽然Python也提供了内置的array(数组)模块,但它仅仅支持一维数组,不支持多维数组,也没有各种运算函数,因此并不适合数值运算。
为了弥补Python数值计算的不足,吉姆·弗贾宁(Jim Hugunin)、特拉维斯·奥利芬特(Travis Oliphant)等人联合开发了NumPy项目。
NumPy是Python语言的一个基于C语言的扩展程序库,支持多维度的数组(即N维数组对象ndarray)与矩阵运算,并对数组运算提供了大量的数学函数库。NumPy功能非常强大,支持广播、线性代数运算、傅里叶变换、随机数生成等功能,对很多第三方库(如SciPyPandasmatplotlib等)提供了底层支持。

1.2 导入NumPy

NumPy是Python的第三方库,使用前需要安装。
# pip install numpy
在使用时,NumPy还是需要显式导入的。为了方便,我们常会为NumPy起一个别名,通常这个别名为np

#导入numpy并指定别名
import numpy as np 
#输出numpy别名
print(np.__version__) 
1.20.1

二、创建NumPy数组

NumPy最重要的一个特点就是支持N维数组对象ndarray
NumPy数组中的元素只能是同一种数据类型

2.1 利用序列生成——array()方法

生成NumPy数组最简单的方式,莫过于利用array()方法。array()方法可以接收任意数据类型(如列表、元组等)作为数据源。
array()方法的签名为:numpy.array(object, dtype=None, copy=True, order='K',subok=False, ndmin=0)
array()方法的返回值为ndarray对象。

arr1 = np.array([1,2,3])
arr1
array([1, 2, 3])

(1)精度转换

1)自动转换

如果构造NumPy数组的数据源类型精度不统一,且这些数据类型可以相互转换,那么NumPy会遵循 “就高不就低”(upcast) 的规则进行类型转换,比如说列表中的数据有整数,也有浮点数,NumPy会把所有数据都转换为浮点数,这是因为浮点数的精度更高。

2)手动转换
我们也可以用astype()方法显式指定被转换数组的数据类型。

(2)适配高维数组

如果数据序列是嵌套的,且嵌套序列是等长的,则通过array()方法可以把嵌套的序列转为与嵌套级别适配的高维数组。

2.2 利用特定函数生成

(1)arange()函数

函数作用:arange()根据startstop指定的范围及step设定的步长,生成一个ndarray对象。
函数格式:arange(start, stop, step, dtype)
参数说明:
start为起始值,默认为0
stop为终止值。取值区间是左闭右开的,即stop这个终止值是不包括在内的。
step为步长,如果不指定,默认值为1
dtype指明返回ndarray的数据类型,如果没有提供,则会使用输入数据的类型。

arr1 = np.arange(10)
arr1
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

arange()方法的使用与Python的内置函数range()十分类似。两者都能均匀地(evenly)等分区间,但是range()无法将步长设置为浮点数,而np.arange()可以将步长设置为任意实数。

arr1 = np.arange(0,10,0.5)
arr1
array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. ,
       6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])

(2)linspace()函数
函数作用:linspace()在指定区间内生成指定个数的元素的数组
函数格式:linspace(start, stop, num)
参数说明:前两个参数分别指明生成元素的左右区间边界,第三个参数确定上下限之间均匀等分的数据个数。
需要注意的是,arange()中数据区间是左闭右开的(即区间的最后一个数值是取不到的),linspace()生成的数据区间为闭区间。当然我们也可以在该函数中指定endpoint=False,使生成数据区间变为左闭右开区间。
案例:在[1,10]中生成20个等间隔的数据

arr1 = np.linspace(1,10,20)
arr1
array([ 1.        ,  1.47368421,  1.94736842,  2.42105263,  2.89473684,
        3.36842105,  3.84210526,  4.31578947,  4.78947368,  5.26315789,
        5.73684211,  6.21052632,  6.68421053,  7.15789474,  7.63157895,
        8.10526316,  8.57894737,  9.05263158,  9.52631579, 10.        ])

(3)zeros()ones()函数
np.zeros()np.ones()等函数可以生成指定维度和填充固定数值的数组,它们通常用来对某些变量进行初始化。其中,
np.zeros()函数生成的数组由0来填充,
np.ones()生成的数组由1来填充。

arr1 = np.zeros((3,4))
arr1
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

np.zeros((3,4))等价于np.zeros(shape=(3,4))shape参数处需要通过一个元组或列表来指明生成数组的尺寸。
类似地,我们可以用np.ones()生成指定尺寸、元素全为1的数组,dtype参数设置元素的类型。

arr1 = np.ones((3,4),dtype=float)
arr1
array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])

(3)zeros_like()ones_like()

还有一种生成全0数组的方法是np.zeros_like()该方法的核心思想可概括为“借壳上市”,它会借用某个给定数组的类型、尺寸(即维度信息),但其中的所有元素都被置换为0,这也是“zeros_like”名称的来源。

arr1 = np.array([[1,2,3],[4,5,6]])
arr1
array([[1, 2, 3],
       [4, 5, 6]])
arr2 = np.zeros_like(arr1)
arr2
array([[0, 0, 0],
       [0, 0, 0]])

np.zero_like()非常相似的一个操作是ones_like()。它的功能是将数组中的元素都填充为1,数组的尺寸信息和数据类型来自一个给定数组。
zeros_like()ones_like()功能类似的方法还有以下两个。
empty_like():产生和给定数组尺寸和类型相同的数组,但该数组中的元素没有被初始化(uninitialized),你可以认为它是一个“万事俱备,只欠数据”的数组。
full_like():产生和给定数组尺寸和类型相同的数组,该数组中的元素都被初始化为某个给定值。

2.3 多维数组的属性

属性 说明
ndim 返回 int。表示数组的维数
shape 返回 tuple。表示数组的尺寸,对于 n 行 m 列的矩阵,形状为(n,m)
size 返回 int。表示数组的元素总数,等于数组形状的乘积
dtype 返回 data-type。描述数组中元素的类型
itemsize 返回 int。表示数组的每个元素的大小(以字节为单位)
arr1 = np.array([[1,2,3],[4,5,6]])
arr1.ndim, arr1.shape, arr1.size, arr1.dtype
(2, (2, 3), 6, dtype('int32'))

但NumPy数组形状并不是一成不变的,可以通过reshape()方法将原有数组进行**“重构”(变形)**。
通常返回的是非拷贝副本,即改变返回后数组的元素,原数组对应元素的值也会改变

arr1 = np.arange(10)
arr1
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr1.reshape(2,5)
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

里需要注意的是,NumPy表示三维数组维度信息的方式和我们通常的认知稍有不同。
比如,我们想创建两个3行5列的数组,它的形状参数为(2, 3, 5),而不是(3, 5, 2)

arr1 = np.arange(30).reshape(2,3,5)
arr1
array([[[ 0,  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, 28, 29]]])

三、NumPy数组中的运算

3.1 向量运算

案例:数组向量加法

arr1 = np.array([1,2,3])
arr2 = np.array([4,5,6])
arr3 = arr1 + arr2
arr3
array([5, 7, 9])

类似于加法,基于NumPy数组的减法、乘法、除法等各种数学运算都可以这么高效地完成。

3.2 算术运算

NumPy吸纳了Fortran或MATLAB等语言的优点,只要操作数组的形状(维度)一致,我们就可以很方便地对它们**逐元素(element-wise)**实施加、减、乘、除、取余、指数运算等操作。这些操作特别适合大规模的并行计算。
NumPy中还有很多好用的统计函数,如sum()min()max()median()mean()average()std()var()分别用于求和、最小值、最大值、中位数、平均数、加权平均数、标准差、方差。
NumPy中还有很多常用的数学函数,如三角函数sin()cos()tan()等,这也使得导入了NumPy后,Python宛若一个功能强大的科学计算器。

arr1 = np.array([1,2,3,3,4,5])
np.min(arr1)
1
np.mean(arr1)
3.0

3.3 NumPy中的广播

在NumPy中,如果对两个数组实施加、减、乘、除等运算,参与运算的两个数组需形状相同。但实际上,NumPy具有“智能填充”功能,当两个数组的形状不相同时,可扩充较小数组中的元素来匹配较大数组的形状,这种机制叫作广播(broadcasting)

图片

这种广播机制,也称为张量自动扩展,它是一种轻量级的张量复制手段。需要说明的是,对于大部分场景,广播机制仅仅在逻辑上改变了张量的尺寸,只待实际需要时才真正实现张量的赋值和扩展。这种优化流程节省了大量计算资源,并由计算框架(如NumPy)隐式完成,用户无须关心实现细节。

NumPy的广播规则:

  • 扩展维度:如果两个张量的尺寸不同,则NumPy的广播机制会为尺寸较小的张量添加一个轴(广播轴),使其维度信息与较大张量的相同。
  • 复制数据:尺寸较小的张量沿着新添加的轴不断重复之前的元素,直至尺寸与较大的张量相同。
  • 低维有1:如果两个张量的尺寸在任何维度上都不匹配,则需将某维度中尺寸为1的张量拉伸,以匹配另一个较大张量的尺寸。
    如果两个张量在任何维度上尺寸都不一致,且两者均没有任何一个维度为1,则会出现广播错误,即广播不会发生。也就是说,为了让广播操作能够顺利进行,广播操作的两个对象,它们某个维度上的尺寸,要么相等,要么为1。

3.4 NumPy数组中的轴方向

在NumPy的多维数组中,常有“约减”(Reduce,亦有文献译作“规约”)的提法。它表示将众多数据按照某种规则合并成一个或几个数据。“约减”之后,数据的个数在总量上是减少的。在这里,“约减”的“减”并非减法之意,而是元素的减少。比如说,数组的加法操作就是一种“约减”操作,因为它对众多元素按照加法指令实施操作,最后合并为少数的一个或几个值。
求N维数组的均值(mean)、最大值(max)和最小值(min)等,这些操作都属于约减操作。但有时,我们会有这样的需求,对指定维度方向的值进行统计,如统计某一行(或列)的和、均值、最大值、最小值等。这个时候,就需要给“约减”指令指定方向。
诸如sum()min()max()mean()等函数,它们都有一个名为操作轴(axis)的参数。
其默认值为None,也就是不指定约减方向,它将所有数据都“约减”为一个元素。
如果axis的值为0,可简单地理解为从垂直方向进行“约减”。
如果axis的值为1,则可以简单理解为从水平方向进行“约减”。
在这里插入图片描述

四、NumPy数组的索引

4.1 通过索引访问数组元素

索引(index)是指数组元素所在的位置编号,有点类似于邮编之于地区。我们可以通过NumPy数组的索引来获取、设置数组元素的值。如果希望访问数组中的值,像访问列表元素一样,给出数组的下标即可。
相应地,访问二维数组时,需要通过两个索引来执行相应操作。访问二维数组的方式有两种:

  • 第一种是类似于C、C++一样,使用两个方括号,每个方括号对应一个维度信息。
  • 另一种更为简便的访问方式,把两个方括号合并,在一个方括号内分别给出两个维度信息,不同维度信息间用逗号(,)隔开。
arr1 = np.array([[1,2,3],[4,5,6]])
arr1
array([[1, 2, 3],
       [4, 5, 6]])
arr1[0][2]
3
arr1[0,2]
3

通过这种方法同样可以修改二维数组中的值。

arr1[0,2]=100
arr1[0,2]
100

4.2 NumPy中的切片访问

与Python中列表的操作类似,除了通过索引访问数组元素,在NumPy中还可以通过切片操作来访问和修改数组数据。
切片操作的核心是从原始数组中,按照给定规则提取出一个新的数组,对原始数组没有任何影响。
切片规则通常是这样的:数组名[start:end:step]。其中start表示起始索引(从0开始),end表示结束索引(至-1结束),step表示步长,步长为正时表示从左向右取值,步长为负时则反向取值。

切片的步长step可取负值。当step=−1时,start: end: −1表示从start开始逆序读取至end结束(不包含end)。考虑最特殊的一种例子,当切片方式为:: -1时就完成了逆序读取。

arr1=np.arange(10)
arr1[2:7:2]
array([2, 4, 6])

4.3 花式索引:列表作为索引

前面的索引方法有一个特点:索引要么是一个值,要么是一片值(即切片访问)
如果索引只是一个值,那么自然只能访问一个数组元素。
如果索引基于切片方法,那么被访问的数组元素或连续分布,或通过设置步长有规律地间隔分布。
如果我们想一次性访问数组中的多个元素,而它们又没什么规律可循,该怎么呢?“花式”索引(FancyIndexing)就是用来解决这个问题的。
“花式”索引是指,将多个需要访问元素的索引汇集起来,构成一个整型数组,然后把这个内含索引的数组,整体作为目标数组的索引,这样就能一次性地读取多个“杂乱无序”甚至重复的数组元素。由于这种读取数组元素的方式有些花哨,故称“花式”索引,又因为索引都是整数,亦有文献称之为整数索引。

arr1 = np.array([1,2,3,3,4,5])
arr1[[1,3]]
array([2, 3])

对于二维数组,如果要读取某一个元素,必须指定行和列两个参数。如果没有显式指定二维索引,就无法正确读取具体的元素。
如果访问一个二维数组,但只给出一维坐标,那么这个一维坐标指的是行索引坐标,数组的内层括号[0,2,1,0]表示的是行号,它也是一种“花式”索引,表示要读取第0行、第2行、第1行和第0行(第2次访问)的数据。

4.4 布尔索引:比较表达式作为索引

a = np.array([1,2,3,3,4,5])
a[a>3]
array([4, 5])

方括号内的a > 3意义并不简单。我们知道,a是一个数组对象,而5是一个标量,二者之所以能比较,是因为NumPy悄悄地使用了前面章节提到的广播技术,它把3广播(复制)成与数组a尺寸一模一样的数组,数组内的元素都是3。然后数组a中个每个元素都与3做比较,逐个判断该元素是否大于3,因此返回的是一个形状与数组a相同的布尔数组
这个尺寸(维度信息)与原始数组相同的数组,叫作布尔数组。布尔数组可以整体作为索引,形成一个布尔索引,然后NumPy会依据逐元素(element-wise)规则,返回对应位置布尔值为True的元素。因此,a[a>3]的含义,就是返回数组中大于3的元素。

五、数组的变形操作

5.1 二维数组的转置与展平

(1)二维数组转置
我们可以通过transpose()方法将二维数组转置,这种转置仅仅得到原有二维数组的视图,原始数组并没有发生变化。
(2)二维数组展平
1)ravel()
我们可以利用ravel()方法来完成将多维数组降维成一维数组。同样地,ravel()返回的仅仅是原始数组的视图而已,原始数组本身并没有发生变化。
2)flatten()
flatten()函数同样可以完成将多维数组展平成一维数组的操作。不同于ravel()返回的是原始数组的视图,flatten()会重新分配内存,完成一次从原始数据到新内存空间的深拷贝,但原始数组并没有发生任何变化。

5.2 数组的堆叠操作

有时,我们需要将不同的NumPy数组,通过堆叠(stack)操作,拼接为一个新的较大的数组。堆叠方式大致分为水平方向堆叠(horizontal stack)、垂直方向堆叠(vertical stack)、深度方向堆叠(depth-wise stack)等。、
在这里插入图片描述

上图所示的这三种排列方式,分别体现了hstack()vstack()dstack()方法在拼接数组时的特点。除此之外,堆叠函数还包括concatenate()column_stack()row_stack()等。很多实现堆叠功能的不同函数有着“异曲同工”之妙,通过配置不同的参数,可达到相同的数组拼接目的。
(1)水平方向堆叠hstack()
hstack的首字母“h”来自英文单词“horizontal”(水平),表示所操作的数组是在水平方向堆叠的,其实就是按列顺序堆叠起来。

arr1 = np.zeros((2,2))
arr1
array([[0., 0.],
       [0., 0.]])
arr2 = np.ones((2,3))
arr2
array([[1., 1., 1.],
       [1., 1., 1.]])
arr3 = np.hstack((arr1,arr2))
arr3
array([[0., 0., 1., 1., 1.],
       [0., 0., 1., 1., 1.]])

hstack()实际上表示的是不同数组在水平方向上的堆叠。我们利用concatenate()函数,并设置水平轴方向(axis=1)的连接,就可以达到相同的堆叠效果。
为了完成堆叠,hstack()要求参与堆叠操作的两个数组在垂直(即行)方向的尺寸是相同的。
(2)垂直方向堆叠vstack()
类似地,vstack()实现的是轴0方向(即垂直方向)的数组堆叠。vstack一词的首字母v表示的是vertical(垂直)的意思。vstack()的函数原型为vstack(tup),其中参数tup表示元组,元组内的元素可以是元组、列表或NumPy数组等,返回结果为NumPy数组。

arr1 = np.zeros((3,3))
arr1
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])
arr2 = np.ones((2,3))
arr2
array([[1., 1., 1.],
       [1., 1., 1.]])
arr3 = np.vstack((arr1,arr2))
arr3
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [1., 1., 1.],
       [1., 1., 1.]])

类似于hstack(),为了完成数组堆叠操作,vstack()要求除第一个轴(参与堆叠)方向的维度尺寸不一样以外,其他维度的尺寸必须保证一致。

5.3 数组分割操作

NumPy也提供了数组的分割操作。和堆叠类似,分割也包括水平方向分割、垂直方向分割和深度方向分割,分别用hsplit()vsplit()dsplit()实现。类似于concatenate()方法可通过设置轴方向,既实现水平方向堆叠,又实现垂直方向堆叠,split()也可以通过设置分割方向,分别实现hsplit()vsplit()dsplit()的功能。

六、NumPy中的随机数模块

NumPy中也含有随机数模块,即numpy.random模块。
numpy.random模块中提供了大量与随机数相关的函数。
python内置的random模块用于生成随机数,numpy.random用于生成随机数矩阵。

随机数是由随机种子根据一定的规则计算出来的数值,所以,只要计算方法一定,随机种子一定,那么产生的随机数就不会变。若不设定随机种子,随机数生成器会将系统时间作为随机种子来生成随机数。

# 在区间[0,1)中生成服从均匀分布的数组,每个维度占据一个参数位置,rand()支持对不确定参数赋值,二维数组就有两个参数,以此类推。
# rand(d0, d1, ..., dn)
np.random.rand(2,5)
array([[0.84492252, 0.98072095, 0.63809183, 0.74701318, 0.74079855],
       [0.23183099, 0.87364001, 0.57100839, 0.70213023, 0.70982771]])
# 在区间[0,1)中生成服从均匀分布的数组,参数即结果数组的形状元组
# random(size=None)
np.random.random((2,5))
array([[0.74867025, 0.11237504, 0.81028004, 0.54099473, 0.67045688],
       [0.31118362, 0.55093925, 0.48897572, 0.73137661, 0.50420562]])
# 在区间[0,1)中生成服从标准正态分布的数组,参数即结果数组的形状
# randn(d0, d1, ..., dn)
np.random.randn(2,5)
array([[-0.29269022,  0.85976458, -1.21948284, -0.85250544,  1.62391503],
       [ 0.30559764,  0.17003518,  1.89516677,  0.59479242,  0.13385514]])
# 在1-20之间生成形状为2,5的整数数组
# randint(low, high=None, size=None, dtype=int)
np.random.randint(1,20,(2,5))
array([[15,  4, 13, 14,  9],
       [ 3, 15,  8,  1,  1]])
# 从序列a中按概率p随机输出size个元素的一维数组
# choice(a, size=None, replace=True, p=None)
np.random.choice(['a','b','c'])
'c'
# p参数中概率和必须为1
np.random.choice(['a', 'b', 'c'], 2,p=[0.5, 0.3, 0.2])
array(['b', 'b'], dtype='<U1')

参考资料

《Python极简讲义:一本书入门数据分析与机器学习》

https://www.numpy.org.cn

https://www.runoob.com/numpy/numpy-tutorial.html

习题

https://zhuanlan.zhihu.com/p/58576235

https://github.com/rougier/numpy-100/blob/master/100_Numpy_exercises_with_hints_with_solutions.md

猜你喜欢

转载自blog.csdn.net/mighty13/article/details/115420742