Python 数据分析(PYDA)第三版(七)(1)

简介: Python 数据分析(PYDA)第三版(七)

附录

附录 A:高级 NumPy

原文:wesmckinney.com/book/advanced-numpy

译者:飞龙

协议:CC BY-NC-SA 4.0

此开放访问网络版本的《Python 数据分析第三版》现已作为印刷版和数字版的伴侣提供。如果您发现任何勘误,请在此处报告。请注意,由 Quarto 生成的本站点的某些方面与 O’Reilly 的印刷版和电子书版本的格式不同。

如果您发现本书的在线版本有用,请考虑订购纸质版无 DRM 的电子书以支持作者。本网站的内容不得复制或再生产。代码示例采用 MIT 许可,可在 GitHub 或 Gitee 上找到。

在这个附录中,我将深入探讨 NumPy 库的数组计算。这将包括有关 ndarray 类型的更多内部细节以及更高级的数组操作和算法。

这个附录包含各种主题,不一定需要按顺序阅读。在各章节中,我将为许多示例生成随机数据,这些示例将使用numpy.random模块中的默认随机数生成器:

In [11]: rng = np.random.default_rng(seed=12345)

A.1 ndarray 对象内部

NumPy ndarray 提供了一种将块状同类型数据(连续或分步)解释为多维数组对象的方法。数据类型,或dtype,决定了数据被解释为浮点数、整数、布尔值或我们一直在查看的其他类型之一。

ndarray 灵活的部分之一是每个数组对象都是对数据块的步进视图。例如,您可能想知道,例如,数组视图arr[::2, ::-1]如何不复制任何数据。原因是 ndarray 不仅仅是一块内存和一个数据类型;它还具有步进信息,使数组能够以不同的步长在内存中移动。更准确地说,ndarray 内部包含以下内容:

  • 一个数据指针—即 RAM 中的数据块或内存映射文件
  • 描述数组中固定大小值单元的数据类型或 dtype
  • 一个指示数组形状的元组
  • 一个步长元组—表示在一个维度上前进一个元素所需的字节数

请参见图 A.1 以查看 ndarray 内部的简单模拟。

图 A.1:NumPy ndarray 对象

例如,一个 10×5 的数组将具有形状(10, 5)

In [12]: np.ones((10, 5)).shape
Out[12]: (10, 5)

一个典型的(C 顺序)3×4×5 的float64(8 字节)值数组具有步长(160, 40, 8)(了解步长可以是有用的,因为一般来说,特定轴上的步长越大,沿着该轴执行计算的成本就越高):

In [13]: np.ones((3, 4, 5), dtype=np.float64).strides
Out[13]: (160, 40, 8)

虽然典型的 NumPy 用户很少会对数组的步长感兴趣,但它们需要用来构建“零拷贝”数组视图。步长甚至可以是负数,这使得数组可以在内存中“向后”移动(例如,在像obj[::-1]obj[:, ::-1]这样的切片中)。

NumPy 数据类型层次结构

您可能偶尔需要检查代码是否包含整数、浮点数、字符串或 Python 对象的数组。由于有多种浮点数类型(float16float128),检查数据类型是否在类型列表中会非常冗长。幸运的是,数据类型有超类,如np.integernp.floating,可以与np.issubdtype函数一起使用:

In [14]: ints = np.ones(10, dtype=np.uint16)
In [15]: floats = np.ones(10, dtype=np.float32)
In [16]: np.issubdtype(ints.dtype, np.integer)
Out[16]: True
In [17]: np.issubdtype(floats.dtype, np.floating)
Out[17]: True

您可以通过调用类型的mro方法查看特定数据类型的所有父类:

In [18]: np.float64.mro()
Out[18]: 
[numpy.float64,
 numpy.floating,
 numpy.inexact,
 numpy.number,
 numpy.generic,
 float,
 object]

因此,我们还有:

In [19]: np.issubdtype(ints.dtype, np.number)
Out[19]: True

大多数 NumPy 用户永远不需要了解这一点,但有时会有用。请参见图 A.2 以查看数据类型层次结构和父-子类关系的图表。¹


图 A.2:NumPy 数据类型类层次结构

A.2 高级数组操作

除了花式索引、切片和布尔子集之外,还有许多处理数组的方法。虽然大部分数据分析应用程序的繁重工作由 pandas 中的高级函数处理,但您可能在某个时候需要编写一个在现有库中找不到的数据算法。

重新塑形数组

在许多情况下,您可以将一个数组从一种形状转换为另一种形状而不复制任何数据。为此,将表示新形状的元组传递给 reshape 数组实例方法。例如,假设我们有一个希望重新排列成矩阵的值的一维数组(这在图 A.3 中有说明):

In [20]: arr = np.arange(8)
In [21]: arr
Out[21]: array([0, 1, 2, 3, 4, 5, 6, 7])
In [22]: arr.reshape((4, 2))
Out[22]: 
array([[0, 1],
 [2, 3],
 [4, 5],
 [6, 7]])


图 A.3:按 C(行主要)或 FORTRAN(列主要)顺序重新塑形

多维数组也可以被重新塑形:

In [23]: arr.reshape((4, 2)).reshape((2, 4))
Out[23]: 
array([[0, 1, 2, 3],
 [4, 5, 6, 7]])

传递的形状维度中可以有一个为 -1,在这种情况下,该维度的值将从数据中推断出来:

In [24]: arr = np.arange(15)
In [25]: arr.reshape((5, -1))
Out[25]: 
array([[ 0,  1,  2],
 [ 3,  4,  5],
 [ 6,  7,  8],
 [ 9, 10, 11],
 [12, 13, 14]])

由于数组的 shape 属性是一个元组,它也可以传递给 reshape

In [26]: other_arr = np.ones((3, 5))
In [27]: other_arr.shape
Out[27]: (3, 5)
In [28]: arr.reshape(other_arr.shape)
Out[28]: 
array([[ 0,  1,  2,  3,  4],
 [ 5,  6,  7,  8,  9],
 [10, 11, 12, 13, 14]])

从一维到更高维的 reshape 的相反操作通常称为 展平raveling

In [29]: arr = np.arange(15).reshape((5, 3))
In [30]: arr
Out[30]: 
array([[ 0,  1,  2],
 [ 3,  4,  5],
 [ 6,  7,  8],
 [ 9, 10, 11],
 [12, 13, 14]])
In [31]: arr.ravel()
Out[31]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])

如果结果中的值在原始数组中是连续的,ravel 不会生成基础值的副本。

flatten 方法的行为类似于 ravel,只是它总是返回数据的副本:

In [32]: arr.flatten()
Out[32]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])

数据可以以不同的顺序被重新塑形或展开。这对于新的 NumPy 用户来说是一个略微微妙的主题,因此是下一个子主题。

C 与 FORTRAN 顺序

NumPy 能够适应内存中数据的许多不同布局。默认情况下,NumPy 数组是按 行主要 顺序创建的。从空间上讲,这意味着如果您有一个二维数据数组,数组中每行的项都存储在相邻的内存位置上。与行主要顺序相反的是 列主要 顺序,这意味着数据中每列的值都存储在相邻的内存位置上。

出于历史原因,行和列主要顺序也被称为 C 和 FORTRAN 顺序。在 FORTRAN 77 语言中,矩阵都是列主要的。

reshaperavel 这样的函数接受一个 order 参数,指示数组中使用数据的顺序。在大多数情况下,这通常设置为 'C''F'(还有一些不常用的选项 'A''K';请参阅 NumPy 文档,并参考图 A.3 以了解这些选项的说明):

In [33]: arr = np.arange(12).reshape((3, 4))
In [34]: arr
Out[34]: 
array([[ 0,  1,  2,  3],
 [ 4,  5,  6,  7],
 [ 8,  9, 10, 11]])
In [35]: arr.ravel()
Out[35]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
In [36]: arr.ravel('F')
Out[36]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

使用超过两个维度的数组进行重新塑形可能有点令人费解(参见图 A.3)。C 和 FORTRAN 顺序之间的关键区别在于维度的遍历方式:

C/行主要顺序

在遍历更高维度时,首先 遍历(例如,先在轴 1 上再在轴 0 上前进)。

FORTRAN/列主要顺序

在遍历更高维度时,最后 遍历(例如,先在轴 0 上再在轴 1 上前进)。

连接和分割数组

numpy.concatenate 接受一个数组序列(元组,列表等),并按顺序沿着输入轴连接它们:

In [37]: arr1 = np.array([[1, 2, 3], [4, 5, 6]])
In [38]: arr2 = np.array([[7, 8, 9], [10, 11, 12]])
In [39]: np.concatenate([arr1, arr2], axis=0)
Out[39]: 
array([[ 1,  2,  3],
 [ 4,  5,  6],
 [ 7,  8,  9],
 [10, 11, 12]])
In [40]: np.concatenate([arr1, arr2], axis=1)
Out[40]: 
array([[ 1,  2,  3,  7,  8,  9],
 [ 4,  5,  6, 10, 11, 12]])

有一些便利函数,如 vstackhstack,用于常见类型的连接。前面的操作可以表示为:

In [41]: np.vstack((arr1, arr2))
Out[41]: 
array([[ 1,  2,  3],
 [ 4,  5,  6],
 [ 7,  8,  9],
 [10, 11, 12]])
In [42]: np.hstack((arr1, arr2))
Out[42]: 
array([[ 1,  2,  3,  7,  8,  9],
 [ 4,  5,  6, 10, 11, 12]])

另一方面,split 将数组沿着一个轴分割成多个数组:

In [43]: arr = rng.standard_normal((5, 2))
In [44]: arr
Out[44]: 
array([[-1.4238,  1.2637],
 [-0.8707, -0.2592],
 [-0.0753, -0.7409],
 [-1.3678,  0.6489],
 [ 0.3611, -1.9529]])
In [45]: first, second, third = np.split(arr, [1, 3])
In [46]: first
Out[46]: array([[-1.4238,  1.2637]])
In [47]: second
Out[47]: 
array([[-0.8707, -0.2592],
 [-0.0753, -0.7409]])
In [48]: third
Out[48]: 
array([[-1.3678,  0.6489],
 [ 0.3611, -1.9529]])

传递给 np.split 的值 [1, 3] 指示在哪些索引处将数组分割成片段。

请参见表 A.1 以获取所有相关连接和分割函数的列表,其中一些仅作为非常通用的 concatenate 的便利。

表 A.1:数组连接函数

函数 描述
concatenate 最通用的函数,沿一个轴连接数组集合
vstack, row_stack 按行堆叠数组(沿轴 0)
hstack 按列堆叠数组(沿轴 1)
column_stack 类似于hstack,但首先将 1D 数组转换为 2D 列向量
dstack 按“深度”(沿轴 2)堆叠数组
split 沿特定轴在传递位置分割数组
hsplit/vsplit 在轴 0 和 1 上分割的便利函数
堆叠助手:r_ 和 c_

NumPy 命名空间中有两个特殊对象,r_c_,使堆叠数组更简洁:

In [49]: arr = np.arange(6)
In [50]: arr1 = arr.reshape((3, 2))
In [51]: arr2 = rng.standard_normal((3, 2))
In [52]: np.r_[arr1, arr2]
Out[52]: 
array([[ 0.    ,  1.    ],
 [ 2.    ,  3.    ],
 [ 4.    ,  5.    ],
 [ 2.3474,  0.9685],
 [-0.7594,  0.9022],
 [-0.467 , -0.0607]])
In [53]: np.c_[np.r_[arr1, arr2], arr]
Out[53]: 
array([[ 0.    ,  1.    ,  0.    ],
 [ 2.    ,  3.    ,  1.    ],
 [ 4.    ,  5.    ,  2.    ],
 [ 2.3474,  0.9685,  3.    ],
 [-0.7594,  0.9022,  4.    ],
 [-0.467 , -0.0607,  5.    ]])

这些还可以将切片转换为数组:

In [54]: np.c_[1:6, -10:-5]
Out[54]: 
array([[  1, -10],
 [  2,  -9],
 [  3,  -8],
 [  4,  -7],
 [  5,  -6]])

查看文档字符串以了解您可以使用c_r_做什么。

重复元素:tile 和 repeat

用于重复或复制数组以生成更大数组的两个有用工具是repeattile函数。repeat将数组中的每个元素重复若干次,生成一个更大的数组:

In [55]: arr = np.arange(3)
In [56]: arr
Out[56]: array([0, 1, 2])
In [57]: arr.repeat(3)
Out[57]: array([0, 0, 0, 1, 1, 1, 2, 2, 2])

注意

需要复制或重复数组的情况在 NumPy 中可能不像其他数组编程框架(如 MATLAB)中那样常见。其中一个原因是广播通常更好地满足这种需求,这是下一节的主题。

默认情况下,如果传递一个整数,每个元素将重复该次数。如果传递一个整数数组,每个元素可以重复不同次数:

In [58]: arr.repeat([2, 3, 4])
Out[58]: array([0, 0, 1, 1, 1, 2, 2, 2, 2])

多维数组可以沿特定轴重复其元素:

In [59]: arr = rng.standard_normal((2, 2))
In [60]: arr
Out[60]: 
array([[ 0.7888, -1.2567],
 [ 0.5759,  1.399 ]])
In [61]: arr.repeat(2, axis=0)
Out[61]: 
array([[ 0.7888, -1.2567],
 [ 0.7888, -1.2567],
 [ 0.5759,  1.399 ],
 [ 0.5759,  1.399 ]])

请注意,如果没有传递轴,数组将首先被展平,这可能不是您想要的。同样,当重复多维数组以不同次数重复给定切片时,可以传递整数数组:

In [62]: arr.repeat([2, 3], axis=0)
Out[62]: 
array([[ 0.7888, -1.2567],
 [ 0.7888, -1.2567],
 [ 0.5759,  1.399 ],
 [ 0.5759,  1.399 ],
 [ 0.5759,  1.399 ]])
In [63]: arr.repeat([2, 3], axis=1)
Out[63]: 
array([[ 0.7888,  0.7888, -1.2567, -1.2567, -1.2567],
 [ 0.5759,  0.5759,  1.399 ,  1.399 ,  1.399 ]])

另一方面,tile是一个沿轴堆叠数组副本的快捷方式。在视觉上,您可以将其视为类似于“铺设瓷砖”:

In [64]: arr
Out[64]: 
array([[ 0.7888, -1.2567],
 [ 0.5759,  1.399 ]])
In [65]: np.tile(arr, 2)
Out[65]: 
array([[ 0.7888, -1.2567,  0.7888, -1.2567],
 [ 0.5759,  1.399 ,  0.5759,  1.399 ]])

第二个参数是瓷砖的数量;对于标量,瓦片是按行而不是按列进行的。tile的第二个参数可以是一个元组,指示“瓦片”的布局:

In [66]: arr
Out[66]: 
array([[ 0.7888, -1.2567],
 [ 0.5759,  1.399 ]])
In [67]: np.tile(arr, (2, 1))
Out[67]: 
array([[ 0.7888, -1.2567],
 [ 0.5759,  1.399 ],
 [ 0.7888, -1.2567],
 [ 0.5759,  1.399 ]])
In [68]: np.tile(arr, (3, 2))
Out[68]: 
array([[ 0.7888, -1.2567,  0.7888, -1.2567],
 [ 0.5759,  1.399 ,  0.5759,  1.399 ],
 [ 0.7888, -1.2567,  0.7888, -1.2567],
 [ 0.5759,  1.399 ,  0.5759,  1.399 ],
 [ 0.7888, -1.2567,  0.7888, -1.2567],
 [ 0.5759,  1.399 ,  0.5759,  1.399 ]])

花式索引等效:take 和 put

正如您可能从 Ch 4:NumPy 基础:数组和矢量化计算中记得的那样,通过使用整数数组进行花式索引来获取和设置数组的子集是一种方法:

In [69]: arr = np.arange(10) * 100
In [70]: inds = [7, 1, 2, 6]
In [71]: arr[inds]
Out[71]: array([700, 100, 200, 600])

在仅在单个轴上进行选择的特殊情况下,有一些替代的 ndarray 方法是有用的:

In [72]: arr.take(inds)
Out[72]: array([700, 100, 200, 600])
In [73]: arr.put(inds, 42)
In [74]: arr
Out[74]: array([  0,  42,  42, 300, 400, 500,  42,  42, 800, 900])
In [75]: arr.put(inds, [40, 41, 42, 43])
In [76]: arr
Out[76]: array([  0,  41,  42, 300, 400, 500,  43,  40, 800, 900])

要在其他轴上使用take,可以传递axis关键字:

In [77]: inds = [2, 0, 2, 1]
In [78]: arr = rng.standard_normal((2, 4))
In [79]: arr
Out[79]: 
array([[ 1.3223, -0.2997,  0.9029, -1.6216],
 [-0.1582,  0.4495, -1.3436, -0.0817]])
In [80]: arr.take(inds, axis=1)
Out[80]: 
array([[ 0.9029,  1.3223,  0.9029, -0.2997],
 [-1.3436, -0.1582, -1.3436,  0.4495]])

put不接受axis参数,而是索引到数组的展平(一维,C 顺序)版本。因此,当您需要使用索引数组在其他轴上设置元素时,最好使用基于[]的索引。

A.3 广播

广播规定了不同形状数组之间的操作方式。它可以是一个强大的功能,但即使对于有经验的用户也可能会引起混淆。广播的最简单示例是将标量值与数组组合时发生:

In [81]: arr = np.arange(5)
In [82]: arr
Out[82]: array([0, 1, 2, 3, 4])
In [83]: arr * 4
Out[83]: array([ 0,  4,  8, 12, 16])

在这里,我们说标量值 4 已经广播到乘法操作中的所有其他元素。

例如,我们可以通过减去列均值来对数组的每一列进行去均值处理。在这种情况下,只需要减去包含每列均值的数组即可:

In [84]: arr = rng.standard_normal((4, 3))
In [85]: arr.mean(0)
Out[85]: array([0.1206, 0.243 , 0.1444])
In [86]: demeaned = arr - arr.mean(0)
In [87]: demeaned
Out[87]: 
array([[ 1.6042,  2.3751,  0.633 ],
 [ 0.7081, -1.202 , -1.3538],
 [-1.5329,  0.2985,  0.6076],
 [-0.7793, -1.4717,  0.1132]])
In [88]: demeaned.mean(0)
Out[88]: array([ 0., -0.,  0.])

请参见图 A.4 以了解此操作的示例。将行作为广播操作去均值需要更多的注意。幸运的是,跨任何数组维度广播潜在较低维值(例如从二维数组的每列中减去行均值)是可能的,只要遵循规则。

这将我们带到了广播规则。

两个数组在广播时兼容,如果对于每个尾部维度(即,从末尾开始),轴的长度匹配,或者长度中的任何一个为 1。然后在缺失或长度为 1 的维度上执行广播。

图 A.4:在 1D 数组的轴 0 上进行广播

即使作为一个经验丰富的 NumPy 用户,我经常发现自己在思考广播规则时不得不停下来画图。考虑最后一个示例,假设我们希望减去每行的平均值。由于arr.mean(0)的长度为 3,它在轴 0 上是兼容的进行广播,因为arr中的尾部维度为 3,因此匹配。根据规则,要在轴 1 上进行减法(即,从每行减去行均值),较小的数组必须具有形状(4, 1)

In [89]: arr
Out[89]: 
array([[ 1.7247,  2.6182,  0.7774],
 [ 0.8286, -0.959 , -1.2094],
 [-1.4123,  0.5415,  0.7519],
 [-0.6588, -1.2287,  0.2576]])
In [90]: row_means = arr.mean(1)
In [91]: row_means.shape
Out[91]: (4,)
In [92]: row_means.reshape((4, 1))
Out[92]: 
array([[ 1.7068],
 [-0.4466],
 [-0.0396],
 [-0.5433]])
In [93]: demeaned = arr - row_means.reshape((4, 1))
In [94]: demeaned.mean(1)
Out[94]: array([-0.,  0.,  0.,  0.])

查看图 A.5 以了解此操作的示例。

图 A.5:在 2D 数组的轴 1 上进行广播

查看图 A.6 以获得另一个示例,这次是在轴 0 上将二维数组添加到三维数组中。

图 A.6:在 3D 数组的轴 0 上进行广播


Python 数据分析(PYDA)第三版(七)(2)https://developer.aliyun.com/article/1482402

相关文章
|
7月前
|
数据采集 数据可视化 数据挖掘
Python数据分析实战:Pandas处理结构化数据的核心技巧
在数据驱动时代,结构化数据是分析决策的基础。Python的Pandas库凭借其高效的数据结构和丰富的功能,成为处理结构化数据的利器。本文通过真实场景和代码示例,讲解Pandas的核心操作,包括数据加载、清洗、转换、分析与性能优化,帮助你从数据中提取有价值的洞察,提升数据处理效率。
347 3
|
9月前
|
数据采集 数据可视化 搜索推荐
Python数据分析全流程指南:从数据采集到可视化呈现的实战解析
在数字化转型中,数据分析成为企业决策核心,而Python凭借其强大生态和简洁语法成为首选工具。本文通过实战案例详解数据分析全流程,涵盖数据采集、清洗、探索、建模、可视化及自动化部署,帮助读者掌握从数据到业务价值的完整技能链。
1051 0
|
6月前
|
数据可视化 大数据 关系型数据库
基于python大数据技术的医疗数据分析与研究
在数字化时代,医疗数据呈爆炸式增长,涵盖患者信息、检查指标、生活方式等。大数据技术助力疾病预测、资源优化与智慧医疗发展,结合Python、MySQL与B/S架构,推动医疗系统高效实现。
|
7月前
|
数据可视化 数据挖掘 大数据
基于python大数据的水文数据分析可视化系统
本研究针对水文数据分析中的整合难、分析单一和可视化不足等问题,提出构建基于Python的水文数据分析可视化系统。通过整合多源数据,结合大数据、云计算与人工智能技术,实现水文数据的高效处理、深度挖掘与直观展示,为水资源管理、防洪减灾和生态保护提供科学决策支持,具有重要的应用价值和社会意义。
|
8月前
|
存储 数据挖掘 大数据
基于python大数据的用户行为数据分析系统
本系统基于Python大数据技术,深入研究用户行为数据分析,结合Pandas、NumPy等工具提升数据处理效率,利用B/S架构与MySQL数据库实现高效存储与访问。研究涵盖技术背景、学术与商业意义、国内外研究现状及PyCharm、Python语言等关键技术,助力企业精准营销与产品优化,具有广泛的应用前景与社会价值。
|
机器学习/深度学习 数据可视化 数据挖掘
使用Python进行数据分析的入门指南
本文将引导读者了解如何使用Python进行数据分析,从安装必要的库到执行基础的数据操作和可视化。通过本文的学习,你将能够开始自己的数据分析之旅,并掌握如何利用Python来揭示数据背后的故事。
|
机器学习/深度学习 数据可视化 数据挖掘
使用Python进行数据分析的入门指南
【10月更文挑战第42天】本文是一篇技术性文章,旨在为初学者提供一份关于如何使用Python进行数据分析的入门指南。我们将从安装必要的工具开始,然后逐步介绍如何导入数据、处理数据、进行数据可视化以及建立预测模型。本文的目标是帮助读者理解数据分析的基本步骤和方法,并通过实际的代码示例来加深理解。
299 3
|
11月前
|
机器学习/深度学习 数据采集 数据可视化
Python数据分析,别再死磕Excel了!
Python数据分析,别再死磕Excel了!
415 2
|
机器学习/深度学习 算法 数据挖掘
数据分析的 10 个最佳 Python 库
数据分析的 10 个最佳 Python 库
1628 4
数据分析的 10 个最佳 Python 库
|
机器学习/深度学习 存储 数据可视化
这份Excel+Python飞速搞定数据分析手册,简直可以让Excel飞起来
本书介绍了如何将Python与Excel结合使用,以提升数据分析和处理效率。内容涵盖Python入门、pandas库的使用、通过Python包操作Excel文件以及使用xlwings对Excel进行编程。书中详细讲解了Anaconda、Visual Studio Code和Jupyter笔记本等开发工具,并探讨了NumPy、DataFrame和Series等数据结构的应用。此外,还介绍了多个Python包(如OpenPyXL、XlsxWriter等)用于在无需安装Excel的情况下读写Excel文件,帮助用户实现自动化任务和数据处理。

推荐镜像

更多