原文:
towardsdatascience.com/introducing-numpy-part-4-doing-math-with-arrays-5e77ac595641
快速成功数据科学
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/df6c4b8532ec3ec89c0f3131326b03e1.png
DALL-E3 想象中的进行数学运算的数组
欢迎来到入门系列的第四和最后一期,介绍 NumPy!在前面的文章中,我们回顾了 NumPy 的功臣数组:它们是什么以及如何创建它们(第一部分);如何索引和切片它们(第二部分);以及如何操作它们(第三部分)。现在,是时候将它们应用到其主要目的:数学运算上了。
NumPy 使用两种内部实现来高效地对数组进行数学运算:向量化和广播。向量化支持在等大小数组之间的操作,而广播扩展了这种行为到不同形状的数组。
向量化
ndarrays最强大的功能之一是向量化,它允许您在数据上执行批量操作,而无需显式for循环。这意味着您可以在一次操作中应用整个数组,而无需从其中选择每个元素。
等大小数组上的算术运算是以逐元素方式应用的,如下所示:
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e86b4a93c2de8bd0fa71d019025b447b.png
涉及等大小数组的数学运算是在对应元素上执行的(来自Python Tools for Scientists)(此及未来指向我的书的链接代表联盟链接)
因为循环是在 C 语言编写的代码背后进行的,所以向量化导致了更快的处理速度。让我们看看一个例子,我们将比较 Python 中的循环与 NumPy 中的向量化。
首先,创建两个包含 100,000 个介于 0 到 500 之间的随机整数的数据集:
In[1]:importnumpyasnp In[2]:data_a=np.random.randint(500,size=100_000)In[3]:data_b=np.random.randint(500,size=100_000)现在,创建一个空列表,然后遍历两个数据集,如果data_a中的每个项目也出现在data_b中,则将其追加到列表中:
In[4]:shared_list=[]In[5]:foritemindata_a:...:ifitemindata_b:...:shared_list.append(item)注意,这也可以使用列表推导式来编写:
shared_list = [item for item in data_a if item in data_b]
根据您的硬件,您可能需要等待五秒钟或更长时间才能完成此循环。
列表中的前三个值如下(您的可能不同,因为这些是随机生成的):
In[6]:shared_list[:3]Out[6]:[383,40,144]让我们使用 NumPy 的isin()方法重复这个练习。这个优化方法将目标数组中的每个元素与另一个数组进行比较,并返回一个布尔值。我们可以将其与索引结合使用,以返回值为True的元素:
In[7]:data_a[np.isin(data_a,data_b)]Out[7]:array([383,40,144,...,469,199,411])与之前的标准 Python 循环相比,这个计算几乎瞬间完成。
向量化还允许编写更简洁、更易读的代码,这些代码可以类似于数学表达式。例如,为了将两个数组相乘,您可以省略编写嵌套循环,只需声明arr1 * arr2即可,如下所示:
In[8]:arr1=np.array([[1,1,1],[2,2,2]])In[9]:arr1 Out[9]:array([[1,1,1],[2,2,2]])In[10]:arr2=np.array([[3,3,3],[4,4,4]])In[11]:arr2 Out[11]:array([[3,3,3],[4,4,4]])In[12]:arr1*arr2 Out[12]:array([[3,3,3],[8,8,8]])这种行为适用于所有基本算术运算,例如加法、减法、乘法和除法。
广播
广播技术允许对形状不同的数组进行操作。它在现实世界问题中相当常见,例如图像处理、数据归一化、矢量量化、机器学习和地理空间分析。
要理解它是如何工作的,请考虑下一个图,其中四个元素的 1D 数组与一个单元素的 1D 数组相乘。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/979f853b568f2da682ee0feac9854146.png
当将 1D ndarray 与标量相乘时的广播示例(来自 Python Tools for Scientists)
如您所见,较小的数组被拉伸到较大的数组上,直到它们具有兼容的形状。形状为(1,)的数组通过重复其单个值变为形状为(4,)的数组,以便可以进行逐元素乘法。这种相同的行为也适用于标量和数组之间的操作。
为了使广播工作,两个数组的维度必须兼容。当它们相等或其中之一为 1 时,两个维度是兼容的。NumPy 通过比较数组形状元组来确定这种兼容性,从最右侧的维度开始,向左移动。
例如,为了检查不同的 24 元素 3D 数组是否可广播,NumPy 会比较它们的形状元组,如图所示:
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/56e19a72d5dda58710bc5bfeb4de9896.png
检查 3D 数组维度以进行兼容性(灰色阴影值)(来自 Python Tools for Scientists)
从最右侧的维度(圆圈 1)开始,NumPy 确定这两对数组是兼容的,因为至少有一个等于 1。这同样适用于下一个比较(圆圈 2),但最后一对在最后一个比较(圆圈 3)中失败,因为 6 和 3 不相等。因此,我们无法在这两个数组之间执行任何数学运算。
相比之下,在下面的图中,一个二维数组和一维数组是兼容的,因此一维数组可以广播到填充缺失的行。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ef6fef118522ac224eb6d1e647dcc48e.png
当将 2D 数组添加到 1D 数组时广播的示例(来自 Python 工具科学家)
这允许进行元素级的加法。广播可以沿着行、列或平面进行,根据需要。有关广播的更多信息,包括一个有趣的实际示例,请访问官方文档。
矩阵点积
在 NumPy 中,数组之间基本的乘法是按元素进行的。换句话说,一个数组中的每个元素与第二个数组中相应的元素相乘。这包括二维数组的乘法,也称为矩阵。
然而,您可能还记得从数学课上学到的,正确的矩阵乘法涉及对行和列进行操作,而不是元素。这是矩阵点积,其中第一个矩阵的水平方向与第二个矩阵的垂直方向相乘。然后,将结果相加,如下一张图中灰色阴影的值所示。这个过程不是按元素进行的,而且它还是非交换的,因为arr1 * arr2不等于arr2 * arr1。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1145e3ab9d315138e5733bd7f9c2581a.png
矩阵点积(来自 Python 工具科学家)
NumPy 提供了dot()方法以这种方式进行乘法。以下是一个使用上一张图中矩阵的示例:
In[13]:arr1=np.array([[0,1],[2,3]])In[14]:arr2=np.array([[4,5],[6,7]])In[15]:np.dot(arr1,arr2)Out[15]:array([[6,7],[26,31]])您还可以使用arr1.dot(arr2)的替代语法来计算点积。
除了点积之外,NumPy 还提供了一些执行线性代数的方法。要查看完整列表,请访问官方文档中的线性代数页面。
数组的递增和递减
您可以使用扩展运算符,如+=,在不创建新数组的情况下更改数组中的值。以下是一些使用 1D 数组的示例:
In[16]:arr1d=np.array([0,1,2,3])In[17]:arr1d+=10In[18]:arr1d Out[18]:array([10,11,12,13])In[19]:arr1d-=10In[20]:arr1d Out[20]:array([0,1,2,3])In[21]:arr1d*=2In[22]:arr1d Out[22]:array([0,2,4,6])在这些情况下,标量值应用于数组中的每个元素。
使用 NumPy 函数
类似于 Python 的标准math模块,NumPy 也附带了自己的数学函数集。这些包括通用函数和聚合函数。一个通用函数,也称为ufunc,以元素为单位进行操作,并生成与输入数组相同大小的新的数组。聚合函数作用于整个数组,并产生一个单一值,例如数组中元素的总和
通用函数
执行简单元素级转换的通用函数,例如取对数或平方一个元素,被称为单值ufunc。要使用它们,请调用函数并传递一个ndarray,如下所示:
In[23]:arr1d=np.array([10,20,30,40])In[24]:np.log10(arr1d)Out[24]:array([1\.,1.30103,1.47712125,1.60205999])In[25]:np.square(arr1d)Out[25]:array([100,400,900,1600],dtype=int32)一些更有用的单值 ufunc 列出如下。您可以在文档中找到完整的列表。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b1384aae084e08d931c1fba473ff7453.png
有用的 NumPy 一元通用函数(来自 Python Tools for Scientists)
接受两个数组作为输入并返回单个数组的通用函数称为二元ufunc。以下二元函数在两个数组中找到最大值和最小值,并将它们返回到一个新数组中:
In[26]:a=np.array([1,2,500])In[27]:b=np.array([0,2,-1])In[28]:np.maximum(a,b)Out[28]:array([1,2,500])In[29]:np.minimum(a,b)Out[29]:array([0,2,-1])下面列出了其他一些二进制函数:
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0084f433138ec7fc720b1e180b94e7bb.png
有用的 NumPy 二进制通用函数(来自 Python Tools for Scientists)
统计方法
NumPy 还附带了一些方法,可以计算整个数组或沿轴的数据的统计数据。将数组中的元素减少到单个值可以称为聚合或缩减。
让我们尝试使用一个随机生成的整数 2D 数组来试用其中的一些:
In[30]:arr=np.random.randint(100,size=(3,5))In[31]:arr Out[31]:array([[13,22,16,30,32],[11,1,1,35,68],[84,2,18,84,30]])要计算该数组中所有元素的平均值,请在数组上使用点符号调用mean():
In[32]:arr.mean()Out[32]:29.8您也可以将数组传递给mean()函数,如下所示:
In[33]:np.mean(arr)Out[33]:29.8可选的轴参数允许您指定计算统计数据的轴。例如,指定轴 1 表示计算是在列之间进行的,产生一个与数组行数相同数量的元素的 1D 数组:
In[34]:arr.mean(axis=1)Out[34]:array([22.6,23.2,43.6])指定轴 0 告诉该方法按行计算。在以下示例中,这产生了一个包含五个元素的 1D 数组,等于列数:
In[35]:arr.sum(axis=0)Out[35]:array([108,25,35,149,130])这些方法也可以不使用轴关键字来调用:
In[36]:arr.mean(1)Out[36]:array([22.6,23.2,43.6])下表列出了数组的一些有用的统计方法。您可以使用整个数组或指定一个轴。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c6e9ffbe0fa9e4dc5043b8d826ca3b58.png
有用的 NumPy 统计方法(来自 Python Tools for Scientists)
注意,NumPy 还附带了一个apply_along_axis()聚合方法,允许您将统计方法、轴和数组作为参数传递。以下是一个使用先前数组的示例:
In[37]:np.apply_along_axis(np.mean,axis=1,arr=arr)Out[37]:array([22.6,23.2,43.6])您还可以定义自己的函数并将它们传递给apply_along _axis():
In[39]:np.apply_along_axis(cube,axis=1,arr=arr)Out[39]:array([[2197,10648,4096,27000,32768],[1331,1,1,42875,314432],[592704,8,5832,592704,27000]],dtype=int32)注意,在这些示例中,您能够处理数组而无需显式迭代每个元素。这又是 NumPy 的强大之处之一。
生成伪随机数
NumPy 附带从不同类型的概率分布创建数组的函数。这些函数对于生成随机数据以测试机器学习模型、创建具有已知形状或分布的数据分布、为蒙特卡洛模拟随机抽取数据等任务非常有用。它们也比 Python 内置的random模块中的类似函数快至少一个数量级。
下表列出了您可以在np.random中找到的一些函数。要获取完整列表,请访问文档。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3f4beb014fb847c774a92db330fd04bc.png
有用的 NumPy 伪随机函数(来自 Python Tools for Scientists)
读写数组数据
NumPy 可以以二进制和文本格式加载数据和保存数据。支持的文字格式是**.txt和.csv**。通常,你将想要使用基于 NumPy 构建的pandas库来处理文本或表格数据。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f2658bf017c2b288a663b8b6976d2272.png
DALL-E3 想象中的数组写入数据
对于以二进制格式存储和检索数据,NumPy 提供了save()和load()函数。要将数组保存到磁盘,传递一个文件名和数组作为参数,如下所示:
In[40]:arr=np.arange(8).reshape(2,4)In[41]:arr Out[41]:array([[0,1,2,3],[4,5,6,7]])In[42]:np.save('my_array',arr)这将生成名为 _myarray.npy的二进制文件(.npy扩展名会自动添加)。
要重新加载此文件,请输入以下内容:
In[43]:np.load('my_array.npy')Out[43]:array([[0,1,2,3],[4,5,6,7]])np.savez()方法允许你将多个数组以未压缩的*.npz*格式保存到单个文件中。提供关键字参数可以让你在输出文件中按对应名称存储它们:
In[44]:arr1=np.arange(5)In[45]:arr2=np.arange(4)In[46]:np.savez('arr_arch.npz',a=arr1,b=arr2)In[47]:archive=np.load('arr_arch.npz')In[48]:archive['a']Out[48]:array([0,1,2,3,4])如果数组被指定为位置参数(无关键字),它们的名称将默认为 _arr0、_arr1等。
在存档时压缩数据,请使用savez_compressed()方法:
In[49]:np.savez_compressed('arr_arch_compressed.npz',a=arr1,b=arr2)如果你确实想读取文本文件,NumPy 提供了genfromtxt()(从文本生成)方法。例如,要加载*.csv*文件,你需要将文件路径、分隔值的字符(逗号)以及数据列是否有标题等信息传递给该方法,如下所示:
arr=np.genfromtxt('my_data.csv',delimiter=',',names=True)这将生成一个包含记录而不是单个项目的结构化数组。我们尚未讨论结构化数组,因为它们是一个低级工具,你通常使用 pandas 进行如加载*.csv*文件之类的操作。然而,你可以在这里了解更多关于结构化数组的信息。
摘要
当处理均匀数据集时,NumPy 的ndarrays比 Python 列表等竞争数据结构更快、更高效。可以不使用for循环执行复杂计算,并且ndarrays比其他 Python 数据类型需要的内存显著更少。
本系列的第一部分介绍了如何创建数组。第二部分介绍了数组的索引和切片,第三部分介绍了数组的操作。
尽管这个系列涉及了许多 NumPy 的基础知识,但仍有许多东西需要学习。为了扩展你对 NumPy 的了解,我推荐阅读 NumPy 的“超越基础”页面以及 Wes McKinney 的《Python 数据分析:使用 Pandas、NumPy 和 Jupyter 的数据处理,第 3 版》(O’Reilly,2022 年)。
测试你的知识
通过测试新获得的知识来巩固所学内容是一种很好的方法。这里有一个快速问答来帮助您。答案在文章的末尾。
问题 1:在 NumPy 中,数组乘法是如何进行的:
a.按行按列
b.按列按行
c.元素级
d.先按行再按列
问题 2:哪个数组可以与形状为 (4, 3, 6, 1) 的数组进行广播?
a.(4, 6, 6, 1)
b.(1, 6, 3, 1)
c.(4, 1, 6, 6)
d.(6, 3, 1, 6)
问题 3:从array_1中减去array_2:
In[1]:array_1=np.array([10,20,30,40]).reshape(2,2)In[2]:array_2=np.array([5,10,15,20]).reshape(2,2)问题 4:如何计算array_2的平均值?
问题 5:哪个 NumPy 特性让您能够消除for循环:
a.点阵乘法
b.向量化
c.枚举
d.广播
进一步阅读
如果您是初学者,对 Python 的基本库感兴趣,比如 NumPy、Matplotlib、pandas 等,请查看我的最新书籍《Python Tools for Scientists》(它不仅适合科学家):
Python Tools for Scientists: An Introduction to Using Anaconda, JupyterLab, and Python’s Scientific…
问答
c
c
In[4]:array_1-array_2 Out[4]:array([[5,10],[15,20]])In[8]:array_2.mean()Out[8]:12.5- b
如果您觉得这篇文章有用,并想支持我,请买我一杯咖啡。我保证会喝的。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/642ceb8e3ee9d4deff0578cc8cf7aae4.png
谢谢!
感谢阅读和点赞,并请关注我,未来将有更多快速成功数据科学项目。