数据科学手册笔记:Numpy 解析和使用 (超详细)

NumPy 入门

Numpy (Numerical Python 的简称) 是数据科学学习中最重要的工具之一,它提供了高效存储和操作密集数据缓存的接口。Numpy 数组和 Python 内置的列表类型非常相似。但是随着数组维度变大,Numpy 数组提供了更高效的存储和数据操作。Numpy 数组几乎是整个 Python 数据科学工具生态系统的核心。

切记:Numpy 数组不同于 Python 列表,Numpy 要求数组必须包含同一类型的数据,如果类型不匹配,Numpy将会向上转换 (如果可行) ,这里整型会被转换为浮点型:
在这里插入图片描述
遵循传统,一般大多数人导入 Numpy 时会将 np 作为别名,更方便操作。
在这里插入图片描述如果想获取更详细的文档及教程和其它资源,可以访问 http://www.numpy.org

1. Numpy 数组基础

1.1 Numpy 数组的属性

我们先用 Numpy 的随即数生成器设置一组种子,以确保每次程序执行时都可以生成同样的随即数组:
在这里插入图片描述

一般来说,可以认为 nbytes 数值等于 itemsize 与 size 的乘积大小

1.2 数组索引:获取单个元素

1.通过括号中指定的索引获取第 i 个值,(从0开始计数)

在这里插入图片描述

2.获取数组末尾的数值,可以用负值索引

在这里插入图片描述

3.在多维数组中,可以用逗号分隔的索引元组获取元素

在这里插入图片描述

4.也可以用以上索引方式修改元素值

在这里插入图片描述

和 Python列表不同,Numpy 数组是固定类型的,这意味着当你试图将一个浮点型值插入到一个整型数组时,浮点型浮点型会被截短成整型,并且这种截短是自动完成的,不会给你提示和警告!

1.3 数组切片:获取子数组

Numpy 切片语法和 Python 列表的标准切片语法相同,获取数组 x 的一个切片,可以用一下公式:x[start:stop:step],如果三个参数都没有被指定,那么它们分别会被设置成默认参数 start = 0,stop = 维度的大小,step = 1。

1.一维子数组

在这里插入图片描述
数组逆序切片操作,start 和 stop 参数默认是被交换的
在这里插入图片描述

2.多维子数组

在这里插入图片描述

3.获取数组的行和列(索引和切片组合实现)

用一个冒号(:)表示空切片:在这里插入图片描述
在获取行时,为了使语法更简介,可以省略空的切片:
在这里插入图片描述

4.非副本视图的子数组

在上面我们每次进行数组切片操作后,其实返回的是数组数据的视图,而不是数值数据的副本。这一点也是 Numpy 数组切片和 Python 列表切片的不同之处,在 Python 列表中,切片返回的是值的副本,比如下面的二维数组:

在这里插入图片描述
如果要修改这个子数组,将会看到原始数组也会被修改!
在这里插入图片描述

这种默认的处理方式在实际中非常有用,因为这意味着在处理非常大的数据集时,可以在获取或处理其中的一些数据集片段的时候,不用去复制底层大量的数据缓存,从而提高效率!

5.创建数组的副本

虽然数组视图有些很好的特性,但是有时候明确的要复制数组里的数据或子数组,可以通过 copy() 方法实现:
在这里插入图片描述

1.4 数组的变形

数组变形最灵活的方式是通过 reshape() 函数来实现,例如将一维数组中的数字 1~9 放入一个 3x3 的矩阵中:
在这里插入图片描述
另外一个变形模式是将一个一维数组转变成二维的行或列的矩阵,既可以通过 reshape 实现,也可以更简单的在一个切片操作中利用 newaxis 关键字,利用 np.newaxis 可以增加数组的一个维度:
在这里插入图片描述

1.5 数组的拼接与分裂

1.数组的拼接

拼接或连接 Numpy 中的两个数组主要由 np.concatenate, np.vstack 和 np.hstack 实现。
np.concatenate 将数组元组或数组列表作为第一个参数:
在这里插入图片描述
np.concatenate 也可用于二维数组的拼接:
在这里插入图片描述
当沿着固定维度处理数组时,使用 np.vstack (垂直栈) 和 np.hstack (水平栈) 函数会更简洁:
在这里插入图片描述

同样 np.dstack() 将会沿着第三个维度拼接数组

2.数组的分裂

分裂可通过 np.split(), np.hsplit(), np.vsplit() 函数来实现,可以向以上函数传递一个索引列表作为参数,索引列表记录的是分裂点的位置,且 N 个分裂点会得到 N+1 个数组,比如下面 2 个分裂点得到了三个子数组:
在这里插入图片描述
np.vsplit() 和 np.hsplit() 的用法类似:
在这里插入图片描述

同样 np.dsplit() 会将数组沿着第三个维度进行分割

2. Numpy 数组的计算:通用函数

2.1 通用函数的介绍

Numpy 为很多类型的操作提供了非常方便的,静态类型,可编译程序的接口,也被称作
向量操作。你可以通过简单地对数组执行操作来实现,这里对数组的操作将会被用于数组中的每一个元素。这种向量方法被用于将循环推送至 Numpy 之下的编译层,这样会取得更快的执行效率。Numpy 中的向量操作是通过函数 通用函数 实现的。通用函数的主要的目的是对 Numpy 数组中的值执行更快操作。它非常的灵活,前面我们看过标量和数组的运算,但是也是对两个数组的运算:
在这里插入图片描述
通用函数并不仅限于一维数组的运算,它们也可以进行多维数组的运算:
在这里插入图片描述

通过 Numpy 的通用函数使用向量的方式进行计算几乎总比用 Python 循环实现的计算更加有效,尤其是当数组很大的时候,只要你看到 Python 脚本中有这样的循环,就应该考虑是否能用向量的方式替换这个循环。

2.2 探索 Numpy 的通用函数

通用函数有两种存在形式:一元通用函数 对单个输入操作,二元通用函数 对两个输入操作,以下将会对两种类型的操作进行示范。

1. 数组的运算

Numpy 通用函数的使用方式非常自然,因为和 Python 的原生运算符相同,标准的加,减,乘,除都可以使用:
在这里插入图片描述
表1.1: Numpy 实现的算术运算符
在这里插入图片描述

2. 绝对值

绝对值函数对应的函数是 np.absolute(), 该函数也可以用别名 np.abs()来访问:
在这里插入图片描述

3. 三角函数

Numpy 提供了大量好用的通用函数,其中对于数据科学家最有用的就是三角函数。首先定义一个角度数组:
在这里插入图片描述
反三角函数:
在这里插入图片描述

这些值是在机器精度内计算的,所以有些应该是 0 的值并没有精确到 0

4. 指数和对数

指数运算:
在这里插入图片描述
对数运算:
在这里插入图片描述
其中有些特殊的版本,对于非常小的输入值可以保持较好的精度。当 x 的数值很小时,以下函数计算出的值比 np.log() 和 np,exp() 的计算更精确:
在这里插入图片描述

5. 专用的通用函数

上面介绍的是平时用的比较多的基础通用函数,如果你想要进行一些晦涩的数学计算,可以使用 scipy.special 模块。该模块中包含了非常多的计算函数,你所需要的计算函数可能就在里面,下面会列举一些可能在统计学中会用到的函数:
在这里插入图片描述

2.3 通用函数的高级特性

1. 指定输出

在进行大量计算时,有时候指定一个用于存放运算结果的数组是非常有用的。不同于创建一个临时数组,你可以用这个特性将计算结果直接写入到你期望的储存位置 。所有的通用函数都可以通过 out 参数来指定计算结果的存放位置:
在这里插入图片描述
数组视图也可以用这个特性:
在这里插入图片描述

如果这里写的是 y = 2 ** x ,那么结果就是创建一个临时数组,该数组存放的是 2**x 的结果,并且接下来会将这些值复制到 y 数组中。对于上述计算量比较小的数组来说,这辆两种方式的差别并不大。但是对于较大的数组,通过慎重使用 out 参数将能够有效节约内存。

2. 聚合

二元通用函数有些非常有趣的聚合功能,这些聚合可以直接在对象上计算。什么意思呢?举个例子,如果我们希望用一个特定的运算 reduce 一个数组,那么可以用任何通用函数的 reduce 方法。一个 reduce 方法会对给定的元素和操作重复执行,直至得到单个的结果,什么意思呢?(通俗点讲,你可以根据自己的需要,通过函数方法将一行或一列元素,聚合成一个元素。二维数组聚合成一维数组,甚至聚合成一个元素)请看下面的例子:
在这里插入图片描述

在一些特殊情况中,Numpy 提供了专用的函数 (np.sum, np.prod, np.cumsum, np.cumprod ),它们也可以实现上面 reduce 方法,下面一节中将具体介绍。

3. 外积 (线性代数中一般指两个向量的张量积,结果是个矩阵)

任何通用函数都可以用 outer 方法获得两个不同输入数组所有元素对的函数运算结果,这意味着你也可以用一行代码实现一个乘法表:
在这里插入图片描述

2.4 聚合 (多维度聚合)

上一小节已经简单介绍了聚合的概念和使用,这一节将会详细介绍。
一种常见的聚合操作是沿着一行或一列聚合。例如,你有一些数据储存在二维数组中,默认情况下,每一个 Numpy 聚合函数将会返回对整个数组的聚合结果:
在这里插入图片描述
聚合函数还有一个参数,用于指定沿哪个维度的方向进行聚合。例如,可以通过指定 axis=0 找到每一列的最小值,axis=1 找到每一行的最大值:
在这里插入图片描述

其它语言的用户可能会对维度的指定方式比较困惑。axis 关键字指定的是 数组将会被折叠的维度, 而不是将要返回的维度。因此指定 axis=0 意味着第一个维度将要被折叠(对于二维数组,这意味着每一列的值将会被聚合)。

Numpy 中可用的聚合函数:
在这里插入图片描述

2.5 数组的计算:广播

前面我们介绍了 Numpy 如何通过通用函数的向量化操作来减少缓慢的 Python 循环,另一种向量化操作的方法是利用 Numpy 的广播功能。广播可以简单理解为用于不同大小数组的二进制通用函数(加,减,乘等)的一组规则。

1. 广播的介绍

对于同样大小的数组,二进制操作,是对相应的元素逐个计算:
在这里插入图片描述
广播允许这些二进制操作可以用于不同大小的数组。例如,可以简单地将一个标量(可以认为是一个 0 维的数组)和一个数组想加:
在这里插入图片描述
我们可以认为这个操作是将数值 5 扩展或复制到数组 [5, 5, 5],然后执行加法。Numpy 广播功能的好处是,这种对值的重复实际上并没有发生,但是这是一种很好用的理解广播功能的模型。

我们同样也可以将这个原理扩展到更高的维度。观察以下将一个一维数组和一个二维数组相加的结果:
在这里插入图片描述
这里这个一维数组就被扩展了或者说是广播了。它沿着第二维度扩展,扩展到匹配 M 数组的形状,再进行相加。

以上的这些例子理解起来相对容易,更复杂的情况会涉及到对两个数组的同时广播,例如以下示例:
在这里插入图片描述
正如之前将一个值扩展或者广播以匹配另外一个数组的形状,这里将 a 和 b都进行了扩展来匹配一个公共的形状,最终的结果是一个二维数组,下面是这些例子的广播几何可视化图:
广播的可视化

浅色的盒子表示广播值。同时需要注意的是,这些额外的内存并没有在实际操作中进行分配,但是这样的思考方式更方便我们从概念上理解广播的含义。

2. 广播的规则

NumPy中的广播遵循一套严格的规则来确定两个数组之间的交互:

规则1:如果两个数组的维数不同,则维数较少的数组的形状将在其前(左侧)填充。
规则2:如果两个数组的形状在任何维度上都不匹配,则将在该维度上形状等于1的数组拉伸以匹配其他形状。
规则3:如果尺寸在任何维度上都不相同,且都不等于1,则会引发错误。

广播示例1
将一个二维数组与一个一维数组相加:

M = np.ones((2, 3))
a = np.arange(3)

接下来看两个数组的加法操作,两个数组的形状如下:

M.shape = (2, 3)
a.shape = (3,)

可以看到,根据规则 1 ,数组 a 的维度数更小,所以在其左边补 1 :

M.shape -> (2, 3)
a.shape -> (1, 3)

然后根据规则 2 ,第一个维度不匹配,因此扩展这个维度以匹配数组:

M.shape -> (2, 3)
a.shape -> (2, 3)

现在两个数组的形状匹配了,可以看到它们最终的形状都为 (2, 3):
在这里插入图片描述
广播示例2
下面我们来看一下两个数组均需要广播的示例:

a = np.arange(3).reshape(3, 1)
b = np.arange(3)

同样,首先写出两个数组的形状:

a.shape = (3, 1)
b.shape = (3,)

规则1告诉我们,需要用 1 将 b 的形状补全:

a.shape = (3, 1)
b.shape = (1, 3)

规则2告诉我们,需要更新这两个数组的维度来互相匹配:

a.shape = (3, 3)
b.shape = (3, 3)

因为结果匹配,所以这两个形状兼容的,可以看到以下结果:
在这里插入图片描述
广播示例3
现在来看两个数组不兼容的示例:

M = np.ones((3, 2))
a = np.arange(3)

和第一个示例略有不同的情况:矩阵 M 已转置。这如何影响计算?数组的形状是:

M.shape = (3, 2)
a.shape = (3,)

同样规则 1 告诉我们,a 数组的形状必须用 1 进行补全:

M.shape -> (3, 2)
a.shape -> (1, 3)

根据规则 2 ,a 数组的第一个维度进行扩展以匹配 M 的维度:

M.shape -> (3, 2)
a.shape -> (3, 2)

现在你需要用到规则 3 (最终形状还是不匹配),因此这两个数组是不兼容的。当我们执行运算时会看到以下结果:
在这里插入图片描述

请注意此处可能存在的混淆:你可能想通过在 a 数组的右边补 1,而不是左边补 1,从而让 a 和 M 的维度变得兼容。但这是广播规则不允许的!这种灵活性在某些情况下可能有用,但这也可能会导致歧义。如果您想要右侧填充,则可以通过数组变形来明确地做到这一点 (将会用到关键字 np.newaxis )
在这里插入图片描述
另外需要注意的是,这里仅用到 + 运算符,而这些广播规则对于 任意二进制通用函数 都是适用的。

2.6 比较 掩码和布尔逻辑

本节将会介绍使用布尔掩码来检查和操作 NumPy 数组中的值。当您要基于某些条件提取,修改,计数或以其他方式操纵数组中的值时,掩码就会派上用场。例如,您可能希望对大于某个值的所有值进行计数,或者可能删除高于某个值的所有异常值阈。在 NumPy 中,布尔掩码通常是完成这些类型任务的最有效方法。

1. 和通用函数类似的比较操作

这些比较的结果是一个布尔数据类型:
在这里插入图片描述
另外,利用符合表达式实现对两个数组的逐个元素比较也是可行的:
在这里插入图片描述
与算术运算符一样,比较运算符在 NumPy 中也是借助通用函数来实现的。例如,在编写时 x < 3,NumPy 在内部使用 np.less(x, 3)。这些比较运算符和其对应的通用函数如下:
在这里插入图片描述
和算术运算通用函数一样,这些比较运算通用函数也可以用于任意形状,大小的数组。下面是一个二维数组的例子:
在这里插入图片描述

2. 操作布尔数组

统计记录个数
在这里插入图片描述

**sum()**有着 和其它 Numpy 聚合函数一样的好处,可以沿着行或列进行
在这里插入图片描述

np.anynp.all 可以快速检查任意或者所有这些值是否为 True
在这里插入图片描述

np.allnp.any可以沿着特定的坐标轴在这里插入图片描述

3. 将布尔数组作为掩码

在这里插入图片描述

2.7 花式索引

在前面的部分中,我们看到了如何使用简单索引(例如arr[0]),切片(例如arr[:5])和布尔掩码(例如arr[arr > 0])访问和修改数组的部分。在本节中,我们将介绍另一种样式的数组索引,即 花式索引 (fancy indexing)。花式索引就像我们已经看到的简单索引一样,但是我们传递索引数组来代替单个标量。这使我们能够非常快速地访问和修改数组值的复杂子集。

1. 探索花式的索引

花式索引可以通过传递一个索引数组来一次性获得多个数组元素。
方法一:
在这里插入图片描述
方法二 (通过传递索引的单个列表或数组来获得同样的结果):
在这里插入图片描述
利用花式索引,结果的形状与 索引数组 的形状一致,而不是与 被索引数组 的形状一致:
在这里插入图片描述
花式索引也对多个维度数组适用 (第一个索引指行,第二个索引指列):
在这里插入图片描述

结果中的第一个值为X[0, 2],第二个为X[1, 1],第三个为X[2, 3]。花式索引中的索引配对遵循“阵列计算:广播”中提到的所有广播规则。因此,例如,如果我们在索引内合并列向量和行向量,则将获得二维结果 (每个行值都与每个列向量匹配):
在这里插入图片描述
在这里插入图片描述

2. 组合索引

花式索引和简单的索引组合使用:
在这里插入图片描述
花式索引和切片的组合使用:
在这里插入图片描述
花式索引和掩码的组合使用:
在这里插入图片描述

3. 花式索引修改值

在这里插入图片描述
不过需要注意,操作中重复索引会导致一些出乎意料的结果产生
在这里插入图片描述
4去了哪里?此操作的结果是先分配 x[0] = 4,然后是 x[0] = 6。结果当然是 x[0] 包含值6。
以上还算合理,但是设想以下操作:
在这里插入图片描述
您可能希望其中 x[3] 的值为 2,并且 x[4] 的值为3,因为这是每个索引重复的次数。为什么不是这样?从概念上讲,这是因为 x[i] += 1 是 x[i] = x[i] + 1 的简写。x[i] + 1进行计算后,然后将结果赋值给 x 中的索引。记住这个原理后,我们却发现数组并没有发生多次累加,而是发生了赋值,显然这不是我们想要的结果。
那么,如果您想要重复操作的其他行为怎么办?为此,您可以使用 at() 方法(自NumPy 1.8起可用),然后执行以下操作:
在这里插入图片描述
at() 函数中 x 为被操作的数组,i 为给定的索引值,1 为执行操作的值。另一个可以实现该功能的类似方法是通用函数中的 reduceat() 函数,您可以在 NumPy 文档中阅读该方法。

2.8 数组排序

到目前为止,我们主要关注的是使用 NumPy 访问和处理数组数据的工具。本节介绍与 NumPy 数组中的值排序有关的算法,这些算法是计算机科学入门课程中最喜欢的主题。

1.Numpy 中的快速排序:np.sort 和 np.argsort

尽管 Python 具有内置功能 sort 和 sorted 可使用列表的功能,但由于NumPy 的 np.sort 功能对于我们的目的而言更加高效和有用,因此我们在此不进行讨论。默认情况下,np.sort 使用的是 快速排序,其算法的时间复杂度为 O(N log N),另外也可以选择 归并排序堆排序

如果想在不修改原始输入数组的基础上返回一个排序好的数组,可以使用 np.sort
在这里插入图片描述
如果希望用排序好的数组替代原始数组,可以使用数组的 sort 方法:
在这里插入图片描述
另外一个相关的函数是 argsort,该函数返回的是原始数组排序好的索引值:
在这里插入图片描述
此结果的第一个元素给出最小元素的索引,第二个值给出第二个最小元素的索引,依此类推。然后可以根据需要使用这些索引(通过花式索引)来构造排序后的数组:
在这里插入图片描述
(沿着行或列排序)

NumPy 排序算法的一个有用功能是能够使用axis参数沿多维数组的特定行或列进行排序。例如:
在这里插入图片描述

请记住,这会将每一行或每一列视为一个独立的数组,并且行或列值之间的任何关系都将丢失!

2. 部分排序:分区

有时我们不希望对整个数组进行排序,而只是想找到数组中的 k 个最小值。NumPy 在np.partition 函数中提供了此功能。np.partition函数的输入是一个数组和一个数字 K ; 结果是一个新的数组,最左边是第 K 小的值,往右是任意顺序的其它值:
在这里插入图片描述

请注意,结果数组中的前三个值是该数组中的三个最小值,其余数组位置包含其余值。在两个分区中,元素具有任意顺序。

沿着多维数组任意轴进行分隔
在这里插入图片描述
输出结果是一个数组,该数组每一行的前两个元素是该行的最小的两个值,每行的其它值分布在剩下的位置。

最后,就像有一个 np.argsort 计算排序的索引一样,有一个np.argpartition 计算分区的索引。

数据科学手册 Github 地址:https://jakevdp.github.io/PythonDataScienceHandbook/

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值