首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

NumPy初学教程,可视化指南

数据科学三分天下,Python占其一。Python数据科学 NumPy是基础,不管pandas还是tensorflow, NumPy都是基础库,学习NumPy基础类型和操作必不可少。本文我们就介绍NumPy基础,并以图形方式展现,以方便初学者理解。

概述

NumPy中最基本数据类型是数组,所有数据组织都是n维数组形式组织的。其中一维和二维数组是基础,其他多维数组的操作和其类似。

NumPy数组形式上和Python列表相似。两者都可以用作数据容器,可以快速访问和设置项目,但是数据插入和移动比较慢。

NumPy数组支持对其进行简单算术运算:

NumPy数组还具有:

比较紧凑,尤其是在一个以上的维度上;

可以向量化操作时比列表快;

将元素追加到末尾比列表慢(O(1)和O(N));

通常是同质的:只能快速处理一种类型的元素;

注意:O(N)表示完成操作所需的时间与数组的大小成正比,以及O(1),表示消耗时间固定和数组的大小无关。

向量——一维数组

向量初始化:创建NumPy数组的一种方法通过Python列表转换。数组类型会从列表元素类型中自动推导:

确保输入为同类型列表,否则其类型dtype='object',不仅转换耗时,而且只保留NumPy中包含的语法糖。

NumPy数组无法像Python列表那样增长:在数组末尾没有保留空间以方便快速追加。

一种常见的做法要么长出一个Python列表,并将其转换为NumPy的数组时,它已准备就绪或预分配必要的空间,可以用np.zeros和np.empty。

通常可以使用zeros_like()函数创建一个空数组,以大小和元素类型匹配现有数组:

创建以常量值填充的数组的函数都有一个对应的的_like函数:

在NumPy中,对有序数组有两个初始化函数:argnge()和lispace()

如果需要类似的浮点数组,例如[0., 1., 2.],可以修改arange输出的类型:arange(3).astype(float)。arange函数对类型敏感:如果将int作为参数输入,它将生成int,并且如果输入float(例如arange(3.)),则将生成float。

但是arange在处理浮点数方面并不是特别擅长:

0.1对于我们来说,这看起来像是一个有限的十进制数,但对计算机而言却不是:用二进制表示,它是一个无穷小数,必须四舍五入到精度需求的位数。所以给arange赋值为小数通常会有问题:会抛出一个错误。可以使间隔的非整数步数,但这会降低可读性和可维护性。

而linspace不受舍入错误的影响,它始终生成要求的元素数量。不过,需要注意的的是它计算的是点,而不是间隔,因此最后一个参数始终,通常认为是加一。因此结果11,而不是上面示例中的10。

在数值计算中,通常需要生成随机数组:

向量索引

一旦将数据保存在了数组中,就可以使用NumPy数组索引对其进行操作。

除花式索引外,以上介绍的所有索引方法实际上都是所谓的"视图":如果索引的值做操作发生更改,则它们不会影响原始数组中存储的数据。

所有这些方法,包括花式索引,都是可变的:如上所述,它们允许通过分配修改原始数组的内容。该功能通过切片来改变数组复制行为:

从NumPy数组中获取数据的另一种超级有用的方法是布尔索引,它允许使用各种逻辑运算符:

any和all行为都和python中的一样,但不支持短路。

Python中的"三元"比较,比如3

如上所述,布尔索引也是可写的。它有两个常见的用例,它们是专用功能:过度重载的功能np.where和np.clip。

向量运算

算术是NumPy性能最耀眼的地方之一。向量运算符已经用c++重构,可使我们避免慢的Python循环。NumPy允许像普通数字一样操作整个数组:

和Python中一样,a//b表示div b(除法的商),x ** n表示xⁿ

将加法或减法将int提升为浮点数的方式相同,将标量提升(也称为broadcast)至数组:

大多数数学函数都有NumPy对应项,可以处理矢量:

标量类型支持特殊的运算符:

三角函数:

数组可以进行四舍五入:

名称np.around只是np.round为了避免round和Python函数干扰,而引入的别名from numpy import *(对比常见的import numpy as np)。也可以使用a.round()。

NumPy还可以执行以下基本统计计算:

这些函数中的每一个都有支持nan的变体:例如nansum,nanmax等

排序功能比Python对应功能具有更少的功能:

向量搜索

与Python列表相反,NumPy数组没有index方法。

查找元素的一种方法是np.where(a==x)[0][0],它既不优雅也不高效,因为即使要查找的项在开头,从一开始就需要遍历数组的所有元素。

更快的方式做到这一点是通过Numba加速:

但是,一旦对数组进行排序,情况就会变得更好:复杂度为O(log N)的情况下确实非常快,但是首先需要O(N log N)的时间。

实际上,通过在C中实现搜索来加速搜索不是问题。问题是浮点数比较。

浮点数比较

函数np.allclose(a, b)比较具有给定精确度范围内的浮点数组

np.allclose假设所有的比较数字是1。例如一个典型的规模,如果在纳秒范围的操作,你需要设置默认atol为1E9:

math.isclose使得要比较没有关于数字的假设,而是基于用户给出一个合理的abs_tol值,而不是(以默认np.allclose atol的1E-8是为1的典型比例的数字不够好值)

除此之外np.allclose,绝对和相对公差公式中还存在一些小问题,例如,

矩阵——二维数组

matrixNumPy中曾经有一个专用的类,但现在已弃用,因此我将交替使用矩阵和2D数组一词。

矩阵初始化语法和向量相似:

这里需要双括号,因为第二个位置参数是为(可选)dtype(也接受整数)保留的。

随机矩阵的生成也类似于矢量的生成:

二维索引语法比嵌套列表更方便:

"视图"符号表示切片数组时实际上并未进行任何复制。修改数组后,更改也将只反映在切片中。

轴参数

在许多操作中(例如sum),需要告诉NumPy是否要跨行或跨列进行操作。为了拥有适用于任意数量维的通用符号,NumPy引入了轴的概念:axis事实上,参数的值是所讨论索引的数量:第一个索引是axis=0,第二个索引是axis=1,依此类推。因此在二维数组中axis=0是按列的,axis=1意味着按行。

矩阵算术

除了普通的运算符(如+,-,*,/,//和**)以元素方式工作外,还有一个@运算符可计算矩阵乘积:

作为在第一部分中已经看到的从标量广播的概括,NumPy允许向量和矩阵之间,甚至两个向量之间的混合运算:

请注意,在最后一个示例中,这是一个对称的逐元素乘法。要使用非对称线性代数矩阵乘法来计算外部乘积,应反转操作数的顺序:

行向量和列向量

从上面的示例可以看出,在二维上下文中,行向量和列向量被不同地对待。这与通常的NumPy做法相反,后者在任何可能的情况下都具有一种类型的一维数组(例二维数组的a[:,j],第j列a是一维数组)。默认情况下,一维数组在二维操作中被视为行向量,因此,将矩阵乘以行向量时,可以使用形状(n,)或(1,n)-结果将相同。如果需要列向量,则有几种方法可以从一维数组中对其进行操作,但令人惊讶的transpose是,它们不是其中一种:

能够从一维数组中生成二维列向量的两个操作是使用reshape和index:

这里的-1参数指示reshape一个方向的密度大。

因此,NumPy中总共有三种类型的向量:一维数组,二维行向量和二维列向量。这是两者之间显式转换的示意图:

根据广播规则,一维数组被隐式解释为二维行向量,因此通常不必在这两个数组之间进行转换。

矩阵操作

连接数组有两个主要功能:

这两种方法仅适用于仅堆叠矩阵或仅堆叠矢量,但在将一维数组和矩阵进行混合堆叠时,仅vstack按预期工作:hstack会报下不匹配错误,因为如上所述,一维数组被解释为行向量,而不是列向量。解决方法是将其转换为行向量,或者使用column_stack自动执行此功能的专用功能:

堆叠的逆向是split:

可以通过两种方式完成矩阵复制:tile类似于复制粘贴;repeat类似分页打印的行为:

特定的列和行可以delete像这样:

逆运算为insert:

append和hstack类似无法自动转置一维数组,因此再次需要对向量进行整形或添加大小,或者column_stack需要使用向量代替:

如果需要做的是向数组的边界添加常量值,则pad函数就足够了:

网状网格

广播规则使使用网格网格的工作更加简单。假设需要以下矩阵(但尺寸很大):

上面两种方法明显都很慢,因为都要使用Python循环。在MATLAB中处理这类问题的方法是创建一个meshgrid:

meshgrid函数接受任意一组索引,mgrid仅是切片,indices并且只能生成完整的索引范围。fromfunction如上所述,仅使用I和J参数一次调用提供的函数。

在NumPy中实际上还有一种更好的方法。无需在整个I和J矩阵上花费内存。仅存储形状正确的矢量就足够了,广播规则将处理其余的内容:

没有indexing='ij'参数的情况下,meshgrid将更改参数的顺序:J, I= np.meshgrid(j, i)—这是一种" xy"模式,用于可视化3D图。

除了在二维或三维网格上初始化函数外,网格还可以用于索引数组:

也适用于稀疏网格

矩阵统计

和sum一样,所有其他的统计功能接受参数(,,,),并进行对应计算:

二维及更高版本中的argmin和argmax函数讨厌返回平坦索引(最小和最大值的第一个实例)。要将其转换为两个坐标,需要一个函数:

量词all和any也支持axis参数:

矩阵排序

尽管axis参数对上面列出的函数很有用,但对二维排序却没有帮助:

这并不是通常希望通过对矩阵或电子表格进行排序得到的结果:axis绝不是key参数的替代。但是幸运的是,NumPy具有几个帮助程序功能,这些功能允许按列或按需要按几列进行排序:

按第一列对数组排序:a[a[:,0].argsort()]

argsort排序后,此处返回原始数组的索引数组。

这个技巧可以重复,但是必须小心,以免下一类混淆前一类的结果:

a = a[a[:,2].argsort()]

a = a[a[:,1].argsort(kind='stable')]

a = a[a[:,0].argsort(kind='stable')]

有一个辅助函数lexsort,该函数按上述方式对所有可用列进行排序,但始终按行执行,并且要排序的行的顺序颠倒了(即,从下到上),因此它的用法有点做作,例如

-a[np.lexsort(np.flipud(a[2,5].T))]排序由5列2列的第一和然后(其中在第2列中的值是相等的)

-a[np.lexsort(np.flipud(a.T))],由所有列在左到右的顺序。

此处flipud沿上下方向翻转矩阵(准确地说,是在与axis=0相同的方向上,a[::-1,...]其中三个点表示"所有其他维度"",因此翻转一维数组flipud并不是突然的fliplr)。

还有一个order参数sort,但是如果从普通(非结构化)数组开始,则既不快速也不容易使用。

因为这个特殊的操作方式更具可读性和它可能是一个更好的选择,pandas用这种方式,不易出错。通过第二列和第五列排序:

按照从左到右的按照各列排序:

三维及以上

通过重塑一维向量或转换嵌套的Python列表来创建三维数组时,索引的含义为(z,y,x)。第一个索引是平面的编号,然后坐标在该平面上移动:

该索引顺序很方便,例如,用于保留一堆灰度图像:这a[i]是引用第i个图像的快捷方式。

但是此索引顺序不是通用的。在处理RGB图像时,通常使用(y,x,z)顺序:第一个是两个像素坐标,最后一个是颜色坐标:

这样,可以方便地引用特定像素:a[i,j]给出像素的RGB元组(i,j)。

因此,创建特定几何形状的实际命令取决于正在处理的域的约定:

NumPy函数类似于hstack,vstack或,但是使用硬编码的索引顺序是(y,x,z),RGB图像顺序:

堆叠RGB图像(此处仅两种颜色)

如果数据的布局不同,则使用concatenate命令堆叠图像,并在axis参数中提供显式索引号会更方便:

堆叠通用3D阵列

如果不方便考虑轴数,可以将数组转换为硬编码为hstack和co的形式:

这种转换是便宜的:没有实际的复制发生。它只是动态混合索引的顺序。

混合索引顺序的另一个操作是数组转置。检查它可能会让三维阵列更加熟悉。根据决定的轴顺序,转置数组所有平面的实际命令将有所不同:对于通用数组,它交换索引1和2,对于RGB图像,它交换0和1:

有趣的是,(和唯一的操作模式)默认axes参数颠倒了索引顺序,这与上述两个索引顺序约定都不相符。transposea.T

einsum函数,可以在处理多维数组时为节省很多Python循环,并使的代码更简洁—:

它将沿重复索引的数组求和。在此特定示例中,这两种情况都可以满足要求,但是在更复杂的情况下,一旦您了解其背后的逻辑,它们的工作速度可能会更快,并且通常更容易读写。

总结

本文我们介绍了NumPy的基本类型和对应的操作,每一步都用图形化方式进行展示,希望能对大家学习有益。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201228A0FVYE00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券