Skip to content

Latest commit

 

History

History
773 lines (607 loc) · 39.7 KB

File metadata and controls

773 lines (607 loc) · 39.7 KB

一、使用 NumPy 数组

科学计算是一个多学科领域,其应用跨越数值分析,计算金融和生物信息学等学科。

让我们考虑一下金融市场的情况。 当您考虑金融市场时,会有巨大的相互联系的互动网络。 政府,银行,投资基金,保险公司,养老金,个人投资者和其他人都参与了这种金融工具的交换。 您不能简单地模拟市场参与者之间的所有互动,因为参与金融交易的每个人都有不同的动机和不同的风险/回报目标。 还有其他因素会影响金融资产的价格。 即使为一个资产价格建模也需要您做大量工作,并且不能保证成功。 用数学术语来说,这没有封闭形式的解决方案,这为利用科学计算提供了一个很好的案例,您可以在其中使用高级计算技术来解决此类问题。

通过编写计算机程序,您将有能力更好地了解正在使用的系统。 通常,您将要编写的计算机程序将是某种模拟,例如蒙特卡洛模拟。 通过使用诸如蒙特卡洛的模拟,您可以对期权合约的价格进行建模。 仅仅由于金融市场的复杂性,对金融资产进行定价是进行模拟的良好材料。 在进行计算时,所有这些数学计算都需要一个功能强大,可扩展且方便的数据结构(大多数为矩阵形式)。 换句话说,您需要比列表更紧凑的结构,以简化您的任务。 NumPy 是高效向量/矩阵运算的理想选择,其广泛的数学运算库使数值计算变得简单而高效。

在本章中,我们将涵盖以下主题:

  • NumPy 的重要性
  • 关于向量和矩阵的理论和实践信息
  • NumPy 数组操作及其在多维数组中的用法

问题是,我们应该从哪里开始练习编码技能? 在本书中,由于 Python 在科学界的广泛应用,您将在使用它,并且您将主要使用名为 NumPy 的特定库(它代表数字 Python)。

技术要求

在本书中,我们将使用 Jupyter 笔记本。 我们将通过网络浏览器编辑和运行 Python 代码。 这是一个开源平台,您可以按照此链接中的说明进行安装

本书将使用 Python 3.x,因此在打开新笔记本时,应选择 Python 3 内核。 另外,也可以使用 Anaconda(Python 版本 3.6)安装 Jupyter 笔记本。 您可以按照此链接中的说明进行安装

为什么我们需要 NumPy?

Python 最近成为一种摇滚明星的编程语言,不仅因为它具有友好的语法和可读性,而且因为它可以用于多种用途。 各种库的 Python 生态系统使程序员相对容易进行各种计算。 堆栈溢出是程序员最受欢迎的网站之一。 用户可以通过标记与之相关的编程语言来提问。 下图通过计算这些标签显示了主要编程语言的增长,并绘制了多年来主流编程语言的流行程度。 通过 Stack Overflow 进行的研究可以通过其官方博客链接进行进一步分析

主要编程语言的发展

NumPy 是 Python 中科学计算的最基本软件包,也是许多其他软件包的基础。 由于 Python 最初并不是为数字计算而设计的,因此在 90 年代后期开始出现这种需求时,Python 开始在需要更快的向量运算的工程师和程序员中流行。 从下图可以看到,许多流行的机器学习和计算包都使用了 NumPy 的某些功能,最重要的是它们在其方法中大量使用了 NumPy 数组,这使 NumPy 成为科学项目的基本库。

该图显示了一些使用 NumPy 功能的知名库:

NumPy 栈

对于数值计算,您主要处理向量和矩阵。 您可以通过使用一系列数学函数以不同的方式来操作它们。 NumPy 非常适合此类情况,因为它允许用户有效地完成其计算。 尽管 Python 列表很容易创建和操作,但它们不支持向量化操作。 Python 在列表中没有固定的类型元素,例如,for循环效率不高,因为在每次迭代中都需要检查数据类型。 但是,在 NumPy 数组中,数据类型是固定的,并且还支持向量化运算。 与 Python 列表相比,NumPy 在多维数组操作中不仅效率更高。 它还提供了许多数学方法,您可以在导入后立即应用它们。 NumPy 是用于科学 Python 数据科学堆栈的核心库。

SciPy 与 NumPy 有着密切的关系,因为它使用 NumPy 多维数组作为其线性代数,优化,插值,积分,FFT,信号,图像处理和其它的科学函数的基础数据结构。 SciPy 建立在 NumPy 数组框架之上,并凭借其先进的数学函数提升了科学编程能力。 因此,NumPy API 的某些部分已移至 SciPy。 在许多情况下,与 NumPy 的这种关系使 SciPy 更便于进行高级科学计算。

综上所述,我们可以总结出 NumPy 的优势如下:

  • 它是开源的,零成本
  • 这是一种具有用户友好语法的高级编程语言
  • 比 Python 列表更有效
  • 它具有更高级的内置函数,并与其他库很好地集成在一起

谁使用 NumPy?

在学术界和商业界,您都会听到人们谈论他们在工作中使用的工具和技术。 根据环境和条件,您可能需要使用特定技术。 例如,如果您的公司已经投资了 SAS,则需要在适合您问题的 SAS 开发环境中进行项目。

但是,NumPy 的优点之一是它是开源的,在您的项目中使用它无需花费任何成本。 如果您已经使用 Python 编写过代码,那么它非常容易学习。 如果您关心性能,则可以轻松嵌入 C 或 Fortran 代码。 此外,它将为您介绍其他完整的库集,例如 SciPy 和 Scikit-learn,您可以使用它们来解决几乎所有问题。

由于数据挖掘和预测分析在最近变得非常重要,因此数据科学家数据分析师等角色在 21 世纪最热门的工作中被提及,例如《福布斯》,彭博社, 等等。 需要处理数据并进行分析,建模或预测的人员应该熟悉 NumPy 的用法及其功能,因为它将帮助您快速创建原型并测试您的想法。 如果您是专业工作人员,那么您的公司很可能希望使用数据分析方法,以使其领先于竞争对手。 如果他们能够更好地理解他们拥有的数据,那么他们就可以更好地理解业务,这将使他们做出更好的决策。 NumPy 在这里起着至关重要的作用,因为它能够执行各种操作,并使您的项目及时有效。

向量和矩阵简介

矩阵是一组数字或元素,它们以矩形数组的形式排列。 矩阵的行和列通常按字母索引。 对于n x m矩阵,n表示行数,m表示列数。 如果我们有一个假设的n×m矩阵,则其结构如下:

如果n = m,则称为称为方阵:

向量实际上是具有多于一个元素的一行或一列的矩阵。 也可以将其定义为1 x mn x 1矩阵。 您可以将向量解释为m维空间中的箭头或方向。 通常,大写字母表示矩阵,例如X;小写字母带有下标,例如x[11],表示矩阵X的元素。

此外,还有一些重要的特殊矩阵:零矩阵(空矩阵)和恒等矩阵。0表示零矩阵,它是所有0的矩阵(MacDufee 1943 p.27)。 在0矩阵中,添加下标是可选的:

I表示的单位矩阵及其对角线元素为1,其他元素为0

将矩阵X与单位矩阵相乘时,结果将等于X

单位矩阵对于计算矩阵的逆非常有用。 当您将任何给定矩阵与其逆矩阵相乘时,结果将是一个单位矩阵:

让我们简要地看一下 NumPy 数组上的矩阵代数。 矩阵的加减法运算与具有普通单数的数学方程式相似。 举个例子:

标量乘法也非常简单。 例如,如果将矩阵X乘以4,则唯一要做的就是将每个元素乘以4值,如下所示:

开始时矩阵处理看似复杂的部分是矩阵乘法。

假设您有两个矩阵,分别为XY,其中X 矩阵,Y 矩阵:

 

这两个矩阵的乘积将如下所示:

因此,乘积矩阵的每个元素的计算方式如下:

如果您不了解该符号,请不要担心。 以下示例将使事情变得更清楚。 您有矩阵XY,目标是获得这些矩阵的矩阵乘积:

基本思想是Xi行与Yj列的乘积将成为结果矩阵的第i, j个元素。 乘法将从X的第一行和Y的第一列开始,因此它们的乘积将为Z[1, 1]

您可以使用以下四行代码轻松地交叉检查结果:

In [1]: import numpy as np
        x = np.array([[1,0,4],[3,3,1]])
        y = np.array([[2,5],[1,1],[3,2]])
        x.dot(y)
Out[1]: array([[14, 13],[12, 20]])

先前的代码块只是演示如何使用 NumPy 计算两个矩阵的点积。 在后面的章节中,我们将更深入地研究矩阵运算和线性代数。

NumPy 数组对象的基础

如上一节所述,使 NumPy 与众不同的是使用称为ndarrays的多维数组。 所有ndarray项目都是同类的,并且在内存中使用相同的大小。 让我们首先导入 NumPy,然后通过创建数组来分析 NumPy 数组对象的结构。 您可以通过在控制台中键入以下语句来轻松导入该库。 您可以使用任何命名约定代替np,但是在本书中,将使用np作为标准约定。 让我们创建一个简单的数组,并说明 Python 在幕后所具有的属性作为所创建数组的元数据,即所谓的属性

In [2]: import numpy as np
        x = np.array([[1,2,3],[4,5,6]])
        x
Out[2]: array([[1, 2, 3],[4, 5, 6]])
In [3]: print("We just create a ", type(x))
Out[3]: We just create a <class 'numpy.ndarray'>
In [4]: print("Our template has shape as" ,x.shape)
Out[4]: Our template has shape as (2, 3)
In [5]: print("Total size is",x.size)
Out[5]: Total size is 6
In [6]: print("The dimension of our array is " ,x.ndim)
Out[6]: The dimension of our array is 2
In [7]: print("Data type of elements are",x.dtype)
Out[7]: Data type of elements are int32
In [8]: print("It consumes",x.nbytes,"bytes")
Out[8]: It consumes 24 bytes

如您所见,我们对象的类型是 NumPy 数组。x.shape返回一个元组,该元组为您提供数组的维度作为输出,例如(n, m)。 您可以使用x.size获得数组中元素的总数。 在我们的示例中,我们总共有六个元素。 知道形状和大小等属性非常重要。 您了解的越多,您对计算的适应就越多。 如果您不知道数组的大小和大小,那么开始使用数组进行计算就不明智了。 在 NumPy 中,您可以使用x.ndim来检查数组的维数。 还有其他属性,例如dtypenbytes,这些属性在检查内存使用情况并验证应该在数组中使用哪种数据类型时非常有用。 在我们的示例中,每个元素的数据类型为int32,总共消耗 24 个字节。 您还可以在创建数组时强制使用其中某些属性,例如dtype。 以前,数据类型是整数。 让我们将其切换为floatcomplexuint(无符号整数)。 为了查看数据类型更改的作用,让我们分析一下字节消耗量,如下所示:

In [9]: x = np.array([[1,2,3],[4,5,6]], dtype = np.float)
         print(x)
Out[9]: print(x.nbytes)
        [[ 1\. 2\. 3.]
        [ 4\. 5\. 6.]]
        48
In [10]: x = np.array([[1,2,3],[4,5,6]], dtype = np.complex)
         print(x)
         print(x.nbytes)
Out[10]: [[ 1.+0.j 2.+0.j 3.+0.j]
         [ 4.+0.j 5.+0.j 6.+0.j]]
         96
In [11]: x = np.array([[1,2,3],[4,-5,6]], dtype = np.uint32)
         print(x)
         print(x.nbytes)
Out[11]: [[ 1 2 3]
         [ 4 4294967291 6]]
         24

如您所见,每种类型消耗不同数量的字节。 假设您有一个如下矩阵,并且您正在使用int64int32作为数据类型:

In [12]: x = np.array([[1,2,3],[4,5,6]], dtype = np.int64)
         print("int64 consumes",x.nbytes, "bytes")
         x = np.array([[1,2,3],[4,5,6]], dtype = np.int32)
         print("int32 consumes",x.nbytes, "bytes")
Out[12]: int64 consumes 48 bytes
         int32 consumes 24 bytes

如果使用int64,则内存需求将增加一倍。 向自己问这个问题; 哪种数据类型就足够了? 在您的数字大于 2,147,483,648 且小于 -2,147,483,647 之前,使用int32就足够了。 假设您有一个大小超过 100MB 的巨大数组。 在这种情况下,这种转换对性能起着至关重要的作用。

正如您在上一个示例中可能已经注意到的那样,当您更改数据类型时,每次都在创建一个数组。 从技术上讲,创建数组后无法更改dtype。 但是,您可以做的是再次创建它,或者使用新的dtypeastype属性复制现有的文件。 让我们使用新的dtype创建数组的副本。 下面是一个示例,说明如何使用astype属性也可以更改dtype

In [13]: x_copy = np.array(x, dtype = np.float)
         x_copy
Out[13]: array([[ 1., 2., 3.],
         [ 4., 5., 6.]])
In [14]: x_copy_int = x_copy.astype(np.int)
         x_copy_int
Out[14]: array([[1, 2, 3],
         [4, 5, 6]])

请记住,使用astype属性时,即使将其应用于x_copy,它也不会更改x_copydtype。它保留x_copy,但创建一个x_copy_int

In [15]: x_copy
Out[15]: array([[ 1., 2., 3.],
         [ 4., 5., 6.]])

假设有一个案例,您正在一个研究小组中工作,该研究小组试图确定和计算每个患癌症患者的风险。 您有 100,000 条记录(行),其中每一行代表一位患者,每位患者具有 100 个特征(某些测试的结果)。 结果,您有(100000, 100)的数组:

In [16]: Data_Cancer= np.random.rand(100000,100)
         print(type(Data_Cancer))
         print(Data_Cancer.dtype)
         print(Data_Cancer.nbytes)
         Data_Cancer_New = np.array(Data_Cancer, dtype = np.float32)
         print(Data_Cancer_New.nbytes)
Out[16]: <class 'numpy.ndarray'>
         float64
         80000000
         40000000

从前面的代码中可以看到,仅通过更改dtype即可将它们的大小从 80MB 减小到 40MB。 我们得到的回报是小数点后的精度较低。 除了精确到 16 位小数点外,您将只有 7 位小数。 在某些机器学习算法中,精度可以忽略不计。 在这种情况下,您应该随意调整dtype,以最大程度地减少内存使用量。

NumPy 数组操作

本节将指导您使用 NumPy 创建和处理数字数据。 让我们从列表中创建一个 NumPy 数组开始:

In [17]: my_list = [2, 14, 6, 8]
         my_array = np.asarray(my_list)
         type(my_array)
Out[17]: numpy.ndarray

让我们用标量值做一些加法,减法,乘法和除法:

In [18]: my_array + 2
Out[18]: array([ 4, 16, 8, 10])
In [19]: my_array - 1
Out[19]: array([ 1, 13, 5, 7])
In [20]: my_array * 2
Out[20]: array([ 4, 28, 12, 16, 8])
In [21]: my_array / 2
Out[21]: array([ 1\. , 7\. , 3\. , 4\. ])

在列表中执行相同的操作要困难得多,因为该列表不支持向量化操作,并且您需要迭代其元素。 创建 NumPy 数组的方法有很多,现在您将使用这些方法之一来创建一个充满零的数组。 稍后,您将执行一些算术运算,以查看 NumPy 在两个数组之间的按元素运算中的行为:

In [22]: second_array = np.zeros(4) + 3
         second_array
Out[22]: array([ 3., 3., 3., 3.])
In [23]: my_array - second_array
Out[23]: array([ -1., 11., 3., 5.])
In [24]: second_array / my_array
Out[24]: array([ 1.5 , 0.21428571, 0.5 , 0.375 ])

就像我们在前面的代码中所做的那样,您可以创建一个充满np.ones的数组或一个充满np.identity的标识数组,并执行与之前相同的代数运算:

In [25]: second_array = np.ones(4) + 3
         second_array
Out[25]: array([ 4., 4., 4., 4.])
In [26]: my_array - second_array
Out[26]: array([ -2., 10., 2., 4.])
In [27]: second_array / my_array
Out[27]: array([ 2\. , 0.28571429, 0.66666667, 0.5 ])

它可以通过np.ones方法按预期工作,但是当您使用单位矩阵时,计算将返回(4, 4)数组,如下所示:

In [28]: second_array = np.identity(4)
         second_array
Out[28]: array([[ 1., 0., 0., 0.],
                [ 0., 1., 0., 0.],
                [ 0., 0., 1., 0.],
                [ 0., 0., 0., 1.]])
In [29]: second_array = np.identity(4) + 3
         second_array
Out[29]: array([[ 4., 3., 3., 3.],
                [ 3., 4., 3., 3.],
                [ 3., 3., 4., 3.],
                [ 3., 3., 3., 4.]])
In [30]: my_array - second_array
Out[30]: array([[ -2., 11., 3., 5.],
                [ -1., 10., 3., 5.],
                [ -1., 11., 2., 5.],
                [ -1., 11., 3., 4.]])

这是从second_array的第一列的所有元素和第二列的second_element的所有元素中减去my_array的第一个元素,依此类推。 同样的规则也适用于除法。 请记住,即使形状不完全相同,也可以成功执行数组操作。 在本章的后面,您将了解当两个数组由于形状不同而无法进行计算时的广播错误:

In [31]: second_array / my_array
Out[31]: array([[ 2\.  , 0.21428571, 0.5       , 0.375      ],
                [ 1.5 , 0.28571429, 0.5       , 0.375      ],
                [ 1.5 , 0.21428571, 0.66666667, 0.375      ],
                [ 1.5 , 0.21428571, 0.5       , 0.5        ]])

创建 NumPy 数组最有用的方法之一是arange。 这将在起始值和结束值之间的给定间隔内返回一个数组。 第一个参数是数组的起始值,第二个参数是终止值(在该位置它不再创建值),第三个参数是间隔。 您可以选择将dtype定义为第四个参数。 默认间隔值为 1:

In [32]: x = np.arange(3,7,0.5)
         x
Out[32]: array([ 3\. , 3.5, 4\. , 4.5, 5\. , 5.5, 6\. , 6.5])

当您无法确定间隔应该是多少时,还有另一种方法可以在起点和终点之间创建具有固定间隔的数组,但是您应该知道数组应该有多少个拆分:

In [33]: x = np.linspace(1.2, 40.5, num=20)
         x
Out[33]: array([ 1.2        , 3.26842105,  5.33684211,  7.40526316,   9.47368421,
                 11.54210526, 13.61052632, 15.67894737, 17.74736842, 19.81578947,
                 21.88421053, 23.95263158, 26.02105263, 28.08947368, 30.15789474,
                 32.22631579, 34.29473684, 36.36315789, 38.43157895, 40.5       ])

有两种用法相似的不同方法,但由于它们的基本尺度不同,它们返回不同的数字序列。 这意味着数字的分布也将不同。 第一个是geomspace,它以几何级数返回对数刻度上的数字:

In [34]: np.geomspace(1, 625, num=5)
Out[34]: array([ 1., 5., 25., 125., 625.])

另一个重要的方法是logspace,您可以在其中返回起点和终点的值,这些值在以下位置均匀缩放:

In [35]: np.logspace(3, 4, num=5)
Out[35]: array([ 1000\. , 1778.27941004, 3162.27766017, 5623.4132519 ,
                10000\. ])

这些参数是什么? 如果起始点为3,结束点为4,则这些函数将返回比初始范围大得多的数字。 实际上,您的起点默认设置为10,终点也设置为10。 因此,从技术上讲,在此示例中,起点为10**3,终点为10**4。 您可以避免这种情况,并使起点和终点与在方法中将其用作参数时相同。 诀窍是使用给定参数的以 10 为底的对数:

In [36]: np.logspace(np.log10(3) , np.log10(4) , num=5)
Out[36]: array([ 3\. , 3.2237098 , 3.46410162, 3.72241944, 4\. ])

到目前为止,您应该熟悉创建具有不同分布的数组的不同方法。 您还学习了如何对它们进行一些基本操作。 让我们继续您在日常工作中肯定会使用的其他有用函数。 大多数时候,您将不得不使用多个数组,并且需要非常快速地比较它们。 NumPy 对这个问题有很好的解决方案。 您可以像比较两个整数一样比较数组:

In [37]: x = np.array([1,2,3,4])
         y = np.array([1,3,4,4])
         x == y
Out[37]: array([ True, False, False, True], dtype=bool)

比较是逐个元素进行的,无论元素是否在两个不同的数组中匹配,它都会返回一个布尔向量。 此方法在小大小数组中效果很好,以及也为您提供了更多详细信息。 您可以从输出数组中看到以False表示的值,这些索引值在这两个数组中不匹配。 如果数组很大,则还可以选择对问题的单个答案,即元素是否在两个不同的数组中匹配:

In [38]: x = np.array([1,2,3,4])
         y = np.array([1,3,4,4])
         np.array_equal(x,y)
Out[38]: False

在这里,您只有一个布尔输出。 您只知道数组不相等,但是您不知道哪些元素完全不相等。 比较不仅限于检查两个数组是否相等。 您还可以在两个数组之间进行逐元素的上下比较:

In [39]: x = np.array([1,2,3,4])
         y = np.array([1,3,4,4])
         x < y
Out[39]: array([False, True, True, False], dtype=bool)

当需要进行逻辑比较(ANDORXOR)时,可以在数组中使用它们,如下所示:

In [40]: x = np.array([0, 1, 0, 0], dtype=bool)
         y = np.array([1, 1, 0, 1], dtype=bool)
         np.logical_or(x,y)
Out[40]: array([ True, True, False, True], dtype=bool)
In [41]: np.logical_and(x,y)
Out[41]: array([False, True, False, False], dtype=bool)
In [42]: x = np.array([12,16,57,11])
         np.logical_or(x < 13, x > 50)
Out[42]: array([ True, False, True, True], dtype=bool)

到目前为止,已经介绍了诸如加法和乘法之类的代数运算。 我们如何将这些运算与指数函数,对数函数或三角函数之类的超越函数一起使用?

In [43]: x = np.array([1, 2, 3,4 ])
         np.exp(x)
Out[43]: array([ 2.71828183, 7.3890561 , 20.08553692, 54.59815003])
In [44]: np.log(x)
Out[44]: array([ 0\. , 0.69314718, 1.09861229, 1.38629436])
In [45]: np.sin(x)
Out[45]: array([ 0.84147098, 0.90929743, 0.14112001, -0.7568025 ])

矩阵的转置呢? 首先,您将reshape函数与arange一起使用以设置所需的矩阵形状:

In [46]: x = np.arange(9)
         x
Out[46]: array([0, 1, 2, 3, 4, 5, 6, 7, 8])
In [47]: x = np.arange(9).reshape((3, 3))
         x
Out[47]: array([[0, 1, 2],
                [3, 4, 5],
                [6, 7, 8]])
In [48]: x.T
Out[48]: array([[0, 3, 6],
                [1, 4, 7],
                [2, 5, 8]])

您转置了3 * 3数组,因此形状不会改变,因为两个大小均为3。 让我们看看没有平方数组时会发生什么:

In [49]: x = np.arange(6).reshape(2,3)
         x
Out[49]: array([[0, 1, 2],
                [3, 4, 5]])
In [50]: x.T
Out[50]: array([[0, 3],
                [1, 4],
                [2, 5]])

转置将按预期工作,并且大小也会切换。 您还可以从数组中获取摘要统计信息,例如均值,中位数和标准差。 让我们从 NumPy 提供的用于计算基本统计信息的方法开始:

方法 说明
np.sum 返回数组所有值沿指定轴的总和
np.amin 返回数组所有值或沿指定轴的最小值
np.amax 返回数组所有值或沿指定轴的最大值
np.percentile 返回数组所有值或沿指定轴的第 n 个的给定百分数
np.nanmin np.amin相同,但忽略数组中的 NaN 值
np.nanmax np.amax相同,但忽略数组中的 NaN 值
np.nanpercentile np.percentile相同,但忽略数组中的 NaN 值

以下代码块给出了前面的 NumPy 统计方法的示例。 这些方法非常有用,因为您可以根据需要在整个数组中或轴向操作这些方法。 您应该注意,由于 SciPy 使用 NumPy 多维数组作为数据结构,因此可以在 SciPy 中找到这些方法的功能更完善的更好的实现:

In [51]: x = np.arange(9).reshape((3,3))
         x
Out[51]: array([[0, 1, 2],
                [3, 4, 5],
                [6, 7, 8]])
In [52]: np.sum(x)
Out[52]: 36
In [53]: np.amin(x)
Out[53]: 0
In [54]: np.amax(x)
Out[54]: 8
In [55]: np.amin(x, axis=0)
Out[55]: array([0, 1, 2])
In [56]: np.amin(x, axis=1)
Out[56]: array([0, 3, 6])
In [57]: np.percentile(x, 80)
Out[57]: 6.4000000000000004

axis参数确定此函数将要作用的大小。 在此示例中,轴= 0代表第一个轴,即x轴,轴= 1代表第二个轴,即y。 当我们使用常规amin(x)时,我们返回单个值,因为它会计算所有数组中的最小值,但是当我们指定轴时,它将开始沿轴方向计算函数并返回一个数组,该数组显示每行或列的结果。 想象一下,您有很多人。 您可以使用amax找到最大值,但是如果您需要将此值的索引传递给另一个函数,将会发生什么? 在这种情况下,argminargmax可以提供帮助,如以下代码片段所示:

In [58]: x = np.array([1,-21,3,-3])
         np.argmax(x)
Out[58]: 2
In [59]: np.argmin(x)
Out[59]: 1

让我们继续更多的统计函数:

方法 说明
np.mean 返回数组所有值或沿特定轴的平均值
np.median 返回数组所有值或沿特定轴的中值
np.std 返回数组所有值或沿特定轴的标准差
np.nanmean np.mean相同,但忽略数组中的 NaN 值
np.nanmedian np.nanmedian相同,但忽略数组中的 NaN 值
np.nonstd np.nanstd相同,但忽略数组中的 NaN 值

以下代码提供了 NumPy 的先前统计方法的更多示例。 这些方法在数据发现阶段中大量使用,您可以在其中分析数据特征和分布:

In [60]: x = np.array([[2, 3, 5], [20, 12, 4]])
         x
Out[60]: array([[ 2, 3, 5],
                [20, 12, 4]])
In [61]: np.mean(x)
Out[61]: 7.666666666666667
In [62]: np.mean(x, axis=0)
Out[62]: array([ 11\. , 7.5, 4.5])
In [63]: np.mean(x, axis=1)
Out[63]: array([ 3.33333333, 12\. ])
In [64]: np.median(x)
Out[64]: 4.5
In [65]: np.std(x)
Out[65]: 6.3944420310836261

处理多维数组

本节将通过执行不同的矩阵运算使您对多维数组有一个简要的了解。

为了在 NumPy 中进行矩阵乘法,您必须使用dot()而不是*。 让我们看一些例子:

In [66]: c = np.ones((4, 4))
         c*c
Out[66]: array([[ 1., 1., 1., 1.],
                [ 1., 1., 1., 1.],
                [ 1., 1., 1., 1.],
                [ 1., 1., 1., 1.]])
In [67]: c.dot(c)
Out[67]: array([[ 4., 4., 4., 4.],
                [ 4., 4., 4., 4.],
                [ 4., 4., 4., 4.],
                [ 4., 4., 4., 4.]])

使用多维数组时,最重要的主题是堆栈,即如何合并两个数组。hstack用于水平(列方式)堆叠数组,vstack用于垂直(行方式)堆叠数组。 您还可以使用hsplitvsplit方法拆分列,方法与堆叠它们相同:

In [68]: y = np.arange(15).reshape(3,5)
         x = np.arange(10).reshape(2,5)
         new_array = np.vstack((y,x))
         new_array
Out[68]: array([[ 0, 1, 2, 3, 4],
                [ 5, 6, 7, 8, 9],
                [10, 11, 12, 13, 14],
                [ 0, 1, 2, 3, 4],
                [ 5, 6, 7, 8, 9]])
In [69]: y = np.arange(15).reshape(5,3)
         x = np.arange(10).reshape(5,2)
         new_array = np.hstack((y,x))
         new_array
Out[69]: array([[ 0, 1, 2, 0, 1],
                [ 3, 4, 5, 2, 3],
                [ 6, 7, 8, 4, 5],
                [ 9, 10, 11, 6, 7],
                [12, 13, 14, 8, 9]])

这些方法在机器学习应用中非常有用,尤其是在创建数据集时。 堆叠数组后,可以使用Scipy.stats检查其描述性统计信息。 假设有一个100记录,并且每个记录都具有10个特征,这意味着您有一个 2D 矩阵,其中包含100行和10列。 下面的示例说明如何轻松获取每个特征的一些描述性统计信息:

In [70]: from scipy import stats
         x= np.random.rand(100,10)
         n, min_max, mean, var, skew, kurt = stats.describe(x)
         new_array = np.vstack((mean,var,skew,kurt,min_max[0],min_max[1]))
         new_array.T
Out[70]: array([[ 5.46011575e-01, 8.30007104e-02, -9.72899085e-02,
                 -1.17492785e+00, 4.07031246e-04, 9.85652100e-01],
                [ 4.79292653e-01, 8.13883169e-02, 1.00411352e-01,
                 -1.15988275e+00, 1.27241020e-02, 9.85985488e-01],
                [ 4.81319367e-01, 8.34107619e-02, 5.55926602e-02,
                 -1.20006450e+00, 7.49534810e-03, 9.86671083e-01],
                [ 5.26977277e-01, 9.33829059e-02, -1.12640661e-01,
                 -1.19955646e+00, 5.74237697e-03, 9.94980830e-01],
                [ 5.42622228e-01, 8.92615897e-02, -1.79102183e-01,
                 -1.13744108e+00, 2.27821933e-03, 9.93861532e-01],
                [ 4.84397369e-01, 9.18274523e-02, 2.33663872e-01,
                 -1.36827574e+00, 1.18986562e-02, 9.96563489e-01],
                [ 4.41436165e-01, 9.54357485e-02, 3.48194314e-01,
                 -1.15588500e+00, 1.77608372e-03, 9.93865324e-01],
                [ 5.34834409e-01, 7.61735119e-02, -2.10467450e-01,
                 -1.01442389e+00, 2.44706226e-02, 9.97784091e-01],
                [ 4.90262346e-01, 9.28757119e-02, 1.02682367e-01,
                 -1.28987137e+00, 2.97705706e-03, 9.98205307e-01],
                [ 4.42767478e-01, 7.32159267e-02, 1.74375646e-01,
                 -9.58660574e-01, 5.52410464e-04, 9.95383732e-01]])

NumPy 有一个很棒的模块numpy.ma,用于屏蔽数组元素。 当您要在计算时屏蔽(忽略)某些元素时,它非常有用。 当 NumPy 数组被屏蔽时,它将被视为无效,并且不考虑计算:

In [71]: import numpy.ma as ma
         x = np.arange(6)
         print(x.mean())
         masked_array = ma.masked_array(x, mask=[1,0,0,0,0,0])
         masked_array.mean()
         2.5 
Out[71]: 3.0

在前面的代码中,您有一个数组x = [0,1,2,3,4,5]。 您要做的是屏蔽数组的第一个元素,然后计算平均值。 当一个元素被屏蔽为1(True)时,数组中的关联索引值将被屏蔽。 在替换 NAN 值时,此方法也非常有用:

In [72]: x = np.arange(25, dtype = float).reshape(5,5)
         x[x<5] = np.nan
         x
Out[72]: array([[ nan, nan, nan, nan, nan],
                [ 5., 6., 7., 8., 9.],
                [ 10., 11., 12., 13., 14.],
                [ 15., 16., 17., 18., 19.],
                [ 20., 21., 22., 23., 24.]])
In [73]: np.where(np.isnan(x), ma.array(x, mask=np.isnan(x)).mean(axis=0), x)
Out[73]: array([[ 12.5, 13.5, 14.5, 15.5, 16.5],
                [ 5\. , 6\. , 7\. , 8\. , 9\. ],
                [ 10\. , 11\. , 12\. , 13\. , 14\. ],
                [ 15\. , 16\. , 17\. , 18\. , 19\. ],
                [ 20\. , 21\. , 22\. , 23\. , 24\. ]])

在前面的代码中,我们通过放置一个带有索引的条件将前五个元素的值更改为nanx[x<5]指的是索引为 0、1、2、3 和 4 的元素。然后,我们用每列的平均值覆盖这些值(nan值除外)。 为了帮助您的代码更简洁,数组操作中还有许多其他有用的方法:

方法 说明
np.concatenate 连接列表中的给定数组
np.repeat 沿特定轴重复数组的元素
np.delete 返回一个带有删除的子数组的新数组
np.insert 在指定轴之前插入值
np.unique 在数组中查找唯一值
np.tile 通过以给定的重复次数重复给定的输入来创建数组

索引,切片,重塑,调整大小和广播

当您在机器学习项目中使用大量数组时,通常需要索引,切片,调整形状和调整大小。

索引是数学和计算机科学中使用的一个基本术语。 一般而言,索引可帮助您指定如何返回各种数据结构的所需元素。 下面的示例显示列表和元组的索引:

In [74]: x = ["USA","France", "Germany","England"]
         x[2]
Out[74]: 'Germany'
In [75]: x = ('USA',3,"France",4)
         x[2]
Out[75]: 'France'

在 NumPy 中,索引的主要用途是控制和操纵数组的元素。 这是一种创建通用查找值的方法。 索引包含三个子操作,分别是字段访问,基本切片和高级索引。 在字段访问中,只需指定数组中元素的索引即可返回给定索引的值。

NumPy 在索引和切片方面非常强大。 在许多情况下,您需要在数组中引用所需的元素,然后对该切片区域执行操作。 您可以对数组进行索引,类似于对元组或带有方括号的列表进行索引。 让我们从字段访问和使用一维数组的简单切片开始,然后继续使用更高级的技术:

In [76]: x = np.arange(10)
         x
Out[76]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [77]: x[5]
Out[77]: 5
In [78]: x[-2]
Out[78]: 8
In [79]: x[2:8]
Out[79]: array([2, 3, 4, 5, 6, 7])
In [80]: x[:]
Out[80]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [81]: x[2:8:2]
Out[81]: array([2, 4, 6])

索引从0开始,因此在创建带有元素的数组时,第一个元素被索引为x[0],与最后一个元素x[n-1]相同。 如您在前面的示例中看到的,x[5]指的是第六个元素。 您也可以在索引中使用负值。 NumPy 将这些值理解为倒数第n个。 像示例中一样,x[-2]指倒数第二个元素。 您还可以通过声明开始索引和结束索引来选择数组中的多个元素,也可以通过将增量级别作为第三个参数声明来创建顺序索引,如代码的最后一行所示。

到目前为止,我们已经看到了一维数组中的索引和切片。 逻辑不会改变,但是为了演示起见,我们也对多维数组进行一些练习。 当您拥有多维数组时,唯一发生变化的就是只有更多的轴。 您可以在以下代码中将 n 维数组切片为[x 轴切片,y 轴切片]

In [82]: x = np.reshape(np.arange(16),(4,4))
         x
Out[82]: array([[ 0, 1, 2, 3],
                [ 4, 5, 6, 7],
                [ 8, 9, 10, 11],
                [12, 13, 14, 15]])
In [83]: x[1:3]
Out[83]: array([[ 4, 5, 6, 7],
                [ 8, 9, 10, 11]])
In [84]: x[:,1:3]
Out[84]: array([[ 1, 2],
                [ 5, 6],
                [ 9, 10],
                [13, 14]])
In [85]: x[1:3,1:3]
Out[85]: array([[ 5, 6],
                [ 9, 10]])

您按行和列对数组进行了切片,但没有以更加不规则或更具动态性的方式对元素进行切片,这意味着您始终以矩形或正方形的方式对其进行切片。 想象一下我们要切片的4 * 4数组,如下所示:

为了获得前面的切片,我们执行以下代码:

In [86]: x = np.reshape(np.arange(16),(4,4))
         x
Out[86]: array([[ 0, 1, 2, 3],
                [ 4, 5, 6, 7],
                [ 8, 9, 10, 11],
                [12, 13, 14, 15]])
In [87]: x[[0,1,2],[0,1,3]]
Out[87]: array([ 0, 5, 11])

在高级索引中,第一部分指示要切片的行的索引,第二部分指示相应的列。 在前面的示例中,您首先切片了第一,第二和第三行([0,1,2]),然后切片第一,第二和第四列。

整形和调整大小方法似乎很相似,但是这些操作的输出有所不同。 重新排列数组的形状时,只是输出会临时更改数组的形状,但不会更改数组本身。 调整数组大小时,它将永久更改数组的大小,如果新数组的大小大于旧数组的大小,则新数组元素将填充旧数组的重复副本。 相反,如果新数组较小,则新数组将从旧数组中获取元素,并按索引顺序填充新数组。 请注意,相同的数据可以由不同的ndarray共享。这意味着一个ndarray可以是另一个ndarray的视图。 在这种情况下,对一个数组进行的更改将对其他视图产生影响。

下面的代码给出了一个示例,说明当大小大于或小于原始数组时如何填充新数组元素:

In [88]: x = np.arange(16).reshape(4,4)
         x
Out[88]: array([[ 0, 1, 2, 3],
                [ 4, 5, 6, 7],
                [ 8, 9, 10, 11],
                [12, 13, 14, 15]])
In [89]: np.resize(x,(2,2))
Out[89]: array([[0, 1],
                 [2, 3]])
In [90]: np.resize(x,(6,6))
Out[90]: array([[ 0, 1, 2, 3, 4, 5],
                [ 6, 7, 8, 9, 10, 11],
                [12, 13, 14, 15, 0, 1],
                [ 2, 3, 4, 5, 6, 7],
                [ 8, 9, 10, 11, 12, 13],
                [14, 15, 0, 1, 2, 3]])

本小节的最后一个重要术语是广播,它解释了当 NumPy 具有不同形状时在数组的算术运算中如何表现。 NumPy 有两个广播规则:数组的大小相等或其中之一为 1。如果不满足以下条件之一,则将得到以下两个错误之一:frames are not alignedoperands could not be broadcast together

In [91]: x = np.arange(16).reshape(4,4)
         y = np.arange(6).reshape(2,3)
         x+y
        ---------------------------------------------------------------                           ------------
        ValueError Traceback (most recent call last)
        <ipython-input-102-083fc792f8d9> in <module>()
        1 x = np.arange(16).reshape(4,4)
        2 y = np.arange(6).reshape(2,3)
        ----> 3 x+y
        12
        ValueError: operands could not be broadcast together with                      shapes (4,4) (2,3)

您可能已经看到可以将两个矩阵的形状分别为(4, 4)(4,)(2, 2)(1, 2)。 第一种情况满足具有一维的条件,因此乘法成为向量*数组,这不会引起任何广播问题:

In [92]: x = np.ones(16).reshape(4,4)
          y = np.arange(4)
          x*y
Out[92]: array([[ 0., 1., 2., 3.],
                 [ 0., 1., 2., 3.],
                 [ 0., 1., 2., 3.],
                 [ 0., 1., 2., 3.]])
In [93]: x = np.arange(4).reshape(2,2)
         x
Out[93]: array([[0, 1],
                [2, 3]])
In [94]: y = np.arange(2).reshape(1,2)
         y
Out[94]: array([[0, 1]])
In [95]: x*y
Out[95]: array([[0, 1],
                [0, 3]])

前面的代码块提供了第二种情况的示例,其中在计算过程中,小数组迭代通过大数组,并且输出扩展到整个数组。 这就是为什么有(4, 4)(2, 2)输出的原因:在乘法过程中,两个数组都以较大的大小广播。

总结

在本章中,您熟悉了 NumPy 数组操作的基础知识,并刷新了有关基本矩阵操作的知识。 NumPy 是用于 Python 科学堆栈的极其重要的库,它具有用于数组操作的广泛方法。 您已经学习了如何使用多维数组,并涵盖了重要的主题,例如索引,切片,整形,调整大小和广播。 本章的主要目的是让您简要了解有关数字数据集的 NumPy 的工作方式,这将对您的日常数据分析工作有所帮助。

在下一章中,您将学习线性代数的基础知识以及使用 NumPy 完整的实际示例。